英文原文:React.js Best Practices for 2016
作者:Péter Márton
轉(zhuǎn)載自AlloyTeam:
http://www.alloyteam.com/2016/01/reactjs-best-practices-for-2016/
譯者按:近幾個(gè)月React相關(guān)話題依舊火熱,相信越來(lái)越多的開(kāi)發(fā)者在嘗試這樣一項(xiàng)技術(shù),我們團(tuán)隊(duì)也在PC和移動(dòng)端不斷總結(jié)經(jīng)驗(yàn)。2016來(lái)了,這應(yīng)該是React走向成熟的一年,不管你是新手,還是已經(jīng)對(duì)React有所了解,是時(shí)候總結(jié)一下最佳實(shí)踐了,讓我們看看國(guó)外的開(kāi)發(fā)者總結(jié)了哪些好的實(shí)踐吧~
2015可以算是React之年了,關(guān)于其版本發(fā)布和開(kāi)發(fā)者大會(huì)的話題遍布全球。關(guān)于去年React的發(fā)展里程碑詳情,可以查看我們整理的React 2015這一年。
2016年最有趣的問(wèn)題可能是,我們?cè)撊绾尉帉?xiě)一個(gè)應(yīng)用呢,有什么推薦的庫(kù)或框架?
作為一個(gè)長(zhǎng)時(shí)間使用React.js的開(kāi)發(fā)者,我已經(jīng)有自己的答案和最佳實(shí)踐了,但你可能不會(huì)同意我說(shuō)的所有點(diǎn)。我對(duì)你的想法和意見(jiàn)很感興趣,請(qǐng)留言進(jìn)行討論。

如果你只是剛開(kāi)始接觸React.js,請(qǐng)閱讀React.js教程,或Pete Hunt的React howto。
數(shù)據(jù)處理
在React.js應(yīng)用中處理數(shù)據(jù)超級(jí)簡(jiǎn)單的,但同時(shí)還是有些挑戰(zhàn)。
這是因?yàn)槟憧梢允褂枚喾N方式,來(lái)給一個(gè)React組件傳遞屬性數(shù)據(jù),從而構(gòu)建出渲染樹(shù)。但這種方式并不總是能明顯地看出,你是否應(yīng)該更新某些視圖。
2015開(kāi)始涌現(xiàn)出一批具有更強(qiáng)功能和響應(yīng)式解決方案的Flux庫(kù),讓我們一起看看:
Flux
根據(jù)我們的經(jīng)驗(yàn),F(xiàn)lux通常被過(guò)度使用了(就是大家在不需使用的場(chǎng)景下,還是使用了)。
Flux提供了一種清爽的方式存儲(chǔ)和管理應(yīng)用的狀態(tài),并在需要的時(shí)候觸發(fā)渲染。
Flux對(duì)于那些應(yīng)用的全局state(譯者注:為了對(duì)應(yīng)React中的state概念,本文將不對(duì)state進(jìn)行翻譯)特別有用,比如:管理登錄用戶的狀態(tài)、路由狀態(tài),或是活躍賬號(hào)狀態(tài)。如果使用臨時(shí)變量或者本地?cái)?shù)據(jù)來(lái)處理這些狀態(tài),會(huì)非常讓人頭疼。
我們不建議使用Flux來(lái)管理路由相關(guān)的數(shù)據(jù),比如/items/:itemId。應(yīng)該只是獲取它并存在組件的state中,這種情況下,它會(huì)在組件銷毀時(shí)一起被銷毀。
如果需要Flux的更多信息,建議閱讀The Evolution of Flux Frameworks。
使用Redux
Redux是一個(gè)JavaScript app的可預(yù)測(cè)state容器。
如果你覺(jué)得需要Flux或者相似的解決方案,你應(yīng)該了解一下redux,并學(xué)習(xí)Dan Abramov的redux入門(mén)指南,來(lái)強(qiáng)化你的開(kāi)發(fā)技能。
Rudux發(fā)展了Flux的思想,同時(shí)降低了其復(fù)雜度。
扁平化state
API通常會(huì)返回嵌套的資源,這讓Flux或Redux架構(gòu)很難處理。我們推薦使用normalizr這類庫(kù)來(lái)盡可能地扁平化state。
像這樣:
const data = normalize(response, arrayOf(schema.user))
state = _.merge(state, data.entities)
(我們使用isomorphic-fetch與API進(jìn)行通信)
使用immutable state
共享的可變數(shù)據(jù)是罪惡的根源——Pete Hunt, React.js Conf 2015

