如今前端程序員都知道,React 是組件化的。當我開始學習 React 的時候,我記得當時已經存在了不少不一樣編寫組件的方式了。現在,React
社區已經愈發成熟,可是對於組件正確編寫姿式卻沒有一個相對完備的指導。javascript
這篇文章僅從做者的觀點出發,來談一談咱們究竟應該如何來寫高質量的 React 組件。css
在開始前,須要說明如下幾個問題:前端
基於 Class 的組件是狀態化的,包含有自身方法、生命週期函數、組件內狀態等。最佳實踐包括但不限於如下一些內容:java
我很喜歡 CSS in JavaScript 這一理念。在 React 中,咱們能夠爲每個 React 組件引入相應的 CSS 文件,這一「夢想」成爲了現實。在下面的代碼示例,我把 CSS 文件的引入與其餘依賴隔行分開,以示區別:react
import React, {Component} from 'react'
import {observer} from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'複製代碼
固然,這並非真正意義上的 CSS in JS,具體實現其實社區上有不少方案。個人 Github 上 fork 了一份各類 CSS in JS 方案的多維度對比,感興趣的讀者能夠點擊這裏。git
在編寫組件過程當中,必定要注意初始狀態的設定。利用 ES6 模塊化的知識,咱們確保該組件暴露都是 「export default」 形式,方便其餘模塊(組件)的調用和團隊協做。程序員
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 和 defaultProps 都是組件的靜態屬性。在組件的代碼中,這兩個屬性的設定位置越高越好。由於這樣方便其餘閱讀代碼者或者開發者本身 review,一眼就能看到這些信息。這些信息就如同組件文檔同樣,對於理解或熟悉當前組件很是重要。github
一樣,原則上,你編寫的組件都須要有 propTypes 屬性。如同如下代碼:redux
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'
}複製代碼
Functional Components 是指沒有狀態、沒有方法,純組件。咱們應該最大限度地編寫和使用這一類組件。這類組件做爲函數,其參數就是 props, 咱們能夠合理設定初始狀態和賦值。瀏覽器
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>
)
}複製代碼
在編寫組件方法時,尤爲是你將一個方法做爲 props 傳遞給子組件時,須要確保 this 的正確指向。咱們一般使用 bind 或者 ES6 箭頭函數來達到此目的。
export default class ProfileContainer extends Component {
state = { expanded: false }
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 })
}複製代碼
固然,這並非惟一作法。實現方式多種多樣,我專門有一片文章來對比 React 中對於組件 this 的綁定,能夠點擊此處參考。
在上面的代碼示例中,咱們使用了:
this.setState({ expanded: !this.state.expanded })複製代碼
這裏,關於 setState hook 函數,其實有一個很是「有意思」的問題。React 在設計時,爲了性能上的優化,採用了 Batch 思想,會收集「一波」 state 的變化,統一進行處理。就像瀏覽器繪製文檔的實現同樣。因此 setState 以後,state 也許不會立刻就發生變化,這是一個異步的過程。
這說明,咱們要謹慎地在 setState 中使用當前的 state,由於當前的state 也許並不可靠。
爲了規避這個問題,咱們能夠這樣作:
this.setState(prevState => ({ expanded: !prevState.expanded }))複製代碼
咱們給 setState 方法傳遞一個函數,函數參數爲上一刻 state,便保證setState 可以馬上執行。
關於 React setState 的設計, Eric Elliott 也曾經這麼噴過:setState() Gate,並由此展開了多方「撕逼」。做爲圍觀羣衆,咱們在吃瓜的同時,必定會在大神論道當中收穫不少思想,建議閱讀。
若是你對 setState 方法的異步性還有困惑,能夠同我討論,這裏再也不展開。
這個其實沒有太多可說的,仔細觀察代碼吧:咱們使用瞭解構賦值。除此以外,若是一個組件有不少的 props 的話,每一個 props 應該都另起一行,這樣書寫上和閱讀性上都有更好的體驗。
export default class ProfileContainer extends Component {
state = { expanded: false }
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>
)
}
}複製代碼
這一條是對使用 mobx 的開發者來講的。若是你不懂 mobx,能夠大致掃一眼。
咱們強調使用 ES next decorate 來修飾咱們的組件,如同:
@observer
export default class ProfileContainer extends Component {複製代碼
使用修飾器更加靈活且可讀性更高。即使你不使用修飾器,也須要如此暴露你的組件:
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"/>複製代碼
不要:
onChange = {(e) => { model.name = e.target.value }}複製代碼
而是:
onChange = {this.handleChange}複製代碼
緣由其實很簡單,每次父組件 render 的時候,都會新建一個新的函數並傳遞給 input。
若是 input 是一個 React 組件,這會粗暴地直接致使這個組件的 re-render,須要知道,Reconciliation 但是 React 成本最高的部分。
另外,咱們推薦的方法,會使得閱讀、調試和更改更加方便。
真正寫過 React 項目的同窗必定會明白,JSX 中可能會存在大量的條件判別,以達到根據不一樣的狀況渲染不一樣組件形態的效果。
就像下圖這樣:
這樣的結果是不理想的。咱們丟失了代碼的可讀性,也使得代碼組織顯得混亂異常。多層次的嵌套也是應該避免的。
針對於此,有很對類庫來解決 JSX-Control Statements 此類問題,可是與其引入第三方類庫的依賴,還不如咱們先本身嘗試探索解決問題。
此時,是否是有點懷念if...else?
咱們可使用大括號內包含當即執行函數IIFE,來達到使用 if...else 的目的:
固然,大量使用當即執行函數會形成性能上的損失。因此,考慮代碼可讀性上的權衡,仍是有必要好好斟酌的。
我更加建議的作法是分解此組件,由於這個組件的邏輯已通過於複雜而臃腫了。如何分解?請看我這篇文章。
其實所謂 React 「最佳實踐」,想必每一個團隊都有本身的一套「心得」,哪裏有一個統一套? 本文指出的幾種方法未必對任何讀者都適用。針對不一樣的代碼風格,開發習慣,擁有本身團隊一套「最佳實踐」是頗有必要的。從另外一方面,也說明了 React 技術棧自己的靈活於強大。
另外,這篇文章並非我原創,而是翻譯了Our Best Practices for Writing React Components一文,並在此基礎上進行了較大幅度擴展。
若是您對React生態有興趣,一樣推薦個人其餘幾篇文章:
Happy Coding!