基于 Class 的組件最佳實踐(Class Based Components)
基于 Class 的組件是狀態(tài)化的,包含有自身方法、生命周期函數(shù)、組件內(nèi)狀態(tài)等。最佳實踐包括但不限于以下一些內(nèi)容:
1)引入 CSS 依賴 (Importing CSS)
我很喜歡 CSS in JavaScript 這一理念。在 React 中,我們可以為每一個 React 組件引入相應(yīng)的 CSS 文件,這一“夢想”成為了現(xiàn)實。在下面的代碼示例,我把 CSS 文件的引入與其他依賴隔行分開,以示區(qū)別:
import React, {Component} from 'react'
import {observer} from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
當(dāng)然,這并不是真正意義上的 CSS in JS,具體實現(xiàn)其實社區(qū)上有很多方案。我的 Github 上 fork 了一份各種 CSS in JS 方案的多維度對比,感興趣的讀者可以參考這里:HOUCe/css-in-js。
2)設(shè)定初始狀態(tài)(Initializing State)
在編寫組件過程中,一定要注意初始狀態(tài)的設(shè)定。同時,利用 ES6 模塊化的知識,我們確保該組件暴露都是 “export default” 形式,方便其他模塊(組件)的調(diào)用和團隊協(xié)作。
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 }
......
3)設(shè)定 propTypes 和 defaultProps
propTypes 和 defaultProps 都是組件的靜態(tài)屬性。在組件的代碼中,這兩個屬性的設(shè)定位置越高越好。因為這樣方便其他閱讀代碼者或者開發(fā)者自己 review,一眼就能看到這些信息。這些信息就如同組件文檔一樣,對于理解或熟悉當(dāng)前組件非常重要。
同樣,原則上,你編寫的組件都需要有 propTypes 屬性。如同以下代碼:
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 是指沒有狀態(tài)、沒有方法,純組件。應(yīng)該最大限度地編寫和使用這一類組件。這類組件作為函數(shù),其參數(shù)就是 props, 我們可以合理設(shè)定初始狀態(tài)和賦值。
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>
)
}
4)組件方法(Methods)
在編寫組件方法時,尤其是你將一個方法作為 props 傳遞給子組件時,需要確保 this 的正確指向。我們通常使用 bind 或者 ES6 箭頭函數(shù)來達到此目的。
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 })
}
當(dāng)然,這并不是唯一做法。實現(xiàn)方式多種多樣,我專門有一片文章來對比 React 中對于組件 this 的綁定,可以參考:從 React 綁定 this,看 JS 語言發(fā)展和框架設(shè)計。
5)setState 接受一個函數(shù)作為參數(shù)(Passing setState a Function)
在上面的代碼示例中,我們使用了:
this.setState({ expanded: !this.state.expanded })
這里,關(guān)于 setState hook 函數(shù),其實有一個非?!坝幸馑肌钡膯栴}。React 在設(shè)計時,為了性能上的優(yōu)化,采用了 Batch 思想,會收集“一波” state 的變化,統(tǒng)一進行處理。就像瀏覽器繪制文檔的實現(xiàn)一樣。所以 setState 之后,state 也許不會馬上就發(fā)生變化,這是一個異步的過程。
這說明,我們要謹(jǐn)慎地在 setState 中使用當(dāng)前的 state,因為當(dāng)前的state 也許并不可靠。 為了規(guī)避這個問題,我們可以這樣做:
this.setState(prevState => ({ expanded: !prevState.expanded }))
我們給 setState 方法傳遞一個函數(shù),函數(shù)參數(shù)為上一刻 state,便保證setState 能夠立刻執(zhí)行。
關(guān)于 React setState 的設(shè)計, Eric Elliott 也曾經(jīng)在一篇文章中這么噴過:https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82#.ftefj7nn2, 并由此展開了多方“撕逼”。作為圍觀群眾,我們在吃瓜的同時,一定會在大神論道當(dāng)中收獲很多思想,建議閱讀。