刚接触 React 的时候,在一个又一个的教程上面看到很多种编写组件的方法,尽管那时候 React 框架已经相当成熟,但是并没有一个固定的规则去规范我们去写代码。

在过去的一年里,我们在不断的完善我们的做法,直到满意为止。

本文会列出我们自己在使用的最佳实践,不管你是刚入门的新手还是很有经验的开发者,我们都希望本文对你有所帮助。

开始之前,先列几条:

  • 我们使用ES6/ES7
  • 如果你无法区分页面组件和容器组件,推荐阅读 这篇文章
  • 如果有更好的意见或建议,请在评论区告诉我,谢谢

基于 Class 的组件

基于 Class 的组件是有状态的,不管它包不包含函数,我们都会尽量少用。但是它也有它的用处。

现在来一行一行的编写我们的组件:

导入 CSS

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

我喜欢 CSS in Javascript,但是这个概念还比较新,现在也并没有成熟的解决方案,所以我们在每个组件里面去引用 CSS

初始化 State

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 }

当然你也可以选择在构造函数里面去初始化,但是我们觉得这种方式更加清晰。

当然也会保证 Class 是默认导出的。

propTypes 和 defaultProps

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
state = { expanded: false } static propTypes = {
model: object.isRequired,
title: string
} static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}

propTypes 和 defaultProps 是静态属性,尽可能的把它们写在组件的最上方,以便其他开发者阅读。

如果使用 React 15.3.0 或更高的版本,使用 prop-types 代替 React.PropTypes

所有的组件都必须声明 propTypes

函数

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
state = { expanded: false } static propTypes = {
model: object.isRequired,
title: string
} static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}
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 })
}

使用基于 Class 的组件时,当你传递函数给子组件的时候,要确保他们有正确的 this,通常用这种形式实现 this.handleSubmit.bind(this)

但是如果你使用箭头函数,就不需要 bind(this)

为 setState 传递函数

上面的例子中我们是这么做的:

this.setState({ expanded: !this.state.expanded })

这里有个 setState 的小知识 —— 它是异步的,为了保证性能,React 会分批修改 state,所以 state 不会在调用 setState 之后立即改变

这意味着你不能依赖当前的状态,因为你不知道当前的状态是什么状态

这里有个解决方案 —— 传递函数给 setState,React 会把上一个状态 prevState 传递给你

this.setState(prevState => ({ expanded: !prevState.expanded }))

解构 Props

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
state = { expanded: false } static propTypes = {
model: object.isRequired,
title: string
} static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}
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>
)
}
}

像上面的例子一样,每个 prop 都独占一行

装饰器(Decorators)

@observer
export default class ProfileContainer extends Component {

如果你使用了类似 mobx 的库,你可以这样去装饰你的 Class 组件

修改函数式组件使用 decorators 很灵活并且可读

如果你不想使用装饰器,可以这么做:

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"/>

这种方式的好处是每次render,不会重新创建一个函数,没有额外的性能损失。

这里是完整的组件:

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
// Separate local imports from dependencies
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css' // Use decorators if needed
@observer
export default class ProfileContainer extends Component {
state = { expanded: false }
// Initialize state here (ES7) or in a constructor method (ES6) // Declare propTypes as static properties as early as possible
static propTypes = {
model: object.isRequired,
title: string
} // Default props below propTypes
static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
} // Use fat arrow functions for methods to preserve context (this will thus be the component instance)
handleSubmit = (e) => {
e.preventDefault()
this.props.model.save()
} handleNameChange = (e) => {
this.props.model.name = e.target.value
} handleExpand = (e) => {
e.preventDefault()
this.setState(prevState => ({ expanded: !prevState.expanded }))
} render() {
// Destructure props for readability
const {
model,
title
} = this.props
return (
<ExpandableForm
onSubmit={this.handleSubmit}
expanded={this.state.expanded}
onExpand={this.handleExpand}>
// Newline props if there are more than two
<div>
<h1>{title}</h1>
<input
type="text"
value={model.name}
// onChange={(e) => { model.name = e.target.value }}
// Avoid creating new closures in the render method- use methods like below
onChange={this.handleNameChange}
placeholder="Your Name"/>
</div>
</ExpandableForm>
)
}
}

