Day8. React中的setState及更新机制看这篇就够了

至关重要的知识点, 关系到能不能写出高效率的代码

为什么使用setState

  • 开发中我们并不能直接通过修改state的值来让界面发生更新:
    • 因为我们修改了state之后, 希望React根据最新的State来重新渲染界面, 但是这种方式的修改React并不知道数据发生了变化;
    • React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来监听数据的变化;
    • 我们必须通过setState来告知React数据已经发生了变化;
修改了数据, 但是没有页面刷新, React不知道自己要刷新.png
报了一个警告.png
this.setState({
  counter : this.state.counter + 1
})
  • 抛出来一个疑惑, 没有在类中定义过setState, 原因App类继承自Component, 继承了父类的方法, 回到源码里面看


    源码.png

    image.png

setState异步更新

  • 异步更新, 不等待, 直接执行下面的代码


    改了文本, 打印的是Hello World.png
  • 为什么要设计成异步的?
    image.png

    Rudux作者Dan Abramov的回答
  • 简单的总结:
  • setState设计为异步, 可以显著的提升性能;
    • 如果每次调用setState都进行一次更新, 那么意味着render函数会被频繁的调用, 界面重新渲染, 这样效率很低;
    • 最好的办法应该是获取到多个更新, 之后进行批量更新, 放入到一个队列里, 进行一个合并, 批量更新;
  • 如果同步更新了state, 但是还没有执行render函数, 那么state和props不能保持同步;
    • state和props不能保持一致性, 会在开发中产生很多的问题;

如何获取异步的结果

  • 两种方式拿到最新的数据
// 方式一: 获取异步更新后的数据
// setState(更新的state, 回调函数)
this.setState({
  message: "你好哇, 李银河"
}, () => {
  console.log(this.state.message);
})

// 方式二: 生命周期函数, 获取异步更新的state
componentDidUpdate() {
  console.log(this.state.message);
}

有些情况下setState是同步更新的

  • 定时器延迟0秒钟, 定时器函数是异步的,


    放在外面.png
  • 情况一: 将setState放入到定时器中


    放在里面变成了同步的代码.png
情况二.png

setState一定是异步吗?

image.png
  • 其实分成两种情况:

    • 在组件生命周期或React合成事件中, setState是异步;
    • 在setTimeout或者原生dom事件中, setState是同步;
  • React框架不仅仅想要跑在浏览器里面, 也可以跑在原生里面, 原生控件对象, => 合成对象


    合成事件对象.png
  • 看源码, 看的最多的三个文件夹 react-reconclier react-dom react

  • 不同的上下文, 不同的时间


    源码.png

    返回同步处理或批量处理.png
  • 有一个优先级, 根据不同的值采取不同的处理方式

  • 链表的数据结构, 一个节点连着一个节点


    本质上是一个链表.png

setState数据的合并

  • 什么叫做数据的合并? 思考一个问题, 只传入一个, 另一个会不会消失? 没有消失, 源码中做了操作
this.state = {
  message: "Hello World",
  name: coderwhy
}

// 源码中的操作
Object.assign({}, this.state, {message: "你好哇, 李银河"})
  • 了解源码更放心大胆的写代码, 使用API
  • 了解真相你才能获得真正的自由

setState本身的合并

  • 源码内部进行了合并, do while循环, 遍历了队列
  • setState本身被合并
increment() {
  this.setState({
    counter: this.state.counter + 1
  })
  this.setState({
    counter: this.state.counter + 1
  })
  this.setState({
    counter: this.state.counter + 1
  })
}
源码, 每次合并都是一样的.png
  • 不希望被合并可以怎么做?
  • setState合并时进行累加
this.setState((prevState, props) => {
  return {
    counter: prevState.counter + 1
  }
});
this.setState((prevState, props) => {
  return {
    counter: prevState.counter + 1
  }
});
this.setState((prevState, props) => {
  return {
    counter: prevState.counter + 1
  }
});
把前一次的值传到里面.png

React更新机制

  • 我们在前面已经学习了React的渲染流程:


    渲染流程.png
  • React的更新流程

  • props/state改变, render函数重新执行, 产生新的DOM数, diff算法把原来的DOM和新的DOM进行对比, 计算出差异进行更新, 更新到真实的DOM


    更新流程.png
  • React在props或state发生改变时, 会调用React的render方法, 会创建一棵不同的树.

  • React需要基于这两棵不同的树之间的差别来判断如何有效的更新UI:

    • 如果一棵树参考另外一棵树进行完全比较更新, 那么即使是最先进的算法, 改算法的时间复杂度为O(n3), 其中n是树中元素的数量;
      image.png

      两棵树比较的算法.png
  • 于是, React对这个算法进行了优化, 将其优化成了O(n)

    • 同层节点之间相互比较, 不会跨节点比较, 只比较当前层;
    • 不同类型的节点, 产生不同的树结构;
    • 开发中, 可以通过key来指定那些节点在不同的渲染下保持稳定;

情况一: 对比不同类型的元素

  • 当节点为不同的元素, React会拆卸原有的数, 并且建立起新的树:


    image.png
  • 比如下面的代码更改:


    不会复用.png

情况二: 对比同一类型的元素

image.png

image.png

同类型的组件元素.png

情况三: 对子节点进行递归

  • 在默认条件下, 当递归DOM节点的子元素使, React会同事遍历两个子元素的列表; 当产生差异时, 生成一个mutation.


    image.png
对子节点进行递归.png
  • 但是如果我们是在中间插入一条数据:


    image.png
image.png

keys的优化

  • 我们在之前遍历一个列表时, 总是会提示一个警告⚠️, 让我们添加一个key属性


    image.png

render函数被调用

  • 我们使用之前的一个嵌套案例:
    • 在App中, 我们增加了一个计数器的代码


      image.png
  • 函数组件什么时候会被调用? 创建的时候会被调用一次,
  • 调用setState时会执行render(), 需要做一个优化, 需要调用的时候调用


    其他的东西都重新render了, 浪费性能.png

shouldComponentUpdate

  • 点击添加按钮阻断过程, 实现一个生命周期函数shouldComponentUpdate, 默认返回true, 改成false, 不会阻止第一次渲染
  • 应该是想要阻断的时候才阻断, 做一个判断
做一个判断.png
  • 一个项目里面有很多类, 每一个都需要做一个优化? 类组件特有的, 函数式组件如何优化?

PureComponent

image.png
  • 继承自PureComponent, 内部自动帮助做一件事情, 进行一个比较, 判断要不要重新调用


    源码, 默认返回true.png
浅层比较.png
浅层比较源码.png
  • 原因: 都继承自PureComponent, props没有改变, counter的值发生改变return true, render重新调用

  • 开发中只需要做浅层比较就行了, 开发文档中提到过, 最好不要做深层比较, 太耗性能


    官方文档.png
  • 函数式组件还是每次调用, 如何解决?

=> memo的使用

image.png
  • 函数组件如何优化? 导入memo高阶组件, 对另一个组件进行操作
const MemoHeader = memo(function Header() {})
  • ProductList也没有重新调用的原因, 上层Main组件继承自PureComponent

coderwhy的React核心技术与开发实战课程链接

少年~来做同学呀~.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345