翻译:Our Best Practices for Writing React Components
image.png

Reat组件最佳实践

几点建议:

Class Based Components

对于类组件,应该尽可能少的应用,

Imporing CSS

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

从理论上讲, CSS in JavaScript,方案是好的,但是还不够成熟。到目前为止,为每一个组件定义一个CSS文件是比较好的做法。

Initializing State

  1. import React, { Component } from 'react'
  2. import { observer } from 'mobx-react'
  3. import ExpandableForm from './ExpandableForm'
  4. import './styles/ProfileContainer.css'
  5. export default class ProfileContainer extends Component {
  6. state = { expanded: false }

propTypes and defaultProps

  1. import React, { Component } from 'react'
  2. import { observer } from 'mobx-react'
  3. import { string, object } from 'prop-types'
  4. import ExpandableForm from './ExpandableForm'
  5. import './styles/ProfileContainer.css'
  6. export default class ProfileContainer extends Component {
  7. state = { expanded: false }
  8. static propTypes = {
  9. model: object.isRequired,
  10. title: string
  11. }
  12. static defaultProps = {
  13. model: {
  14. id: 0
  15. },
  16. title: 'Your Name'
  17. }

propTypesdefaultProps是静态属性,在组件代码应该尽可能早的声明。这对于其他的开发者应该是立即可见的,这起到了组件文档的作用。

Methods

  1. import React, { Component } from 'react'
  2. import { observer } from 'mobx-react'
  3. import { string, object } from 'prop-types'
  4. import ExpandableForm from './ExpandableForm'
  5. import './styles/ProfileContainer.css'
  6. export default class ProfileContainer extends Component {
  7. state = { expanded: false }
  8. static propTypes = {
  9. model: object.isRequired,
  10. title: string
  11. }
  12. static defaultProps = {
  13. model: {
  14. id: 0
  15. },
  16. title: 'Your Name'
  17. }
  18. handleSubmit = (e) => {
  19. e.preventDefault()
  20. this.props.model.save()
  21. }
  22. handleNameChange = (e) => {
  23. this.props.model.changeName(e.target.value)
  24. }
  25. handleExpand = (e) => {
  26. e.preventDefault()
  27. this.setState({ expanded: !this.state.expanded })
  28. }

在类组件里,当你把方法传递给子组件的时候,你必须确保有正确的this值。通常情况下,我们是通过this.handleSubmit.bind(this)的方式实现。
ES6的箭头函数,可以自动的保持正确的上下文,这种方式干净又整洁。

Passing setState a Function

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

由于setState的更新是异步的,出于性能的原因,React的state是批量更新的。state是不会立即更新的在setState调用之后。

这意味着你不能依赖当前的state当你调用setState方法的时候,这是因为你并不能确定state将会是什么。

这里有一个解决方案,就是传递一个函数给setState,之前的state作为参数。

Destructuring Props

  1. import React, { Component } from 'react'
  2. import { observer } from 'mobx-react'
  3. import { string, object } from 'prop-types'
  4. import ExpandableForm from './ExpandableForm'
  5. import './styles/ProfileContainer.css'
  6. export default class ProfileContainer extends Component {
  7. state = { expanded: false }
  8. static propTypes = {
  9. model: object.isRequired,
  10. title: string
  11. }
  12. static defaultProps = {
  13. model: {
  14. id: 0
  15. },
  16. title: 'Your Name'
  17. }
  18. handleSubmit = (e) => {
  19. e.preventDefault()
  20. this.props.model.save()
  21. }
  22. handleNameChange = (e) => {
  23. this.props.model.changeName(e.target.value)
  24. }
  25. handleExpand = (e) => {
  26. e.preventDefault()
  27. this.setState(prevState => ({ expanded: !prevState.expanded }))
  28. }
  29. render() {
  30. const {
  31. model,
  32. title
  33. } = this.props
  34. return (
  35. <ExpandableForm
  36. onSubmit={this.handleSubmit}
  37. expanded={this.state.expanded}
  38. onExpand={this.handleExpand}>
  39. <div>
  40. <h1>{title}</h1>
  41. <input
  42. type="text"
  43. value={model.name}
  44. onChange={this.handleNameChange}
  45. placeholder="Your Name"/>
  46. </div>
  47. </ExpandableForm>
  48. )
  49. }
  50. }

当一个组件有很多props的时候,每一个prop应该有一个新行。

Decorators

  1. @observer
  2. export default class ProfileContainer extends Component {

装饰器这种方式是灵活的,可读的。应该尽可能对的应用。
如果你不想用decorators,可以考虑下面的方式。

  1. class ProfileContainer extends Component {
  2. // Component code
  3. }
  4. export default observer(ProfileContainer)

Closures

应避免传递一个闭包给子组件,如下所示:

  1. <input
  2. type="text"
  3. value={model.name}
  4. // onChange={(e) => { model.name = e.target.value }}
  5. // ^ Not this. Use the below:
  6. onChange={this.handleChange}
  7. placeholder="Your Name"/>

每一次父组件渲染时,一个新的函数就被创建了,并传递给输入。

如果组件是输入时一个React组件,那么就会自动的触发重新渲染,而不会考虑props是不是更新了。

调和是React组件消耗最大的部分。别把它变的更难以维护。
以下是完整示例。

  1. import React, { Component } from 'react'
  2. import { observer } from 'mobx-react'
  3. import { string, object } from 'prop-types'
  4. // Separate local imports from dependencies
  5. import ExpandableForm from './ExpandableForm'
  6. import './styles/ProfileContainer.css'
  7. // Use decorators if needed
  8. @observer
  9. export default class ProfileContainer extends Component {
  10. state = { expanded: false }
  11. // Initialize state here (ES7) or in a constructor method (ES6)
  12. // Declare propTypes as static properties as early as possible
  13. static propTypes = {
  14. model: object.isRequired,
  15. title: string
  16. }
  17. // Default props below propTypes
  18. static defaultProps = {
  19. model: {
  20. id: 0
  21. },
  22. title: 'Your Name'
  23. }
  24. // Use fat arrow functions for methods to preserve context (this will thus be the component instance)
  25. handleSubmit = (e) => {
  26. e.preventDefault()
  27. this.props.model.save()
  28. }
  29. handleNameChange = (e) => {
  30. this.props.model.name = e.target.value
  31. }
  32. handleExpand = (e) => {
  33. e.preventDefault()
  34. this.setState(prevState => ({ expanded: !prevState.expanded }))
  35. }
  36. render() {
  37. // Destructure props for readability
  38. const {
  39. model,
  40. title
  41. } = this.props
  42. return (
  43. <ExpandableForm
  44. onSubmit={this.handleSubmit}
  45. expanded={this.state.expanded}
  46. onExpand={this.handleExpand}>
  47. // Newline props if there are more than two
  48. <div>
  49. <h1>{title}</h1>
  50. <input
  51. type="text"
  52. value={model.name}
  53. // onChange={(e) => { model.name = e.target.value }}
  54. // Avoid creating new closures in the render method- use methods like below
  55. onChange={this.handleNameChange}
  56. placeholder="Your Name"/>
  57. </div>
  58. </ExpandableForm>
  59. )
  60. }
  61. }

Functional Components

函数式组件没有state和方法,他们简单易读,尽可能多的应用他们。

propTypes

  1. import React from 'react'
  2. import { observer } from 'mobx-react'
  3. import { func, bool } from 'prop-types'
  4. import './styles/Form.css'
  5. ExpandableForm.propTypes = {
  6. onSubmit: func.isRequired,
  7. expanded: bool
  8. }
  9. // Component declaration

在组件声明之前就确定propTypes,他们可以立即可见。这是因为JS函数的提升。

Destructuring Props and defaultProps

  1. import React from 'react'
  2. import { observer } from 'mobx-react'
  3. import { func, bool } from 'prop-types'
  4. import './styles/Form.css'
  5. ExpandableForm.propTypes = {
  6. onSubmit: func.isRequired,
  7. expanded: bool,
  8. onExpand: func.isRequired
  9. }
  10. function ExpandableForm(props) {
  11. const formStyle = props.expanded ? {height: 'auto'} : {height: 0}
  12. return (
  13. <form style={formStyle} onSubmit={props.onSubmit}>
  14. {props.children}
  15. <button onClick={props.onExpand}>Expand</button>
  16. </form>
  17. )
  18. }

组件是函数,props是函数的参数。可以像如下扩展:

  1. import React from 'react'
  2. import { observer } from 'mobx-react'
  3. import { func, bool } from 'prop-types'
  4. import './styles/Form.css'
  5. ExpandableForm.propTypes = {
  6. onSubmit: func.isRequired,
  7. expanded: bool,
  8. onExpand: func.isRequired
  9. }
  10. function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
  11. const formStyle = expanded ? {height: 'auto'} : {height: 0};
  12. return (
  13. <form style={formStyle} onSubmit={onSubmit}>
  14. {children}
  15. <button onClick={onExpand}>Expand</button>
  16. </form>
  17. )
  18. }

注意我们可以以一种高度可读的方式用默认参数的方式来充当defaultProps。如果expandedundefined,就会被设置为false。(由于它是一个布尔值,可以避免Cannot read <property> of undefined的问题。)

Wrapping

  1. import React from 'react'
  2. import { observer } from 'mobx-react'
  3. import { func, bool } from 'prop-types'
  4. // Separate local imports from dependencies
  5. import './styles/Form.css'
  6. // Declare propTypes here, before the component (taking advantage of JS function hoisting)
  7. // You want these to be as visible as possible
  8. ExpandableForm.propTypes = {
  9. onSubmit: func.isRequired,
  10. expanded: bool,
  11. onExpand: func.isRequired
  12. }
  13. // Destructure props like so, and use default arguments as a way of setting defaultProps
  14. function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
  15. const formStyle = expanded ? { height: 'auto' } : { height: 0 }
  16. return (
  17. <form style={formStyle} onSubmit={onSubmit}>
  18. {children}
  19. <button onClick={onExpand}>Expand</button>
  20. </form>
  21. )
  22. }
  23. // Wrap the component instead of decorating it
  24. export default observer(ExpandableForm)

Conditionals in JSX