函数式组件

这些组件没有状态和函数,他们很纯,非常容易阅读,尽量多的使用他们。

propTypes

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool
}
// Component declaration

这里我们把 propTypes 写在最前面,他会被组件立即可见,这要归功于JavaScript的 函数提升

解构 Props 和 defaultProps

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}
function ExpandableForm(props) {
const formStyle = props.expanded ? {height: 'auto'} : {height: 0}
return (
<form style={formStyle} onSubmit={props.onSubmit}>
{props.children}
<button onClick={props.onExpand}>Expand</button>
</form>
)
}

我们的组件是一个函数,我们获取他的 props 就是在获取函数的参数值,我们可以直接用 ES6 的解构:

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}
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>
)
}

我们也可以使用默认参数值去设置 defaultProps,就像上面的 expanded = false

避免使用下面的 ES6 语法:

const ExpandableForm = ({ onExpand, expanded, children }) => {

看起来很先(逼)进(格),但这个函数是匿名的。

如果你的Babel设置正确,这个匿名函数不会成为一个问题 —— 但是如果不是的话,任何错误都会显示在 << anonymous >> 中,这对于调试来说是非常糟糕的。

Wrapping

函数式组件中不能使用 decorators,你只需把它作为参数传递给过去

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}
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>
)
}
export default observer(ExpandableForm)

这里是完整的组件:

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
// Separate local imports from dependencies
import './styles/Form.css' // Declare propTypes here, before the component (taking advantage of JS function hoisting)
// You want these to be as visible as possible
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
} // Destructure props like so, and use default arguments as a way of setting defaultProps
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>
)
} // Wrap the component instead of decorating it
export default observer(ExpandableForm)

JSX 中的条件判断

你可能会有很复杂的条件判断语句,但是你要避免下面的写法:

嵌套的三元表达式不是一个好的方法,太难阅读了

有一些库可以解决这个问题(jsx-control-statements),但是我们没有引入其他的库,我们是这么解决的:

我们使用了 立即执行函数 把条件语句写在里面,虽然这样可能会导致性能下降,但是在大多数情况下,它带来的负面影响还是小于糟糕的可读性。

当然如果组件分的足够细,你可能不会用到这么复杂的条件判断。

此外,如果你只在一个表达式里面去渲染组件,避免这么做:

{
isTrue
? <p>True!</p>
: <none/>
}

你可以使用短路语法:

{
isTrue &&
<p>True!</p>
}

总结

这篇文章对你有帮助吗?请在评论区给出你的意见和建议,感谢阅读!

原文链接,翻译并首发于我的个人博客,推荐下我前阵子写的一个 React 脚手架 parcel-typescript-react-boilerplate,请给出意见和建议,相互学习。无耻的求个星,谢谢~~!

