博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从 0 到 1 实现 React 系列 —— 4.优化setState和ref的实现
阅读量:6595 次
发布时间:2019-06-24

本文共 3848 字,大约阅读时间需要 12 分钟。

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/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 (      
{this.state.count}
) }}

异步调用 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 有 stringcallbackcreateRef 三种形式,分别如下:

// string 这种写法未来会被抛弃class MyComponent extends Component {  componentDidMount() {    this.refs.myRef.focus()  }  render() {    return   }}// callback(比较通用)class MyComponent extends Component {  componentDidMount() {    this.myRef.focus()  }  render() {    return  {      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   }}

罗列了三种写法的差异,下面对上述例子中的第二种写法(比较通用)进行实现。

首先在 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 
{this.state.count}
}}class B extends Component { constructor() { super() this.click = this.click.bind(this) } click() { this.A.click() } render() { return (
) }}

效果如下:

本系列文章拜读和借鉴了 ,在此特别感谢 的分享。

转载地址:http://xmcio.baihongyu.com/

你可能感兴趣的文章
rsync安装部署
查看>>
快速排序
查看>>
数据库周边资源汇总
查看>>
lucene全文索引遇到的问题
查看>>
【2】Hello World示例
查看>>
【Canal源码分析】配置项
查看>>
asp.net mvc文件下载
查看>>
VS2005 制作安装程序
查看>>
LNMP安装
查看>>
ocjp 121-130
查看>>
没买书,先观摩源码--《linux高性能服务器编程》1
查看>>
数组的相关处理函数
查看>>
nd2odb启动失败
查看>>
python-selenum3 第二天启动浏览器
查看>>
linux基础概念和个人笔记总结(5)
查看>>
python requests自定义方法
查看>>
我的友情链接
查看>>
有关在linux 下跑asp.net文章博客
查看>>
vue填坑之引入iconfont字体图标
查看>>
C# DES
查看>>