React.js生態系統概覽 [譯]

jopen 9年前發布 | 61K 次閱讀 React.js JavaScript開發

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>

    最近幾年,YeomanBowerGrunt被譽為現代前端開發的三劍客。他們解決了生成基礎模板,包管理和各種通用任務問題,后面也很多人從Grunt換到了Gulp

    在React的生態系統里,基本上可以丟掉這些東西了,不是說你用不到他們,而是說可以用更先進的WebpackNPM。怎么做到的呢?Webpack是一個模塊化打包工具,用它可以在瀏覽器環境下使用Node.js中常用的CommonJS模塊語法。其實它要更簡單點,因為你不用為了前端另外學一種包管理方案。只需要用NPM,就可以做到服務端與前端模塊的共用。也不用處理JS文件按順序加載的問題,因為它能夠從每個文件的import語法中推測出依賴關系,整個串聯成一個可以在瀏覽器中加載的腳本。

    Webpack

    更強大的是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也已納入標準化日程中,它們的特性也都已經被很多庫采用了。

    ECMAScript 7

    可能你覺得到IE支持之前都用不上這些JS新特性,但實際我們不需要等瀏覽器完全支持,ES轉譯器已經廣泛應用了。目前最好的ES轉譯器是Babel,它能夠把ES6+代碼轉換成ES5,所以你馬上就能用上新ES特性了(指已經在Babel中實現的那些,其實一般新出的特性也會很快被支持)。

    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>

    Koa

    進入第一個中間件時(上游),遇到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

    目前還沒有出現大多數人都認同的Flux實現,非死book官方的Flux實現很多人都覺得很冗余。我們主要關心能不能少寫點模板代碼,配置方便,并且給多層組件提供些好用的功能,支持服務端渲染等等這些。可以看看這里給這些實現庫列出的一些指標。我關注了Alt, Reflux, Flummox, Fluxxor, 和Marty.js。

    我選擇庫的方式并不能說客觀,但很有用。Fluxxor是我發現的第一個庫,但現在看起來它有點舊了。Marty.js很有意思,有很多功能,但還是需要寫很多模板代碼,有些功能看起來比較多余。Reflux看起來蠻有吸引力,但感覺對初學者來說有點難,還缺少合適的文檔。FlummoxAlt很相似,但Alt不需要寫很多模板代碼,開發也非常活躍,文檔更新塊,而且有個Slack社區。所以我選了Alt。

    Alt Flux

    Alt Flux

    Alt的Flux實現簡單而強大。非死book的Flux文檔描述了很多關于dispatcher的東西,但在Alt中這些都可以忽略,因為dispatcher被隱式關聯到了action,通常不需要寫多余代碼。只需要關心store,action,component。這三層對應MVC模型:store為模型層,action為控6制層,component為視圖層。差異是Flux模型是單向數據流,意味著控制層不能直接修改視圖層,而是觸發模型層后,視圖層被動修改。這對有些Angular開發者來說已經是最佳實踐了。

    1. component初始化action
    2. store監聽action然后更新數據
    3. component被綁定到store,當數據更新時就重新渲染
    4. </ol>

      Alt Flux

      Action

      使用Alt庫時,action通常有兩種寫法:自動與手動。自動action用generateActions函數創建,它們直接發給dispatcher,手動方法則寫成action類的方法,它們可以附帶一個payload發給dispatcher。常用例子是自動action通知store有關app的一些事件。其余由手動action負責,它是處理服務端交互的首選方式。

      REST API調用這種就屬于action,完整流程如下:

      1. Component觸發action
      2. action創建者發起一個異步服務器請求,把結果作為一個payload發給dispatcher
      3. store監聽action,對應的action處理器接收結果,然后store更新它的相應狀態
      4. </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-pikadayreact-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,對原文有理解性改動,水平有限,歡迎提出意見。:)

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!