- 原文地址:How to NOT React: Common Anti-Patterns and Gotchas in React
- 原文做者:NeONBRAND
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:MechanicianW
- 校对者:anxsec ClarenceC
什么是反模式?反模式是软件开发中被认为是糟糕的编程实践的特定模式。一样的模式,可能在过去一度被认为是正确的,可是如今开发者们已经发现,从长远来看,它们会形成更多的痛苦和难以追踪的 Bug。html
做为一个 UI 库,React 已经成熟,而且随着时间的推移,许多最佳实践也逐渐造成。咱们将从数千名开发者集体的智慧中学习,他们曾用笨方法(the hard way)学习这些最佳实践。前端
此言不虚!react
让咱们开始吧!android
在使用自定义函数做为组件属性以前你必须将你的自定义函数写在 constructor
中。若是你是用 extends
关键字声明组件的话,自定义函数(以下面的 updateValue
函数)会失去 this
绑定。所以,若是你想使用 this.state
,this.props
或者 this.setState
,你还得从新绑定。webpack
class app extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
this.updateValue = this.updateValue.bind(this);
}
updateValue(evt) {
this.setState({
name: evt.target.value
});
}
render() {
return (
<form>
<input onChange={this.updateValue} value={this.state.name} />
</form>
)
}
}
复制代码
有两种方法能够将自定义函数绑定到组件的 this
。一种方法是如上面所作的那样,在 constructor
中绑定。另外一种方法是在传值的时候做为属性的值进行绑定:ios
<input onChange={this.updateValue.bind(this)} value={this.state.name} />
复制代码
这种方法有一个问题。因为 .bind()
每次运行时都会建立一个函数,这种方法会致使每次 render
**函数执行时都会建立一个新函数。**这会对性能形成一些影响。然而,在小型应用中这可能并不会形成显著影响。随着应用体积变大,差异就会开始显现。这里 有一个案例研究。git
箭头函数所涉及的性能问题与 bind
相同。github
<input onChange={ (evt) => this.setState({ name: evt.target.value }) } value={this.state.name} />
复制代码
这种写法明显更清晰。能够看到 prop onChange
函数中发生了什么。可是,这也致使了每次 input
组件渲染时都会建立一个新的匿名函数。所以,箭头函数有一样的性能弊端。web
避免上述性能弊端的最佳方法是在函数自己的构造器中进行绑定。这样,在组件建立时仅建立了一个额外函数,即便再次执行 render
也会使用该函数。数据库
有一种状况常常发生就是你忘记在构造函数中去 bind
你的函数,而后就会收到报错(Cannot find X on undefined.)。Babel 有个插件可让咱们使用箭头语法写出自动绑定的函数。插件是 Class properties transform。如今你能够这样编写组件:
class App extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
// 看!无需在此处进行函数绑定!
}
updateValue = (evt) => {
this.setState({
name: evt.target.value
});
}
render() {
return (
<form>
<input onChange={this.updateValue} value={this.state.name} />
</form>
)
}
}
复制代码
this
的方法遍历元素集合时,key 是必不可少的 prop。key 应该是稳定,惟一,可预测的,这样 React 才能追踪元素。key 是用来帮助 React 轻松调和虚拟 DOM 与真实 DOM 间的差别的。然而,使用某些值集例如数组索引,可能会致使你的应用崩溃或是渲染出错误数据。
{elements.map((element, index) =>
<Display
{...element}
key={index}
/>
)
}
复制代码
当子元素有了 key,React 就会使用 key 来匹配原始树结构和后续树结构中的子元素。**key 被用于做身份标识。**若是两个元素有一样的 key,React 就会认为它们是相同的。当 key 冲突了,即超过两个元素具备一样的 key,React 就会抛出警告。
警告出现重复的 key。
这里 是 CodePen 上使用索引做为 key 可能致使的问题的一个示例。
被使用的 key 应该是:
数组索引是惟一且可预测的。然而,并不稳定。一样,随机数或时间戳不该被用做为 key。
因为随机数既不惟一也不稳定,使用随机数就至关于根本没有使用 key。即便内容没有改变,组件也会每次都从新渲染。
时间戳既不稳定也不可预测。**时间戳也会一直递增。**所以每次刷新页面,你都会获得新的时间戳。
一般,你应该依赖于数据库生成的 ID 如关系数据库的主键,Mongo 中的对象 ID。若是数据库 ID 不可用,你能够生成内容的哈希值来做为 key。关于哈希值的更多内容能够在这里阅读。
React 组件主要由三部分组成:state
,props
和标记(或其它组件)。props 是不可变的,state 是可变的。state 的改变会致使组件从新渲染。若是 state 是由组件在内部管理的,则使用 this.setState
来更新 state。关于这个函数有几件重要的事须要注意。咱们来看看:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
counter: 350
};
}
updateCounter() {
// 这行代码不会生效
this.state.counter = this.state.counter + this.props.increment;
// ---------------------------------
// 不会如预期生效
this.setState({
counter: this.state.counter + this.props.increment; // 可能不会渲染
});
this.setState({
counter: this.state.counter + this.props.increment; // this.state.counter 的值是什么?
});
// ---------------------------------
// 如期生效
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
}
}
复制代码
请注意第 11 行代码。若是你直接修改了 state,组件并不会从新渲染,修改也不会有任何体现。这是由于 state 是进行浅比较(shallow compare)的。你应该永远都使用 setState
来改变 state 的值。
如今,若是你在 setState
中经过当前的 state
值来更新至下一个 state (正如第 15 行代码所作的),React 可能不会从新渲染。这是由于 state
和 props
是异步更新的。也就是说,DOM 并不会随着 setState
被调用就当即更新。React 会将屡次更新合并到同一批次进行更新,而后渲染 DOM。查询 state
对象时,你可能会收到已通过期的值。文档也提到了这一点:
因为
this.props
和this.state
是异步更新的,你不该该依赖它们的值来计算下一个 state。
另外一个问题出现于一个函数中有屡次 setState
调用时,如第 16 和 20 行代码所示。counter 的初始值是 350。假设 this.props.increment
的值是 10。你可能觉得在第 16 行代码第一次调用 setState
后,counter 的值会变成 350+10 = **360。**而且,当第 20 行代码再次调用 setState
时,counter 的值会变成 360+10 = 370。然而,这并不会发生。第二次调用时所看到的 counter
的值仍为 350。**这是由于 setState 是异步的。**counter 的值直到下一个更新周期前都不会发生改变。setState 的执行在事件循环中等待,直到 updateCounter
执行完毕前,setState
都不会执行, 所以 state
的值也不会更新。
你应该看看第 27 和 31 行代码使用 setState
的方式。以这种方式,你能够给 setState
传入一个接收 currentState 和 currentProps 做为参数的函数。这个函数的返回值会与当前 state 合并以造成新的 state。
setState
是异步的所作的超级棒的解释setState
中使用函数而不是对象React 文档提到这也是反模式:
在 getInitialState 中使用 props 来生成 state 常常会致使重复的“事实来源”,即真实数据的所在位置。这是由于 getInitialState 仅仅在组件第一次建立时被调用。
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {
someValue: props.someValue,
};
}
}
复制代码
constructor
(getInitialState) 仅仅在组件建立阶段被调用。也就是说,constructor
只被调用一次。所以,当你下一次改变 props
时,state 并不会更新,它仍然保持为以前的值。
经验尚浅的开发者常常设想 props
的值与 state 是同步的,随着 props
改变,state
也会随之变化。然而,真实状况并非这样。
若是你须要特定的行为即你但愿 state 仅由 props 的值生成一次的话,可使用这种模式。state 将由组件在内部管理。
在另外一个场景下,你能够经过生命周期方法 componentWillReceiveProps
保持 state 与 props 的同步,以下所示。
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {
someValue: props.someValue,
};
}
componentWillReceiveProps(nextProps){
if (nextProps.inputValue !== this.props.inputValue) {
this.setState({ inputVal: nextProps.inputValue })
}
}
}
复制代码
要注意,关于使用 componentWillReceiveProps
有一些注意事项。你能够在文档中阅读。
最佳方法是使用状态管理库如 Redux 去 connect state 和组件。
在 React 中,若是你想使用 JSX 渲染你的组件,组件名必须以大写字母开头。
<MyComponent>
<app /> // 不会生效 :(
</MyComponent>
<MyComponent>
<App /> // 能够生效!
</MyComponent>
复制代码
若是你建立了一个 app
组件,以 <app label="Save" />
的形式去渲染它,React 将会报错。
使用非大写自定义组件时的警告。
报错代表 <app>
是没法识别的。只有 HTML 元素和 SVG 标签能够以小写字母开头。所以 <div />
是能够识别的,<app>
却不能。
你须要确保在 JSX 中使用的自定义组件是以大写字母开头的。
可是也要明白,声明组件无需听从这一规则。所以,你能够这样写:
// 在这里以小写字母开头是能够的
class primaryButton extends Component {
render() {
return <div />;
}
}
export default primaryButton;
// 在另外一个文件中引入这个按钮组件。要确保以大写字母开头的名字引入。
import PrimaryButton from 'primaryButton';
<PrimaryButton />
复制代码
以上这些都是 React 中不直观,难以理解也容易出现问题的地方。若是你知道任何其它的反模式,请回复本文。😀
我还写了一篇 能够帮助快速开发的优秀 React 和 Redux 包
若是你仍在学习如何构建 React 项目,这个含有两部分的系列文章 能够帮助你理解 React 构建系统的多个方面。
我写做 JavaScript,Web 开发与计算机科学领域的文章。关注我能够每周阅读新文章。若是你喜欢,能够分享本文。
关注我 @ Facebook @ Linkedin @ Twitter.
✉️ 订阅 CodeBurst的每周邮件 Email Blast, 🐦能够在Twitter 上关注 CodeBurst, 浏览 🗺️ The 2018 Web Developer Roadmap, 和 🕸️ 学习 Web 全栈开发。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。