React 17 带来了哪些改变

250 阅读4分钟

最重要的是以下三点:

  • 新的 JSX 转换逻辑
  • 事件系统重构
  • Lane 模型的引入

1. 重构 JSX 转换逻辑

在过去,如果我们在 React 项目中写入下面这样的代码:

function MyComponent() {  return <p>这是我的组件</p>} 

React 是会报错的,原因是 React 中对 JSX 代码的转换依赖的是 React.createElement 这个函数。因此但凡我们在代码中包含了 JSX,那么就必须在文件中引入 React,像下面这样:

import React from 'react';function MyComponent() {  return <p>这是我的组件</p>}

而 React 17 则允许我们在不引入 React 的情况下直接使用 JSX。这是因为在 React 17 中,编译器会自动帮我们引入 JSX 的解析器,也就是说像下面这样一段逻辑:

function MyComponent() {  return <p>这是我的组件</p>}

会被编译器转换成这个样子:

import {jsx as _jsx} from 'react/jsx-runtime';function MyComponent() {  return _jsx('p', { children: '这是我的组件' });}​

react/jsx-runtime 中的 JSX 解析器将取代 React.createElement 完成 JSX 的编译工作,这个过程对开发者而言是自动化、无感知的。因此,新的 JSX 转换逻辑带来的最显著的改变就是降低了开发者的学习成本。

react/jsx-runtime 中的 JSX 解析器看上去似乎在调用姿势上和 React.createElement 区别不大,那么它是否只是 React.createElement 换了个马甲呢?当然不是,它在内部实现了 React.createElement 无法做到的性能优化和简化。在一定情况下,它可能会略微改善编译输出内容的大小

2. 事件系统重构

事件系统在 React 17 中的重构要从以下两个方面来看:

  • 卸掉历史包袱
  • 拥抱新的潮流

2.1 卸掉历史包袱:放弃利用 document 来做事件的中心化管控

React 16.13.x 版本中的事件系统会通过将所有事件冒泡到 document 来实现对事件的中心化管控

这样的做法虽然看上去已经足够巧妙,但仍然有它不聪明的地方——document 是整个文档树的根节点,操作 document 带来的影响范围实在是太大了,这将会使事情变得更加不可控

在 React 17 中,React 团队终于正面解决了这个问题:事件的中心化管控不会再全部依赖 document,管控相关的逻辑被转移到了每个 React 组件自己的容器 DOM 节点中。比如说我们在 ID 为 root 的 DOM 节点下挂载了一个 React 组件,像下面代码这样:

const rootElement = document.getElementById("root");ReactDOM.render(<App />, rootElement);​

那么事件管控相关的逻辑就会被安装到 root 节点上去。这样一来, React 组件就能够自己玩自己的,再也无法对全局的事件流构成威胁了

2.2 拥抱新的潮流:放弃事件池

在 React 17 之前,合成事件对象会被放进一个叫作“事件池”的地方统一管理。这样做的目的是能够实现事件对象的复用,进而提高性能:每当事件处理函数执行完毕后,其对应的合成事件对象内部的所有属性都会被置空,意在为下一次被复用做准备。这也就意味着事件逻辑一旦执行完毕,我们就拿不到事件对象了,React 官方给出的这个例子就很能说明问题,请看下面这个代码

function handleChange(e) {  // This won't work because the event object gets reused.  setTimeout(() => {    console.log(e.target.value); // Too late!  }, 100);} 

异步执行的 setTimeout 回调会在 handleChange 这个事件处理函数执行完毕后执行,因此它拿不到想要的那个事件对象 e

要想拿到目标事件对象,必须显式地告诉 React——我永远需要它,也就是调用 e.persist() 函数,像下面这样:

function handleChange(e) {  // Prevents React from resetting its properties:  e.persist();  setTimeout(() => {    console.log(e.target.value); // Works  }, 100);} 

在 React 17 中,我们不需要 e.persist(),也可以随时随地访问我们想要的事件对象。

3. Lane 模型的引入

初学 React 源码的同学由此可能会很自然地认为:优先级就应该是用 Lane 来处理的。但事实上,React 16 中处理优先级采用的是 expirationTime 模型

expirationTime 模型使用 expirationTime(一个时间长度) 来描述任务的优先级;而 Lane 模型则使用二进制数来表示任务的优先级

lane 模型通过将不同优先级赋值给一个位,通过 31 位的位运算来操作优先级。

Lane 模型提供了一个新的优先级排序的思路,相对于 expirationTime 来说,它对优先级的处理会更细腻,能够覆盖更多的边界条件。

公众号:乘风破浪的前端 专注于react和javaScript知识分享