SyMind / learning

路漫漫其修远兮,吾将上下而求索。
10 stars 1 forks source link

为什么要使用新的 JSX 转换 #3

Open SyMind opened 3 years ago

SyMind commented 3 years ago

相关的 RFC:https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#detailed-design

动机

React.createElement(...) 有以下几个问题

除了性能,这也是为了简化学习使用 React 时不得不掌握的大量概念。特别是,forwardRefdefaultProps 将不再是什么特别的东西。

设计细节

JSX 转换变更

Auto-import

理想情况下,您不需要编写任何导入来使用 JSX:

function Foo() {
  return <div />;
}

然后,它将被编译来包含这个依赖项。

import {jsx} from "react";
function Foo() {
  return jsx('div', ...);
}

将 key 从 props 中分开传递

jsx('div', props, key)

总是将 children 作为属性传递

createElement 中,children 以可变参数进行传递。在新的转换中,我们总是将它们添加到 props 对象中。

将它们以可变参数进行传递的原因是为了在 DEV 中区分静态子变量和动态子变量。我们可以传递一个布尔值或者使用两个不同的函数来区分它们。我的建议是将 <div>{a}{b}</div> 编译为 jsxs('div', {children: [a, b]}) ,将 <div>{a}</div> 编译为 jsx('div', {children:a})jsxs 函数指明顶部数组由 React 创建。此策略的优点是,即使你没有针对 PROD 和 DEV 进行单独构建,我们仍然可以发出关键警告,并且在 PROD 下也不会产生任何开销。

废除『模块模式(module pattern)』组件

const Foo = (props) => {
  return {
    onClick() {
      //...
    }
    render() {
      return <div onClick={this.onClick.bind(this)} />;
    }
  }
};

它的存在会导致一些实现的复杂性。

这是很直接的升级。这是一种非常不常用的模式,大多数人都不知道。关键是你的类构造函数需要具有 Component.prototype.isReactComponent 属性并可以被 new 调用(即不是箭头函数)。即使你碰巧使用了模块模式,在原型中添加 isReactComponent 属性,并使用函数表达式而不是箭头函数。

function Foo(props) {
  return {
    onClick() {
      //...
    }
    render() {
      return <div onClick={this.onClick.bind(this)} />;
    }
  }
};
Foo.prototype = {isReactComponent: true};

这里的重要目的是,如果我们要在类和函数组件之间引入不同的语义,我们需要在调用它们之前就要知道我们要使用哪个语义。

废除函数组件的 defaultProps

defaultProps 在类中非常有用,因为 props 对象被传递给许多不同的方法,如生命周期、回调等。每一个都有自己的作用域。这导致使用 JS 默认参数变得困难,因为您必须在每个函数中反复确定相同的默认值。

class Foo {
  static defaultProps = {foo: 1};
  componentDidMount() {
    let foo = this.props.foo;
    console.log(foo);
  }
  componentDidUpdate() {
    let foo = this.props.foo;
    console.log(foo);
  }
  componentWillUnmount() {
    let foo = this.props.foo;
    console.log(foo);
  }
  handleClick = () => {
    let foo = this.props.foo;
    console.log(foo);
  }
  render() {
    let foo = this.props.foo;
    console.log(foo);
    return <div onClick={this.handleClick} />;
  }
}

但是,在函数组件中,实际上不需要这种模式,因为您可以只使用 JS 默认参数,并且通常这些值的所有使用的位置都在同一作用域内。

function Foo({foo = 1}) {
  useEffect(() => {
    console.log(foo);
    return () => {
      console.log(foo);
    };
  });
  let handleClick = () => {
    console.log(foo);
  };
  console.log(foo);
  return <div onClick={handleClick} />;
}

当在没有 .prototype.isReactComponent 的组件上使用 defaultProps 时,createElement 将发出警告。这包括那些特殊的组件,如 forwardRefmemo

如果 props 整体进行传递,那么升级将变得困难,不过你总是可以在需要时对它进行重构。

function Foo({foo = 1, bar = "hello"}) {
  let props = {foo, bar};
  //...
}