MuseFind:編寫React組件的最佳實現

xypb1184 7年前發布 | 19K 次閱讀 React

React 起源于 非死book 的內部項目,因為該公司對市場上所有 JavaScript MVC框架,都不滿意,就決定自己寫一套,用來架設 Instagram 的網站。做出來以后,發現這套東西很好用,就在2013年5月 開源 了。

由于React的設計思想極其獨特,屬于革命性創新,性能出眾,代碼邏輯卻非常簡單。所以,越來越多的人開始關注和使用,認為它可能是將來Web開發的主流工具。

當我第一次開始寫React代碼時,我記得看到過許多不同的編寫組件的方法,各教程之間有很大的不同。雖然自那時以來框架已經相當成熟,但似乎還沒有一個確定“正確”編寫的方式。

在過去一年里,在 MuseFind ,我們的團隊編寫了很多React組件。我們已經逐漸完善了方法,直到我們滿意為止。

本指南代表我們建議的最佳做法。我們認為本文對新手和老手都有所幫助。

閱讀本文之前,讀者需要注意以下幾點:

  • 我們使用ES6和ES7語法。
  • 如果你不確定展示型組件和容器組件之間的區別,我們建議你先閱讀 這篇文章
  • 如果您有任何建議問題或反饋,請在原文的評論區中告訴我們。

基于類的組件

基于類的組件是有狀態的,或許還包含方法。我們盡可能少地使用它們,但它們也有自己的位置。

讓我們逐行構建我們的組件。

導入CSS

import React, {Component} from 'react'
import {observer} from 'mobx-react'
import ExpandableForm 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

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的組件,每個props應該占據單獨一個行,如上所示。

裝飾器

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

這就是為什么每次父組件渲染時,創建一個新的函數并傳遞給輸入的原因。

如果輸入是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

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
ExpandableForm.propTypes = expandableFormRequiredProps

這里,我們在組件聲明前分配 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以高度可讀的方式。如果展開沒有定義的話,我們將其設置為false。(這是一個有點強迫的例子,因為它是一個布爾,但非常有用,以避免“無法讀取未定義”錯誤與對象)。

避免使用以下ES6語法:

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

看起來很現代,但此處的函數實際上是未命名的。

如果你的Babel設置正確,這個名字的缺失不會成為一個問題:但如果不是,任何錯誤將<<anonymous>>中顯示,對調試而言,是一個非常嚴重的問題。

未命名的函數也可能導致Jest(一個React測試庫)的問題。由于潛在的難以理解的bug,以及并沒有什么真正的好處,我們建議使用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)

這是我們的完整組件:

JSX條件

很可能你要做很多條件渲染。這里是你想避免的地方:

(點擊放大圖像)

這是我在MuseFind早期寫的實際代碼,饒恕我吧。

不,嵌套的三元運算并不是一個好主意。

有一些庫解決了這個問題( JSX控制語句 ),但是,而不是引入另一個依賴,我們解決了這種應對復雜條件的方法:

(點擊放大圖像)

以上所示是重構版本。

使用花括號括起一個 IIFE ,然后把你的if語句置于里面,返回任何你想要的渲染。請注意,這樣的IIFE可能會導致性能下降,但在大多數情況下,它不會嚴重到以致失去可讀性。

此外,當你只想渲染一個條件上的元素,而不是這樣做:

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

使用short-circuit賦值:

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

 

來自:http://www.infoq.com/cn/articles/our-best-practices-for-writing-react-components

 

 本文由用戶 xypb1184 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!