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組件的最簡單方式就是定義一個函數組件,它接受單一的 props 並返回一個React元素。react
// 函數組件
function Counter(props) {
return <div>Counter: {props.count}</div>
}
// 類組件
class Counter extends React.Component {
render() {
return <div>Counter: {this.props.count}</div>
}
}
複製代碼
受控和非受控主要是取決於組件是否受父級傳入的 props 控制git
用 props 傳入數據的話,組件能夠被認爲是受控(由於組件被父級傳入的 props 控制)。github
數據只保存在組件內部的 state 的話,是非受控組件(由於外部沒辦法直接控制 state)。api
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} />; } } 複製代碼
onChange & value 模式(單選按鈕和複選按鈕對應的是 checked props)瀏覽器
react經過這種方式消除了組件的局部狀態,使得應用的整個狀態可控。性能優化
注意 <input type="file" />
,它是一個非受控組件。app
可使用計算屬性名將多個類似的操做組合成一個。
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> ); } } 複製代碼
非受控組件是最簡單快速的實現方式,項目中出現極簡的表單時,使用它,但受控組件纔是是最權威的。
一般指定一個 defaultValue/defaultChecked 默認值來控制初始狀態,不使用 value。
非受控組件相比於受控組件,更容易同時集成 React 和非 React 代碼。
使用場景
特徵 | 非受控組件 | 受控組件 |
---|---|---|
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 生命週期的關係
想看更過系列文章,點擊前往 github 博客主頁
走在最後,歡迎關注:前端瓶子君,每日更新