如今前端程序员都知道,React 是组件化的。当我开始学习 React 的时候,我记得当时已经存在了不少不一样编写组件的方式了。现在,React
社区已经愈发成熟,可是对于组件正确编写姿式却没有一个相对完备的指导。javascript
这篇文章仅从做者的观点出发,来谈一谈咱们究竟应该如何来写高质量的 React 组件。css
在开始前,须要说明如下几个问题:前端
基于 Class 的组件是状态化的,包含有自身方法、生命周期函数、组件内状态等。最佳实践包括但不限于如下一些内容:java
我很喜欢 CSS in JavaScript 这一理念。在 React 中,咱们能够为每个 React 组件引入相应的 CSS 文件,这一“梦想”成为了现实。在下面的代码示例,我把 CSS 文件的引入与其余依赖隔行分开,以示区别:react
import React, {Component} from 'react'
import {observer} from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'复制代码
固然,这并非真正意义上的 CSS in JS,具体实现其实社区上有不少方案。个人 Github 上 fork 了一份各类 CSS in JS 方案的多维度对比,感兴趣的读者能够点击这里。git
在编写组件过程当中,必定要注意初始状态的设定。利用 ES6 模块化的知识,咱们确保该组件暴露都是 “export default” 形式,方便其余模块(组件)的调用和团队协做。程序员
import React, {Component} from 'react'
import {observer} from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
state = { expanded: false }
......复制代码
propTypes 和 defaultProps 都是组件的静态属性。在组件的代码中,这两个属性的设定位置越高越好。由于这样方便其余阅读代码者或者开发者本身 review,一眼就能看到这些信息。这些信息就如同组件文档同样,对于理解或熟悉当前组件很是重要。github
一样,原则上,你编写的组件都须要有 propTypes 属性。如同如下代码:redux
export default class ProfileContainer extends Component {
state = { expanded: false }
static propTypes = {
model: React.PropTypes.object.isRequired,
title: React.PropTypes.string
}
static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}复制代码
Functional Components 是指没有状态、没有方法,纯组件。咱们应该最大限度地编写和使用这一类组件。这类组件做为函数,其参数就是 props, 咱们能够合理设定初始状态和赋值。浏览器
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
const formStyle = expanded ? {height: 'auto'} : {height: 0}
return (
<form style={formStyle} onSubmit={onSubmit}>
{children}
<button onClick={onExpand}>Expand</button>
</form>
)
}复制代码
在编写组件方法时,尤为是你将一个方法做为 props 传递给子组件时,须要确保 this 的正确指向。咱们一般使用 bind 或者 ES6 箭头函数来达到此目的。
export default class ProfileContainer extends Component {
state = { expanded: false }
handleSubmit = (e) => {
e.preventDefault()
this.props.model.save()
}
handleNameChange = (e) => {
this.props.model.changeName(e.target.value)
}
handleExpand = (e) => {
e.preventDefault()
this.setState({ expanded: !this.state.expanded })
}复制代码
固然,这并非惟一作法。实现方式多种多样,我专门有一片文章来对比 React 中对于组件 this 的绑定,能够点击此处参考。
在上面的代码示例中,咱们使用了:
this.setState({ expanded: !this.state.expanded })复制代码
这里,关于 setState hook 函数,其实有一个很是“有意思”的问题。React 在设计时,为了性能上的优化,采用了 Batch 思想,会收集“一波” state 的变化,统一进行处理。就像浏览器绘制文档的实现同样。因此 setState 以后,state 也许不会立刻就发生变化,这是一个异步的过程。
这说明,咱们要谨慎地在 setState 中使用当前的 state,由于当前的state 也许并不可靠。
为了规避这个问题,咱们能够这样作:
this.setState(prevState => ({ expanded: !prevState.expanded }))复制代码
咱们给 setState 方法传递一个函数,函数参数为上一刻 state,便保证setState 可以马上执行。
关于 React setState 的设计, Eric Elliott 也曾经这么喷过:setState() Gate,并由此展开了多方“撕逼”。做为围观群众,咱们在吃瓜的同时,必定会在大神论道当中收获不少思想,建议阅读。
若是你对 setState 方法的异步性还有困惑,能够同我讨论,这里再也不展开。
这个其实没有太多可说的,仔细观察代码吧:咱们使用了解构赋值。除此以外,若是一个组件有不少的 props 的话,每一个 props 应该都另起一行,这样书写上和阅读性上都有更好的体验。
export default class ProfileContainer extends Component {
state = { expanded: false }
handleSubmit = (e) => {
e.preventDefault()
this.props.model.save()
}
handleNameChange = (e) => {
this.props.model.changeName(e.target.value)
}
handleExpand = (e) => {
e.preventDefault()
this.setState(prevState => ({ expanded: !prevState.expanded }))
}
render() {
const {model, title} = this.props
return (
<ExpandableForm
onSubmit={this.handleSubmit}
expanded={this.state.expanded}
onExpand={this.handleExpand}>
<div>
<h1>{title}</h1>
<input
type="text"
value={model.name}
onChange={this.handleNameChange}
placeholder="Your Name"/>
</div>
</ExpandableForm>
)
}
}复制代码
这一条是对使用 mobx 的开发者来讲的。若是你不懂 mobx,能够大致扫一眼。
咱们强调使用 ES next decorate 来修饰咱们的组件,如同:
@observer
export default class ProfileContainer extends Component {复制代码
使用修饰器更加灵活且可读性更高。即使你不使用修饰器,也须要如此暴露你的组件:
class ProfileContainer extends Component {
// Component code
}
export default observer(ProfileContainer)复制代码
必定要尽可能避免如下用法:
<input
type="text"
value={model.name}
// onChange={(e) => { model.name = e.target.value }}
// ^ Not this. Use the below:
onChange={this.handleChange}
placeholder="Your Name"/>复制代码
不要:
onChange = {(e) => { model.name = e.target.value }}复制代码
而是:
onChange = {this.handleChange}复制代码
缘由其实很简单,每次父组件 render 的时候,都会新建一个新的函数并传递给 input。
若是 input 是一个 React 组件,这会粗暴地直接致使这个组件的 re-render,须要知道,Reconciliation 但是 React 成本最高的部分。
另外,咱们推荐的方法,会使得阅读、调试和更改更加方便。
真正写过 React 项目的同窗必定会明白,JSX 中可能会存在大量的条件判别,以达到根据不一样的状况渲染不一样组件形态的效果。
就像下图这样:
这样的结果是不理想的。咱们丢失了代码的可读性,也使得代码组织显得混乱异常。多层次的嵌套也是应该避免的。
针对于此,有很对类库来解决 JSX-Control Statements 此类问题,可是与其引入第三方类库的依赖,还不如咱们先本身尝试探索解决问题。
此时,是否是有点怀念if...else?
咱们可使用大括号内包含当即执行函数IIFE,来达到使用 if...else 的目的:
固然,大量使用当即执行函数会形成性能上的损失。因此,考虑代码可读性上的权衡,仍是有必要好好斟酌的。
我更加建议的作法是分解此组件,由于这个组件的逻辑已通过于复杂而臃肿了。如何分解?请看我这篇文章。
其实所谓 React “最佳实践”,想必每一个团队都有本身的一套“心得”,哪里有一个统一套? 本文指出的几种方法未必对任何读者都适用。针对不一样的代码风格,开发习惯,拥有本身团队一套“最佳实践”是颇有必要的。从另外一方面,也说明了 React 技术栈自己的灵活于强大。
另外,这篇文章并非我原创,而是翻译了Our Best Practices for Writing React Components一文,并在此基础上进行了较大幅度扩展。
若是您对React生态有兴趣,一样推荐个人其余几篇文章:
Happy Coding!