使用 ReactJS 作為 Backbone 的 view 實現
使用 ReactJS 作為 Backbone 的 view 實現
在 Venmo(電子商務公司)公司,我們開始將我們的前端重新設計并重寫成一個清晰的,純 Backbone 模式的代碼結構。Backbone 是一個編碼模式的靈活的框架(也就是 MVC 框架)。但是它的視圖層最少被設計,只為視圖層提供了少量的生命周期鉤子函數。和 Ember.js 的組件和 AngularJS 的指令相比,它缺少很多將數據和視圖層相關聯的鉤子函數,也沒有起到層與層之前的分離的作用。
為此我們很吃驚,于是我們不僅使用 Backone 視圖,也在開始探索編寫我們 UI 的更高級的選擇,我的目標是找到一個即能和 Backones 視圖層交互的,又能在一個大型框架里將視圖寫數據綁定和限定作用域的框架。幸運的是,我的朋友 Scott 介紹了 React 給我,經過幾個小時的折騰之后,React 給了我一個很深刻的印象。
概述
React 是 非死book 用來創建隔離化組件的一個很新穎的 JavaSript 庫。在很多方面,它和 Angular directives 或者 Polymer web 組件很相似。React 組件本質是是一個帶有作用域的自定義 DOM 元素。不管是在 JavaScript 中還是在 DOM 中,它都不能和你的應用程序狀態的其它部分進行直接交互。
React 最獨特也是最有爭議的部分是使用 JSX,JSX 把內嵌 JavaScript 代碼中的 HTML 轉換成可以解析的 JavaScript 代碼。這是一個 React 組件,使用 JSX 來渲染 a 標簽 :
/** @jsx React.DOM */ var component = React.createClass({ render: function() { return <a >Venmo</a> } });
轉換后的 JavaScript 代碼:
/** @jsx React.DOM */ var component = React.createClass({ render: function() { return React.DOM.a( {href:"http://venmo.com"}, "Venmo") } });
由于有大量的編譯 / 轉換工具,集成 JSX 到你的工作流中是很容易的:
-
require-jsx,是一個用來加載 JSX 文件的 RequireJS 插件
-
reactify 是一個 JSX 文件的 Browserify 轉換工具
-
grunt-react 使用 Grunt 編譯 JSX 文件
JSX 是可以選的 - 如果你愿意,你可以使用 React.DOM DSL 來寫你的模板, 盡管我僅在模板很簡單時才推薦這么干。
在這篇文章中我不會教 React 的基礎知識, 你可以參考 excellent React tutorial 來學習. 那篇文章有點長,不過在繼續往下看之前還是有必要瀏覽一下的!
從 Backbone 視圖中渲染組件
讓我們創建一個非常基礎的組件:一個連接,當點擊的時候能觸發點擊事件處理函數。我們想要把這個組件作為 Backbone 視圖的一部分進行渲染,而不是單獨使用它。這個組件很容易創建:
var MyWidget = React.createClass({ handleClick: function() { alert('Hello!'); }, render: function() { return ( <a href="#" onClick={this.handleClick}>Do something!</a> ); } });
這段代碼和上個例子的代碼幾乎一樣,除了增加了 handleClick 函數來響應點擊事件。現在我們唯一需要做的就是在 Backbone 視圖中渲染它:
var MyView = Backbone.View.extend({ el: 'body', template: '<div class="widget-container"></div>', render: function() { this.$el.html(this.template); React.renderComponent(new MyWidget(), this.$('.widget-container').get(0)); return this; } }); new MyView().render();
這就是我們完整的新組件:
/** @jsx React.DOM */ /* global React, Backbone, $ */ var MyWidget = React.createClass({ handleClick: function() { alert('Hello!'); }, render: function() { return ( <a href="#" onClick={this.handleClick}>Do something!</a> ); } });
組件 -> Backbone 通信
當然,要想真正利用 React 組件的優勢,我們需要將 React 組件的變化通知到 Backbone。隨便舉個例子,假設當組件里的鏈接被點擊的時候顯示一些文本。不止如此,我們還想讓文本位于組件的 DOM元素之外。
關于不同的組件之間如何通信,React 文檔已經解釋得很好了,但是沒有很明顯地說明子組件如何跟Backbone 父視圖交互。不過,有個簡單容易的方式通信:我們可以通過組件的屬性綁定一個事件處理器。
這里有個例子程序,JSX 代碼:
function anEventHandler() { ... } React.RenderComponent(<MyComponent customHandler={anEventHandler} />, this.$('body').get(0));
我們可以在 Backbone view 里做同樣的事,但我們直接用 class,不使用 JSX:
var MyView = Backbone.View.extend({ el: 'body', template: '<div class="widget-container"></div>' + '<div class="outside-container"></div>', render: function() { this.$el.html(this.template); React.renderComponent(new MyWidget({ handleClick: this.clickHandler.bind(this) }), this.$('.widget-container').get(0)); return this; }, clickHandler: function() { this.$(".outside-container").html("The link was clicked!"); } });
另外,在組件內部綁定 onClick 到事件處理器:
var MyWidget = React.createClass({ render: function() { return ( <a href="#" onClick={this.props.handleClick}>Do something!</a> ); } });
這里是更新過的完整的例子:
/** @jsx React.DOM */ /* global React, Backbone, $ */ var MyWidget = React.createClass({ render: function() { return ( <a href="#" onClick={this.props.handleClick}>Do something!</a> ); } }); var MyView = Backbone.View.extend({ el: 'body', template: '<div class="widget-container"></div>' + '<div class="outside-container"></div>', render: function() { this.$el.html(this.template); React.renderComponent(new MyWidget({ handleClick: this.clickHandler.bind(this) }), this.$('.widget-container').get(0)); return this; }, clickHandler: function() { this.$(".outside-container").html("The link was clicked!"); } }); new MyView().render();
再次說明,這是一個不太自然的例子,但是想象出一個更加實用的用例應該不難。
比如,在 Venmo 我們用 React 重新開發了“使用 非死book 登錄”按鈕。實際的 非死book API 調用發生在組件內部,但是組件所在的 view 針對不同的事件綁定了不同的處理函數。這些事件(比如“非死book 認證通過”或者“ 非死book 已登出”)本質上是組件的“公共API”。React 也可以在事件中傳遞參數,以便在用戶連接到 非死book 的時候,Backbone view 可以獲取到用戶的 非死book ID,同時把它附加到 user model 上。
Backbone與reactjs 組件通信
現在,我們知道了組件通信了,下一步,我們用 Backbone 模型來更新 reactjs 組件的狀態,來作為一個例子,我們會讓改變的模型字段反應到視圖上面.這需要定義一些模板(雖然這比 Backbone 手動綁定視圖強不了多少),但是在實踐當中還是相當容易的。
首先: 我們創建一個小模型類:
var ExampleModel = Backbone.Model.extend({ defaults: { name: 'Backbone.View' } });
然后用一個簡單的 React 組件顯示 name 字段:
var DisplayView = React.createClass({ render: function() { return ( <p> {this.props.model.get('name')} </p> ); } });
盡管這個組件不能自已反應模型字段的更改,但是我們可以為模型加一個 change 監聽事件,來告訴組件去重新渲染:
var DisplayView = React.createClass({ componentDidMount: function() { this.props.model.on('change', function() { this.forceUpdate(); }.bind(this)); }, render: function() { // ... } });
然后我們增加另一個組件改變 name 字段:
var ToggleView = React.createClass({ handleClick: function() { this.props.model.set('name', 'React'); }, render: function() { return ( <button onClick={this.handleClick}> model.set('name', 'React'); </button> ); } });
最后,我們創建一個模型,然后用 JSX 來渲染兩個組件:
var model = new ExampleModel(); React.renderComponent(( <div> <DisplayView model={model} /> <ToggleView model={model} /> </div> ), document.body);
例子到此結束, 完整的例子可以到這里觀看http://runjs.cn/detail/eqituk3o
結論
Backbone + React 是一個神奇的組合。有其他幾個博客有這樣的討論帖子:
-
Clay Allsopp 寫的 Propeller 移植到 React 的帖子,包括一個非常酷的 React Mixin 被綁定到Backbone Model/Collection changes ,類似我們上面做的最后一個例子。
-
Paul Seiffert (require-jsx的作者)有另外一篇文章是關于用 React 取代 Backbone 的視圖
React 也有一個 Backbone+React 的例子 TodoMVC app 值得試試。
當 React 還不成熟的時候,它是一個非常令人興奮的庫,看起來好像是可供生產使用的一個庫了。它很容易分享和重用組件,我最感興趣的是它與 Backbone 集成地如此之好,彌補了默認的視圖邏輯。
本文地址:http://www.oschina.net/translate/using-reactjs-as-a-backbone-view
原文地址:http://www.thomasboyt.com/2013/12/17/using-reactjs-as-a-backbone-view.html