- 原文地址:How to NOT React: Common Anti-Patterns and Gotchas in React
- 原文做者:NeONBRAND
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:MechanicianW
- 校對者:anxsec ClarenceC
什麼是反模式?反模式是軟件開發中被認爲是糟糕的編程實踐的特定模式。一樣的模式,可能在過去一度被認爲是正確的,可是如今開發者們已經發現,從長遠來看,它們會形成更多的痛苦和難以追蹤的 Bug。html
做爲一個 UI 庫,React 已經成熟,而且隨着時間的推移,許多最佳實踐也逐漸造成。咱們將從數千名開發者集體的智慧中學習,他們曾用笨方法(the hard way)學習這些最佳實踐。前端
此言不虛!react
讓咱們開始吧!android
在使用自定義函數做爲組件屬性以前你必須將你的自定義函數寫在 constructor
中。若是你是用 extends
關鍵字聲明組件的話,自定義函數(以下面的 updateValue
函數)會失去 this
綁定。所以,若是你想使用 this.state
,this.props
或者 this.setState
,你還得從新綁定。webpack
class app extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
this.updateValue = this.updateValue.bind(this);
}
updateValue(evt) {
this.setState({
name: evt.target.value
});
}
render() {
return (
<form>
<input onChange={this.updateValue} value={this.state.name} />
</form>
)
}
}
複製代碼
有兩種方法能夠將自定義函數綁定到組件的 this
。一種方法是如上面所作的那樣,在 constructor
中綁定。另外一種方法是在傳值的時候做爲屬性的值進行綁定:ios
<input onChange={this.updateValue.bind(this)} value={this.state.name} />
複製代碼
這種方法有一個問題。因爲 .bind()
每次運行時都會建立一個函數,這種方法會致使每次 render
**函數執行時都會建立一個新函數。**這會對性能形成一些影響。然而,在小型應用中這可能並不會形成顯著影響。隨着應用體積變大,差異就會開始顯現。這裏 有一個案例研究。git
箭頭函數所涉及的性能問題與 bind
相同。github
<input onChange={ (evt) => this.setState({ name: evt.target.value }) } value={this.state.name} />
複製代碼
這種寫法明顯更清晰。能夠看到 prop onChange
函數中發生了什麼。可是,這也致使了每次 input
組件渲染時都會建立一個新的匿名函數。所以,箭頭函數有一樣的性能弊端。web
避免上述性能弊端的最佳方法是在函數自己的構造器中進行綁定。這樣,在組件建立時僅建立了一個額外函數,即便再次執行 render
也會使用該函數。數據庫
有一種狀況常常發生就是你忘記在構造函數中去 bind
你的函數,而後就會收到報錯(Cannot find X on undefined.)。Babel 有個插件可讓咱們使用箭頭語法寫出自動綁定的函數。插件是 Class properties transform。如今你能夠這樣編寫組件:
class App extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
// 看!無需在此處進行函數綁定!
}
updateValue = (evt) => {
this.setState({
name: evt.target.value
});
}
render() {
return (
<form>
<input onChange={this.updateValue} value={this.state.name} />
</form>
)
}
}
複製代碼
this
的方法遍歷元素集合時,key 是必不可少的 prop。key 應該是穩定,惟一,可預測的,這樣 React 才能追蹤元素。key 是用來幫助 React 輕鬆調和虛擬 DOM 與真實 DOM 間的差別的。然而,使用某些值集例如數組索引,可能會致使你的應用崩潰或是渲染出錯誤數據。
{elements.map((element, index) =>
<Display
{...element}
key={index}
/>
)
}
複製代碼
當子元素有了 key,React 就會使用 key 來匹配原始樹結構和後續樹結構中的子元素。**key 被用於做身份標識。**若是兩個元素有一樣的 key,React 就會認爲它們是相同的。當 key 衝突了,即超過兩個元素具備一樣的 key,React 就會拋出警告。
警告出現重複的 key。
這裏 是 CodePen 上使用索引做爲 key 可能致使的問題的一個示例。
被使用的 key 應該是:
數組索引是惟一且可預測的。然而,並不穩定。一樣,隨機數或時間戳不該被用做爲 key。
因爲隨機數既不惟一也不穩定,使用隨機數就至關於根本沒有使用 key。即便內容沒有改變,組件也會每次都從新渲染。
時間戳既不穩定也不可預測。**時間戳也會一直遞增。**所以每次刷新頁面,你都會獲得新的時間戳。
一般,你應該依賴於數據庫生成的 ID 如關係數據庫的主鍵,Mongo 中的對象 ID。若是數據庫 ID 不可用,你能夠生成內容的哈希值來做爲 key。關於哈希值的更多內容能夠在這裏閱讀。
React 組件主要由三部分組成:state
,props
和標記(或其它組件)。props 是不可變的,state 是可變的。state 的改變會致使組件從新渲染。若是 state 是由組件在內部管理的,則使用 this.setState
來更新 state。關於這個函數有幾件重要的事須要注意。咱們來看看:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
counter: 350
};
}
updateCounter() {
// 這行代碼不會生效
this.state.counter = this.state.counter + this.props.increment;
// ---------------------------------
// 不會如預期生效
this.setState({
counter: this.state.counter + this.props.increment; // 可能不會渲染
});
this.setState({
counter: this.state.counter + this.props.increment; // this.state.counter 的值是什麼?
});
// ---------------------------------
// 如期生效
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
}
}
複製代碼
請注意第 11 行代碼。若是你直接修改了 state,組件並不會從新渲染,修改也不會有任何體現。這是由於 state 是進行淺比較(shallow compare)的。你應該永遠都使用 setState
來改變 state 的值。
如今,若是你在 setState
中經過當前的 state
值來更新至下一個 state (正如第 15 行代碼所作的),React 可能不會從新渲染。這是由於 state
和 props
是異步更新的。也就是說,DOM 並不會隨着 setState
被調用就當即更新。React 會將屢次更新合併到同一批次進行更新,而後渲染 DOM。查詢 state
對象時,你可能會收到已通過期的值。文檔也提到了這一點:
因爲
this.props
和this.state
是異步更新的,你不該該依賴它們的值來計算下一個 state。
另外一個問題出現於一個函數中有屢次 setState
調用時,如第 16 和 20 行代碼所示。counter 的初始值是 350。假設 this.props.increment
的值是 10。你可能覺得在第 16 行代碼第一次調用 setState
後,counter 的值會變成 350+10 = **360。**而且,當第 20 行代碼再次調用 setState
時,counter 的值會變成 360+10 = 370。然而,這並不會發生。第二次調用時所看到的 counter
的值仍爲 350。**這是由於 setState 是異步的。**counter 的值直到下一個更新週期前都不會發生改變。setState 的執行在事件循環中等待,直到 updateCounter
執行完畢前,setState
都不會執行, 所以 state
的值也不會更新。
你應該看看第 27 和 31 行代碼使用 setState
的方式。以這種方式,你能夠給 setState
傳入一個接收 currentState 和 currentProps 做爲參數的函數。這個函數的返回值會與當前 state 合併以造成新的 state。
setState
是異步的所作的超級棒的解釋setState
中使用函數而不是對象React 文檔提到這也是反模式:
在 getInitialState 中使用 props 來生成 state 常常會致使重複的「事實來源」,即真實數據的所在位置。這是由於 getInitialState 僅僅在組件第一次建立時被調用。
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {
someValue: props.someValue,
};
}
}
複製代碼
constructor
(getInitialState) 僅僅在組件建立階段被調用。也就是說,constructor
只被調用一次。所以,當你下一次改變 props
時,state 並不會更新,它仍然保持爲以前的值。
經驗尚淺的開發者常常設想 props
的值與 state 是同步的,隨着 props
改變,state
也會隨之變化。然而,真實狀況並非這樣。
若是你須要特定的行爲即你但願 state 僅由 props 的值生成一次的話,可使用這種模式。state 將由組件在內部管理。
在另外一個場景下,你能夠經過生命週期方法 componentWillReceiveProps
保持 state 與 props 的同步,以下所示。
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {
someValue: props.someValue,
};
}
componentWillReceiveProps(nextProps){
if (nextProps.inputValue !== this.props.inputValue) {
this.setState({ inputVal: nextProps.inputValue })
}
}
}
複製代碼
要注意,關於使用 componentWillReceiveProps
有一些注意事項。你能夠在文檔中閱讀。
最佳方法是使用狀態管理庫如 Redux 去 connect state 和組件。
在 React 中,若是你想使用 JSX 渲染你的組件,組件名必須以大寫字母開頭。
<MyComponent>
<app /> // 不會生效 :(
</MyComponent>
<MyComponent>
<App /> // 能夠生效!
</MyComponent>
複製代碼
若是你建立了一個 app
組件,以 <app label="Save" />
的形式去渲染它,React 將會報錯。
使用非大寫自定義組件時的警告。
報錯代表 <app>
是沒法識別的。只有 HTML 元素和 SVG 標籤能夠以小寫字母開頭。所以 <div />
是能夠識別的,<app>
卻不能。
你須要確保在 JSX 中使用的自定義組件是以大寫字母開頭的。
可是也要明白,聲明組件無需聽從這一規則。所以,你能夠這樣寫:
// 在這裏以小寫字母開頭是能夠的
class primaryButton extends Component {
render() {
return <div />;
}
}
export default primaryButton;
// 在另外一個文件中引入這個按鈕組件。要確保以大寫字母開頭的名字引入。
import PrimaryButton from 'primaryButton';
<PrimaryButton />
複製代碼
以上這些都是 React 中不直觀,難以理解也容易出現問題的地方。若是你知道任何其它的反模式,請回複本文。😀
我還寫了一篇 能夠幫助快速開發的優秀 React 和 Redux 包
若是你仍在學習如何構建 React 項目,這個含有兩部分的系列文章 能夠幫助你理解 React 構建系統的多個方面。
我寫做 JavaScript,Web 開發與計算機科學領域的文章。關注我能夠每週閱讀新文章。若是你喜歡,能夠分享本文。
關注我 @ Facebook @ Linkedin @ Twitter.
✉️ 訂閱 CodeBurst的每週郵件 Email Blast, 🐦能夠在Twitter 上關注 CodeBurst, 瀏覽 🗺️ The 2018 Web Developer Roadmap, 和 🕸️ 學習 Web 全棧開發。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。