譯者按:近幾個月React相關(guān)話題依舊火熱,相信越來越多的開發(fā)者在嘗試這樣一項技術(shù),我們團隊也在PC和移動端不斷總結(jié)經(jīng)驗。2016來了,這應(yīng)該是React走向成熟的一年,不管你是新手,還是已經(jīng)對React有所了解,是時候總結(jié)一下最佳實踐了,讓我們看看國外的開發(fā)者總結(jié)了哪些好的實踐吧~
2015可以算是React之年了,關(guān)于其版本發(fā)布和開發(fā)者大會的話題遍布全球。關(guān)于去年React的發(fā)展里程碑詳情,可以查看我們整理的React 2015這一年。
2016年最有趣的問題可能是,我們該如何編寫一個應(yīng)用呢,有什么推薦的庫或框架?
作為一個長時間使用React.js的開發(fā)者,我已經(jīng)有自己的答案和最佳實踐了,但你可能不會同意我說的所有點。我對你的想法和意見很感興趣,請留言進行討論。
如果你只是剛開始接觸React.js,請閱讀React.js教程,或Pete Hunt的React howto。
數(shù)據(jù)處理
在React.js應(yīng)用中處理數(shù)據(jù)超級簡單的,但同時還是有些挑戰(zhàn)。
這是因為你可以使用多種方式,來給一個React組件傳遞屬性數(shù)據(jù),從而構(gòu)建出渲染樹。但這種方式并不總是能明顯地看出,你是否應(yīng)該更新某些視圖。
2015開始涌現(xiàn)出一批具有更強功能和響應(yīng)式解決方案的Flux庫,讓我們一起看看:
Flux
根據(jù)我們的經(jīng)驗,F(xiàn)lux通常被過度使用了(就是大家在不需使用的場景下,還是使用了)。
Flux提供了一種清爽的方式存儲和管理應(yīng)用的狀態(tài),并在需要的時候觸發(fā)渲染。
Flux對于那些應(yīng)用的全局state(譯者注:為了對應(yīng)React中的state概念,本文將不對state進行翻譯)特別有用,比如:管理登錄用戶的狀態(tài)、路由狀態(tài),或是活躍賬號狀態(tài)。如果使用臨時變量或者本地數(shù)據(jù)來處理這些狀態(tài),會非常讓人頭疼。
我們不建議使用Flux來管理路由相關(guān)的數(shù)據(jù),比如/items/:itemId。應(yīng)該只是獲取它并存在組件的state中,這種情況下,它會在組件銷毀時一起被銷毀。
如果需要Flux的更多信息,建議閱讀The Evolution of Flux Frameworks。
使用Redux
Redux是一個JavaScript app的可預(yù)測state容器。
如果你覺得需要Flux或者相似的解決方案,你應(yīng)該了解一下redux,并學(xué)習(xí)Dan Abramov的redux入門指南,來強化你的開發(fā)技能。
Rudux發(fā)展了Flux的思想,同時降低了其復(fù)雜度。
扁平化state
API通常會返回嵌套的資源,這讓Flux或Redux架構(gòu)很難處理。我們推薦使用normalizr這類庫來盡可能地扁平化state。
像這樣:
const data = normalize(response, arrayOf(schema.user)) state = _.merge(state, data.entities
(我們使用isomorphic-fetch與API進行通信)
使用immutable state
共享的可變數(shù)據(jù)是罪惡的根源——Pete Hunt, React.js Conf 2015
不可變對象是指在創(chuàng)建后不可再被修改的對象。
不可變對象可以減少那些讓我們頭痛的工作,并且通過引用級的比對檢查來提升渲染性能。比如在shouldComponentUpdate中:
shouldComponentUpdate(nexProps) { // 不進行對象的深度對比 return this.props.immutableFoo !== nexProps.immutableFoo }
如何在JavaScript中實現(xiàn)不可變
比較麻煩的方式是,小心地編寫下面的例子,總是需要使用deep-freeze-node(在變動前進行凍結(jié),結(jié)束后驗證結(jié)果)進行單元測試。
return { ...state, foo } return arr1.concat(arr2)
相信我,這是最明顯的例子了。
更簡單自然的方式,就是使用Immutable.js。
import { fromJS } from 'immutable' const state = fromJS({ bar: 'biz' }) const newState = foo.set('bar', 'baz')
Immutable.js非常快,其背后的思想也非常美妙。就算沒準(zhǔn)備使用它,還是推薦你去看看Lee Byron的視頻Immutable Data and React,可以了解到它內(nèi)部的實現(xiàn)原理。
Observables and reactive解決方案
如果你不喜歡Flux/Redux,或者想要更加reactive,不用失望!還有很多方案供你選擇,這里是你可能需要的:
- cycle.js(“一個更清爽的reactive框架”)
- rx-flux(“Flux與Rxjs結(jié)合的產(chǎn)物”)
- redux-rx(“Redux的Rxjs工具庫”)
- mobservable(“可觀測的數(shù)據(jù),reactive的功能,簡潔的代碼”)
路由
現(xiàn)在幾乎所有app都有路由功能。如果你在瀏覽器中使用React.js,你將會接觸到這個點,并為其選擇一個庫。
我們選擇的是出自優(yōu)秀rackt社區(qū)的react-router,這個社區(qū)總是能為React.js愛好者們帶來高質(zhì)量的資源。
要使用react-router需要查看它的文檔,但更重要的是:如果你使用Flux/Redux,我們推薦你將路由state與store或全局state保持同步。
同步路由state可以讓Flux/Redux來控制路由行為,并讓組件讀取到路由信息。
Redux的用戶可以使用redux-simple-router來省點事兒。
代碼分割,懶加載
只有一小部分webpack的用戶知道,應(yīng)用代碼是可以分割成多個js包的。
require.ensure([], () => { const Profile = require('./Profile.js') this.setState({ currentComponent: Profile }) })
這對于大型應(yīng)用十分有用,因為用戶瀏覽器不用下載那些很少會使用到的代碼,比如Profile頁。
多js包會導(dǎo)致額外的HTTP請求數(shù),但對于HTTP/2的多路復(fù)用,完全不是問題。
與chunk hashing 結(jié)合可以優(yōu)化緩存命中率。
下個版本的react-router將會對代碼分隔做更多支持。
對于react-router的未來規(guī)劃,可以去看博文Ryan Florence: ** to Future of Web Application Delivery。
組件
很多人都在抱怨JSX,但首先要知道,它只是React中可選的一項能力。
最后,它們都會被Bable編譯成JavaScript。你可以繼續(xù)使用JavaScript編寫代碼,但是在處理HTML時使用JSX會感覺更自然。特別是對于那些不懂js的人,他們可以只修改HTML相關(guān)的部分。
JSX是一個類似于XML的JavaScript擴展,可以配合一個簡單的語法編譯工具來使用它。——深入淺出JSX
如果你想了解更多JSX的內(nèi)容,查看文章JSX Looks Like An Abomination – But it’s Good for You。
使用類
React中可以順暢地使用ES2015的Class語法。
class HelloMessage extends React.Component { render() { returnHello {this.props.name}} }
我們在高階組件和mixins之間更看重前者,所以拋棄createClass更像是一個語法問題,而不是技術(shù)問題。(譯者注:在Class語法中,React組件的mixins方法將無法使用。)我們認(rèn)為使用createClass和React.Component沒有對錯之分。
屬性類型(PropType)
如果你以前不檢查props的類型,那么2016你應(yīng)該開始改正了。它會幫你節(jié)省未來很多時間,相信我。
MyComponent.propTypes = { isLoading: PropTypes.bool.isRequired, items: ImmutablePropTypes.listOf( ImmutablePropTypes.contains({ name: PropTypes.string.isRequired, }) ).isRequired }
是的,同時也盡可能使用react-immutable-proptypes檢查Immutable.js的props。
高階組件(Higher order components)
minins將死,ES6的Class將不對其進行支持,我們需要尋找新的方法。
什么是高階組件?
PassData({ foo: 'bar' })(MyComponent)
簡單地,你創(chuàng)建一個從原生組件繼承下來的組件,并且擴展了原始組件的行為。你可以在多種場景來使用它,比如鑒權(quán):requireAuth({ role: 'admin' })(MyComponent)(在高階組件中檢查用戶權(quán)限,如果還沒有登錄就進行跳轉(zhuǎn)),或者將組件與Flux/Redux的store相連通。
在RisingStack,我們也喜歡分離數(shù)據(jù)拉取和controller類的邏輯到高階組件中,這樣可以盡可能地保持view層的簡單。
測試
好的代碼覆蓋測試是開發(fā)周期中的重要一環(huán)。幸運的是,React.js社區(qū)有很多這樣的庫來幫助我們。
組件測試
我們最喜愛的組件測試庫是AirBnb的enzyme。有了它的淺渲染特性,可以對組件的邏輯和渲染結(jié)果進行測試,非常棒對不對?它現(xiàn)在還不能替代selenium測試,但是將前端測試提升到了一個新高度。
it('simulates click events', () => { const onButtonClick = sinon.spy() const wrapper = shallow() wrapper.find('button').simulate('click') expect(onButtonClick.calledOnce).to.be.true })
看起來很清爽,不是嗎?
你使用chai來作為斷言庫嗎?你會喜歡chai-enyzime的。
Redux測試
測試一個reducer非常簡單,它響應(yīng)actions然后將原來的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' }) })
測試actions也很簡單,但是異步actions就不一樣了。測試異步的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測試,請參考官方文檔。
使用npm
雖然React.js并不依賴代碼構(gòu)建工具,我們推薦Webpack和Browserify,它們都具有npm出色的能力。Npm有很多React.js的package,還可以幫助你優(yōu)雅地管理依賴。
(請不要忘記復(fù)用你自己的組件,這是優(yōu)化代碼的絕佳方式。)
包大小(Bundle size)
這本身不是一個React相關(guān)的問題,但多數(shù)人都會對其React進行打包,所以我在這里提一下。
當(dāng)你對源代碼進行構(gòu)建時,要保持對包大小的關(guān)注。要將其控制在最小體積,你需要思考如何require/import依賴。
查看下面的代碼片段,有兩種方式可以對輸出產(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,因為第三方代碼的更新頻率比我們自己帶嗎低很多。
對輸出文件進行hash命名(WebPack中的chunk hash),并使用長緩存,我們可以顯著地減少訪問用戶需要下載的代碼。結(jié)合代碼懶加載,優(yōu)化效果可想而知。
如果你對WebPack還很陌生,可以去看超贊的React webpack指南。
組件級的hot reload
如果你曾使用livereload寫過單頁面應(yīng)用,你可能知道當(dāng)在處理一些與狀態(tài)相關(guān)的事情,一點代碼保存整個頁面就刷新了,這種體驗有多煩人。你需要逐步點擊操作到剛才的環(huán)節(jié),然后在這樣的重復(fù)中奔潰。
在React開發(fā)中,是可以reload一個組件,同時保持它的state不變——耶,從此無需苦惱!
搭建hot reload,可參考react-transform-boilerplate。
使用ES2015
前面提到過,在React.js中使用的JSX,最終會被Babel.js進行編譯。
Bable的能力還不止這些,它可以讓我們在瀏覽器中放心地使用ES6/ES2015。在RisingStack,我們在服務(wù)器端和客戶端都使用了ES2015的特性,ES2015已經(jīng)可以在最新的LTS Node.js版本中使用了。
代碼檢查(Linters)
也許你已經(jīng)對你的代碼制定了代碼規(guī)范,但是你知道React的各種代碼規(guī)范嗎?我們建議你選擇一個代碼規(guī)范,然后照著下面說的來做。
在RisingStack,我們強制將linters運行在持續(xù)集成(CI)系統(tǒng),已經(jīng)git push功能上。查看pre-push和pre-commit。
我們使用標(biāo)準(zhǔn)的JavaScript代碼風(fēng)格,并使用eslint-plugin-react來檢查React.js代碼。
(是的,我們已經(jīng)不再使用分號了)
GraphQL和Relay
GraphQL和Relay是相關(guān)的新技術(shù)。在RisingStack,我們不在生產(chǎn)環(huán)境使用它們,暫時保持關(guān)注。
我們寫了一個Relay的MongoDB ORM,叫做graffiti,可以使用你已有的mongoose models來創(chuàng)建GraphQL server。
如果你想學(xué)習(xí)這些新技術(shù),我們建議你去看看這個庫,然后寫幾個demo玩玩。
這些React.js最佳實踐的核心點
有些優(yōu)秀的技術(shù)和庫其實跟React都沒什么關(guān)系,關(guān)鍵在于要關(guān)注社區(qū)都在做些什么。2015這一年,React社區(qū)被Elm架構(gòu)啟發(fā)了很多。
如果你知道其他2016年大家應(yīng)該使用的React.js工具,請留言告訴我們。
來源:https://blog.risingstack.com/react-js-best-practices-for-2016/