我们编写 React 组件的最佳实践的更多相关文章

  1. 编写React组件的最佳实践

    此文翻译自这里. 当我刚开始写React的时候,我看过很多写组件的方法.一百篇教程就有一百种写法.虽然React本身已经成熟了,但是如何使用它似乎还没有一个"正确"的方法.所以我( ...

  2. React服务器渲染最佳实践

    源码地址:https://github.com/skyFi/dva-starter React服务器渲染最佳实践 dva-starter 完美使用 dva react react-router,最好用 ...

  3. 编写Shell脚本的最佳实践

    编写Shell脚本的最佳实践 http://kb.cnblogs.com/page/574767/ 需要记住的 代码有注释 #!/bin/bash # Written by steven # Name ...

  4. 《React设计模式与最佳实践》笔记

    书里的demo都是15.3.2以下版本的,有些demo用最新的react 16.x版本会报错,安装包的时候记得改一下版本   第一章 React 基础 命令式编程描述代码如何工作,而声明式编程则表明想 ...

  5. 编写Shell脚本的最佳实践,规范二

    需要养成的习惯如下: 代码有注释 #!/bin/bash # Written by steven # Name: mysqldump.sh # Version: v1.0 # Parameters : ...

  6. 编写 Shell 脚本的最佳实践

    转自:http://kb.cnblogs.com/page/574767/ 前言 由于工作需要,最近重新开始拾掇shell脚本.虽然绝大部分命令自己平时也经常使用,但是在写成脚本的时候总觉得写的很难看 ...

  7. React 代码共享最佳实践方式

    任何一个项目发展到一定复杂性的时候,必然会面临逻辑复用的问题.在React中实现逻辑复用通常有以下几种方式:Mixin.高阶组件(HOC).修饰器(decorator).Render Props.Ho ...

  8. React项目的最佳实践

    项目代码 从零开始简书项目 ​ 从我第一次接触vue这个框架已经过了快一年的时间,陪伴我从前端小白到前端工程师,前端时间也是使用了 ts+vue这样的组合写代码,明显感觉vue与ts似乎没有产生比较好 ...

  9. Vue2.0 keep-alive 组件的最佳实践

    1.基本用法 vue2.0提供了一个keep-alive组件用来缓存组件,避免多次加载相应的组件,减少性能消耗 <keep-alive> <component> <!-- ...

随机推荐

  1. 《代码大全(第二版)》【PDF】下载

    <代码大全(第二版)>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382264 内容简介 <代码大全(第2版)>是著 ...

  2. C++模板显式实例化,隐式实例化,特化(具体化,偏特化)辨析

    最近再次看C++ PRIMER PLUS的时候看到这个部分感觉讲得很烂,前后口径不一致,所以写个辨析让自己明白的同时也希望对此不太清楚的朋友能搞懂. 总结一下,C++只有模板显式实例化(explici ...

  3. sublime学习笔记

    学习课程地址:快乐的sublime编辑器_sublime编辑器使用 另可参考笔记地址:http://c.haoduoshipin.com/happysublime/ PS:博主的一些文章地址:http ...

  4. 2018年的UX设计师薪酬预测,你能拿多少?

    以下内容由Mockplus团队翻译整理,仅供学习交流,Mockplus是更快更简单的原型设计工具.   一个经验丰富的设计师完全可以根据地区和专业来可以预期薪酬之间的差距,其中悬殊最高可达80K. 本 ...

  5. lesson - 6 Linux下磁盘管理

    1. 查看磁盘或者目录的容量df  查看磁盘各分区使用情况   不加参数以k为单位   df -i inode数,df -h  以G或者T或者M   df -m  以M单位显示  du 查看目录或者文 ...

  6. Jmeter+Ant+Jenkins接口自动化测试(一)_环境部署

    前言: 2017年最后一个月份,今天抽出时间把之前的一些记录分享出来,也为今年画上个简单的句号吧,无论好与坏,无论成功与失败,简单的记忆,不要留下点点空白. 特别提示: 知识是用来分享的,但是也要尊重 ...

  7. 【quickhybrid】iOS端的项目实现

    前言 18年元旦三天内和朋友突击了下,勉强是将雏形做出来了,后续的API慢慢完善.(当然了,主力还是那个朋友,本人只是初涉iOS,勉强能看懂,修修改改而已) 大致内容如下: JSBridge核心交互部 ...

  8. JaCoCo 代码覆盖率工具(基于Maven+TestNG)

    JaCoco是一个代码覆盖率库. 官方网站:http://www.jacoco.org/ 安装: 以 Maven(http://www.testclass.net/maven/) 安装为例: < ...

  9. c语言中的转义序列

    c中的输出函数printf()可以带以下的转义序列,不同的转义序列会得到不同的结果. 1.\a:警报 2.\b:退格(光标回退一格)3.\f:换页4.\n:换行(光标去到下一行的起始处)5.\r:回车 ...

  10. Mysql5.7.20使用group by查询(select *)时出现错误--修改sql mode

    使用select * from 表 group by 字段 时报错 错误信息说明: 1055 - Expression #1 of SELECT list is not in GROUP BY cla ...