事件处理与更新

Misaka10032事件处理高阶函数diff算法大约 5 分钟

事件处理

  1. 通过 onClick、onMousemove、onMouseenter 等属性指定事件处理函数(注意大小写)
  2. React 使用的是自定义事件,而不是原生 DOM 事件,为了更好的兼容性
  3. React 中的事件是通过事件委托方式处理的(委托给组件外层的元素)
  4. 通过event.target可以得到发生事件的 DOM 元素对象

高阶函数

如果一个函数符合下面 2 个规范中的任何一个,那该函数就是高阶函数

  1. 若 A 函数,接收的参数是一个函数,那么 A 就可以称之为高阶函数
  2. 若 A 函数,调用的返回值依然是一个函数,那么 A 就可以称之为高阶函数

常见高阶函数:Promise、定时器、数组高阶方法(map、filter 等)

函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式

注意:

  1. 父子通信的函数调用需要使用柯里化维持 this 对象
  2. JSX 循环时需要使用高阶函数 map 返回 JSX 对象

JSX 循环

以下是 state 中的数组元素循环生成 JSX 的写法

import { Component } from "react";

export default class Count extends Component {
  state = {
    count: 0,
    structure: ["苹果", "香蕉", "牛奶"],
  };

  add = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <div>{this.state.count}</div>
        {/* 注意,此处禁止写 this.state.count++,因为自增运算符是对原count值的修改,React禁止对state的直接修改 */}
        <button onClick={this.add}>计数按钮</button>
        {this.state.structure.map((item, index) => (
          <div key={index}>{item}</div>
        ))}
      </div>
    );
  }
}

key 的原理

key 的作用

简单地说,key 是虚拟 DOM 对象的标识符,在更新显示时 key 起着极其重要的作用

详细地说,当状态中的数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】, 随后 React 进行【新虚拟 DOM】与【旧虚拟 DOM】的 diff 比较,比较规则如下:

a.旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:

(1)若虚拟 DOM 中内容没变,直接使用之前的真实 DOM

(2)若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM

用 index 作为 key 可能会引发的问题

1.若对数据进行:逆序添加、逆序删除等破坏顺序的操作,会产生没有必要的真实 DOM 更新,界面效果没问题,但效率低。

2.如果结构中还包含输入类的 DOM:会产生错误 DOM 更新,界面有问题。

3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。

开发中如何选择 key

  1. 最好使用每条数据的唯一标识作为 key,比如 id、手机号、身份证号、学号等唯一值。
  2. 如果确定只是简单展示数据,用 index 也是可以的。
react-diff更新
react-diff更新

组件树更新原理

React 中全部的组件树会转换为 Fiber 节点,这些 Fiber 节点连起来形成 Fiber 树(又称 vdom 树),Fiber 树中判断节点的复用条件有:

  1. Fiber 节点的 type 属性是否发生变化;
  2. Fiber 节点的 props 属性是否发生变化;
  3. Fiber 节点的 state 是否发生变化;
  4. Fiber 节点的 context 是否发生变化;

如果以上的条件都为否,那么就可以判断这个节点没有发生变化,不需要重新渲染

但是!!!因为 React 并没有采用 Vue 那样的方式,以组件作为更新单位,中等粒度地找出哪些节点发生了更新,所以 React 每次触发更新都会从头开始生成一个新的 Fiber 树,然后与上一次更新之后生成的旧 Fiber 树对比,判断上述几个条件,找出发生了变化的 Fiber 节点,再将其更新到页面上

这样逻辑上虽然可行,但是每次都从头开始对比整棵树,难免会做很多冗余比较。因此 React 触发更新之后,实际会从当前触发更新的节点开始向上对其所有的父节点打上有子节点需要更新的标记,这时会新增第 5 项判断:子节点是否存在变化,满足前 4 条件后,如果连同满足额外的第 5 个条件,那么当前节点及其所有子节点都可以跳过对比直接复用!

所以完整的五项更新 or 复用判断条件是:

  1. Fiber 节点的 type 属性是否发生变化;
  2. Fiber 节点的 props 属性是否发生变化;
  3. Fiber 节点的 state 是否发生变化;
  4. Fiber 节点的 context 是否发生变化;
  5. Fiber 节点是否存在子节点更新标记

React 组件树中有组件触发更新后,从顶层父节点开始递归遍历各个子节点

  • 前 4 项条件有一项发生变化,当前组件及其下属子组件全部 rerender
  • 前 4 项条件有一项发生变化,第 5 项存在更新标记,父组件自身不会 rerender,但是子组件继续进入对比判断
  • 上述 5 项条件均无变化的情况,父组件及下属子组件均不触发 rerender