不可變對(duì)象是指在創(chuàng)建后不可再被修改的對(duì)象。
不可變對(duì)象可以減少那些讓我們頭痛的工作,并且通過(guò)引用級(jí)的比對(duì)檢查來(lái)提升渲染性能。比如在shouldComponentUpdate中:
shouldComponentUpdate(nexProps) {
// 不進(jìn)行對(duì)象的深度對(duì)比
return this.props.immutableFoo !== nexProps.immutableFoo
}
如何在JavaScript中實(shí)現(xiàn)不可變
比較麻煩的方式是,小心地編寫(xiě)下面的例子,總是需要使用deep-freeze-node(在變動(dòng)前進(jìn)行凍結(jié),結(jié)束后驗(yàn)證結(jié)果)進(jìn)行單元測(cè)試。
return {
...state,
foo
}
return arr1.concat(arr2)
相信我,這是最明顯的例子了。
更簡(jiǎn)單自然的方式,就是使用Immutable.js。
import { fromJS } from 'immutable'
const state = fromJS({ bar: 'biz' })
const newState = foo.set('bar', 'baz')
Immutable.js非???,其背后的思想也非常美妙。就算沒(méi)準(zhǔn)備使用它,還是推薦你去看看Lee Byron的視頻Immutable Data and React,可以了解到它內(nèi)部的實(shí)現(xiàn)原理。
Observables and reactive解決方案
如果你不喜歡Flux/Redux,或者想要更加reactive,不用失望!還有很多方案供你選擇,這里是你可能需要的:
- cycle.js(“一個(gè)更清爽的reactive框架”)
- rx-flux(“Flux與Rxjs結(jié)合的產(chǎn)物”)
- redux-rx(“Redux的Rxjs工具庫(kù)”)
- mobservable(“可觀測(cè)的數(shù)據(jù),reactive的功能,簡(jiǎn)潔的代碼”)
路由
現(xiàn)在幾乎所有app都有路由功能。如果你在瀏覽器中使用React.js,你將會(huì)接觸到這個(gè)點(diǎn),并為其選擇一個(gè)庫(kù)。
我們選擇的是出自優(yōu)秀rackt社區(qū)的react-router,這個(gè)社區(qū)總是能為React.js愛(ài)好者們帶來(lái)高質(zhì)量的資源。
要使用react-router需要查看它的文檔,但更重要的是:如果你使用Flux/Redux,我們推薦你將路由state與store或全局state保持同步。
同步路由state可以讓Flux/Redux來(lái)控制路由行為,并讓組件讀取到路由信息。
Redux的用戶可以使用redux-simple-router來(lái)省點(diǎn)事兒。
代碼分割,懶加載
只有一小部分webpack的用戶知道,應(yīng)用代碼是可以分割成多個(gè)js包的。
require.ensure([], () => {
const Profile = require('./Profile.js')
this.setState({
currentComponent: Profile
})
})
這對(duì)于大型應(yīng)用十分有用,因?yàn)橛脩魹g覽器不用下載那些很少會(huì)使用到的代碼,比如Profile頁(yè)。
多js包會(huì)導(dǎo)致額外的HTTP請(qǐng)求數(shù),但對(duì)于HTTP/2的多路復(fù)用,完全不是問(wèn)題。
與chunk hashing 結(jié)合可以優(yōu)化緩存命中率。
下個(gè)版本的react-router將會(huì)對(duì)代碼分隔做更多支持。
對(duì)于react-router的未來(lái)規(guī)劃,可以去看博文Ryan Florence: Welcome to Future of Web Application Delivery。
組件
很多人都在抱怨JSX,但首先要知道,它只是React中可選的一項(xiàng)能力。
最后,它們都會(huì)被Bable編譯成JavaScript。你可以繼續(xù)使用JavaScript編寫(xiě)代碼,但是在處理HTML時(shí)使用JSX會(huì)感覺(jué)更自然。特別是對(duì)于那些不懂js的人,他們可以只修改HTML相關(guān)的部分。
JSX是一個(gè)類似于XML的JavaScript擴(kuò)展,可以配合一個(gè)簡(jiǎn)單的語(yǔ)法編譯工具來(lái)使用它?!?a target="_blank" rel="nofollow">深入淺出JSX
如果你想了解更多JSX的內(nèi)容,查看文章JSX Looks Like An Abomination – But it’s Good for You。
使用類
React中可以順暢地使用ES2015的Class語(yǔ)法。
class HelloMessage extends React.Component {
render() {
return <div>Hello {this.props.name}</div>
}
}
我們?cè)诟唠A組件和mixins之間更看重前者,所以拋棄createClass更像是一個(gè)語(yǔ)法問(wèn)題,而不是技術(shù)問(wèn)題。(譯者注:在Class語(yǔ)法中,React組件的mixins方法將無(wú)法使用。)我們認(rèn)為使用createClass和React.Component沒(méi)有對(duì)錯(cuò)之分。
屬性類型(PropType)
如果你以前不檢查props的類型,那么2016你應(yīng)該開(kāi)始改正了。它會(huì)幫你節(jié)省未來(lái)很多時(shí)間,相信我。
MyComponent.propTypes = {
isLoading: PropTypes.bool.isRequired,
items: ImmutablePropTypes.listOf(
ImmutablePropTypes.contains({
name: PropTypes.string.isRequired,
})
).isRequired
}
是的,同時(shí)也盡可能使用react-immutable-proptypes檢查Immutable.js的props。
高階組件(Higher order components)
minins將死,ES6的Class將不對(duì)其進(jìn)行支持,我們需要尋找新的方法。
什么是高階組件?
PassData({ foo: 'bar' })(MyComponent)
簡(jiǎn)單地,你創(chuàng)建一個(gè)從原生組件繼承下來(lái)的組件,并且擴(kuò)展了原始組件的行為。你可以在多種場(chǎng)景來(lái)使用它,比如鑒權(quán):requireAuth({ role: 'admin' })(MyComponent)(在高階組件中檢查用戶權(quán)限,如果還沒(méi)有登錄就進(jìn)行跳轉(zhuǎn)),或者將組件與Flux/Redux的store相連通。
在RisingStack,我們也喜歡分離數(shù)據(jù)拉取和controller類的邏輯到高階組件中,這樣可以盡可能地保持view層的簡(jiǎn)單。
測(cè)試
好的代碼覆蓋測(cè)試是開(kāi)發(fā)周期中的重要一環(huán)。幸運(yùn)的是,React.js社區(qū)有很多這樣的庫(kù)來(lái)幫助我們。
組件測(cè)試
我們最喜愛(ài)的組件測(cè)試庫(kù)是AirBnb的enzyme。有了它的淺渲染特性,可以對(duì)組件的邏輯和渲染結(jié)果進(jìn)行測(cè)試,非常棒對(duì)不對(duì)?它現(xiàn)在還不能替代selenium測(cè)試,但是將前端測(cè)試提升到了一個(gè)新高度。
it('simulates click events', () => {
const onButtonClick = sinon.spy()
const wrapper = shallow(
<Foo onButtonClick={onButtonClick} />
)
wrapper.find('button').simulate('click')
expect(onButtonClick.calledOnce).to.be.true
})
看起來(lái)很清爽,不是嗎?
你使用chai來(lái)作為斷言庫(kù)嗎?你會(huì)喜歡chai-enyzime的。
Redux測(cè)試
測(cè)試一個(gè)reducer非常簡(jiǎn)單,它響應(yīng)actions然后將原來(lái)的state轉(zhuǎn)為新的state:
it('should set token', () => {
const nextState = reducer(undefined, {
type: USER_SET_TOKEN,
token: 'my-token'
})
// immutable.js state output
expect(nextState.toJS()).to.be.eql({
token: 'my-token'
})
})
測(cè)試actions也很簡(jiǎn)單,但是異步actions就不一樣了。測(cè)試異步的redux actions我們推薦redux-mock-store,它能幫不少忙。
it('should dispatch action', (done) => {
const getState = {}
const action = { type: 'ADD_TODO' }
const expectedActions = [action]
const store = mockStore(getState, expectedActions, done)
store.dispatch(action)
})
關(guān)于更深入的redux測(cè)試,請(qǐng)參考官方文檔。
使用npm
雖然React.js并不依賴代碼構(gòu)建工具,我們推薦Webpack和Browserify,它們都具有npm出色的能力。Npm有很多React.js的package,還可以幫助你優(yōu)雅地管理依賴。
(請(qǐng)不要忘記復(fù)用你自己的組件,這是優(yōu)化代碼的絕佳方式。)
包大?。˙undle size)
這本身不是一個(gè)React相關(guān)的問(wèn)題,但多數(shù)人都會(huì)對(duì)其React進(jìn)行打包,所以我在這里提一下。
當(dāng)你對(duì)源代碼進(jìn)行構(gòu)建時(shí),要保持對(duì)包大小的關(guān)注。要將其控制在最小體積,你需要思考如何require/import依賴。
查看下面的代碼片段,有兩種方式可以對(duì)輸出產(chǎn)生重大影響:
import { concat, sortBy, map, sample } from 'lodash'
// vs.
import concat from 'lodash/concat';
import sortBy from 'lodash/sortBy';
import map from 'lodash/map';
import sample from 'lodash/sample';
查看Reduce Your bundle.js File Size By Doing This One Thing,獲取更多詳情。
我們喜歡將代碼分隔到vendors.js和app.js,因?yàn)榈谌酱a的更新頻率比我們自己帶嗎低很多。
對(duì)輸出文件進(jìn)行hash命名(WebPack中的chunk hash),并使用長(zhǎng)緩存,我們可以顯著地減少訪問(wèn)用戶需要下載的代碼。結(jié)合代碼懶加載,優(yōu)化效果可想而知。
如果你對(duì)WebPack還很陌生,可以去看超贊的React webpack指南。
組件級(jí)的hot reload
如果你曾使用livereload寫(xiě)過(guò)單頁(yè)面應(yīng)用,你可能知道當(dāng)在處理一些與狀態(tài)相關(guān)的事情,一點(diǎn)代碼保存整個(gè)頁(yè)面就刷新了,這種體驗(yàn)有多煩人。你需要逐步點(diǎn)擊操作到剛才的環(huán)節(jié),然后在這樣的重復(fù)中奔潰。
在React開(kāi)發(fā)中,是可以reload一個(gè)組件,同時(shí)保持它的state不變——耶,從此無(wú)需苦惱!
搭建hot reload,可參考react-transform-boilerplate。
使用ES2015
前面提到過(guò),在React.js中使用的JSX,最終會(huì)被Babel.js進(jìn)行編譯。

