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、defaultProps
在React.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发展的,并且为了优化预设了很多微小的改动,未来肯定会越来越好用。