React - 解构时的 defaultProps 与 ES6 默认参数(性能问题)

React - defaultProps vs ES6 default params when destructuring (performances issues)

我刚刚在我的 无状态功能组件.

中设置默认值时遇到了一个关于 React 性能的问题

这个组件有一个 defaultProps 定义了 row: false,但我不喜欢它,因为 defaultProps 最后的 的文件,这实际上使它更难看到。因此,我们不知道默认值 属性。所以我直接把它移到函数声明中,并使用ES6默认参数赋值。

const FormField = ({
  row = false,
  ...others,
}) => {
  // logic...
};

但后来我们与一位同事争论这个是不是个好主意。因为这样做可能看起来微不足道,但也可能对性能产生很大影响,因为 react 不知道 默认值。

我相信在这种情况下,这是微不足道的。因为它是布尔值而不是 object/array,因此在 协调 期间不会被视为不同的值。


但是,现在让我们看一个更高级的用例:

const FormField = ({
  input: { name, value, ...inputRest },
  label = capitalize(name),
  placeholder = label,
  row = false,
  meta: { touched, error, warning },
  ...others,
}) => {
  // logic...
};

在这里,我将 placeholder 的值基于 label,它本身基于 input.name。使用带有默认参数值的 ES6 解构使得整个事情变得非常容易 write/understand 并且它的工作就像一个魅力。

但这是个好主意吗?如果没有,那么您将如何正确地做到这一点?

看看您拥有的高级 use-case,您正在向组件添加不必要的属性。 labelplaceholder 取决于传入的其他属性,在我看来,不应列为组件本身的参数。

如果我试图在我的应用程序中使用 <FormField /> 并且我需要查看特定组件具有哪些依赖项,我会有点困惑为什么要创建参数基于其他参数。我会将 labelplaceholder 移动到函数的主体中,因此 明确 它们不是组件依赖项,而只是副作用。

就此处的性能而言,我不确定这两种方式是否会有显着差异。无状态组件实际上没有有状态组件那样的 'backing instance',这意味着没有内存对象跟踪组件。我相信它只是一个传入参数并返回视图的纯函数。

同理.. 添加 PropTypes 将有助于类型检查。

const FormField = ({
  input: { name, value, ...inputRest },
  row = false,
  meta: { touched, error, warning },
  ...others,
}) => {
  const label = capitalize(name),
  const placeholder = label,

  return (
    // logic
  );
};

FormField.propTypes = {
  input: PropTypes.shape({
    name: PropTypes.string.isRequired,
    value: PropTypes.string,
  }).isRequired,
  meta: PropTypes.shape({
    touched: PropTypes.bool.isRequired,
    error: PropTypes.bool.isRequired,
    warning: PropTypes.bool.isRequired,
  }).isRequired,
  row: PropTypes.bool.isRequired,
};

我在 Discord #reactiflux 频道上与几个人交谈,实际上得到了我想要的答案。

React 组件基本上有三个用例,在其中一些用例中,解构 参数会影响性能,因此了解幕后发生的事情很重要。

无状态功能组件

const MyComponent = ({ name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() }) => {
  return (
    <div>{displayName}</div>
  );
};

这是一个无状态的功能组件。没有状态,它是函数式的,因为它不是 Class 实例,而是一个简单的函数。

在这种情况下,没有生命周期,那里不能有 componentWillMountshouldComponentUpdateconstructor。而且因为没有生命周期的管理,所以对性能没有任何影响。此代码完全有效。有些人可能更喜欢在函数体内处理默认的 displayName 值,但最终这并不重要,它不会影响性能。

无状态非功能组件

(不要这样做!)

class MyComponent extends React.Component {
    render() {
        const { name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() } = this.props;
        return (
            <div>{displayName}</div>
          );
    }
}

这是一个无状态的非功能性组件。没有状态,但它不是“功能性的”,因为它是 class。并且因为它是 class,扩展 React.Component,这意味着您将拥有一个生命周期。您可以在那里设置 componentWillMountshouldComponentUpdateconstructor

而且,因为它有生命周期,所以这个组件的写法不好。但是为什么?

简单地说,React 提供了一个 defaultProps 属性,来处理默认的 props 值。而且在处理非功能组件的时候用它其实更好,因为它会被所有依赖this.props.

的方法调用

前面的代码片段创建了名为 namedisplayName 的新局部变量,但是 默认值仅适用于此 render 方法!.如果您希望为每个方法应用默认值,例如来自 React 生命周期(shouldComponentUpdate 等)的方法,那么您 必须 使用 defaultProps 代替。

所以,前面的代码实际上是一个错误,可能导致对name默认值的误解。

为了获得相同的行为,应该如何编写它:

class MyComponent extends React.Component {
    render() {
        const { name, displayName = humanize(name), address } = this.props;
        return (
            <div>{displayName}</div>
          );
    }
}

MyComponent.defaultProps = {
    name: 'John Doe',
    address: helper.getDefaultAddress(),
};

这样更好。因为如果未定义,名称将始终为 John Doeaddress 默认值也被处理,但不是 displayName... 为什么?

好吧,我还没有找到解决这个特殊用例的方法。因为 displayName 应该基于 name 属性,我们在定义 defaultProps 时无法访问(AFAIK)。我看到的唯一方法是直接在 render 方法中处理它。也许有更好的方法。

address 属性 没有这个问题,因为它不是基于 MyComponent 属性,而是依赖于不需要道具的完全独立的东西。

有状态的非功能组件

它的工作原理与“Stateless non-functional component”完全一样。因为仍然存在生命周期,所以行为将是相同的。事实上,组件中有一个额外的内部 state 不会改变任何东西。


我希望这有助于理解对组件使用解构时的理解。我真的很喜欢功能性方式,恕我直言,它更简洁(为简单起见 +1)。

您可能更喜欢始终使用 defaultProps,无论是使用功能组件还是非功能组件,它都是有效的。 (+1 表示一致性)

请注意“需要”使用 defaultProps 的非功能组件的生命周期。但最终选择权在你手中 ;)


编辑 10-2019:defaultProps 最终会在未来的某个时候从 React API 中删除,请参阅 and https://github.com/reactjs/rfcs/pull/107 了解 RFC。

defaultProps 和默认函数参数之间的一大区别是前者将根据 propTypes 检查。 eslint-plugin-reactrequire-default-props规则很好地解释了这一点。

One advantage of defaultProps over custom default logic in your code is that defaultProps are resolved by React before the PropTypes typechecking happens, so typechecking will also apply to your defaultProps. The same also holds true for stateless functional components: default function parameters do not behave the same as defaultProps and thus using defaultProps is still preferred.