React.js生態系統概覽 [譯]
React.js生態系統概覽 [譯]
貓叔 ( 更新) </span>前端開發JavaScript領域發展速度很快,甚至有人認為這已經引起了負效應。一個前端庫從早期開發的小玩具,到流行,再到過時,可能也就幾個月時間。判斷一個工具能否在幾年內依然保持活力都快成了一門藝術了。
React.js在兩年前發布時,我剛開始學Angular,React在我看來只是又一個模板庫而已。這兩年間,Angular得到了JavaScript開發者的認同,它幾乎成了現代前端開發的代名詞。我還看到一些很保守的團隊都在用它,這讓我覺得Angular好像就是未來。
但突然發生了件奇怪的事,Angular好像成了奧斯本效應的受害者,或者說它被提前宣布了死亡。Angular團隊宣布,Angular 2將會完全不同,基本沒有從Angular 1升級遷移的東西,而且Angular 2在接下來的一年里還用不了。這告訴了那些想開發新Web項目的人:你想用一個馬上要被淘汰了的框架寫項目嗎?
開發者們的憂慮影響到了正在建立的React社區,但React總標榜它只是MVC中的視圖層(V),讓一些依賴完整MVC框架做開發的人感覺有點失望。如何補充其他部分的功能?自己寫嗎?還是用別的三方庫?要是的話該選哪一個呢?
果然,非死book(React.js的創始)出了另一個殺手锏:Flux工作流,它聲稱要填補模型層(M)和控制層(C )的功能。非死book還稱Flux只是一種“模式”,不是個框架,他們的Flux實現只是這個模式的一個例子。就像他們所說,這個實現過于簡單,但還是要寫很多代碼和一堆重復的模板才跑得起來。
這時開源社區發力了,一年后便有了各種Flux實現庫,甚至都出來比較他們的元項目了。非死book激起了社區的興趣,不是給出現成的東西,而是鼓勵大家提出自己的解決方案,這點很不錯。
當你要結合各種庫開發一個完整架構時,擺脫了框架的束縛,獨立的庫還可以在很多地方重用,在自己構建架構的過程中,這個優點很明顯。
這便是為什么React相關的東西這么有意思。它們可以很容易地在其他JavaScript環境中實現重用。就算你不打算用React,看看它的生態系統都能受到啟發。可以試試強大又容易配置的模塊化打包工具Webpack來簡化構建系統,或者用Babel轉譯器馬上開始用ECMAScript 6甚至ECMAScript 7來寫代碼。
在這篇文章里我會給你概覽一遍這些有意思的庫和特性,來探索下React整個生態系統吧。
構建系統
創建一個新的Web項目時,首先要考慮的可能就是構建系統了。它不只是做為個運行腳本的工具,還能優化你的項目結構。一個構建系統必須能包括下面幾個最主要功能:
- 管理內部與外部依賴
- 運行編譯器和預處理器(例如CoffeeScript與SASS)
- 為生產環境優化資源(例如Uglify)
- 運行開發環境的Web Server,文件監控,瀏覽器自動刷新 </ul>
最近幾年,Yeoman,Bower與Grunt被譽為現代前端開發的三劍客。他們解決了生成基礎模板,包管理和各種通用任務問題,后面也很多人從Grunt換到了Gulp。
在React的生態系統里,基本上可以丟掉這些東西了,不是說你用不到他們,而是說可以用更先進的Webpack與NPM。怎么做到的呢?Webpack是一個模塊化打包工具,用它可以在瀏覽器環境下使用Node.js中常用的CommonJS模塊語法。其實它要更簡單點,因為你不用為了前端另外學一種包管理方案。只需要用NPM,就可以做到服務端與前端模塊的共用。也不用處理JS文件按順序加載的問題,因為它能夠從每個文件的import語法中推測出依賴關系,整個串聯成一個可以在瀏覽器中加載的腳本。
更強大的是Webpack不只像同類工具Browserify,它還可以處理其他類型的資源。例如用加載器,可以將任何資源文件轉換成JavaScript函數,去內聯或加載引用到的文件。用不著手工預處理還有從HTML中引用資源了,只要在JavaScript中requireCSS/SASS/LESS文件就好了,Webpack會根據配置文件的描述去搞定一切。它還提供了個開發環境的Web Server,文件監控器,你可以在package.json中使用scripts域去定義一個任務:
{ "name": "react-example-filmdb", "version": "0.0.1", "description": "Isomorphic React + Flux film database example", "main": "server/index.js", "scripts": { "build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js", "dev": "node --harmony ./webpack/dev-server.js", "prod": "NODE_ENV=production node server/index.js", "test": "./node_modules/.bin/karma start --single-run", "postinstall": "npm run build" } ... }
這些東西就可以代替Gulp與Bower了。當然,還是可以繼續用Yeoman去生成應用基礎模板的。要是Yeoman生成不了你需要的東西時(其實大多時候也都需要刪掉那些用不到的庫),還可以從Github上Clone一個基礎模板,然后再改改。
馬上試試新的ECMAScript
JavaScript在這幾年有很大改善,移除糟粕穩定語言后,我們看到了很多新特性,ECMAScript 6(ES6)草案已經定稿。ECMAScript 7也已納入標準化日程中,它們的特性也都已經被很多庫采用了。
可能你覺得到IE支持之前都用不上這些JS新特性,但實際我們不需要等瀏覽器完全支持,ES轉譯器已經廣泛應用了。目前最好的ES轉譯器是Babel,它能夠把ES6+代碼轉換成ES5,所以你馬上就能用上新ES特性了(指已經在Babel中實現的那些,其實一般新出的特性也會很快被支持)。
新的JavaScript特性在所有前端框架里都可以用,更新的React能很好的在ES6與ES7下運行。這些新特性可以解決在用React開發時遇到的一些問題。來看下這些改善吧,它們對React項目很有用。稍后我們再看看怎樣利用這些語法,來搭配使用React的工具和庫。
ES6 Classes
面向對象編程是一種強大又廣泛適用的范式,但在JavaScript里感覺有點不一樣。Backbone,Ember,Angular,或React等大多數前端框架都有它們自己的定義類和創建對象的方式。在ES6中,有原生的類支持了,它簡潔清晰而不用我們自己實現,例如:
React.createClass({ displayName: 'HelloMessage', render() { return <div>Hello {this.props.name}</div>; } })
使用ES6就可以寫成:
class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } }
再看個詳細的例子:
React.createClass({ displayName: 'Counter', getDefaultProps: function(){ return {initialCount: 0}; }, getInitialState: function() { return {count: this.props.initialCount} }, propTypes: {initialCount: React.PropTypes.number}, tick() { this.setState({count: this.state.count + 1}); }, render() { return ( <div onClick={this.tick}> Clicks: {this.state.count} </div> ); } });
ES6可以寫成:
class Counter extends React.Component { static propTypes = {initialCount: React.PropTypes.number}; static defaultProps = {initialCount: 0};constructor(props) { super(props); this.state = {count: props.initialCount}; }
state = {count: this.props.initialCount}; tick() { this.setState({count: this.state.count + 1}); }
render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } }</pre>
在這里不用再寫getDefaultProps和getInitialState這兩個React的生命周期函數了。getDefaultProps改為了類的靜態變量defaultProps,初始state也只需要定義在構造函數中。這種方式唯一的缺點是,在JSX中使用的方法的上下文不會再自動綁定到類實例了,必須用bind來指定。
裝飾器
裝飾器是ES7中的特性。通過一個包裝函數,來增強函數或類的行為。例如想為一些組件使用同一個change handler, 而又不想inheritance antipattern,則可以用類的裝飾器去實現。定義一個裝飾器:
addChangeHandler: function(target) { target.prototype.changeHandler = function(key, attr, event) { var state = {}; state[key] = this.state[key] || {}; state[key][attr] = event.currentTarget.value; this.setState(state); }; return target; }在這里,函數addChangeHandler添加了changeHandler方法到目標類target的實例上。
要應用裝飾器,只需要:
MyClass = addChangeHandler(MyClass)或者用更優雅的ES7寫法:
@addChangeHandler class MyClass { ... }因為React沒有雙向數據綁定,在用到input之類的控件時,代碼就會比較冗余,changeHandler函數則把它簡化了。第一個參數表示在state對象中使用的key,它將存儲input的一個數據對象。第二個參數是屬性,它表示input的值。這兩個參數在JSX中被傳入:
@addChangeHandler class LoginInput extends React.Component { constructor(props) { super(props); this.state = { login: {} }; } render() { return ( <input type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'login', 'username')} /> <input type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'login', 'password')} /> ) } }用戶名輸入框發生改變時,輸入框的值會被直接存到this.state.login.username中,不需要一個個去寫handler了。
箭頭函數
JavaScript的動態上下文this不太直觀,成了開發者的常痛了。比如在類里,包裝函數中的this都被指向了全局變量。要fix這個問題,通常是把this存到外部作用域下(例如_this),然后在內部函數中用它:
class DirectorsStore { onFetch(directors) { var _this = this; this.directorsHash = {}; directors.forEach(function(x){ _this.directorsHash[x._id] = x; }) } }在ES6中,函數function(x) {可以寫成(x) => {。這種箭頭方式的函數定義不僅將內部的this綁定到了外部作用域,而且看起來很簡潔,在寫大量異步代碼時很有用:
onFetch(directors) { this.directorsHash = {}; directors.forEach((x) => { this.directorsHash[x._id] = x; }) }解構賦值
ES6中的解構賦值允許在賦值表達式左邊寫個復合對象:
var o = {p: 42, q: true}; var {p, q} = o;console.log(p); // 42 console.log(q); // true</pre>
在React中有什么實際用處嗎?看下面這個例子:
function makeRequest(url, method, params) { var config = { url: url, method: method, params: params }; ... }用解構方式,可以一次給幾個鍵賦值。url, method, params這些值會被自動賦給有著同名鍵的對象中。這讓代碼更不易出錯:
function makeRequest(url, method, params) { var config = {url, method, params}; ... }解構賦值也可以用來加載一個模塊的子集:
const {clone, assign} = require('lodash'); function output(data, optional) { var payload = clone(data); assign(payload, optional); }函數的默認,剩余,擴展參數
在ES6中函數傳參更強大了,可以為函數設置默認參數:
function http(endpoint, method='GET') { console.log(method) ... }http('/api') // GET</pre>
覺得arguments用起來很麻煩?可以把剩余參數寫成一個數組:
function networkAction(context, method, ...rest) { // rest is an array return method.apply(context, rest); }要是不想調用apply()方法,可以把數組擴展成函數的參數:
myArguments = ['foo', 'bar', 123]; myFunction(...myArguments);Generator與Async函數
Generator是一種可以暫停執行,保存狀態并稍后恢復執行的函數,每次遇到yield關鍵字時,就會暫停執行。Generator寫法:
function* sequence(from, to) { console.log('Ready!'); while(from <= to) { yield from++; } }調用Generator函數:
> var cursor = sequence(1,3) Ready! > cursor.next() { value: 1, done: false } > cursor.next() { value: 2, done: false } > cursor.next() { value: 3, done: false } > cursor.next() { value: undefined, done: true }當調用Generator函數時,會立即執行到第一次遇到的yield關鍵字處暫停。在調用next()函數后,返回yield后的表達式的值(value),然后Generator里再繼續執行之后的代碼。每次遇到yield都返回一個值,在第三次調用next()后,Generator函數終止,最后調用的next()將返回{ value: undefined, done: true }。
當然咯,Generator不只能用來創建數字序列。它能夠暫停和恢復函數的執行,這便可以不需要回調函數就完成異步流的控制。
我們用異步函數來證明這一點。一般我們會做些I/O操作,這里為了簡單起見,用setTimeout模擬。這個異步函數立即返回一個promise。(ES6有原生的promise):
function asyncDouble(x) { var deferred = Promise.defer(); setTimeout(function(){ deferred.resolve(x*2); }, 1000); return deferred.promise; }然后寫一個消費者函數:
function consumer(generator){ var cursor = generator(); var value; function loop() { var data = cursor.next(value); if (data.done) { return; } else { data.value.then(x => { value = x; loop(); }) } } loop(); }這個函數接受Generator函數作為參數,只要yield有值,就會繼續調用next()方法。這個例子中,yield的值是promise,所以必須等待promise調用resolve后,再遞歸調用loop()循環。
resolve會調用then()方法,把resolve的結果賦值給value,value被定義在函數外部,它會被傳給下一個next(value)。這次next的調用為yield產生了一個返回值(即value)。這樣可以不用回調函數來寫異步調用了:
function* myGenerator(){ const data1 = yield asyncDouble(1); console.log(Double 1 = ${data1}
); const data2 = yield asyncDouble(2); console.log(Double 2 = ${data2}
); const data3 = yield asyncDouble(3); console.log(Double 3 = ${data3}
); }consumer(myGenerator);</pre>
Generator函數myGenerator將在每次遇到yield時暫停,等待消費者函數的promise去resolve。在控制臺每隔1秒的輸出:
Double 1 = 2 Double 2 = 4 Double 3 = 6上面的示例代碼不推薦在生產環境中用,可以用更完善的co庫,能很簡單地用yield處理異步,還包含了錯誤處理:
co(function *(){ var a = yield Promise.resolve(1); console.log(a); var b = yield Promise.resolve(2); console.log(b); var c = yield Promise.resolve(3); console.log(c); }).catch(function(err){ console.error(err.stack); });在ES7中,將異步處理的改進更近了一步,增加了async和await關鍵字,無需使用Generator。上面的例子可以寫成:
async function (){ try { var a = await Promise.resolve(1); console.log(a); var b = await Promise.resolve(2); console.log(b); var c = await Promise.resolve(3); console.log(c); } catch (err) { console.error(err.stack); } };有了這些特性,不會覺得JavaScript寫異步代碼麻煩了,而且在任何地方都可以用。
Generator不僅簡潔明了,還能做些用回調很難實現的事。比如Node.js中koaWeb框架的中間件。koa的目標是替換掉Express,它有個很不錯的特性:中間件的上下游都可以對服務端響應做進一步修改。看看這段koa服務器代碼:
// Response time logger middleware app.use(function *(next){ // Downstream var start = new Date; yield next; // Upstream this.body += ' World'; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); });// Response handler app.use(function *(){ this.body = 'Hello'; });
app.listen(3000);</pre>
![]()
進入第一個中間件時(上游),遇到yield后,先進入第二個中間件(下游)將響應體設置為Hello,然后再從yield恢復執行,繼續執行上游中間件代碼,為響應體追加了World,然后打印了時間日志,在這里上下游共享了相同的上下文(this)。這要比Express更強大,如果用Express來寫的話可能會是這樣:
var start; // Downstream middleware app.use(function(req, res, next) { start = new Date; next(); // Already returned, cannot continue here });// Response app.use(function (req, res, next){ res.send('Hello World') next(); });
// Upstream middleware app.use(function(req, res, next) { // res already sent, cannot modify var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); next(); });
app.listen(3000);</pre>
也許你已經發現問題了,使用全局的start變量在出現并發請求時會產生污染。
用koa時,可以很容易地用Generator做異步操作:
app.use(function *(){ try { const part1 = yield fs.readFile(this.request.query.file1, 'utf8'); const part2 = yield fs.readFile(this.request.query.file2, 'utf8'); this.body = part1 + part2; } catch (err) { this.status = 404 this.body = err; } });app.listen(3000);</pre>
可以設想下這個例子在Express中用promise和回調會是什么樣子。
![]()
上面討論的Node.js這些跟React有關系嗎?當然咯,Node是React的首選后端服務器。因為Node也用JavaScript,這樣可以前后端共享代碼,用來構建同構的React Web應用,稍后會介紹到。
Flux庫
React很擅長創建視圖組件,但我們還需要一種方式去管理數據和整個應用的狀態。Flux應用架構被普遍認為是React最好的補充。要是你對Flux還很陌生,建議看看這篇快速導覽。
![]()
目前還沒有出現大多數人都認同的Flux實現,非死book官方的Flux實現很多人都覺得很冗余。我們主要關心能不能少寫點模板代碼,配置方便,并且給多層組件提供些好用的功能,支持服務端渲染等等這些。可以看看這里給這些實現庫列出的一些指標。我關注了Alt, Reflux, Flummox, Fluxxor, 和Marty.js。
我選擇庫的方式并不能說客觀,但很有用。Fluxxor是我發現的第一個庫,但現在看起來它有點舊了。Marty.js很有意思,有很多功能,但還是需要寫很多模板代碼,有些功能看起來比較多余。Reflux看起來蠻有吸引力,但感覺對初學者來說有點難,還缺少合適的文檔。Flummox和Alt很相似,但Alt不需要寫很多模板代碼,開發也非常活躍,文檔更新塊,而且有個Slack社區。所以我選了Alt。
Alt Flux
![]()
Alt的Flux實現簡單而強大。非死book的Flux文檔描述了很多關于dispatcher的東西,但在Alt中這些都可以忽略,因為dispatcher被隱式關聯到了action,通常不需要寫多余代碼。只需要關心store,action,component。這三層對應MVC模型:store為模型層,action為控6制層,component為視圖層。差異是Flux模型是單向數據流,意味著控制層不能直接修改視圖層,而是觸發模型層后,視圖層被動修改。這對有些Angular開發者來說已經是最佳實踐了。
- component初始化action
- store監聽action然后更新數據
- component被綁定到store,當數據更新時就重新渲染
</ol>
![]()
Action
使用Alt庫時,action通常有兩種寫法:自動與手動。自動action用generateActions函數創建,它們直接發給dispatcher,手動方法則寫成action類的方法,它們可以附帶一個payload發給dispatcher。常用例子是自動action通知store有關app的一些事件。其余由手動action負責,它是處理服務端交互的首選方式。
REST API調用這種就屬于action,完整流程如下:
- Component觸發action
- action創建者發起一個異步服務器請求,把結果作為一個payload發給dispatcher
- store監聽action,對應的action處理器接收結果,然后store更新它的相應狀態
</ol>對于AJAX請求,我們可以用axios庫,無縫地處理JSON數據和頭數據。可以用ES7的async/await關鍵字,免去promise與回調。如果POST響應狀態不是2XX,拋出錯誤,然后發出返回的數據或接收到的錯誤。
看個簡單的Alt登錄示例,在這里注銷action不需要做任何事情,只需要通知store,所以可以自動生成它。登錄action是手動的,會把登錄數據作為一個參數發給action的創建者。從服務端獲取響應后,發出數據,有錯誤的話就發出錯誤。
class LoginActions { constructor() { // Automatic action this.generateActions('logout'); }// Manual action async login(data) { try { const response = await axios.post('/auth/login', data); this.dispatch({ok: true, user: response.data}); } catch (err) { console.error(err); this.dispatch({ok: false, error: err.data}); } } }
module.exports = (alt.createActions(LoginActions));</pre>
Store
Flux模型的store有兩個任務:作為action的處理器,保存狀態。繼續看看登錄例子是怎么做的吧。
創建一個LoginStore,有兩個狀態屬性:user用于當前登錄的用戶,error用于當前登錄相關的錯誤。為了減少模板代碼數量,Alt可以用一個bindActions函數綁定所有action到store類。
class LoginStore { constructor() { this.bindActions(LoginActions); this.user = null; this.error = null; } ...處理器定義起來很方便,只需要在對應action名字前加on。因此login的action由onLogin處理。注意action名的首字母是陀峰命名法的大寫。在LoginStore中,有以下兩個處理器,被對應的action調用:
... onLogin(data) { if (data.ok) { this.user = data.user; this.error = null; router.transitionTo('home'); } else { this.user = null; this.error = data.error } }onLogout() { this.user = null; this.error = null; } }</pre>
Component
通常將store綁定到component的方式是用mixin。但mixin快過時了,需要找個其他方式,有個新方法是使用嵌套的component。將我們的component包裝到另一個component里面,它會托管store的監聽然后重新調用渲染。component會在props中接收到store的狀態。這個方法對于smart and dumb component的代碼組織很有用,是以后的趨勢。對于Alt來說,包裝component是由AltContainer實現的:
export default class Login extends React.Component { render() { return ( <AltContainer stores={{LoginStore: LoginStore}}> <LoginPage/> </AltContainer> )} }LoginPagecomponent也用了我們之前介紹過的changeHandler裝飾器。LoginStore的數據用來顯示登陸失敗后的錯誤,重新渲染則由AltContainer負責。點擊登錄按鈕后執行loginaction,完整的Alt工作流:
@changeHandler export default class LoginPage extends React.Component { constructor(props) { super(props); this.state = { loginForm: {} }; } login() { LoginActions.login(this.state.loginForm) } render() { return ( <Alert>{{this.props.LoginStore.error}}</Alert> <Input label='Username' type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'loginForm', 'username')} /> <Input label='Password' type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'loginForm', 'password')} /> <Button onClick={this.login.bind(this)}>Login</Button> )} }同構渲染
同構Web應用是近些年來的熱點話題,它能解決傳統單頁面應用最大的問題:瀏覽器用JavaScript動態創建HTML,如果瀏覽器禁用了JavaScript,內容就無法顯示了,搜索引擎索引不到Web頁面,內容不會出現在搜索結果中。實際上有方法解決這個問題,但做的并不夠好。同構的方式是通過服務端渲染內容來解決問題。Node.js是服務端的JavaScript,React當然也能運行在服務端。
一些使用單例方式的Flux庫,很難做服務端渲染,單例的Flux 在遇到并發請求時,store數據就會變得混亂。一些Flux庫用實例來解決這個問題,但需要在代碼間傳遞實例。Alt實際也提供了Flux實例,但它用單例解決服務端渲染問題,它會在每次請求后清空store,以便并發請求時每次store都是初始狀態。
React.renderToString函數是服務端渲染的核心,整個React應用運行在服務端。服務端生成HTML后響應給瀏覽器。瀏覽器JavaScript運行時,再渲染剩余部分。可以用Iso庫實現這點,它同時也被集成到了Alt中。
首先,我們在服務端用alt.bootstrap初始化Flux,這會初始化Flux store數據。然后區分哪個URL對應的渲染哪個component,哪個由客戶端Router渲染。由于使用alt的單例版,在每次渲染完后,需要使用alt.flush()初始化store以便為下次請求做好準備。使用iso插件,可以把Flux的狀態數據序列化到HTML字符串中,以便客戶端知道去哪找到這份狀態數據:
// We use react-router to run the URL that is provided in routes.jsx var getHandler = function(routes, url) { var deferred = Promise.defer(); Router.run(routes, url, function (Handler) { deferred.resolve(Handler); }); return deferred.promise; };app.use(function *(next) { yield next; // We seed our stores with data alt.bootstrap(JSON.stringify(this.locals.data || {})); var iso = new Iso(); const handler = yield getHandler(reactRoutes, this.request.url); const node = React.renderToString(React.createElement(handler)); iso.add(node, alt.flush()); this.render('layout', {html: iso.render()}); });</pre>
在客戶端,拿到了服務端的狀態數據,用來初始化alt的狀態。然后可以運行Router和React.render去更新服務端生成的HTML:
Iso.bootstrap(function (state, _, container) { // Bootstrap the state from the server alt.bootstrap(state) Router.run(routes, Router.HistoryLocation, function (Handler, req) { let node = React.createElement(Handler) React.render(node, container) }) })一些有用的庫
例如CSS布局容器,樣式表單,按鈕,驗證,日期選擇器等等。
React-Bootstrap
![]()
推ter的Bootstrap框架應用已經非常普遍,對那些不想花時間寫CSS的開發者很有用。特別是在原型開發階段,Bootstrap不可或缺。要在React中使用Bootatrap,可以試試React-Bootstrap,它使用原生React component重新實現了Bootstrap的jQuery插件。代碼簡潔易懂:
<Navbar brand='React-Bootstrap'> <Nav> <NavItem eventKey={1} href='#'>Link</NavItem> <NavItem eventKey={2} href='#'>Link</NavItem> <DropdownButton eventKey={3} title='Dropdown'> <MenuItem eventKey='1'>Action</MenuItem> <MenuItem eventKey='2'>Another action</MenuItem> <MenuItem eventKey='3'>Something else here</MenuItem> <MenuItem divider /> <MenuItem eventKey='4'>Separated link</MenuItem> </DropdownButton> </Nav> </Navbar>個人感覺這才是簡單易懂的HTML吧。
也可以在Webpack中使用Bootstrap,看你喜歡哪個CSS預處理器。支持自定義引入的Bootstrap組件,可以在CSS代碼中使用LESS或SASS的全局變量。
React Router
![]()
React Router幾乎已經成了React的路由標準了。它支持嵌套路由,重定向,同構渲染。基于JSX的例子:
<Router history={new BrowserHistory}> <Route path="/" component={App}> <Route path="about" name="about" component={About}/> <Route path="users" name="users" component={Users} indexComponent={RecentUsers}> <Route path="/user/:userId" name="user" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>React Router可以用Linkcomponent做應用導航,只需要指定路由名稱:
<nav> <Link to="about">About</Link> <Link to="users">Users</Link> </nav>還有集成了React-Bootstrap的路由庫,不想手寫Bootstrap的active類,可以用react-router-bootstrap:
<Nav> <NavItemLink to="about">About</NavItemLink> <NavItemLink to="users">Users</NavItemLink> </Nav>Formsy-React
表單開發通常都比較麻煩,用formsy-react來簡化一下吧,它可以用來管理驗證和數據模型。Formsy-React不包含實際的表單輸入,而是推薦用戶自己寫(這是正確的)。如果只用通用表單,可以用formsy-react-components。它包括了Bootstrap類:
import Formsy from 'formsy-react'; import {Input} from 'formsy-react-components'; export default class FormsyForm extends React.Component { enableButton() { this.setState({canSubmit: true}); } disableButton() { this.setState({canSubmit: true}); } submit(model) { FormActions.saveEmail(model.email); } render() { return ( <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Input name="email" validations="isEmail" validationError="This is not a valid email" required/> <button type="submit" disabled={!this.state.canSubmit}>Submit</button> </Formsy.Form> )} }日期與選擇器
日期與選擇器組件算是UI庫的錦上添花了,遺憾的是這兩個組件在Bootstrap 3上被移除了,因為它們對于一個通用CSS框架來說比較特殊了。不過我發現了兩個不錯的代替:react-pikaday和react-select。我嘗試過10多個庫,這兩個總體來說很不錯。非常易用:
import Pikaday from 'react-pikaday'; import Select from 'react-select';export default class CalendarAndTypeahead extends React.Component { constructor(props){ super(props); this.options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' } ]; } dateChange(date) { this.setState({date: date}); }, selectChange(selected) { this.setState({selected: selected}); }, render() { return ( <Pikaday value={this.state.date} onChange={this.dateChange} /> <Select name="form-field-name" value={this.state.selected} options={this.options} onChange={selectChange} /> )} }</pre>
總結 - React.js
這篇文章介紹了目前Web開發的一些技術與框架。有些是React相關的,因為React的開放性,它們也能被用在其他環境。有時候技術進步總會被對新事物的恐懼所阻礙,我希望這篇文章可以打消你對嘗試React, Flux和新的ECMAScript的疑慮。
![]()
要是感興趣,可以看看我用以上技術構建的示例應用。源碼在Github上。
感謝能堅持閱讀到這里 :)
本文譯自2015年年中的《Navigating the React.JS Ecosystem》 - Tomas Holas,對原文有理解性改動,水平有限,歡迎提出意見。:)