内容简介:看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/…)而在现有 setState 逻辑实现中,每调用一次 setState 就会执行 render 一次。因此在如下代码中,每次点击增加按钮,因为 click 方法里调用了 10 次 setState 函数,页面也会被渲染 10 次。而我们希望的是每点击一次增加按钮只执行 render 函数一次。
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/…)
- 从 0 到 1 实现 React 系列 —— JSX 和 Virtual DOM
- 从 0 到 1 实现 React 系列 —— 组件和 state|props
- 从 0 到 1 实现 React 系列 —— 生命周期和 diff 算法
- 从 0 到 1 实现 React 系列 —— 优化 setState 和 ref 的实现
同步 setState 的问题
而在现有 setState 逻辑实现中,每调用一次 setState 就会执行 render 一次。因此在如下代码中,每次点击增加按钮,因为 click 方法里调用了 10 次 setState 函数,页面也会被渲染 10 次。而我们希望的是每点击一次增加按钮只执行 render 函数一次。
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({ // 在先前的逻辑中,没调用一次 setState 就会 render 一次 count: ++this.state.count }) } } render() { console.log(this.state.count) return ( <div> <button onClick={this.click}>增加</button> <div>{this.state.count}</div> </div> ) } }
异步调用 setState
查阅 setState 的 api,其形式如下:
setState(updater, [callback])
它能接收两个参数,其中第一个参数 updater 可以为对象或者为函数 ( (prevState, props) => stateChange
),第二个参数为回调函数;
确定优化思路为:将多次 setState 后跟着的值进行浅合并,并借助事件循环等所有值合并好之后再进行渲染界面。
let componentArr = [] // 异步渲染 function asyncRender(updater, component, cb) { if (componentArr.length === 0) { defer(() => render()) // 利用事件循环,延迟渲染函数的调用 } if (cb) defer(cb) // 调用回调函数 if (_.isFunction(updater)) { // 处理 setState 后跟函数的情况 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 component while (component = componentArr.shift()) { renderComponent(component) // rerender } } // 事件循环,关于 promise 的事件循环和 setTimeout 的事件循环后续会单独写篇文章。 const defer = function(fn) { return Promise.resolve().then(() => fn()) }
此时,每点击一次增加按钮 render 函数只执行一次了。
ref 的实现
在 react 中并不建议使用 ref 属性,而应该尽量使用 状态提升 ,但是 react 还是提供了 ref 属性赋予了开发者操作 dom 的能力,react 的 ref 有 string
、 callback
、 createRef
三种形式,分别如下:
// string 这种写法未来会被抛弃 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 框架还没有同步 class MyComponent extends Component { constructor() { super() { this.myRef = React.createRef() } } componentDidMount() { this.myRef.current.focus() } render() { return <input ref={this.myRef} /> } }
React ref 的前世今生 罗列了三种写法的差异,下面对上述例子中的第二种写法(比较通用)进行实现。
首先在 setAttribute 方法内补充上对 ref 的属性进行特殊处理,
function setAttribute(dom, attr, value) { ... else if (attr === 'ref') { // 处理 ref 属性 if (_.isFunction(value)) { value(dom) } } ... }
针对这个例子中 this.myRef.focus()
的 focus 属性需要异步处理,因为调用 componentDidMount 的时候,界面上还未添加 dom 元素。处理 renderComponent 函数:
function renderComponent(component) { ... else if (component && component.componentDidMount) { defer(component.componentDidMount.bind(component)) } ... }
刷新页面,可以发现 input 框已为选中状态。
处理完普通元素的 ref 后,再来处理下自定义组件的 ref 的情况。之前默认自定义组件上是没属性的,现在只要针对自定义组件的 ref 属性做相应处理即可。稍微修改 vdomToDom 函数如下:
function vdomToDom(vdom) { if (_.isFunction(vdom.nodeName)) { // 此时是自定义组件 ... for (const attr in vdom.attributes) { // 处理自定义组件的 ref 属性 if (attr === 'ref' && _.isFunction(vdom.attributes[attr])) { vdom.attributes[attr](component) } } ... } ... }
跑如下测试用例:
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> ) } }
效果如下:
本系列文章拜读和借鉴了 simple-react ,在此特别感谢 Jiulong Hu 的分享。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- php如何实现session,自己实现session,laravel如何实现session
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
- Docker实现原理之 - OverlayFS实现原理
- 为什么实现 .NET 的 ICollection 集合时需要实现 SyncRoot 属性?如何正确实现这个属性?
- 自己实现集合框架(十):顺序栈的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
大连接
[美] 尼古拉斯•克里斯塔基斯(Nicholas A. Christakis)、[美] 詹姆斯•富勒(James H. Fowler) / 简学 / 中国人民大学出版社 / 2013-1 / 59.90元
[内容简介] 1. 本书是继《六度分隔》之后,社会科学领域最重要的作品。作者发现:相距三度之内是强连接,强连接可以引发行为;相聚超过三度是弱连接,弱连接只能传递信息。 2. 本书讲述了社会网络是如何形成的以及对人类现实行为的影响,如对人类的情绪、亲密关系、健康、经济的运行和政治的影响等,并特别指出,三度影响力(即朋友的朋友的朋友也能影响到你)是社会化网络的强连接原则,决定着社会化网络的......一起来看看 《大连接》 这本书的介绍吧!
RGB CMYK 转换工具
RGB CMYK 互转工具
HSV CMYK 转换工具
HSV CMYK互换工具