从 0 到 1 实现 React 系列 —— 4.setState优化和ref的实现
看源碼一個(gè)痛處是會(huì)陷進(jìn)理不順主干的困局中,本系列文章在實(shí)現(xiàn)一個(gè) (x)react 的同時(shí)理順 React 框架的主干內(nèi)容(JSX/虛擬DOM/組件/生命周期/diff算法/setState/ref/...)
- 從 0 到 1 實(shí)現(xiàn) React 系列 —— JSX 和 Virtual DOM
- 從 0 到 1 實(shí)現(xiàn) React 系列 —— 組件和 state|props
- 從 0 到 1 實(shí)現(xiàn) React 系列 —— 生命周期和 diff 算法
- 從 0 到 1 實(shí)現(xiàn) React 系列 —— 優(yōu)化 setState 和 ref 的實(shí)現(xiàn)
同步 setState 的問(wèn)題
而在現(xiàn)有 setState 邏輯實(shí)現(xiàn)中,每調(diào)用一次 setState 就會(huì)執(zhí)行 render 一次。因此在如下代碼中,每次點(diǎn)擊增加按鈕,因?yàn)?click 方法里調(diào)用了 10 次 setState 函數(shù),頁(yè)面也會(huì)被渲染 10 次。而我們希望的是每點(diǎn)擊一次增加按鈕只執(zhí)行 render 函數(shù)一次。
export default class B extends Component {constructor(props) {super(props)this.state = {count: 0}this.click = this.click.bind(this)}click() {for (let i = 0; i < 10; i++) {this.setState({ // 在先前的邏輯中,沒(méi)調(diào)用一次 setState 就會(huì) render 一次count: ++this.state.count})}}render() {console.log(this.state.count)return (<div><button onClick={this.click}>增加</button><div>{this.state.count}</div></div>)} }異步調(diào)用 setState
查閱 setState 的 api,其形式如下:
setState(updater, [callback])它能接收兩個(gè)參數(shù),其中第一個(gè)參數(shù) updater 可以為對(duì)象或者為函數(shù) ((prevState, props) => stateChange),第二個(gè)參數(shù)為回調(diào)函數(shù);
確定優(yōu)化思路為:將多次 setState 后跟著的值進(jìn)行淺合并,并借助事件循環(huán)等所有值合并好之后再進(jìn)行渲染界面。
let componentArr = []// 異步渲染 function asyncRender(updater, component, cb) {if (componentArr.length === 0) {defer(() => render()) // 利用事件循環(huán),延遲渲染函數(shù)的調(diào)用}if (cb) defer(cb) // 調(diào)用回調(diào)函數(shù)if (_.isFunction(updater)) { // 處理 setState 后跟函數(shù)的情況updater = updater(component.state, component.props)}// 淺合并邏輯component.state = Object.assign({}, component.state, updater)if (componentArr.includes(component)) {component.state = Object.assign({}, component.state, updater)} else {componentArr.push(component)} }function render() {let componentwhile (component = componentArr.shift()) {renderComponent(component) // rerender} }// 事件循環(huán),關(guān)于 promise 的事件循環(huán)和 setTimeout 的事件循環(huán)后續(xù)會(huì)單獨(dú)寫(xiě)篇文章。 const defer = function(fn) {return Promise.resolve().then(() => fn()) }此時(shí),每點(diǎn)擊一次增加按鈕 render 函數(shù)只執(zhí)行一次了。
ref 的實(shí)現(xiàn)
在 react 中并不建議使用 ref 屬性,而應(yīng)該盡量使用狀態(tài)提升,但是 react 還是提供了 ref 屬性賦予了開(kāi)發(fā)者操作 dom 的能力,react 的 ref 有 string、callback、createRef 三種形式,分別如下:
// string 這種寫(xiě)法未來(lái)會(huì)被拋棄 class MyComponent extends Component {componentDidMount() {this.refs.myRef.focus()}render() {return <input ref="myRef" />} }// callback(比較通用) class MyComponent extends Component {componentDidMount() {this.myRef.focus()}render() {return <input ref={(ele) => {this.myRef = ele}} />} }// react 16.3 增加,其它 react-like 框架還沒(méi)有同步 class MyComponent extends Component {constructor() {super() {this.myRef = React.createRef()}}componentDidMount() {this.myRef.current.focus()}render() {return <input ref={this.myRef} />} }React ref 的前世今生 羅列了三種寫(xiě)法的差異,下面對(duì)上述例子中的第二種寫(xiě)法(比較通用)進(jìn)行實(shí)現(xiàn)。
首先在 setAttribute 方法內(nèi)補(bǔ)充上對(duì) ref 的屬性進(jìn)行特殊處理,
function setAttribute(dom, attr, value) {...else if (attr === 'ref') { // 處理 ref 屬性if (_.isFunction(value)) {value(dom)}}... }針對(duì)這個(gè)例子中 this.myRef.focus() 的 focus 屬性需要異步處理,因?yàn)檎{(diào)用 componentDidMount 的時(shí)候,界面上還未添加 dom 元素。處理 renderComponent 函數(shù):
function renderComponent(component) {...else if (component && component.componentDidMount) {defer(component.componentDidMount.bind(component))}... }刷新頁(yè)面,可以發(fā)現(xiàn) input 框已為選中狀態(tài)。
處理完普通元素的 ref 后,再來(lái)處理下自定義組件的 ref 的情況。之前默認(rèn)自定義組件上是沒(méi)屬性的,現(xiàn)在只要針對(duì)自定義組件的 ref 屬性做相應(yīng)處理即可。稍微修改 vdomToDom 函數(shù)如下:
function vdomToDom(vdom) {if (_.isFunction(vdom.nodeName)) { // 此時(shí)是自定義組件...for (const attr in vdom.attributes) { // 處理自定義組件的 ref 屬性if (attr === 'ref' && _.isFunction(vdom.attributes[attr])) {vdom.attributes[attr](component)}}...}... }跑如下測(cè)試用例:
class A extends Component {constructor() {super()this.state = {count: 0}this.click = this.click.bind(this)}click() {this.setState({count: ++this.state.count})}render() {return <div>{this.state.count}</div>} }class B extends Component {constructor() {super()this.click = this.click.bind(this)}click() {this.A.click()}render() {return (<div><button onClick={this.click}>加1</button><A ref={(e) => { this.A = e }} /></div>)} }效果如下:
項(xiàng)目地址,關(guān)于如何 pr
本系列文章拜讀和借鑒了 simple-react,在此特別感謝 Jiulong Hu 的分享。
轉(zhuǎn)載于:https://www.cnblogs.com/MuYunyun/p/9427911.html
總結(jié)
以上是生活随笔為你收集整理的从 0 到 1 实现 React 系列 —— 4.setState优化和ref的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: unity实现一个物体绕着某点旋转
- 下一篇: Vue子组件与父组件之间的通信