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

IT技术 performance reactjs ecmascript-6 destructuring
2021-04-12 16:28:53

我刚刚在我的一个无状态功能组件中设置默认值时遇到了一个关于 React 性能的问题

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

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

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

我相信在这种情况下,这是微不足道的。因为它是一个布尔值而不是一个对象/数组,因此在reconciliation期间不会被视为不同的值


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

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

在这里,我基于placeholderfrom的值label,它本身基于input.name使用带有默认参数值的 ES6 解构使整个事情变得非常容易编写/理解,并且它的工作原理非常迷人。

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

3个回答

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

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

无状态功能组件

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

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

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

无状态非功能组件

(不要这样做!)

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

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

而且,因为它有一个生命周期,这个组件的编写方式是不好的但为什么?

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

前面的代码片段创建了名为nameand 的新局部变量displayName,但默认值仅适用于该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 Doe如果未定义,名称将始终存在address默认值也被处理了,但没有displayName......为什么?

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

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

有状态的非功能组件

它的工作原理与“无状态非功能组件”完全相同。因为仍然存在生命周期,所以行为将是相同的。state组件中有一个额外的内部结构这一事实不会改变任何东西。


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

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

请注意“需要”使用defaultProps. 但最终选择总是你的;)


编辑 10-2019:defaultProps 最终将在未来某个时候从 React API 中删除,请参阅https://stackoverflow.com/a/56443098/2391795https://github.com/reactjs/rfcs/pull/107对于 RFC。

@Vadorequest 现在很想看到这个答案的更新,因为钩子被引入到 React 中。
2021-05-28 16:28:53
@LazarLjubenović stackoverflow.com/a/56443098/998919 tl;对于新的有状态功能组件,将弃用 defaultProps 博士 :)
2021-05-28 16:28:53
我认为持有可以从其他道具派生的道具是一种不好的做法。displayNamein 仅在显示时使用(意思是在render函数中)。这应该是它的范围。
2021-06-01 16:28:53
这是一个观点。此外,我的示例可能被某些人视为“不良做法”,但最终可能并非如此,这取决于您实际想要做什么。在某些情况下,props 应该从其他 props 派生出来,但我的例子不是。
2021-06-11 16:28:53
因为defaultProps和 类已被弃用而支持功能组件(不带钩子),所以这个答案不像以前那样有用。但是,如果您使用基于类的组件,它仍然是准确的。
2021-06-16 16:28:53

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

defaultProps代码中自定义默认逻辑的优势之一defaultProps在类型检查PropTypes发生之前由 React 解决,因此类型检查也将适用于您的defaultProps. 对于无状态函数组件也是如此:默认函数参数的行为不一样defaultProps,因此使用defaultProps仍然是首选。

真的。在不使用传统 JS 时从该功能中受益很有趣。使用 TS 时,我根本不关心 PropTypes,因为它们与 TS 本身重复警告和配置。它们都做不同的工作,并不是 100% 相同,但我个人更喜欢使用 TS 而不是 PropTypes 检查,而不是同时使用两者。
2021-06-21 16:28:53

查看您拥有的高级用例,您正在向组件添加不必要的属性。label并且placeholder依赖于传入的其他属性,在我看来,不应作为组件本身的参数列出。

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

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

在同一个注释上.. 添加 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,
};
当然,也许将label保留placeholder在属性中是个好主意,因此它可以覆盖,但在组件主体中进行条件检查。对我来说,这可能比仅仅读取所需的参数并做出相应的反应更令人困惑。最后,这确实有效,我认为它不会影响 React 如何处理此函数的性能。
2021-05-31 16:28:53
你是对的,但它是因为我想必须有能力label/placeholder不相关的name,我选择建立这种方式。它更灵活。
2021-06-20 16:28:53