歡迎關注個人公衆號睿Talk
,獲取我最新的文章:
javascript
在平常開發和 Code Review 的時候,經常會發現一些共性的問題,也有不少值得提倡的作法。本文針對 React 技術棧,總結了一些最佳實踐,對編寫高質量的代碼有必定的參考做用。css
若是組件是純展現型的,不須要維護 state 和生命週期,則優先使用 Function Component。它有以下好處:java
一個典型的 Function Component 是下面這個樣子:segmentfault
function MenuItem({menuId, menuText, onClick, activeId}) { return ( <div menuId={menuId} className={`${style} ${activeId === menuId ? active : ''}`} onClick={onItemClick} > {menuText} </div> ); };
若是組件須要維護 state 或使用生命週期方法,則優先使用 PureComponent,而不是 Component。Component 的默認行爲是不論 state 和 props 是否有變化,都觸發 render。而 PureComponent 會先對 state 和 props 進行淺比較,不一樣的時候纔會 render。請看下面的例子:api
class Child extends React.Component { render() { console.log('render Child'); return ( <div> {this.props.obj.num} </div> ); } } class App extends React.Component { state = { obj: { num: 1 } }; onClick = () => { const {obj} = this.state; this.setState({obj}); } render() { console.log('render Parent'); return ( <div className="App" > <button onClick={this.onClick}> 點我 </button> <Child obj={this.state.obj}/> </div> ); } }
點擊按鈕後,Parent 和 Child 的 render 都會觸發。若是將 Child 改成 PureComponent,則 Child 的 render 不會觸發,由於 props 仍是同一個對象。若是將 Parent 也改成 PureComponent,則 Parent 的 render 也不會觸發了,由於 state 仍是同一個對象。網絡
好比一個公用的組件,數據來源多是父組件傳過來,又或者是本身主動經過網絡請求獲取數據。這時候能夠先定義一個純展現型的 Function Component,而後再定義一個高階組件去獲取數據:async
function Comp() { ... } class HOC extends PureComponent { async componentDidMount() { const data = await fetchData(); this.setState({data}); } render() { return (<Comp data={this.state.data}/>); } }
筆者在真實項目中就試過以繼承的形式寫組件,本身寫得很爽,代碼的複用性也很好,但最大的問題是別人看不懂。我將複用的業務邏輯和 UI 模版都在父類定義好,子類只須要傳入一些參數,而後再覆蓋父類的幾個方法就好(render的時候會用到)。簡化的代碼以下:函數
class Parent extends PureComponent { componentDidMount() { this.fetchData(this.url); } fetchData(url) { ... } render() { const data = this.calcData(); return ( <div>{data}</data> ); } } class Child extends Parent { constructor(props) { super(props); this.url = 'http://api'; } calcData() { ... } }
這樣的寫法從語言的特性和功能實現來講,沒有任何問題,最大的問題是不符合 React 的組件編寫習慣。父類或者子類確定有一方是不須要實現 render 方法的,而通常咱們看代碼都會優先找 render 方法,找不到就慌了。另外就是搞不清楚哪些方法是父類實現的,哪些方法是子類實現的,若是讓其餘人來維護這份代碼,會比較吃力。fetch
繼承會讓代碼難以溯源,定位問題也比較麻煩。全部經過繼承實現的組件均可以改寫爲組合的形式。上面的代碼就能夠這樣改寫:ui
class Parent extends PureComponent { componentDidMount() { this.fetchData(this.props.url); } fetchData(url) { ... } render() { const data = this.props.calcData(this.state); return ( <div>{data}</data> ); } } class Child extends PureComponent { calcData(state) { ... } render() { <Parent url="http://api" calcData={this.calcData}/> } }
這樣的代碼是否是看起來舒服多了?
常常會看見這樣的代碼:
componentWillReceiveProps(nextProps) { this.setState({num: nextProps.num}); } render() { return( <div>{this.state.num}</div> ); }
num 在組件中不會作任何的改變,這種狀況下直接使用 this.props.num 就能夠了。
render() { const obj = {num: 1} return( <Child obj={obj} onClick={()=>{...}} /> ); }
在上面代碼中,即便 Child 是 PureComponent,因爲 obj 和 onClick 每次 render 都是新的對象,Child 也會跟着 render。
render() { const a = 8; return ( <div> { a > 0 ? a < 9 ? ... : ... : ... } </div> ); }
像上面這種嵌套的三元表達式可讀性很是差,能夠寫成下面的形式:
f() { ... } render() { const a = 8; return ( <div> { this.f() } </div> ); }
const MenuItem = ({ menuId, menuText, onClick, activeId, }) => { return ( ... ); };
例子以下:
class CategorySelector extends PureComponent { ... } CategorySelector.propTypes = { type: PropTypes.string, catList: PropTypes.array.isRequired, default: PropTypes.bool, }; CategorySelector.defaultProps = { default: false, type: undefined, };
下面這種狀況通常外層的div
是多餘的,能夠將樣式直接定義在組件內,或者將定製的樣式做爲參數傳入。例外:當ServiceItem
須要在多個地方使用,並且要疊加不少不同的樣式,原寫法會方便些。
// bad <div key={item.uuid} className={scss.serviceItemContainer}> <ServiceItem item={item} /> </div> // good <ServiceItem key={item.uuid} item={item} className={customStyle} />
本文列舉了筆者在項目實戰和 Code Review 過程當中總結的 10 條最佳實踐,當中的一些寫法和原則只表明我的立場。理解並遵循這些最佳實踐,寫出來的代碼質量會有必定的保證。若是你有不一樣的意見,或者有補充的最佳實踐,歡迎留言交流。