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

Posted

技术标签:

【中文标题】React - 解构时的 defaultProps 与 ES6 默认参数(性能问题)【英文标题】:React - defaultProps vs ES6 default params when destructuring (performances issues) 【发布时间】:2017-05-20 04:52:27 【问题描述】:

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

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

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

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

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


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

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

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

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

【问题讨论】:

就我个人而言,FormField 中的参数列表对我来说太重了。如果它不是立即可读的,它会增加不必要的复杂性恕我直言。我的意思是,你必须解释它。也就是说,默认某些传入值是完全有效的,Redux 使用其 initialState 对象:redux.js.org/docs/basics/Reducers.html 我同意它越来越重。绝对是我目前写的最复杂的,不过IMO已经够清楚了,感谢链接,我去看看! 酷,特别是他开始的部分“一个巧妙的技巧是使用 ES6 默认参数语法......” 是的。但这是针对减速器的,而不是针对组件的。这不是同一个生命周期。这对 reducer 非常有用,因为它只是初始值。我对组件的担心是因为 react 可能认为值发生了某种变化,因为它不知道初始值,因为它从未定义为 props,而是通过默认的 ES6 参数定义的。 但是无状态组件没有生命周期,这就是您的示例。 【参考方案1】:

查看您拥有的高级用例,您正在向组件添加不必要的属性。 labelplaceholder 依赖于传入的其他属性,我认为不应将其列为组件本身的参数。

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

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

在同一个注释中.. 添加 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,
;

【讨论】:

你是对的,但这是因为我希望能够拥有与name 无关的label/placeholder,我选择以这种方式构建它。它更灵活。 当然,在属性中保留labelplaceholder 可能是个好主意,这样它就可以被覆盖,但在组件的主体中进行条件检查。对我来说,这可能比仅仅读取所需的参数并做出相应的反应更令人困惑。最后,这确实有效,我认为它不会影响 react 处理此函数的性能。【参考方案2】:

我在 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。因为它是一个类,扩展React.Component,这意味着你将拥有一个生命周期。你可以在那里有componentWillMountshouldComponentUpdateconstructor

而且,因为它有生命周期,所以编写这个组件的方式糟糕。但是为什么呢?

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

之前的代码 sn-p 创建了名为 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 属性,而是依赖于完全独立的东西,不需要道具。

有状态的非功能组件

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


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

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

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


Edit 10-2019:defaultProps 最终将在未来某个时候从 React API 中删除,请参阅https://***.com/a/56443098/2391795 和 https://github.com/reactjs/rfcs/pull/107 以获取 RFC。

【讨论】:

我认为持有可以从其他道具派生的道具是一种不好的做法。 displayName 仅在显示时使用(即在 render 函数中)。这应该是它的范围。 这是一个观点。另外,我的示例可能被某些人认为是“不好的做法”,但最终可能不是,这取决于您实际想要做什么。可能存在应该从其他道具派生的有效案例,我的示例不是。 @Vadorequest 希望看到这个答案的更新,因为钩子被引入了 React。 @LazarLjubenović ***.com/a/56443098/998919 tl;dr defaultProps 将被新的有状态功能组件弃用:) 因为 defaultProps 和类已被弃用以支持功能组件(没有挂钩),所以这个答案不像以前那么有用。但是,如果您使用基于类的组件,它仍然是准确的。【参考方案3】:

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

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

【讨论】:

是的。在不使用传统 JS 时从该功能中受益是很有趣的。使用 TS 时,我根本不关心 PropTypes,因为它们会重复警告并使用 TS 本身进行配置。他们都做不同的工作,并不是 100% 相同,但我个人的偏好是使用 TS 而不是 PropTypes 检查,而不是同时使用两者。

以上是关于React - 解构时的 defaultProps 与 ES6 默认参数(性能问题)的主要内容,如果未能解决你的问题,请参考以下文章

React defaultProps null vs undefined

Typescript React 空函数作为 defaultProps

可以将 propTypes 和 defaultProps 作为静态 props 放在 React 类中吗?

React - 即使定义了 defaultProps,TypeScript 也会将 undefined 添加到 prop

TypeScript 3.0下react默认属性DefaultProps解决方案

Typescript 和 React defaultProps 仍被视为可能未定义