翻译:Our Best Practices for Writing React Components
Reat组件最佳实践
几点建议:
- 使用 ES6 和ES7 语法
- Presentational and Container Components 组件的区别。
Class Based Components
对于类组件,应该尽可能少的应用,
Imporing CSS
import React, { Component } from 'react'import { observer } from 'mobx-react'import ExpandableForm from './ExpandableForm'import './styles/ProfileContainer.css'
从理论上讲, CSS in JavaScript,方案是好的,但是还不够成熟。到目前为止,为每一个组件定义一个CSS文件是比较好的做法。
Initializing 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 }
propTypes and 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是静态属性,在组件代码应该尽可能早的声明。这对于其他的开发者应该是立即可见的,这起到了组件文档的作用。
Methods
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 })}
在类组件里,当你把方法传递给子组件的时候,你必须确保有正确的this值。通常情况下,我们是通过this.handleSubmit.bind(this)的方式实现。
ES6的箭头函数,可以自动的保持正确的上下文,这种方式干净又整洁。
Passing setState a Function
this.setState({ expanded: !this.state.expanded })
由于setState的更新是异步的,出于性能的原因,React的state是批量更新的。state是不会立即更新的在setState调用之后。
这意味着你不能依赖当前的state当你调用setState方法的时候,这是因为你并不能确定state将会是什么。
这里有一个解决方案,就是传递一个函数给setState,之前的state作为参数。
Destructuring 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.propsreturn (<ExpandableFormonSubmit={this.handleSubmit}expanded={this.state.expanded}onExpand={this.handleExpand}><div><h1>{title}</h1><inputtype="text"value={model.name}onChange={this.handleNameChange}placeholder="Your Name"/></div></ExpandableForm>)}}
当一个组件有很多props的时候,每一个prop应该有一个新行。
Decorators
@observerexport default class ProfileContainer extends Component {
装饰器这种方式是灵活的,可读的。应该尽可能对的应用。
如果你不想用decorators,可以考虑下面的方式。
class ProfileContainer extends Component {// Component code}export default observer(ProfileContainer)
Closures
应避免传递一个闭包给子组件,如下所示:
<inputtype="text"value={model.name}// onChange={(e) => { model.name = e.target.value }}// ^ Not this. Use the below:onChange={this.handleChange}placeholder="Your Name"/>
每一次父组件渲染时,一个新的函数就被创建了,并传递给输入。
如果组件是输入时一个React组件,那么就会自动的触发重新渲染,而不会考虑props是不是更新了。
调和是React组件消耗最大的部分。别把它变的更难以维护。
以下是完整示例。
import React, { Component } from 'react'import { observer } from 'mobx-react'import { string, object } from 'prop-types'// Separate local imports from dependenciesimport ExpandableForm from './ExpandableForm'import './styles/ProfileContainer.css'// Use decorators if needed@observerexport 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 possiblestatic propTypes = {model: object.isRequired,title: string}// Default props below propTypesstatic 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 readabilityconst {model,title} = this.propsreturn (<ExpandableFormonSubmit={this.handleSubmit}expanded={this.state.expanded}onExpand={this.handleExpand}>// Newline props if there are more than two<div><h1>{title}</h1><inputtype="text"value={model.name}// onChange={(e) => { model.name = e.target.value }}// Avoid creating new closures in the render method- use methods like belowonChange={this.handleNameChange}placeholder="Your Name"/></div></ExpandableForm>)}}
Functional Components
函数式组件没有state和方法,他们简单易读,尽可能多的应用他们。
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,他们可以立即可见。这是因为JS函数的提升。
Destructuring Props and 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是函数的参数。可以像如下扩展:
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 是undefined,就会被设置为false。(由于它是一个布尔值,可以避免Cannot read <property> of undefined的问题。)
Wrapping
import React from 'react'import { observer } from 'mobx-react'import { func, bool } from 'prop-types'// Separate local imports from dependenciesimport './styles/Form.css'// Declare propTypes here, before the component (taking advantage of JS function hoisting)// You want these to be as visible as possibleExpandableForm.propTypes = {onSubmit: func.isRequired,expanded: bool,onExpand: func.isRequired}// Destructure props like so, and use default arguments as a way of setting defaultPropsfunction 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 itexport default observer(ExpandableForm)