Bable的能力還不止這些,它可以讓我們?cè)跒g覽器中放心地使用ES6/ES2015。在RisingStack,我們?cè)诜?wù)器端和客戶端都使用了ES2015的特性,ES2015已經(jīng)可以在最新的LTS Node.js版本中使用了。
代碼檢查(Linters)
也許你已經(jīng)對(duì)你的代碼制定了代碼規(guī)范,但是你知道React的各種代碼規(guī)范嗎?我們建議你選擇一個(gè)代碼規(guī)范,然后照著下面說(shuō)的來(lái)做。
在RisingStack,我們強(qiáng)制將linters運(yùn)行在持續(xù)集成(CI)系統(tǒng),已經(jīng)git push功能上。查看pre-push和pre-commit。
我們使用標(biāo)準(zhǔn)的JavaScript代碼風(fēng)格,并使用eslint-plugin-react來(lái)檢查React.js代碼。
(是的,我們已經(jīng)不再使用分號(hào)了)
GraphQL和Relay
GraphQL和Relay是相關(guān)的新技術(shù)。在RisingStack,我們不在生產(chǎn)環(huán)境使用它們,暫時(shí)保持關(guān)注。
我們寫(xiě)了一個(gè)Relay的MongoDB ORM,叫做graffiti,可以使用你已有的mongoose models來(lái)創(chuàng)建GraphQL server。
如果你想學(xué)習(xí)這些新技術(shù),我們建議你去看看這個(gè)庫(kù),然后寫(xiě)幾個(gè)demo玩玩。
這些React.js最佳實(shí)踐的核心點(diǎn)
有些優(yōu)秀的技術(shù)和庫(kù)其實(shí)跟React都沒(méi)什么關(guān)系,關(guān)鍵在于要關(guān)注社區(qū)都在做些什么。2015這一年,React社區(qū)被Elm架構(gòu)啟發(fā)了很多。
如果你知道其他2016年大家應(yīng)該使用的React.js工具,請(qǐng)留言告訴我們。