譯者按:近幾個月React相關話題依舊火熱,相信越來越多的開發者在嘗試這樣一項技術,我們團隊也在PC和移動端不斷總結經驗。2016來了,這應該是React走向成熟的一年,不管你是新手,還是已經對React有所瞭解,是時候總結一下最佳實踐了,讓我們看看國外的開發者總結了哪些好的實踐吧~
===========譯文分割線==============
2015可以算是React之年了,關於其版本釋出和開發者大會的話題遍佈全球。關於去年React的發展里程碑詳情,可以檢視我們整理的React 2015這一年。
2016年最有趣的問題可能是,我們該如何編寫一個應用呢,有什麼推薦的庫或框架?
作為一個長時間使用React.js的開發者,我已經有自己的答案和最佳實踐了,但你可能不會同意我說的所有點。我對你的想法和意見很感興趣,請留言進行討論。
如果你只是剛開始接觸React.js,請閱讀React.js教程,或Pete Hunt的React howto。
資料處理
在React.js應用中處理資料超級簡單的,但同時還是有些挑戰。
這是因為你可以使用多種方式,來給一個React元件傳遞屬性資料,從而構建出渲染樹。但這種方式並不總是能明顯地看出,你是否應該更新某些檢視。
2015開始湧現出一批具有更強功能和響應式解決方案的Flux庫,讓我們一起看看:
Flux
根據我們的經驗,Flux通常被過度使用了(就是大家在不需使用的場景下,還是使用了)。
Flux提供了一種清爽的方式儲存和管理應用的狀態,並在需要的時候觸發渲染。
Flux對於那些應用的全域性state(譯者注:為了對應React中的state概念,本文將不對state進行翻譯)特別有用,比如:管理登入使用者的狀態、路由狀態,或是活躍賬號狀態。如果使用臨時變數或者本地資料來處理這些狀態,會非常讓人頭疼。
我們不建議使用Flux來管理路由相關的資料,比如/items/:itemId。應該只是獲取它並存在元件的state中,這種情況下,它會在元件銷燬時一起被銷燬。
如果需要Flux的更多資訊,建議閱讀The Evolution of Flux Frameworks。
使用Redux
Redux是一個JavaScript app的可預測state容器。
如果你覺得需要Flux或者相似的解決方案,你應該瞭解一下redux,並學習Dan Abramov的redux入門指南,來強化你的開發技能。
Rudux發展了Flux的思想,同時降低了其複雜度。
扁平化state
API通常會返回巢狀的資源,這讓Flux或Redux架構很難處理。我們推薦使用normalizr這類庫來儘可能地扁平化state。
像這樣:
1 2 3 |
const data = normalize(response, arrayOf(schema.user)) state = _.merge(state, data.entities) |
(我們使用isomorphic-fetch與API進行通訊)
使用immutable state
共享的可變資料是罪惡的根源——Pete Hunt, React.js Conf 2015
不可變物件是指在建立後不可再被修改的物件。
不可變物件可以減少那些讓我們頭痛的工作,並且通過引用級的比對檢查來提升渲染效能。比如在shouldComponentUpdate中:
1 2 3 4 |
shouldComponentUpdate(nexProps) { // 不進行物件的深度對比 return this.props.immutableFoo !== nexProps.immutableFoo } |
如何在JavaScript中實現不可變
比較麻煩的方式是,小心地編寫下面的例子,總是需要使用deep-freeze-node(在變動前進行凍結,結束後驗證結果)進行單元測試。
1 2 3 4 5 6 |
return { ...state, foo } return arr1.concat(arr2) |
相信我,這是最明顯的例子了。
更簡單自然的方式,就是使用Immutable.js。
1 2 3 4 |
import { fromJS } from 'immutable' const state = fromJS({ bar: 'biz' }) const newState = foo.set('bar', 'baz') |
Immutable.js非常快,其背後的思想也非常美妙。就算沒準備使用它,還是推薦你去看看Lee Byron的視訊Immutable Data and React,可以瞭解到它內部的實現原理。
Observables and reactive解決方案
如果你不喜歡Flux/Redux,或者想要更加reactive,不用失望!還有很多方案供你選擇,這裡是你可能需要的:
- cycle.js(“一個更清爽的reactive框架”)
- rx-flux(“Flux與Rxjs結合的產物”)
- redux-rx(“Redux的Rxjs工具庫”)
- mobservable(“可觀測的資料,reactive的功能,簡潔的程式碼”)
路由
現在幾乎所有app都有路由功能。如果你在瀏覽器中使用React.js,你將會接觸到這個點,併為其選擇一個庫。
我們選擇的是出自優秀rackt社群的react-router,這個社群總是能為React.js愛好者們帶來高質量的資源。
要使用react-router需要檢視它的文件,但更重要的是:如果你使用Flux/Redux,我們推薦你將路由state與store或全域性state保持同步。
同步路由state可以讓Flux/Redux來控制路由行為,並讓元件讀取到路由資訊。
Redux的使用者可以使用redux-simple-router來省點事兒。
程式碼分割,懶載入
只有一小部分webpack的使用者知道,應用程式碼是可以分割成多個js包的。
1 2 3 4 5 6 |
require.ensure([], () => { const Profile = require('./Profile.js') this.setState({ currentComponent: Profile }) }) |
這對於大型應用十分有用,因為使用者瀏覽器不用下載那些很少會使用到的程式碼,比如Profile頁。
多js包會導致額外的HTTP請求數,但對於HTTP/2的多路複用,完全不是問題。
與chunk hashing 結合可以優化快取命中率。
下個版本的react-router將會對程式碼分隔做更多支援。
對於react-router的未來規劃,可以去看博文Ryan Florence: Welcome to Future of Web Application Delivery。
元件
很多人都在抱怨JSX,但首先要知道,它只是React中可選的一項能力。
最後,它們都會被Bable編譯成JavaScript。你可以繼續使用JavaScript編寫程式碼,但是在處理HTML時使用JSX會感覺更自然。特別是對於那些不懂js的人,他們可以只修改HTML相關的部分。
JSX是一個類似於XML的JavaScript擴充套件,可以配合一個簡單的語法編譯工具來使用它。——深入淺出JSX
如果你想了解更多JSX的內容,檢視文章JSX Looks Like An Abomination – But it’s Good for You。
使用類
React中可以順暢地使用ES2015的Class語法。
1 2 3 4 5 6 7 8 9 |
class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div> } }class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div> } } |
我們在高階元件和mixins之間更看重前者,所以拋棄createClass更像是一個語法問題,而不是技術問題。(譯者注:在Class語法中,React元件的mixins方法將無法使用。)我們認為使用createClass和React.Component沒有對錯之分。
屬性型別(PropType)
如果你以前不檢查props的型別,那麼2016你應該開始改正了。它會幫你節省未來很多時間,相信我。
1 2 3 4 5 6 7 8 |
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將不對其進行支援,我們需要尋找新的方法。
什麼是高階元件?
1 |
PassData({ foo: 'bar' })(MyComponent) |
簡單地,你建立一個從原生元件繼承下來的元件,並且擴充套件了原始元件的行為。你可以在多種場景來使用它,比如鑑權:requireAuth({ role: ‘admin’ })(MyComponent)(檢查使用者是否在高階元件中,如果還沒有登入就進行跳轉),或者將元件與Flux/Redux的store相連通。
在RisingStack,我們也喜歡分離資料拉取和controller類的邏輯到高階元件中,這樣可以儘可能地保持view層的簡單。
測試
好的程式碼覆蓋測試是開發週期中的重要一環。幸運的是,React.js社群有很多這樣的庫來幫助我們。
元件測試
我們最喜愛的元件測試庫是AirBnb的enzyme。有了它的淺渲染特性,可以對元件的邏輯和渲染結果進行測試,非常棒對不對?它現在還不能替代selenium測試,但是將前端測試提升到了一個新高度。
1 2 3 4 5 6 7 8 |
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 }) |
看起來很清爽,不是嗎?
你使用chai來作為斷言庫嗎?你會喜歡chai-enyzime的。
Redux測試
測試一個reducer非常簡單,它響應actions然後將原來的state轉為新的state:
1 2 3 4 5 6 7 8 9 10 11 |
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,它能幫不少忙。
1 2 3 4 5 6 7 8 |
it('should dispatch action', (done) => { const getState = {} const action = { type: 'ADD_TODO' } const expectedActions = [action] const store = mockStore(getState, expectedActions, done) store.dispatch(action) }) |
關於更深入的redux測試,請參考官方文件。
使用npm
雖然React.js並不依賴程式碼構建工具,我們推薦Webpack和Browserify,它們都具有npm出色的能力。Npm有很多React.js的package,還可以幫助你優雅地管理依賴。
(請不要忘記複用你自己的元件,這是優化程式碼的絕佳方式。)
包大小(Bundle size)
這本身不是一個React相關的問題,但多數人都會對其React進行打包,所以我在這裡提一下。
當你對原始碼進行構建時,要保持對包大小的關注。要將其控制在最小體積,你需要思考如何require/import依賴。
檢視下面的程式碼片段,有兩種方式可以對輸出產生重大影響:
1 2 3 4 5 6 7 |
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),並使用長快取,我們可以顯著地減少訪問使用者需要下載的程式碼。結合程式碼懶載入,優化效果可想而知。
如果你對WebPack還很陌生,可以去看超讚的React webpack指南。
元件級的hot reload
如果你曾使用livereload寫過單頁面應用,你可能知道當在處理一些與狀態相關的事情,一點程式碼儲存整個頁面就重新整理了,這種體驗有多煩人。你需要逐步點選操作到剛才的環節,然後在這樣的重複中奔潰。
在React開發中,是可以reload一個元件,同時保持它的state不變——耶,從此無需苦惱!
搭建hot reload,可參考react-transform-boilerplate。
使用ES2015
前面提到過,在React.js中使用的JSX,最終會被Babel.js進行編譯。
Bable的能力還不止這些,它可以讓我們在瀏覽器中放心地使用ES6/ES2015。在RisingStack,我們在伺服器端和客戶端都使用了ES2015的特性,ES2015已經可以在最新的LTS Node.js版本中使用了。
程式碼檢查(Linters)
也許你已經對你的程式碼制定了程式碼規範,但是你知道React的各種程式碼規範嗎?我們建議你選擇一個程式碼規範,然後照著下面說的來做。
在RisingStack,我們強制將linters執行在持續整合(CI)系統,已經git push功能上。檢視pre-push和pre-commit。
我們使用標準的JavaScript程式碼風格,並使用eslint-plugin-react來檢查React.js程式碼。
(是的,我們已經不再使用分號了)
GraphQL和Relay
GraphQL和Relay是相關的新技術。在RisingStack,我們不在生產環境使用它們,暫時保持關注。
我們寫了一個Relay的MongoDB ORM,叫做graffiti,可以使用你已有的mongoose models來建立GraphQL server。
如果你想學習這些新技術,我們建議你去看看這個庫,然後寫幾個demo玩玩。
這些React.js最佳實踐的核心點
有些優秀的技術和庫其實跟React都沒什麼關係,關鍵在於要關注社群都在做些什麼。2015這一年,React社群被Elm架構啟發了很多。
如果你知道其他2016年大家應該使用的React.js工具,請留言告訴我們。