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】:
查看您拥有的高级用例,您正在向组件添加不必要的属性。 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,
;
【讨论】:
你是对的,但这是因为我希望能够拥有与name
无关的label
/placeholder
,我选择以这种方式构建它。它更灵活。
当然,在属性中保留label
和placeholder
可能是个好主意,这样它就可以被覆盖,但在组件的主体中进行条件检查。对我来说,这可能比仅仅读取所需的参数并做出相应的反应更令人困惑。最后,这确实有效,我认为它不会影响 react 处理此函数的性能。【参考方案2】:
我在 Discord #reactiflux 频道上与几个人交谈,实际上得到了我正在寻找的答案。
React 组件基本上有三个用例,在其中的一些用例中,解构参数会影响性能,因此了解幕后发生的事情很重要。
无状态功能组件
const MyComponent = ( name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() ) =>
return (
<div>displayName</div>
);
;
这是一个无状态的功能组件。没有状态,它是函数式的,因为它不是Class
实例,而是一个简单的函数。
在这种情况下,没有生命周期,您不能在那里拥有componentWillMount
或shouldComponentUpdate
或constructor
。而且由于没有生命周期管理,因此对性能没有任何影响。此代码完全有效。有些人可能更喜欢在函数体内处理默认的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
,这意味着你将拥有一个生命周期。你可以在那里有componentWillMount
或shouldComponentUpdate
或constructor
。
而且,因为它有生命周期,所以编写这个组件的方式糟糕。但是为什么呢?
简单地说,React 提供了一个defaultProps
属性来处理默认的 props 值。而且在处理非功能性组件时使用它其实更好,因为它会被所有依赖this.props
的方法调用。
之前的代码 sn-p 创建了名为 name
和 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
属性,在定义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
相对于代码中自定义默认逻辑的一个优势是defaultProps
在PropTypes
类型检查发生之前由 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