React 17对于createElement部分的变化

2022年3月24日 ... ☕️ 4 min read

React在这份计划里,描述了如何简化React.createElement的流程,最终目标是移除forwardRef。大致内容有:

  • 弃用 module pattern 组件.
  • 弃用函数组件的 defaultProps .
  • 弃用对象包含 key 的展开符(如果使用展开符来拆分props,props不能包含key).
  • 弃用存储字符串的 refs (以及移除生产模式下 _owner 字段).
  • 把 ref 的提取,移动到 class 渲染(render)时和 forwardRef 渲染时.
  • 把 defaultProps 解析,放到 class 渲染时(函数组件不再有defaultProps).
  • JSX 转译的更改,采用新的元素创建方法,特点有

    • 始终把children作为props传递.
    • key 和其他 props 分开传递.
    • 在 DEV 环境,

      • 传递一个标志位,判断当前元素是否是静态.
      • __source 和 __self 和其他 props 分开传递.

综上,目标是把元素创建演化到如下形式:

function jsx(type, props, key) {
  return {
    $$typeof: ReactElementSymbol,
    type,
    key,
    props,
  };
}

动机

早期的React设计是针对class组件的,但是在推出函数组件,尤其是hooks之后,很多东西不太对劲了,或者说应该简化一些。

对于defaultProps的取舍

defaultProps还是有自己明确的历史地位的:他可以同时在class组件和函数组件里,为组件设置默认值。并且语法明确,简单直接。

但是随之也有很多问题:

1、效率

在每次createElement的时候,React都会检查defaultProps并进行至少一次赋值操作。示例显示,少量(2个)使用defaultProps,即会带来至少2/3的时间损耗。

With defaultProps, createElement time: 0.01123046875ms
Without defaultProps, createElement time: 0.003662109375ms

尤其是到了函数组件里,由于组件的函数并不是执行一次,所以效率相比class组件,更低了。

2、defaultPropsReact.lazy里使用有问题

在React代码里有这么一个警告,意思是不支持使用lazy加载的组件修改默认属性。要么在定义的时候修改,要么在外面包上一层

warning(
  false,
  'React.lazy(...): It is not supported to assign `defaultProps` to ' +
  'a lazy component import. Either specify them where the component ' +
  'is defined, or create a wrapping component around it.',
);

3、在使用TS时,需要额外添加配置

通常,我们使用TS写组件只要如下形式:

type GreetProps = { age?: number };
const Greet = (props: GreetProps) => // etc

如果需要搭配defaultProps怎么办?

最直接的办法,我们需要修改GreetProps的类型,使它支持defaultProps的类型

const defaultProps = {
  age: 21,
};
type GreetProps = { age: number } & typeof defaultProps;
const Greet = (props: GreetProps) => {
  // etc
};
Greet.defaultProps = defaultProps;

参考文档: Typing defaultProps

如何使用

新的改动在React 17已经内置了,而且就像当时引入fiber一样,给了一个选项来渐进式替代。这次主要是和Babel合作的。使用新的JSX转换:

1、可以单独使用JSX而无需显式引入React(不用顶部import了); 2、JSX可以改善打包后bundle的大小(赢了); 3、减少学习成本(目前没发现)。

选项切换是放在babel参考配置里的:

// 如果你使用的是 @babel/preset-react
{
  "presets": [
    ["@babel/preset-react", {
      "runtime": "automatic"
    }]
  ]
}
// 如果你使用的是 @babel/plugin-transform-react-jsx
{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "runtime": "automatic"
    }]
  ]
}

// 旧的转换的默认选项为 {"runtime": "classic"}

我们通过实际例子比较一下

在之前的版本中,React.createElement接受3个参数:

React.createElement(
  type,
  [props],
  [...children]
)

假设创建了一个函数组件

function hello() {
return <div key="the_key">Hello world!</div>;
}

// 转译为
function hello() {
  return /*#__PURE__*/React.createElement("div", {
    key: "the_key"
  }, "Hello world!");
}

那么如果以后新版本出来,会是如下形式

import { jsx as _jsx } from "react/jsx-runtime";

function hello() {
  return /*#__PURE__*/_jsx("div", {
    children: "Hello world!"
  }, "the_key");
}

可以看到区别: 把children移动到props里、key移动到props外面。

总结

可以看出,React的趋势,是朝着函数组件、hooks发展的,并且为了优化预设了很多微小的改动,未来肯定会越来越好用。

#react

SideEffect is a blog for front-end web development.
Code by Axiu / rss