编写 React 组件的最佳实践

本文讲的是编写 React 组件的最佳实践,


当我一开始写 React 的时候,我记得有许多不同的方法来写组件,每个教程都大不相同。虽然从那以后 React 框架已经变得相当的成熟,但似乎仍然没有一种明确的写组件的“正确”方式。

过去一年在 MuseFind 工作中,我们的团队写过了无数的 React 组件。我们也在不断的改善方法直到我们满意为止。

这篇指南是我们建议的编写 React 组件的最佳方式。不管你是初学者还是有经验的人,我们希望它对你有用。

在开始之前,一些注意事项:

  • 我们使用 ES6 和 ES7 语法。
  • 如果你还不清楚展示组件和容器组件,我们建议先读这篇.
  • 请不吝评论,留下你的建议和问题以及反馈。

基于类的组件

基于类的组件包含了状态和方法。我们应该尽量保守的去使用它们,但是这类组件有他们的用武之地。

接下来,我们来逐行地构建我们的组件

引入 CSS

import React, {Component} from 'react'
import {observer} from 'mobx-react'

import EexpandableFormRequiredPropsxpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

我理论上比较倾向 CSS in JavaScript,但是这还是一个比较新的想法,到目前为止并没有一个相对成熟的解决方法出现。所以目前,我们对每一个组件都引入 CSS 文件。

我们用空行把本地引入和依赖引入分开。

初始化状态

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 }

如果你使用 ES6 而不是 ES7,请在构造方法里初始化状态。除此之外,你可以在 ES7 中使用上面的方法初始化状态。更多信息,请移步这里

当然,我们还需将我们的类作为默认类导出。

propTypes 和 defaultProps

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 }

  static propTypes = {
    model: React.PropTypes.object.isRequired,
    title: React.PropTypes.string
  }

  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }

propTypes 和 defaultProps 是静态的属性,需要尽可能早的在组件代码中声明。因为它们是作为文档而存在的,所以当其他开发者在阅读代码时候,它们应该尽早被看到。

所有的组件都应该有 propTypes。

方法

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 }

  static propTypes = {
    model: React.PropTypes.object.isRequired,
    title: React.PropTypes.string
  }

  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }

  handleSubmit = (e) => {
    e.preventDefault()
    this.props.model.save()
  }

  handleNameChange = (e) => {
    this.props.model.name = e.target.value
  }

  handleExpand = (e) => {
    e.preventDefault()
    this.setState({ expanded: !this.state.expanded })
  }

在基于类的组件中,当你需要向子组件传递方法的时候,你应该确保他们被调用的时候正确地绑定了 this。通常可以由this.handleSubmit.bind(this) 传递给子组件来实现。

我们认为这种方法更简洁易用,通过 ES6 的箭头函数来自动确保正确的上下文。

解构 Props

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 }

  static propTypes = {
    model: React.PropTypes.object.isRequired,
    title: React.PropTypes.string
  }

  static defaultProps = {
    model: {
      id: 0
    },
    title: 'Your Name'
  }

  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() {
    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>
    )
  }
}

有多个 props 的组件应该每行只写一个 prop,就像上面一样。

装饰器

