[译] 你不知道的 React 最佳实践
React ⚛️
React 是一个用于开发用户界面的 JavaScript 库, 是由 Facebook 在 2013 年创建的。 React 集成了许多令人兴奋的组件、库和框架 [1]。 当然,开发人员也可以自己开发组件。
图片在最佳实践之前,我建议在开发 React 应用程序时使用测试驱动开发(TDD) [2]。 测试驱动开发意味着首先编写一个测试,然后根据测试开发你的代码,这样更容易识别出错误。
❝本文翻译自 Medium:https://towardsdatascience.com/react-best-practices-804def6d5215#c966, 已取得作者授权❤
❞
最佳实践
目录
- 文件组织
- 小型函数式组件
- 可重用的组件
- 删除冗余代码
- 索引(Index)作为键
- 不必要加的
<div>
- 只提供必要的注释
- 理解如何处理 'this'
- Props-State-Constructor
- 写完组件内容之后再考虑命名
- 注意 State 和 Rendering
- 避免在 setState 中使用对象
- 使用大写驼峰式名称
- 使用 prop-types
- 在 JavaScript 中写 CSS
- 测试
- 使用 ESLint,Prettier 和 Snippet 库
- 使用 React Developer 工具
1. 文件组织
图片文件组织不仅是 React 应用程序的最佳实践,也是其他应用程序的最佳实践。Create React App [3]程序的文件结构是组织 React 文件的一种可能的方式。 虽然不能说一种文件组织方式比另外一种更好,但保持文件的组织性非常重要。
在 React 中,随着应用不断变大,代码文件个数也会极具膨胀,且因为每个组件至少有一个与之关联的文件。
Assets 资源文件夹
创建一个 「assets」文件夹,其中包含顶层的 「CSS 文件」、 「images」和 「Fonts(字体)」文件。
Helpers 文件夹
维护一个 「helpers」文件夹,用于放置其他功能性的其文件。
「Components 文件夹」
将所有与组件相关的文件保存在一个文件夹中。 通常, 「components」文件夹包含多个组件文件,如测试文件 、CSS 和一个或多个组件文件。 如果只有特定组件使用任何次要组件,最好将这些小组件保存在 「components」文件夹中。 当您将大型组件保存在它们自己的文件夹中,而组件使用的小型组件保存在子文件夹中时,更容易理解文件层次结构。
利用 package.json 组织文件
开发人员主要将主组件文件命名为 **index.js **文件。 一旦你有了几个文件,这些文件都被命名为 index.js,导航起来就会变得很麻烦。 解决这个问题的方法是向每个组件文件夹添加 「package.json」文件,为相应的文件夹设置主入口点。
例如,对于按钮组件,主要入口点是 Button.js。 在每个文件夹中添加 package.json 并不是一个好的做法,但是它有助于轻松处理文件。 因此,我们可以在 src/components/button
文件夹中添加以下 package.json 文件。
{
"main": "Button.js"
}
根据风格组织
当您在 Redux 项目中使用 Redux 时,您可以根据项目使用 「Rails」风格、 「Domain」风格或“ 「Ducks」”模式的文件夹结构。
在 「Rails」风格的模式中,创建单独“ action”、“ constants”、“ reducers”、“ containers”和“ components” 文件夹。
/actions/user.js
/components/user.js
/reducers/user.js
/containers/index.js
在 「Domain」样式模式中,每个特性或域使用单独的文件夹,可能每个文件类型使用子文件夹。
/users/components/index.js
/users/actions/index.js
/users/reducers/index.js
“ 「Duck」”模式类似于域样式,但它通常通过在同一文件中定义 「actions」和 「reducers」来显式地将它们联系在一起。 下面就是一个组织到一起的名为widgets的module:
// widgets.js
// Actions
const LOAD = 'my-app/widgets/LOAD';
const CREATE = 'my-app/widgets/CREATE';
const UPDATE = 'my-app/widgets/UPDATE';
const REMOVE = 'my-app/widgets/REMOVE';
// Reducer
export default function reducer(state = {}, action = {}) {
switch (action.type) {
// do reducer stuff
default: return state;
}
}
// Action Creators
export function loadWidgets() {
return { type: LOAD };
}
export function createWidget(widget) {
return { type: CREATE, widget };
}
export function updateWidget(widget) {
return { type: UPDATE, widget };
}
export function removeWidget(widget) {
return { type: REMOVE, widget };
}
新团队建议用 「Duck」风格来开发 「React」应用。 当团队成熟的时候,会开始使用 「rails」风格。 「Rails」的优势在于可以轻松地理解项目。
Dan Abramov 在 推特上 发布了一个解决方案
图片❝移动文件,直到感觉合适为止。
❞
这正是你应该做的。你应该移动文件,直到它们感觉对了为止。
2. 小型函数式组件
众所周知,React 对大型组件也能驾轻就熟。 但是如果我们把它们分成小尺寸,我们可以重复使用它们。 小型组件更容易阅读、测试、维护和重用。 React 中的大多数初学者甚至在不使用组件状态或生命周期方法的情况下也创建类组件。 相比于类组件,函数组件更写起来更高效。
import React, { Component } from 'react';
class Button extends Component {
render() {
const { children, color, onClick } = this.props;
return (
<button onClick={onClick} className={`Btn ${color}`}>
{children}
</button>
);
}
}
export default Button;
上面的 Class 组件可以如下所示编写。
import React from 'react';
export default function Button({ children, color, onClick }) {
return (
<button onClick={onClick} className={`Btn ${color}`}>
{children}
</button>
);
}
使用函数式组件的优势。
- 更少的代码
- 更容易理解
- 无状态
- 更容易测试
- 没有
this
绑定。 - 更容易提取较小的组件。
当你使用函数组件时,您无法在函数式组件中控制 re-render
过程。 当某些东西发生变化,React 将 re-render
函数式组件。 如果使用 Component 组件,你可以控制组件的渲染,在以前的 React 版本有一个解决方案使用 React.Purecomponent
。Purecomponent 允许对 props
和 state
执行浅比较。 当 props
或者 state
发生变化时,组件将重新渲染。 否则,PureComponent 将跳过 re-render
并重用上次的 rendered
的结果。
在 React v16.6.0之后,React 引入了一个新特性,那就是memo [4]。 Memo 将 props 进行浅比较。 当 props
或者 state
发生变化时,组件将重新渲染。 基于比较的 React 要么重用上次渲染的结果,要么重新渲染。 Memo 允许您创建一个纯粹的函数组件,使得即使是函数式组件也能控制组件的渲染, 这样我们不需要使用有状态组件和 PureComponent。
组件,图片来源: https://www.kirupa.com/react/images/c_app_144.png
3. 可重用的组件 ♻️
每个函数式组件应该有一个函数,这意味着一个函数式组件等于一个函数。 当您使用一个函数创建一个函数式组件时,您可以提高该组件的可重用性。
4. 删除冗余代码 ️
不仅在 React 中,在所有的应用程序开发中,通用的规则都是尽可能保持代码的简洁和小巧。 React 最佳实践指示保持无错误的代码和精辟的代码。 不要重复自己(DRY)是软件开发的一个原则,致力于最小化软件模式的重复,用抽象代替它,或者使用数据规范化来避免冗余。 在编写代码时,你可以使用自己的风格指南,或者使用一个流行的成熟的风格指南(Airbnb react / jsx Style Guide [5],Facebook Style Guide [6]等)。 如果你开始使用其中一个代码风格,请不要和其他的代码风格搞混。
图片来源: https://quotefancy.com/quote/46568/lemony-snicket-don-t-repeat-yourself-it-s-not-only-repetitive-it-s-redundant-and-people
5. 索引作为键
当创建一个 JSX 元素数组时,React 需要给元素添加一个 key 属性。而这通常是通过使用 map 函数来完成的,所以会导致人们使用 Index 来设置 Key属性。 这太糟糕了! React 使用 key 属性跟踪数组中的每个元素,这是由于数组具有折叠特性。 但是如果使用 Index 来作为 Key 属性,那么在遍历生成有状态的类组件数组时,通常会导致错误,所以你应该避免使用 Index 作为 Key 属性。
6. 不必要加的 div
在创建 React 组件时,重要的是要记住,您仍然在构建 HTML 文档。 人们倾向于在 React 中得到分隔符,这最终导致不正确的 HTML。
return (
<div>
<li>Content</li>
</div>
);
在上面的示例中,div 最终将成为 ul 的直接子元素,这是不正确的 HTML,而下面的示例中 li 最终成为 ul 的直接子元素,从而形成正确的 HTML。
return (
<li>Content</li>
);
我们可以使用另一种使用 React.Fragment
方法。 React.Fragment
是在反应 v16.2中引入的,我们可以使用它们而不去使用一些会导致错误格式的 div
。
7. 只加必要的注释
只有必要时在应用程序中添加注释。毫无例外, 从应用程序中移除注释功能意味着我必须根据注释逐行编写额外的代码。 一般来说,注释是一个缺点,它规定了糟糕的设计,特别是冗长的注释,很明显,开发人员不知道他们到底在做什么,并试图通过写注释来弥补。
图片来源: https://www.toptal.com/sql/guide-to-data-synchronization-in-microsoft-sql-server
8. 了解如何处理 this
因为函数组件不需要 this
绑定,所以只要有可能就要使用它们。 但是如果您正在使用 ES6类,您将需要手动绑定这个类,因为 React 不能自动绑定该组件中的函数。 这里有一些这样做的例子。
「例1: 渲染时绑定」
class Foo extends Components {
constructor(props) {
super(props);
this.state = { message: "Hello" };
}
logMessage() {
const { message } = this.state;
console.log(message);
}
render() {
return (
<input type="button" value="Log" onClick={this.logMessage.bind(this)} />
);
}
}
上面的函数的 this
绑定如下:
onClick={this.logMessage.bind(this)}
这种方法清晰、简洁、有效,但是它可能会导致一个轻微的性能问题,因为每次此组件 re-rendered
时都会频繁的调用一个新的 logMessage
函数。「例2: render 函数中的箭头函数。」
class Bar extends Components {
constructor(props) {
super(props);
this.state = { message: "Hello" };
}
logMessage() {
const { message } = this.state;
console.log(message);
}
render() {
return (
<input type="button" value="Log" onClick={() => this.logMessage()} />
);
}
}
上面的 this
绑定如下:
onClick={() => this.logMessage()}
这种方法非常简洁,就像例子1,但是和例子1一样,它也会在每次 render
这个组件时创建一个新的 logMessage
函数。
***示例3: 构造函数中绑定 *** this
class Hello extends Components {
constructor(props) {
super(props);
this.state = { message: "Hello" };
this.logMessage = this.logMessage.bind(this);
}
logMessage() {
const { message } = this.state;
console.log(message);
}
render() {
return (
<input type="button" value="Log" onClick={this.logMessage} />
);
}
}
上述绑定 this
的逻辑如下:
this.logMessage = this.logMessage.bind(this);
这种方法将解决示例1和2的潜在性能问题。 但是不要忘记在构造函数中调用 super 哦。「示例4: Class 属性中的箭头函数」
class Message extends Components {
constructor(props) {
super(props);
this.state = { message: "Hello" };
}
logMessage = () => {
const { message } = this.state;
console.log(message);
}
render() {
return (
<input type="button" value="Log" onClick={this.logMessage} />
);
}
}
上述绑定 this
片段如下:
logMessage = () => {
const { message } = this.state;
console.log(message);
}
这种方式非常干净,可读性强,可以避免示例1和示例2的性能问题,并避免示例3中的重复。 但是要注意,这种方法确实依赖于实验特性,而且它不是 ECMAScript 规范的正式部分。 你可以通过安装和配置 babel 包来实验此语言功能,并且由 create react app 创建的应用程序配置了了许多有用的功能,包括上述功能。
图片来源: https://codeburst.io/javascript-arrow-functions-for-beginners-926947fc0cdc
9. Props — State — Constructor
我们可以将标题分为两个副标题,如:
- 初始状态时不要使用 Props。
- 不要在类构造函数中初始化组件状态。
当您在初始状态中使用 props 时,问题在于构造函数在组件创建时被调用。 所以构造函数只被调用一次。 如果下次 props 变化,则组件状态将不会更新,并且保持与前一个值相同。 您可以使用响应生命周期方法 componentDidUpdate
来修复问题。 当 props 更改时, componentDidUpdate
方法更新组件。 在初始呈现时虽然不会调用 componentDidUpdate
。 但是,在初始状态下使用 props
并不是最佳实践。
将状态初始化为类字段是最佳实践。 使用构造函数初始化组件状态并不是很糟糕的做法,但是它增加了代码中的冗余并造成了一些性能问题。 当您在类构造函数中初始化状态时,它需要调用 super 并记住 props,这会产生性能问题。
class SateInsideConstructor extends React.Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
/* your logic */
}
另一个问题是,当您要在构造函数中初始化状态时,请考虑您需要的行数,是否需要 constructor ()
、 super ()
?
import React from 'react'
class MyComponent extends React.Component {
state = {
counter: 0
}
/* your logic */
}
*图片来源: https://indepth.dev/in-depth-explanation-of-state-and-props-update-in-react/ *
10. 靠后命名
在写完组件代码后为函数或组件命名,因为写完之后你知道它承担什么样的功能。 例如,您可以根据组件代码立即选择像 FacebookButton
这样的组件的名称。 但是在未来,你可以使用这个组件作为 TwitterButton
, YoutubeButton
。 因此,最佳实践是将该组件命名为 Button。 通常,当您完成函数时,您应该能够为组件和函数选择通用名称。 后置命名增加了可重用性。
11. 注意 State 和 Rendering
在 React 中,当我们可以按状态对组件进行分类时。 可以分为 stateful
和 stateless
。 有状态组件存储组件的状态信息并提供必要的上下文。 对于无状态组件,因为不能保持状态,所以不能给用户界面的部分提供上下文。 无状态组件是可伸缩的、可重用的,就像纯 JavaScript 函数一样。 为了将有状态组件的数据获取逻辑与无状态组件的 render
逻辑分离开来,一个更好的方法是使用有状态组件来获取数据,另一个无状态组件来显示获取的数据。
在 React v16.08之后,有一个新特性叫做 React Hooks。 React Hooks 编写有状态函数式组件。 React Hooks 禁止使用类组件。
如果数据没有在渲染中直接使用,那么它不应该放到组件的 State 里面。 未直接在渲染时使用的数据可能导致不必要的 re-renders
。
*图片来源: https://www.barrymichaeldoyle.com/sub-rendering/ *
12. 避免在 setState 中使用对象
根据React Docs [7]的说法,React 并不能保证立即应用 setState 变化。 因此,在调用 setState 之后读取 this.state 可能是一个潜在的陷阱,因为 this.state 可能并不是您所想的那样。
const { ischecked } = this.state;
this.setState({ischecked: !ischecked});
我们可以使用以下函数,而不是像上面的代码片段那样更新对象中的状态。
this .setState((prevState, props) => {
return {ischecked: !prevState.ischecked}
})
上面的函数将接收前一个状态作为它的第一个参数,并在更新应用为它的第二个参数时使用 props。 状态更新是一种异步操作,因此为了更新状态对象,我们需要对 setState 使用 updater 函数。
13. 使用大写驼峰式名称
当您在 React 中工作时,请记住您使用的是 JSX (JavaScript 扩展)而不是 HTML。 您创建的组件应该以大写的 camel 命名,即 「Pascal Case」。 驼峰式大写意味着单词没有空格,每个单词的第一个字母都大写。 例如,如果有一个名为 selectButton
的组件,那么您应该将其命名为 SelectButton
,而不是 selectButton
。 使用大写的驼峰式大小写有助于 JSX 区分默认的 JSX 元素标记和创建的元素。 但是,可以使用小写字母命名组件,但这不是最佳实践。
摄影: Artem Sapegin on Unsplash
14. 使用 prop-types
“ prop-types”是一个用于检查 props 类型的库,它可以通过确保您为 props 使用正确的数据类型来帮助防止错误。 自 React v15.5以来, React.PropTypes
已经被分拆到一个独立的包。 React.PropTypes
使我们能够输入检查组件的 props 并为其提供默认值。 因此,您将通过 npm 安装使用一个外部库来使用它。
npm i prop-types
导入库,将 PropTypes
添加到组件,相应地设置数据类型,如果 props 是必要的,则添加如下所示的 isRequired。
import React, { Component } from "react";
import PropTypes from "prop-types";
class Welcome extends Component {
render() {
const { name } = this.props;
return <h1>Welcome, {name}</h1>;
}
}
Welcome.PropTypes = {
name: PropTypes.string.isRequired
};
可以使用 defaultProps 为 props 分配默认值。 当一个组件没有接收父组件的 props
时,它会使用 defaultProps。如果你已经标记了你的 props 为必要的, 那么没有必要分配 defaultProps。 在下面的代码片段中,您可以看到分配给 ModalButton 的 props 的所有默认值。 在本例中,我使用了 React Bootstrap 框架。
import React, { Component } from "react";
import { Button } from "react-bootstrap";
import PropTypes from 'prop-types'
class ModalButton extends Component {
render() {
return <Button variant={this.props.variant}>{this.props.children}</Button>;
}
}
ModalButton.defaultProps = {
variant: "outline-info",
children: "Info"
};
ModalButton.propTypes = {
variant: PropTypes.string,
children: PropTypes.string
}
ReactDOM.render(<ModalButton />, document.getElementById('root'));
需要注意的是,在分配 defaultProps
之后使用 PropsTypes
进行类型检查。 因此,它也会检查分配给 props 的默认值。
15.CSS in JavaScript
当您有一个大的 CSS (SCSS)文件时,您可以使用全局前缀后跟 Block-Element-Modifier 约定来避免名称冲突。 当应用程序变大时,这种方法是不可伸缩的。 所以你必须评估你的 CSS (SCSS)文件。 还有另外一种方法可以通过 Webpack 的Mini CSS Extract Text plugin [8]来提取 CSS (需要 webpack 4来工作) ,但是它创建了对 webpack 的严重依赖。 如果使用此方法,则很难测试组件。 最佳实践是拥有一个易于测试的应用程序,因此,遵循这种方法并不是最佳实践。
EmotionJS [9], Glamorous [10] and Styled Components [11] 是 JS 库中一些新出现的 CSS。 您可以根据需求使用它们。 当你需要编译一个用于生产的 CSS 时,你可以使用 EmotionJS 库。 当您有一个复杂的主题问题时,您可以使用 Glamorous 和 styled-components。
*图片来源: https://wordpress.org/plugins/custom-css-js/ *
16. Testing
不仅仅是在 React 中,还应该在其他编程语言中进行测试。 测试非常重要,因为它确保代码能够按照预期的方式运行,并且易于快速地进行测试。 在最佳实践中,在 components
文件夹中创建一个 __test__
文件夹。 使用组件的名称作为测试文件 . test.js
的前缀. 您可以使用Jest [12]作为测试运行程序,Enzyme [13]作为 React 的测试工具。
崩溃组件测试是一种简单快速的方法,可以确保所有组件都能正常工作而不会崩溃。 组件崩溃测试很容易应用到您创建的每个组件中。
import React from 'react'
import ReactDom from 'react-dom'
import App from '.'
it('renders without crashing', () => {
const div = document.createElement{'div'};
ReactDOM.render(<App/ >, div);
ReactDOM.unmountComponentAtNode(div);
});
您显然应该进行比崩溃测试更广泛的测试。 如果您编写更多的测试用例,它将为您的代码提供更多的测试覆盖率。 但是,至少你应该做一些崩溃组件测试。 在上面的崩溃组件测试中,我们要做的是创建一个元素,然后它使用 ReactDom 并挂载导入到刚刚创建的 div 中的任何组件,然后卸载 div。
❝真正的 React 开发人员应该对整个 React 应用程序进行适当的测试。
❞
17. 使用 ESLint,Prettier 和 Snippet 库
ESLint [14]通过各种提示来保持你的代码漂亮整洁。 您可以将其链接到您的 IDE。 最佳实践是创建自己的ESLint 配置文件 [15]。
❝一个好的开发人员应该修复所有的 ESlint 错误和警告,而不是禁用该错误。
❞
Prettier [16]是一个代码格式化工具。 Prettier 有一组用于代码格式化和缩进的规则。 你可以使用Sonarlint [17]检查拼写,函数长度和更好的方法建议。 使用Husky [18]不仅是一个很好的 React 实践,也是一个很好的 Git 实践。 您可以在 「package.json」文件中定义 husky。 Husky 防止您的应用程序出现错误的提交和错误的推送。
代码段可以帮助您编写最佳代码和趋势语法。 它们使您的代码相对来说没有错误。 您可以使用许多代码片段库,如 ES7 React、 JavaScript (ES6)代码片段等。
图片来源: https://medium.com/dubizzletechblog/setting-up-prettier-and-eslint-for-js-and-react-apps-bbc779d29062
18. 使用 React Developer Tools️
React 开发工具是Chrome [19]和Firefox [20]的扩展。 如果您使用 Safari 或其他浏览器,请使用以下命令安装它。
npm install -g react-devtools@^4
图片 如果你使用开发者工具正在寻找一个使用 React 中的 Web 应用程序,您可以在 Components 选项卡中看到组件层次结构。 如果您单击一个组件,您可以查看该组件的 Props 和 State。 正如你所看到的,React Developer Tools 扩展对于测试和调试来说是非常有价值的工具,并且可以真正理解这个应用程序发生了什么。
总结 ✌️
本文描述了 React 中的最佳实践。 这些实践提高了应用程序性能、应用程序代码和编码技能。
Happy coding!
想要学习更多精彩的实战技术教程?来图雀社区 [21]逛逛吧。
Reference
[1]组件、库和框架:https://github.com/enaqx/awesome-react
[2]测试驱动开发(TDD):https://www.ibm.com/developerworks/cn/linux/l-tdd/index.html
[3]Create React App:https://github.com/facebook/create-react-app
[4]memo:https://reactjs.org/docs/react-api.html#reactmemo
[5]Airbnb react / jsx Style Guide:https://github.com/airbnb/javascript/tree/master/react
[6]Facebook Style Guide:https://reactjs.org/docs/getting-started.html
[7]React Docs:https://reactjs.org/docs/react-component.html#setstate
[8]Mini CSS Extract Text plugin:https://github.com/webpack-contrib/mini-css-extract-plugin
[9]EmotionJS:https://github.com/emotion-js/emotion
[10]Glamorous:https://glamorous.rocks/
[11]Styled Components:https://github.com/styled-components/styled-components
[12]Jest:https://github.com/facebook/jest
[13]Enzyme:https://github.com/enzymejs/enzyme
[14]ESLint:https://eslint.org/
[15]ESLint 配置文件:https://eslint.org/docs/user-guide/configuring
[16]Prettier:https://prettier.io/
[17]Sonarlint:https://www.sonarlint.org/
[18]Husky:https://www.npmjs.com/package/husky
[19]Chrome:https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
[20]Firefox:https://addons.mozilla.org/en-US/firefox/addon/react-devtools/
[21]图雀社区:https://tuture.co?utm_source=juejin_zhuanlan