React 是一个用于开发用户界面的 JavaScript 库, 是由 Facebook 在 2013 年建立的。 React 集成了许多使人兴奋的组件、库和框架[1]。 固然,开发人员也能够本身开发组件。javascript
在最佳实践以前,我建议在开发 React 应用程序时使用测试驱动开发(TDD)[2]。 测试驱动开发意味着首先编写一个测试,而后根据测试开发你的代码,这样更容易识别出错误。php
❝本文翻译自 Medium:https://towardsdatascience.com/react-best-practices-804def6d5215#c966, 已取得做者受权❤css
❞
目录html
<div>
文件组织不只是 React 应用程序的最佳实践,也是其余应用程序的最佳实践。 Create React App[3] 程序的文件结构是组织 React 文件的一种可能的方式。 虽然不能说一种文件组织方式比另一种更好,但保持文件的组织性很是重要。java
在 React 中,随着应用不断变大,代码文件个数也会极具膨胀,且由于每一个组件至少有一个与之关联的文件。react
建立一个 「assets」 文件夹,其中包含顶层的 「CSS 文件」、「images」 和 「Fonts(字体)」 文件。linux
维护一个 「helpers」 文件夹,用于放置其余功能性的其文件。webpack
将全部与组件相关的文件保存在一个文件夹中。 一般,「components」 文件夹包含多个组件文件,如测试文件 、CSS 和一个或多个组件文件。 若是只有特定组件使用任何次要组件,最好将这些小组件保存在 「components」 文件夹中。 当您将大型组件保存在它们本身的文件夹中,而组件使用的小型组件保存在子文件夹中时,更容易理解文件层次结构。git
开发人员主要将主组件文件命名为 **index.js **文件。 一旦你有了几个文件,这些文件都被命名为 index.js,导航起来就会变得很麻烦。 解决这个问题的方法是向每一个组件文件夹添加 「package.json」 文件,为相应的文件夹设置主入口点。github
例如,对于按钮组件,主要入口点是 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 在 推特上 发布了一个解决方案
❝移动文件,直到感受合适为止。
❞
这正是你应该作的。你应该移动文件,直到它们感受对了为止。
众所周知,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
每一个函数式组件应该有一个函数,这意味着一个函数式组件等于一个函数。 当您使用一个函数建立一个函数式组件时,您能够提升该组件的可重用性。
不只在 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
当建立一个 JSX 元素数组时,React 须要给元素添加一个 key 属性。而这一般是经过使用 map 函数来完成的,因此会致使人们使用 Index 来设置 Key属性。 这太糟糕了! React 使用 key 属性跟踪数组中的每一个元素,这是因为数组具备折叠特性。 可是若是使用 Index 来做为 Key 属性,那么在遍历生成有状态的类组件数组时,一般会致使错误,因此你应该避免使用 Index 做为 Key 属性。
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
。
只有必要时在应用程序中添加注释。毫无例外, 从应用程序中移除注释功能意味着我必须根据注释逐行编写额外的代码。 通常来讲,注释是一个缺点,它规定了糟糕的设计,特别是冗长的注释,很明显,开发人员不知道他们到底在作什么,并试图经过写注释来弥补。
图片来源: https://www.toptal.com/sql/guide-to-data-synchronization-in-microsoft-sql-server
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
咱们能够将标题分为两个副标题,如:
当您在初始状态中使用 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/ *
在写完组件代码后为函数或组件命名,由于写完以后你知道它承担什么样的功能。 例如,您能够根据组件代码当即选择像 FacebookButton
这样的组件的名称。 可是在将来,你可使用这个组件做为 TwitterButton
, YoutubeButton
。 所以,最佳实践是将该组件命名为 Button。 一般,当您完成函数时,您应该可以为组件和函数选择通用名称。 后置命名增长了可重用性。
在 React 中,当咱们能够按状态对组件进行分类时。 能够分为 stateful
和 stateless
。 有状态组件存储组件的状态信息并提供必要的上下文。 对于无状态组件,由于不能保持状态,因此不能给用户界面的部分提供上下文。 无状态组件是可伸缩的、可重用的,就像纯 JavaScript 函数同样。 为了将有状态组件的数据获取逻辑与无状态组件的 render
逻辑分离开来,一个更好的方法是使用有状态组件来获取数据,另外一个无状态组件来显示获取的数据。
在 React v16.08以后,有一个新特性叫作 React Hooks。 React Hooks 编写有状态函数式组件。 React Hooks 禁止使用类组件。
若是数据没有在渲染中直接使用,那么它不该该放到组件的 State 里面。 未直接在渲染时使用的数据可能致使没必要要的 re-renders
。
*图片来源: https://www.barrymichaeldoyle.com/sub-rendering/ *
根据 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 函数。
当您在 React 中工做时,请记住您使用的是 JSX (JavaScript 扩展)而不是 HTML。 您建立的组件应该以大写的 camel 命名,即 「Pascal Case」。 驼峰式大写意味着单词没有空格,每一个单词的第一个字母都大写。 例如,若是有一个名为 selectButton
的组件,那么您应该将其命名为 SelectButton
,而不是 selectButton
。 使用大写的驼峰式大小写有助于 JSX 区分默认的 JSX 元素标记和建立的元素。 可是,可使用小写字母命名组件,但这不是最佳实践。
摄影: Artem Sapegin on Unsplash
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 的默认值。
当您有一个大的 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/ *
不只仅是在 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 应用程序进行适当的测试。
❞
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
React 开发工具是 Chrome[19] 和 Firefox[20] 的扩展。 若是您使用 Safari 或其余浏览器,请使用如下命令安装它。
npm install -g react-devtools@^4
若是你使用开发者工具正在寻找一个使用 React 中的 Web 应用程序,您能够在 Components 选项卡中看到组件层次结构。 若是您单击一个组件,您能够查看该组件的 Props 和 State。 正如你所看到的,React Developer Tools 扩展对于测试和调试来讲是很是有价值的工具,而且能够真正理解这个应用程序发生了什么。
本文描述了 React 中的最佳实践。 这些实践提升了应用程序性能、应用程序代码和编码技能。 ????
Happy coding!????
想要学习更多精彩的实战技术教程?来图雀社区[21]逛逛吧。
组件、库和框架: 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
·END·
图雀社区
汇聚精彩的免费实战教程
推荐阅读
五、或许是一本能够完全改变你刷 LeetCode 效率的题解书
六、迎接Vue3.0 | 在Vue2与Vue3中构建相同的组件
若是以为文章不错,帮忙点个在看呗