从0到1实现react系列——4.setstate优化和ref的实现(代码片段)

muyunyun muyunyun     2022-12-20     726

关键词:

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (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 (
      <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 有 stringcallbackcreateRef 三种形式,分别如下:

// 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>
    )
  

效果如下:

技术分享图片

项目地址关于如何 pr

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

小程序从0到1首页布局案例的实现(代码片段)

欢迎来到我的博客📔博主是一名大学在读本科生,主要学习方向是前端。🍭目前已经更新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏🛠目前正在学习的是🔥React/小程序React/小程序React/小... 查看详情

从0到1实现react-9.onchange事件以及受控组件(代码片段)

该系列文章在实现cpreact的同时理顺React框架的核心内容项目地址从一个疑问点开始接上一章HOC探索抛出的问题————react中的onChange事件和原生DOM事件中的onchange表现不一致,举例说明如下://React中的onChange事件classAppextendsCompon... 查看详情

从0到1实现一个android路由——初探路由(代码片段)

从0到1实现一个Android路由系列文章从0到1实现一个Android路由(1)——初探路由从0到1实现一个Android路由(2)——URL解析器从0到1实现一个Android路由(3)——APT收集路由从0到1实现一个Android路由(4)——多模块的APT收集路由从0到1实现一个A... 查看详情

从0到1实现一个android路由——拦截请求再跳转(代码片段)

从0到1实现一个Android路由系列文章从0到1实现一个Android路由(1)——初探路由从0到1实现一个Android路由(2)——URL解析器从0到1实现一个Android路由(3)——APT收集路由从0到1实现一个Android路由(4)——多模块的APT收集路由从0到1实现一个A... 查看详情

使用webpack实现从0到1搭建一个react项目

参考技术A穿件一个project文件夹用vscode或者其他编辑器打开,接着使用npminit初始化一个项目,初始化完成之后会有一个package.json文件。npmireactreact-dom安装命令:npmiwebpack@4.32.2webpack-cli@2.0.9webpack-dev-server@3.0.0安装成功之后,会出现n... 查看详情

egg.js+react+zarmui+vite2.0全栈项目实战:从0到1实现记账本小册学习笔记合集(完结)(代码片段)

...勿商用。请支持正版的小册【Node+React实战:从0到1实现记账本】。该项目对我提升很大,在此非常感谢作者。该项目涉及的技术栈:React,ZarmUI,Vite2.0,Egg.js,MySQL 查看详情

React - 从 17.0.1 更新到 17.0.2 时出错 [重复]

】React-从17.0.1更新到17.0.2时出错[重复]【英文标题】:React-Errorwhileupdatingfrom17.0.1to17.0.2[duplicate]【发布时间】:2021-08-0122:29:16【问题描述】:从React17.0.1更新到17.0.2时出现以下错误。我并不完全清楚为什么依赖关系会导致问题。我... 查看详情

react从0到1(代码片段)

...就直接干一个项目,并且目前要最新的。我推荐用create-react-app, 别问我为什么,我不知道1.0 npminstall-gcreate-react-app1.1 create-react-appclassw 1.2进到这个文件夹 npmstart   查看详情

如何从 React ^0.14.8 & React-native ^0.24.1 升级到 React 15.0.2 和 React-native 0.26.3

】如何从React^0.14.8&React-native^0.24.1升级到React15.0.2和React-native0.26.3【英文标题】:HowtoupgradefromReact^0.14.8&React-native^0.24.1toReact15.0.2andReact-native0.26.3【发布时间】:2016-10-0315:15:57【问题描述】:我有Xcode版本7.3.1节点版本v4.4 查看详情

小程序从0到1网络数据请求——request合法域名|get|post|跨域?ajax?(代码片段)

...新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏🛠目前正在学习的是🔥React/小程序React/小程序React/小程序🔥,中间穿插了一些基础知识的回顾🌈博客主页& 查看详情

react从0搭配react项目1(代码片段)

知识点:webpack搭配react环境实现适配配置动态式路由webpack搭配react环境安装依赖npminit-ycnpminstallreactreact-dom@types/react@types/react-domreact-router-dom@types/react-router-domantdreduxreact-redux@type 查看详情

从0到1构建react完整项目2022最新无坑版(代码片段)

react更新很快,现在搜到的搭建react项目的博客,经常搭建到一半就卡住了,因此总结了一套从0到1构建react的方法,是对自己亲身实践项目的总结,也对研究学习使用react的小伙伴提供一点借鉴。本文对大量博... 查看详情

从0到1构建react完整项目2022最新无坑版(代码片段)

react更新很快,现在搜到的搭建react项目的博客,经常搭建到一半就卡住了,因此总结了一套从0到1构建react的方法,是对自己亲身实践项目的总结,也对研究学习使用react的小伙伴提供一点借鉴。本文对大量博... 查看详情

从0到1教你学会react(代码片段)

文章目录React是什么组件是什么组件开发的优势React开发环境搭建基于webpack搭建基于脚手架工具搭建JSXJSX基础语法JSX事件操作JSX遍历数据JSX添加内联样式JSX添加外联样式组件的创建创建函数式组件创建类式组件组件传参函数式组... 查看详情

基于索引从大熊猫系列列表中提取到另一个大熊猫系列(代码片段)

我有2个系列的pandas数据框,每个包含2d数组,a是第一个不同长度的系列子数组a:0[[1,2,3,4,5,6,7,7],[1,2,3,4,5],[5,9,3,2]]1[[1,2,3],[6,7],[8,9,10]]而b是第二个但它的子阵列只有一个像b:0[[0],[2],[3]]1[[1],[0],[1]]我想基于b中给出的索引提取系列的元... 查看详情

react系列教程1:实现animate.css官网效果

前言这是React系列教程的第一篇,我们将用React实现Animate.css官网的效果。对于Animate.css官网效果是一个非常简单的例子,原代码使用jQuery编写,就是添加类与删除类的操作。这对于学习React来说是一个非常简易的例子,但是我并... 查看详情

从0到1使用webpack5+react+ts构建标准化应用(代码片段)

...解如何从一个空目录开始,建立起一个基于webpack+react+typescript的标准化前端应用。技术栈:webpack5+React18+TS工程化:eslint+prettier+husky+githooks支持图片、less、sass、fonts、数据资源(JSON、csv、tsv等)、Antd按需 查看详情

从0到1学习边缘容器系列之边缘应用管理

大家对使用Kubernetes管理应用已经比较熟悉,但是边缘场景下的应用部署和管理是否存在不同的需求呢?本文将和大家一起探讨边缘场景下常见的容器应用管理方案。1边缘简单服务场景在笔者接触过的边缘需求中部分用户业务场... 查看详情