React.js 2016 最佳實(shí)踐

英文原文: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)行討論。

React.js logo - Best Practices for 2016
React.js logo - Best Practices for 2016

如果你只是剛開(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 Abramovredux入門(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

Immutable logo for React.js Best Practices 2016
Immutable logo for React.js Best Practices 2016

不可變對(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)建工具,我們推薦WebpackBrowserify,它們都具有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)行編譯。

Babel logo in React.js Best Practices 2016
Babel logo in React.js Best Practices 2016

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-pushpre-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)留言告訴我們。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容