下面總結一下知識點,便於查看:
1.事件
2.表單
3.setState
4.生命周期
5.函數組件
6.非受控組件
7.Portals
8.context
9.異步組件
10.性能優化
11.高階組件HOC
12.Render Props
事件
1.使用bind
import React from 'react'
class EventDemo extends React.Component { constructor(props) { super(props) this.state = { name: 'zhangsan', }
// 修改方法的 this 指向 this.clickHandler1 = this.clickHandler1.bind(this) } render() { // // this - 使用 bind return <p onClick={this.clickHandler1}> {this.state.name} </p> } clickHandler1() { console.log('this....', this) // this 默認是 undefined this.setState({ name: 'lisi' }) }}
export default EventDemo2.使用靜態方法
// this - 使用靜態方法return <p onClick={this.clickHandler2}>clickHandler2 {this.state.name}</p>
// 靜態方法,this 指向當前實例clickHandler2 = () => { this.setState({ name: 'lisi' })}3.event
<a href="https://baidu.com/" onClick={this.clickHandler3}>click me</a>
// 獲取 eventclickHandler3 = (event) => { event.preventDefault() // 阻止默認行為 event.stopPropagation() // 阻止冒泡 console.log('target', event.target) // 指向當前元素,即當前元素觸發 console.log('current target', event.currentTarget) // 指向當前元素,假象!!!
// 注意,event 其實是 React 封裝的。可以看 __proto__.constructor 是 SyntheticEvent 組合事件 console.log('event', event) // 不是原生的 Event ,原生的是 MouseEvent console.log('event.__proto__.constructor', event.__proto__.constructor)
// 原生 event 如下。其 __proto__.constructor 是 MouseEvent console.log('nativeEvent', event.nativeEvent) console.log('nativeEvent target', event.nativeEvent.target) // 指向當前元素,即當前元素觸發 console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 document !!!
// 1. event 是 SyntheticEvent ,模擬出來 DOM 事件所有能力 // 2. event.nativeEvent 是原生事件對象 // 3. 所有的事件,都被掛載到 document 上 // 4. 和 DOM 事件不一樣,和 Vue 事件也不一樣}結果如下:
表單
受控組件:通過change事件來改變state
import React from 'react'
class FormDemo extends React.Component { constructor(props) { super(props) this.state = { name: 'curry', info: '個人信息', city: 'beijing', flag: true, gender: 'male' } } render() {
// // 受控組件 // return <div> // <p>{this.state.name}</p> // <label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for */} // <input id="inputName" value={this.state.name} onChange={this.onInputChange}/> // </div>
// textarea - 使用 value return <div> <textarea value={this.state.info} onChange={this.onTextareaChange}/> <p>{this.state.info}</p> </div>
// // select - 使用 value return <div> <select value={this.state.city} onChange={this.onSelectChange}> <option value="beijing">北京</option> <option value="shanghai">上海</option> <option value="shenzhen">深圳</option> </select> <p>{this.state.city}</p> </div>
// // checkbox return <div> <input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/> <p>{this.state.flag.toString()}</p> </div>
// // radio return <div> male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/> female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/> <p>{this.state.gender}</p> </div> } onInputChange = (e) => { this.setState({ name: e.target.value }) } onTextareaChange = (e) => { this.setState({ info: e.target.value }) } onSelectChange = (e) => { this.setState({ city: e.target.value }) } onCheckboxChange = () => { this.setState({ flag: !this.state.flag }) } onRadioChange = (e) => { this.setState({ gender: e.target.value }) }}
export default FormDemosetState
1.不可變值
2.可能是異步更新
3.可能會被合併
不可變值:
import React from 'react'
class StateDemo extends React.Component { constructor(props) { super(props)
// 第一,state 要在構造函數中定義 this.state = { count: 0 } } render() { return <div> <p>{this.state.count}</p> <button onClick={this.increase}>累加</button> </div> } increase = () => { // 第二,不要直接修改 state ,使用不可變值 --- // this.state.count++ // 錯誤 this.setState({ count: this.state.count + 1 // SCU }) // 操作數組、對象的的常用形式 // 不可變值(函數式編程,純函數) - 數組 const list5Copy = this.state.list5.slice() list5Copy.splice(2, 0, 'a') // 中間插入/刪除 this.setState({ list1: this.state.list1.concat(100), // 追加 list2: [...this.state.list2, 100], // 追加 list3: this.state.list3.slice(0, 3), // 截取 list4: this.state.list4.filter(item => item > 100), // 篩選 list5: list5Copy // 其他操作 }) // 注意,不能直接對 this.state.list 進行 push pop splice 等,這樣違反不可變值
// 不可變值 - 對象 this.setState({ obj1: Object.assign({}, this.state.obj1, {a: 100}), obj2: {...this.state.obj2, a: 100} }) // 注意,不能直接對 this.state.obj 進行屬性設置,這樣違反不可變值 }}
export default StateDemo可能是異步更新:
this.setState({ count: this.state.count + 1}, () => { console.log('count by callback', this.state.count) // 回調函數中可以拿到最新的 state})console.log('count', this.state.count) // 異步的,拿不到最新值列印如下:
setTimeout 中 setState 是同步的:// setTimeout 中 setState 是同步的setTimeout(() => { this.setState({ count: this.state.count + 1 }) console.log('count in setTimeout', this.state.count)}, 0)列印如下:
自己定義的 DOM 事件,setState 是同步的
bodyClickHandler = () => { this.setState({ count: this.state.count + 1 }) console.log('count in body event', this.state.count)}componentDidMount() { // 自己定義的 DOM 事件,setState 是同步的 document.body.addEventListener('click', this.bodyClickHandler)}
列印如下:可能會被合併:
傳入對象,會被合併(類似 Object.assign )
// 傳入對象,會被合併(類似 Object.assign )。執行結果只一次 +1this.setState({ count: this.state.count + 1})this.setState({ count: this.state.count + 1})this.setState({ count: this.state.count + 1})傳入函數,不會被合併。
// 傳入函數,不會被合併。執行結果是 +3 this.setState((prevState, props) => { return { count: prevState.count + 1 } }) this.setState((prevState, props) => { return { count: prevState.count + 1 } }) this.setState((prevState, props) => { return { count: prevState.count + 1 } })生命周期:
http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
React高級特性:
1.函數組件2.非受控組件
3.Portals
4.context
5.異步組件
6.性能優化
7.高階組件HOC
8.Render Props
函數組件:
1.純函數,輸入props,輸出jsx
2.沒有實例,沒有生命周期,沒有state
非受控組件:
1.ref
2.defaultValue defaultChexked
3.手動操作dom元素
ref:
第一步:創建ref
第二步:綁定到dom元素
第三步:通過this.nameInputRef.current 獲取dom
第四步:操作dom
使用場景:必須手動操作dom,不能使用state。如上傳文件。
import React from 'react'
class App extends React.Component { constructor(props) { super(props) this.state = { name: 'curry', flag: true, } // 第一步:創建ref this.nameInputRef = React.createRef() // 創建 ref } render() { return <div> {/* 使用 defaultValue 而不是 value ,使用 ref */} // 第二步:綁定到dom元素 <input defaultValue={this.state.name} ref={this.nameInputRef}/> {/* state 並不會隨著改變 */} <span>state.name: {this.state.name}</span> <br/> <button onClick={this.alertName}>alert name</button> </div> } alertName = () => { // 第三步:通過this.nameInputRef.current 獲取dom const elem = this.nameInputRef.current // 通過 ref 獲取 DOM 節點 // 第四步:操作dom alert(elem.value) // 不是 this.state.name }}
export default App受控組件 vs 非受控組件:
1.優先使用受控組件,符合react設計原則
2.必須操作dom,再使用非受控組件
Portals
作用:讓組件渲染到父組件外
先看下組件正常渲染
import React from 'react'import ReactDOM from 'react-dom'import './style.css'
class App extends React.Component { constructor(props) { super(props) this.state = { } } render() { // // 正常渲染 return <div className="modal"> {this.props.children} {/* vue slot */} </div> }}
export default App.modal { position: fixed; width: 300px; height: 100px; top: 100px; left: 50%; margin-left: -150px; background-color: #000; /* opacity: .2; */ color: #fff; text-align: center;}如下:modal渲染到了body裡面。
使用Portals將modal渲染到body第一層。
import React from 'react'import ReactDOM from 'react-dom'import './style.css'
class App extends React.Component { constructor(props) { super(props) this.state = { } } render() { // 使用 Portals 渲染到 body 上。 // fixed 元素要放在 body 上,有更好的瀏覽器兼容性。 return ReactDOM.createPortal( <div className="modal">{this.props.children}</div>, document.body // DOM 節點 ) }}
export default App
如下:modal就在body第一層了。
Portals使用場景:
1.overflow:hidden
2.z-index值太小
3.fixed需要放在body第一層
context:
適用公共信息傳遞給每個組件
第一步:創建 Context 填入默認值(任何一個 js 變量)
const ThemeContext = React.createContext('light')第二步:將需要共享的數據放到ThemeContext.Provider裡面的value屬性,同時將需要消費共享數據的組件放到ThemeContext.Provider裡面。
直接看下代碼:
<ThemeContext.Provider value={this.state.theme}> <Toolbar /> <hr/> <button onClick={this.changeTheme}>change theme</button></ThemeContext.Provider>第三步:在消費的組件中獲取並使用
ThemedButton.contextType = ThemeContext // 指定 contextType 讀取當前的 theme context。// 底層組件 - class 組件class ThemedButton extends React.Component { // 指定 contextType 讀取當前的 theme context。 // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext render() { const theme = this.context // React 會往上找到最近的 theme Provider,然後使用它的值。 return <div> <p>button's theme is {theme}</p> </div> }}還可以使用ThemeContext.Consumer來消費共享數據
// 底層組件 - 函數是組件function ThemeLink (props) { // const theme = this.context // 會報錯。函數式組件沒有實例,即沒有 this
// 函數式組件可以使用 Consumer return <ThemeContext.Consumer> { value => <p>link's theme is {value}</p> } </ThemeContext.Consumer>}全部代碼如下:
import React from 'react'
// 創建 Context 填入默認值(任何一個 js 變量)const ThemeContext = React.createContext('light')
// 底層組件 - 函數是組件function ThemeLink (props) { // const theme = this.context // 會報錯。函數式組件沒有實例,即沒有 this
// 函數式組件可以使用 Consumer return <ThemeContext.Consumer> { value => <p>link's theme is {value}</p> } </ThemeContext.Consumer>}
// 底層組件 - class 組件class ThemedButton extends React.Component { // 指定 contextType 讀取當前的 theme context。 // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext render() { const theme = this.context // React 會往上找到最近的 theme Provider,然後使用它的值。 return <div> <p>button's theme is {theme}</p> </div> }}ThemedButton.contextType = ThemeContext // 指定 contextType 讀取當前的 theme context。
// 中間的組件再也不必指明往下傳遞 theme 了。function Toolbar(props) { return ( <div> <ThemedButton /> <ThemeLink /> </div> )}
class App extends React.Component { constructor(props) { super(props) this.state = { theme: 'light' } } render() { return <ThemeContext.Provider value={this.state.theme}> <Toolbar /> <hr/> <button onClick={this.changeTheme}>change theme</button> </ThemeContext.Provider> } changeTheme = () => { this.setState({ theme: this.state.theme === 'light' ? 'dark' : 'light' }) }}
export default App異步組件:
1.import()2.React.lazy
3.React.Suspense
import React from 'react'
const ContextDemo = React.lazy(() => import('./ContextDemo'))
class App extends React.Component { constructor(props) { super(props) } render() { return <div> <p>引入一個動態組件</p> <hr /> <React.Suspense fallback={<div>Loading...</div>}> <ContextDemo/> </React.Suspense> </div> }}
export default App性能優化:
1.scu:shouldComponentUpdate
2.PureComponent和React.memo
3.不可變值immutable.js
scu:
默認返回true
shouldComponentUpdate(nextProps, nextState) { if (nextState.count !== this.state.count) { return true // 可以渲染 } return false // 不重複渲染}父組件更新,子組件也會更新。這時需要scu。
PureComponent:SCU中實現了淺比較, Class組件
React.memo:函數組件
高階組件
接收一個組件,返回一個新組件
import React from 'react'
// 高階組件const withMouse = (Component) => { class withMouseComponent extends React.Component { constructor(props) { super(props) this.state = { x: 0, y: 0 } }
handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }) }
render() { return ( <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}> {/* 1. 透傳所有 props 2. 增加 mouse 屬性 */} <Component {...this.props} mouse={this.state}/> </div> ) } } return withMouseComponent}
const App = (props) => { const a = props.a const { x, y } = props.mouse // 接收 mouse 屬性 return ( <div style={{ height: '500px' }}> <h1>The mouse position is ({x}, {y})</h1> <p>{a}</p> </div> )}
export default withMouse(App) // 返回高階函數Render props
import React from 'react'import PropTypes from 'prop-types'
class Mouse extends React.Component { constructor(props) { super(props) this.state = { x: 0, y: 0 } }
handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }) }
render() { return ( <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}> {/* 將當前 state 作為 props ,傳遞給 render (render 是一個函數組件) */} {this.props.render(this.state)} </div> ) }}Mouse.propTypes = { render: PropTypes.func.isRequired // 必須接收一個 render 屬性,而且是函數}
const App = (props) => ( <div style={{ height: '500px' }}> <p>{props.a}</p> <Mouse render={ /* render 是一個函數組件 */ ({ x, y }) => <h1>The mouse position is ({x}, {y})</h1> }/>
</div>)
/** * 即,定義了 Mouse 組件,只有獲取 x y 的能力。 * 至於 Mouse 組件如何渲染,App 說了算,通過 render prop 的方式告訴 Mouse 。 */
export default App