React.PureComponent
,和 React.Component
相似,都是定義一個組件類。不一樣是 React.Component
沒有實現 shouldComponentUpdate()
,而 React.PureComponent
經過 props
和 state
的淺比較實現了。html
// React.PureComponent 純組件 class Counter extends React.PureComponent { constructor(props) { super(props); this.state = {count: 0}; } render() { return ( <button onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> ); } }
在下一節中將會詳細介紹。react
定義React組件的最簡單方式就是定義一個函數組件,它接受單一的 props 並返回一個React元素。git
// 函數組件 function Counter(props) { return <div>Counter: {props.count}</div> } // 類組件 class Counter extends React.Component { render() { return <div>Counter: {this.props.count}</div> } }
受控和非受控主要是取決於組件是否受父級傳入的 props 控制github
用 props 傳入數據的話,組件能夠被認爲是受控(由於組件被父級傳入的 props 控制)。api
數據只保存在組件內部的 state 的話,是非受控組件(由於外部沒辦法直接控制 state)。數組
export default class AnForm extends React.Component { state = { name: "" } handleSubmitClick = () => { console.log("非受控組件: ", this._name.value); console.log("受控組件: ", this.state.name); } handleChange = (e) => { this.setState({ name: e.target.value }) } render() { return ( <form onSubmit={this.handleSubmitClick}> <label> 非受控組件: <input type="text" defaultValue="default" ref={input => this._name = input} /> </label> <label> 受控組件: <input type="text" value={this.state.name} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
與 html 不一樣的是,在 React 中,<input>
或<select>
、<textarea>
等這類組件,不會主動維持自身狀態,並根據用戶輸入進行更新。它們都要綁定一個onChange
事件;每當狀態發生變化時,都要寫入組件的 state 中,在 React 中被稱爲受控組件。瀏覽器
export default class AnForm extends React.Component { constructor(props) { super(props); this.state = {value: ""}; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } render() { return <input type="text" value={this.state.value} onChange={this.handleChange} />; } }
<input type="file" />
,它是一個非受控組件。this.setState({ [name]: value });
非受控組件再也不將數據保存在 state,而使用 refs,將真實數據保存在 DOM 中。性能優化
export default class AnForm extends Component { handleSubmitClick = () => { const name = this._name.value; } render() { return ( <div> <input type="text" ref={input => this._name = input} /> <button onClick={this.handleSubmitClick}>Sign up</button> </div> ); } }
使用場景app
特徵 | 非受控組件 | 受控組件 |
---|---|---|
one-time value retrieval (e.g. on submit) | ✅ | ✅ |
validating on submit | ✅ | ✅ |
instant field validation | ❌ | ✅ |
conditionally disabling submit button | ❌ | ✅ |
enforcing input format | ❌ | ✅ |
several inputs for one piece of data | ❌ | ✅ |
dynamic inputs | ❌ | ✅ |
經過 state 管理狀態框架
export default class Counter extends React.Component { constructor(props) { super(props) this.state = { clicks: 0 } this.handleClick = this.handleClick.bind(this) } handleClick() { this.setState(state => ({ clicks: state.clicks + 1 })) } render() { return ( <Button onClick={this.handleClick} text={`You've clicked me ${this.state.clicks} times!`} /> ) } }
輸入輸出數據徹底由props決定,並且不會產生任何反作用。
const Button = props => <button onClick={props.onClick}> {props.text} </button>
展現組件指不關心數據是怎麼加載和變更的,只關注於頁面展現效果的組件。
class TodoList extends React.Component{ constructor(props){ super(props); } render(){ const {todos} = this.props; return (<div> <ul> {todos.map((item,index)=>{ return <li key={item.id}>{item.name}</li> })} </ul> </div>) } }
容器組件只關心數據是怎麼加載和變更的,而不關注於頁面展現效果。
//容器組件 class TodoListContainer extends React.Component{ constructor(props){ super(props); this.state = { todos:[] } this.fetchData = this.fetchData.bind(this); } componentDidMount(){ this.fetchData(); } fetchData(){ fetch('/api/todos').then(data =>{ this.setState({ todos:data }) }) } render(){ return (<div> <TodoList todos={this.state.todos} /> </div>) } }
高階函數的定義:接收函數做爲輸入,或者輸出另外一個函數的一類函數,被稱做高階函數。
對於高階組件,它描述的即是接受 React 組件做爲輸入,輸出一個新的 React 組件的組件。
更通俗的描述爲,高階組件經過包裹(wrapped)被傳入的 React 組件,通過一系列處理,最終返回一個相對加強(enhanced)的 React 組件,供其餘組件調用。使咱們的代碼更具備複用性、邏輯性和抽象特性,它能夠對 render 方法作劫持,也能夠控制 props 、state。
實現高階組件的方法有如下兩種:
// 屬性代理 export default function withHeader(WrappedComponent) { return class HOC extends React.Component { // 繼承與 React.component render() { const newProps = { test:'hoc' } // 透傳props,而且傳遞新的newProps return <div> <WrappedComponent {...this.props} {...newProps}/> </div> } } } // 反向繼承 export default function (WrappedComponent) { return class Inheritance extends WrappedComponent { // 繼承於被包裹的 React 組件 componentDidMount() { // 能夠方便地獲得state,作一些更深刻的修改。 console.log(this.state); } render() { return super.render(); } } }
WrappedComponent.displayName || WrappedComponent.name || 'Component';
Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。
但與 class 生命週期不一樣的是,Hook 更接近於實現狀態同步,而不是響應生命週期事件。
import React, { useState, useEffect } from 'react'; function Example() { // 聲明一個叫 "count" 的 state 變量 const [count, setCount] = useState(0); useEffect(()=>{ // 須要在 componentDidMount 執行的內容 return function cleanup() { // 須要在 componentWillUnmount 執行的內容 } }, []) useEffect(() => { // 在 componentDidMount,以及 count 更改時 componentDidUpdate 執行的內容 document.title = 'You clicked ' + count + ' times'; return () => { // 須要在 count 更改時 componentDidUpdate(先於 document.title = ... 執行,遵照先清理後更新) // 以及 componentWillUnmount 執行的內容 } // 當函數中 Cleanup 函數會按照在代碼中定義的順序前後執行,與函數自己的特性無關 }, [count]); // 僅在 count 更改時更新 return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
useLayoutEffect
與 componentDidMount
、componentDidUpdate
的調用階段是同樣的。可是,咱們推薦你一開始先用 useEffect,只有當它出問題的時候再嘗試使用 useLayoutEffect
componentDidMount
或 componentDidUpdate
不一樣的是,Hook 在瀏覽器完成佈局與繪製以後,傳給 useEffect
的函數會延遲調用,但會保證在任何新的渲染前執行useEffect
的思惟模型中,默認都是同步的。反作用變成了 React 數據流的一部分。對於每個 useEffect
調用,一旦你處理正確,你的組件可以更好地處理邊緣狀況。首先看一下 React.Component 結構
// ReactBaseClasses.js 文件 /** * Base class helpers for the updating state of a component. */ function Component(props, context, updater) { this.props = props; // 屬性 props this.context = context; // 上下文 context // If a component has string refs, we will assign a different object later. // 初始化 refs,爲 {},主要在 stringRef 中使用,將 stringRef 節點的實例掛載在 this.refs 上 this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the // renderer. this.updater = updater || ReactNoopUpdateQueue; // updater } Component.prototype.isReactComponent = {}; /** * 設置 state 的子集,使用該方法更新 state,避免 state 的值爲可突變的狀態 * `shouldComponentUpdate`只是淺比較更新, * 可突變的類型可能致使 `shouldComponentUpdate` 返回 false,沒法從新渲染 * Immutable.js 能夠解決這個問題。它經過結構共享提供不可突變的,持久的集合: * 不可突變: 一旦建立,集合就不能在另外一個時間點改變。 * 持久性: 可使用原始集合和一個突變來建立新的集合。原始集合在新集合建立後仍然可用。 * 結構共享: 新集合儘量多的使用原始集合的結構來建立,以便將複製操做降至最少從而提高性能。 * * 並不能保證 `this.state` 經過 `setState` 後不可突變的更新,它可能還返回原來的數值 * 不能保證 `setrState` 會同步更新 `this.state` * `setState` 是經過隊列形式來更新 state ,當 執行 `setState` 時, * 會把 state 淺合併後放入狀態隊列,而後批量執行,即它不是當即更新的。 * 不過,你能夠在 callback 回調函數中獲取最新的值 * * 注意:對於異步渲染,咱們應在 `getSnapshotBeforeUpdate` 中讀取 `state`、`props`, * 而不是 `componentWillUpdate` * * @param {object|function} partialState Next partial state or function to * produce next partial state to be merged with current state. * @param {?function} callback Called after state is updated. * @final * @protected */ Component.prototype.setState = function(partialState, callback) { // 當 partialState 狀態爲 object 或 function類型 或 null 時, // 執行 this.updater.enqueueSetState 方法,不然報錯 invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.', ); // 將 `setState` 事務放入隊列中 this.updater.enqueueSetState(this, partialState, callback, 'setState'); }; /** * 強制更新,當且僅當當前不處於 DOM 事物(transaction)中才會被喚起 * This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. * * 默認狀況下,當組件的state或props改變時,組件將從新渲染。 * 若是你的`render()`方法依賴於一些其餘的數據, * 你能夠告訴React組件須要經過調用`forceUpdate()`從新渲染。 * 調用`forceUpdate()`會致使組件跳過 `shouldComponentUpdate()`, * 直接調用 `render()`。但會調用 `componentWillUpdate` 和 `componentDidUpdate`。 * 這將觸發組件的正常生命週期方法,包括每一個子組件的 shouldComponentUpdate() 方法。 * forceUpdate 就是從新 render 。 * 有些變量不在 state 上,當時你又想達到這個變量更新的時候,刷新 render ; * 或者 state 裏的某個變量層次太深,更新的時候沒有自動觸發 render 。 * 這些時候均可以手動調用 forceUpdate 自動觸發 render * * @param {?function} callback 更新完成後的回調函數. * @final * @protected */ Component.prototype.forceUpdate = function(callback) { // updater 強制更新 this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
其中 this.refs
值 emptyObject
爲:
// 設置 refs 初始值爲 {} const emptyObject = {}; if (__DEV__) { Object.freeze(emptyObject); // __DEV__ 模式下, 凍結 emptyObject } // Object.freeze() 凍結一個對象,被凍結的對象不能被修改(添加,刪除, // 修改已有屬性的可枚舉性、可配置性、可寫性與屬性值,原型);返回和傳入的參數相同的對象。
ReactNoopUpdateQueue
爲:
// ReactNoopUpdateQueue.js 文件 /** * 這是一個關於 更新隊列(update queue) 的抽象 API */ const ReactNoopUpdateQueue = { /** * 檢查複合組件是否裝載完成(被插入樹中) * @param {ReactClass} publicInstance 測試實例單元 * @return {boolean} 裝載完成爲 true,不然爲 false * @protected * @final */ isMounted: function(publicInstance) { return false; }, /** * 強制更新隊列,當且僅當當前不處於 DOM 事物(transaction)中才會被喚起 * * 當 state 裏的某個變量層次太深,更新的時候沒有自動觸發 render 。 * 這些時候就能夠調用該方法強制更新隊列 * * 該方法將跳過 `shouldComponentUpdate()`, 直接調用 `render()`, 但它會喚起 * `componentWillUpdate` 和 `componentDidUpdate`. * * @param {ReactClass} publicInstance 將被從新渲染的實例 * @param {?function} callback 組件更新後的回調函數. * @param {?string} callerName 在公共 API 調用該方法的函數名稱. * @internal */ enqueueForceUpdate: function(publicInstance, callback, callerName) { warnNoop(publicInstance, 'forceUpdate'); }, /** * 徹底替換state,與 `setState` 不一樣的是,`setState` 是以修改和新增的方式改變 `state `的, * 不會改變沒有涉及到的 `state`。 * 而 `enqueueReplaceState` 則用新的 `state` 徹底替換掉老 `state` * 使用它或 `setState` 來改變 state,而且應該把 this.state 設置爲不可突變類型對象, * 而且this.state不會當即更改 * 咱們應該在回調函數 callback 中獲取最新的 state * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} completeState Next state. * @param {?function} callback Called after component is updated. * @param {?string} callerName name of the calling function in the public API. * @internal */ enqueueReplaceState: function( publicInstance, completeState, callback, callerName, ) { warnNoop(publicInstance, 'replaceState'); }, /** * 設置 state 的子集 * 它存在的惟一理由是 _pendingState 是內部方法。 * `enqueueSetState` 實現淺合併更新 `state` * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} partialState Next partial state to be merged with state. * @param {?function} callback Called after component is updated. * @param {?string} Name of the calling function in the public API. * @internal */ enqueueSetState: function( publicInstance, partialState, callback, callerName, ) { warnNoop(publicInstance, 'setState'); }, }; export default ReactNoopUpdateQueue;
注意,React API 只是簡單的功能介紹,具體的實現是在 react-dom 中,這是由於不一樣的平臺,React API 是一致的,但不一樣的平臺,渲染的流程是不一樣的,具體的 Component 渲染流程不一致,會根據具體的平臺去定製。
組件生命週期請參考 Hooks 與 React 生命週期的關係