@observer
export default class ProfileContainer extends Component {

如果你使用了像mobx的工具,你可以像上面这样来装饰组件,这和把组件传递到函数一样。

装饰器 是一种灵活可读的用来修饰组件功能的方法,配合 mobx 和 我们自己的 mobx-models 库,我们可以广泛的应用这种方法。

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

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

原因在此:每次父级组件渲染的时候,一个新的函数就会被创建,传递到 input 中。

如果这里的 input 是一个 React 组件,这会自动触发该组件重新渲染,不管该组件当中的 props 有没有被改变。

子级校正 (Reconciliation) 是 React 框架中最耗资源的部分。如果不需要,就不要增加难度。而且传递一个类方法会使代码更易于阅读,易于调试,易于修改。

以下是组件的全貌:

import React, {Component} from 'react'
import {observer} from 'mobx-react'
// 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: React.PropTypes.object.isRequired,
    title: React.PropTypes.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 './styles/Form.css'

const expandableFormRequiredProps = {
  onSubmit: React.PropTypes.func.isRequired,
  expanded: React.PropTypes.bool
}

// Component declaration

这里,我们在文件最开始给变量赋值 propTypes,所以它们立即可见。在下面的组件声明中,我们来更恰当地赋值。

解构 Props 和 defaultProps

import React from 'react'
import {observer} from 'mobx-react'

import './styles/Form.css'

const expandableFormRequiredProps = {
  onSubmit: React.PropTypes.func.isRequired,
  expanded: React.PropTypes.bool
}

function ExpandableForm(props) {
  return (
    <form style={props.expanded ? {height: 'auto'} : {height: 0}}>
      {props.children}
      <button onClick={props.onExpand}>Expand</button>
    </form>
  )
}

我们的组件是一个函数,其中 props 作为参数。我们可以像下面这样把它展开:

import React from 'react'
import {observer} from 'mobx-react'

import './styles/Form.css'

const expandableFormRequiredProps = {
  onExpand: React.PropTypes.func.isRequired,
  expanded: React.PropTypes.bool
}

function ExpandableForm({ onExpand, expanded = false, children }) {
  return (
    <form style={ expanded ? { height: 'auto' } : { height: 0 } }>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

注意我们可以通过更可读的方式来使用默认参数作为 defaultProps。如果 expanded 没有被定义,我们设定它为 false。(一种更合理的解释是,虽然它是布尔类型,但是可以避免出现 ‘Cannot read < property > of undefined’ 此类对象错误的问题)。

避免使用如下的 ES6 语法:

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

看起来非常得时髦,但这里的函数实际上未命名。

如果Babel设置正确,这里未命名不会造成问题。但是如果Babel设置错了的话,任何错误都会以 << anonymous >> 的方式呈现,这对于调错是非常糟糕的体验。

未命名的函数也可以会伴随 Jest (一个 React 测试库)出现问题。由于这些难以理解的 bugs 的潜在问题,我们建议使用function 代替 const.

封装

既然你不能对函数组件使用装饰器,你可以把函数作为参数传递过去。

import React from 'react'
import {observer} from 'mobx-react'

import './styles/Form.css'

const expandableFormRequiredProps = {
  onExpand: React.PropTypes.func.isRequired,
  expanded: React.PropTypes.bool
}

function ExpandableForm({ onExpand, expanded = false, children }) {
  return (
    <form style={ expanded ? { height: 'auto' } : { height: 0 } }>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

ExpandableForm.propTypes = expandableFormRequiredProps

export default observer(ExpandableForm)

以下是组件的全貌:

import React from 'react'
import {observer} from 'mobx-react'
// Separate local imports from dependencies
import './styles/Form.css'

// Declare propTypes here as a variable, then assign below function declaration
// You want these to be as visible as possible
const expandableFormRequiredProps = {
  onSubmit: React.PropTypes.func.isRequired,
  expanded: React.PropTypes.bool
}

// Destructure props like so, and use default arguments as a way of setting defaultProps
function ExpandableForm({ onExpand, expanded = false, children }) {
  return (
    <form style={ expanded ? { height: 'auto' } : { height: 0 } }>
      {children}
      <button onClick={onExpand}>Expand</button>
    </form>
  )
}

// Set propTypes down here to those declared above
ExpandableForm.propTypes = expandableFormRequiredProps

// Wrap the component instead of decorating it
export default observer(ExpandableForm)

JSX 中的条件语句

如果你要使用很多有条件限制的渲染,这里是你需要避免的:

内嵌套的三元运算符不是一个好想法。

虽然有一些第三方的库解决这个问题(JSX-Control Statements),但这里我们用下面的方法来解决复杂的条件语句,而不去引用这些依赖。

使用花括号包装一个 IIFE,然后把 if 语句放进去,返回你想渲染的任何东西。注意 IIFE 可能会导致性能问题,但是在绝大多数情况下,它导致的性能问题还不足以与代码可读性问题相比。

同样,当你只想在一个条件语句中渲染某个元素,不要这么做:

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

应该使用短路求值(short-circuit evaluation)的方式

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





原文发布时间为:2017年3月03日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-09-19 09:04:03

编写 React 组件的最佳实践的相关文章

编写React组件的最佳实践

在我第一次编写 React 代码的时候,我见发现许多不同的方法可以用来编写组件,不同教程教授的内容也大不相同.尽管从那时候起框架已经相当成熟,但并没有一种固定的"正确"方式指导. 在 MuseFind 工作的一年里,我们的团队编写了许多 React 组件,后期我们对方法进行了优化直到满意为止. 本指南描述了我们推荐的最佳实践,不管你是一名初学者还是有经验的老手,希望它能对你有所帮助. 在我们开始之前,有几个地方要注意一下: 我们使用的是 ES6 和 ES7 的语法. 如果你对于现实和容

编写react组件最佳实践

我最开始学习react的时候,看到过各种各样编写组件的方式,不同教程中提出的方法往往有很大不同.当时虽说react这个框架已经十分成熟,但是似乎还没有一种公认正确的使用方法.过去几年中,我们团队编写了很多react组件,我们对实现方法进行了不断的优化,直到满意. 本文介绍了我们在实践中的最佳实践方式,希望能对无论是初学者还是有经验的开发者来说都有一定的帮助. 在我们开始之前,有几点需要说明: 我们是用es6和es7语法 如果你不了解展示组件和容器组件的区别,可以先阅读这篇文章 如果你有任何建议.

专有网络(VPC)使用最佳实践

专有网络VPC(Virtual Private Cloud)是用户基于阿里云创建的自定义私有网络, 不同的专有网络之间二层逻辑隔离,用户可以在自己创建的专有网络内创建和管理云产品实例,比如ECS.负载均衡.RDS等. 本文,会先对 VPC 的使用限制和常见误区进行说明,然后介绍 VPC 的使用最佳实践. VPC 和 VPC 相关组件的介绍 在开始使用 VPC 之前,建议您务必阅读如下文章,了解 VPC 和 VPC 相关组件的介绍: 专有网络 VPC 的介绍 弹性公网 IP 概述 NAT 网关产品

RDS最佳实践(一)–如何选择RDS

在去年双11之前,为了帮助商家准备天猫双11的大促,让用户更好的使用RDS,把RDS的性能发挥到最佳,保障双11当天面对爆发性增加的压力,不会由于RDS的瓶颈导致系统出现问题,编写了RDS的最佳实践.该文档的内容全部出自于生产实践,但由于篇幅的限制,我只是把其中的概要罗列到了ppt中,并没有展开详细的介绍,后续计划写一个系列,把ppt中的内容进一步展开来讲一讲,也算是对RDS用户的一个交代.   我该如何选择RDS?我要购买多大规格的RDS?RDS的连接数,iops指的是什么?上诉这些问题相信是

JSP 最佳实践:组合 JavaBean 组件和 JSP 技术

js JSP 最佳实践:组合 JavaBean 组件和 JSP 技术 使用 JavaBean 和 JSP 参数在 Web 页面之间传递数据级别:入门Brett McLaughlin(brett@oreilly.com)作家,O'Reilly and Associates2003 年 7 月 Web 架构设计师 Brett McLaughlin 演示了 JavaBean 组件和 JSP 技术的结合如何使您能够在 Web 页面之间存储并传递数据,以及这样做如何能实现更为动态的站点设计.到目前为止,我

【直播】React、AliSQL、BeeHive、JStorm等8大阿里开源项目最佳实践分享

  本次峰会精选了目前较为活跃的阿里开源项目,其中较为有看点的是:在GitHub上拥有超过一万Star.在阿里内部落地超过400个项目的React 组件库 antd在蚂蚁金服的实践:MariaDB基金会唯一的中国成员详解AliSQL功能特性:已在天猫.喵师傅,天猫家装等App中应用大型iOS项目解耦方法--BeeHive:Android平台页面路由框架ARouter的一手开发经验:开源的 Android 平台上的秒级编译方案.阿里巴巴 Github 下排行前十的开源项目Freeline背后的奥秘

JSP最佳实践: 组合JavaBean组件和JSP技术

使用 JavaBean 和 JSP 参数在 Web 页面之间传递数据 简介:Web 架构设计师 Brett McLaughlin 演示了 JavaBean 组件和 JSP 技术的结合如何使您能够在 Web 页面之间存储并传递数据,以及这样做如何能实现更为动态的站点设计. 到目前为止,我们在 JSP 最佳实践系列文章 中着重讨论的都是较为基本的主题.在前两篇文章中, 您学习了如何使用 JSP include 机制来将外部内容引入到您的网站或 Web 应用程序.我们使用了两种不 同的 include

基于AngularJS前端云组件最佳实践_AngularJS

AngularJS是google设计和开发的一套前端开发框架,他能帮助开发人员更便捷地进行前端开发.AngularJS是为了克服HTML在构建应用上的不足而设计的,它非常全面且简单易学习,因此AngularJS快速的成为了javascript的主流框架. 一.Amazing的Angular AnguarJS的特性 方便的REST: RESTful逐渐成为了一种标准的服务器和客户端沟通的方式.你只需使用一行javascript代码,就可以快速的从服务器端得到数据.AugularJS将这些变成了JS

如何写出漂亮的React组件

在Walmart Labs的产品开发中,我们进行了大量的Code Review工作,这也保证了我有机会从很多优秀的工程师的代码中学习他们的代码风格与样式.在这篇博文里我会分享出我最欣赏的五种组件模式与代码片.不过我首先还是要谈谈为什么我们需要执着于提高代码的阅读体验.就好像你有很多种方式去装扮一只猫,如果你把你的爱猫装扮成了如下这样子: 你或许可以认为萝卜青菜各有所爱,但是代码本身是应当保证其可读性,特别是在一个团队中,你的代码是注定要被其他人阅读的.电脑是不会在意这些的,不管你朝它们扔过去什么