近來(lái)React.js變得越來(lái)越流行,本文就來(lái)談一談React.js的入門(mén)實(shí)踐,通過(guò)分析一些常用的概念,以及提供一些入門(mén)的最佳編程編程方式,僅供參考。
首先需要搞懂的是,React并不是一個(gè)框架,React提供了一些新穎的概念、庫(kù) 和編程原則讓你能夠同時(shí)在服務(wù)端和客戶端編寫(xiě)快速、緊湊、漂亮的代碼來(lái)構(gòu)建你的web應(yīng)用。
如果你使用React,那么可能會(huì)涉及到一些常用的概念或技術(shù),包括:
ES6 React
虛擬DOM(virtual DOM)
組件驅(qū)動(dòng)開(kāi)發(fā)(component-driven development)
不變性(immutability)
自上而下的渲染(top-down rendering)
渲染路徑和優(yōu)化
打包工具, ES6, 構(gòu)建請(qǐng)求, debugging, 路由等
同構(gòu)React(isomorphic React)
什么是React.js
React.js不是一個(gè)框架
在整個(gè)Web應(yīng)用的MVC架構(gòu)中,你可以將React看作為視圖層,并且是一個(gè)高效 的視圖。React提供了和以往不一樣的方式來(lái)看待視圖,它以組件開(kāi)發(fā)為基礎(chǔ)。 對(duì)React應(yīng)用而言,你需要分割你的頁(yè)面,使其成為一個(gè)個(gè)的組件。也就是說(shuō),你的 應(yīng)用是由這些組件組合而成的。
你可以通過(guò)分割組件的方式去開(kāi)發(fā)復(fù)雜的頁(yè)面或某個(gè)功能區(qū)塊,并且組件是可以 被復(fù)用的。這個(gè)過(guò)程大概類似于用樂(lè)高積木去瓶裝不同的物體。我們稱這種編程方式稱為 組件驅(qū)動(dòng)開(kāi)發(fā)。
React的一大特點(diǎn)是其所擁有的虛擬DOM,它讓頁(yè)面渲染變得非常的高效,并且比直接 操縱DOM變得更為可控。這兩大特點(diǎn)的組合使得React具有強(qiáng)大的自上而下的頁(yè)面渲染 能力。
好了,React的有兩個(gè)特點(diǎn):組件化和高效的虛擬DOM,但是為什么它這么被看好呢? 因?yàn)镽eact更多的是一種概念層面的東西,而庫(kù)是其次的。也有很多其他遵從了這些思想的第三方實(shí)現(xiàn)。和每一個(gè)編程概念一樣,React尤其 獨(dú)有的解決方案、工具和工具。但這里并不會(huì)深入的去討論他們,而是關(guān)注React本身。
Virtual DOM
為了跟蹤模型層的變化,并且將其應(yīng)用到DOM中(也就是渲染),我們需要注意兩個(gè) 重要的事情:
數(shù)據(jù)是什么時(shí)候改變的
哪一個(gè)(些)DOM元素需要被更新
對(duì)于(1)而言,React提供了一個(gè)觀察者模型用于替代傳統(tǒng)的臟檢查(dirty checking), 也就是持續(xù)的檢查模型的變化。這也就是解釋了為什么React不需要計(jì)算哪些發(fā)生 了改變的原因,因?yàn)樗鼤?huì)立即知道。這個(gè)過(guò)程減少了計(jì)算量,并它應(yīng)用程序變得 更平滑。但這里真正有趣的是,React是如何管理DOM操縱的。
對(duì)于DOM改變(2)而言,React在內(nèi)存中構(gòu)建了 DOM 的樹(shù)形表示,并且計(jì)算出哪個(gè)DOM元素應(yīng)該被改變。對(duì)瀏覽器而言,DOM操縱是比較耗費(fèi)性能的,因此我們更傾向于 讓其變得最小化。幸運(yùn)的是,React視圖盡可能少的觸及到DOM元素。給予對(duì)象表示而言, 更少的DOM操縱意味著計(jì)算會(huì)更快,因此DOM改變也被盡可能的減少。
React在底層實(shí)現(xiàn)了一個(gè)diffing算法,該算法使用DOM的樹(shù)形表示法,當(dāng)某個(gè)節(jié)點(diǎn)發(fā)生變化(標(biāo)記為dirty)時(shí)它會(huì)重新計(jì)算整個(gè)子樹(shù),你會(huì)注意到你的模型發(fā)生 了改變,因?yàn)檎麄€(gè)子樹(shù)在之后會(huì)被重新渲染。關(guān)于該算法的詳細(xì)分析可以參考這篇文章。

如何在服務(wù)端渲染
因?yàn)镽eact在DOM表示時(shí)使用了一個(gè)虛擬(假的)DOM,因此借助于這種方式使得在服務(wù)端 渲染輸出HTML稱為可能(不借助于JSDom, PhantomJS等)。React還能智能的識(shí)別出 服務(wù)端渲染出來(lái)的頁(yè)面標(biāo)記,并在客戶端只為這些標(biāo)記添加事件處理器,這對(duì)構(gòu)建 同構(gòu)web app非常有用。
有意思的是,React渲染出來(lái)的HTML標(biāo)記都包含了data-reactid屬性,這有助于 React中追蹤DOM節(jié)點(diǎn)。
一些閱讀資料
The Secrets of React’s virtual DOM
Why is React’s concept of virtual DOM said to be more performant than dirty model checking?
組件驅(qū)動(dòng)開(kāi)發(fā)
對(duì)于component-driven development而言,你在一個(gè)模板中是看不到整個(gè)網(wǎng)站的。 雖然在一開(kāi)始你可能會(huì)遇到一些困難,但是如果進(jìn)一步的使用這種思路,你會(huì)發(fā)現(xiàn) 它易于理解,易于維護(hù),并且容易測(cè)試。
如何使用React的方式來(lái)思考組件開(kāi)發(fā)
下面我們來(lái)看如何實(shí)現(xiàn)組件驅(qū)動(dòng)開(kāi)發(fā)這一理念。我們看一個(gè)例子,這個(gè)例子來(lái)源于?thinking in react?這篇文章。對(duì)于構(gòu)建一個(gè)可過(guò)濾的產(chǎn)品列表而言,通常其包括如下的組件結(jié)構(gòu):
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow

一個(gè)組件應(yīng)該包含什么
首先,理想的,我們應(yīng)該遵守單一責(zé)任原則來(lái)設(shè)計(jì)你的組件。當(dāng)你發(fā)下你的組件應(yīng)該做的更多的時(shí)候,你可以考慮將其分割為 更小的組件集合。
因?yàn)槲覀冊(cè)谟懻摻M件層級(jí),因此在你的組件中也會(huì)使用到其他組件。我們首先看下 在ES5中組件代碼是什么樣子的:
var HelloComponent = React.createClass({?
? ? render: function() {? ? ? ? return <div>Hello {this.props.name}</div>;? ? }});
如果使用ES6,你的組件代碼可以這樣寫(xiě):
class HelloComponent extends React.Component {?
? render() {? ? return <div>Hello {this.props.name}</div>;? }}
JS和JSX
正如你說(shuō)看到的,我們的組件是JS和HTML代碼的混合,你可能會(huì)覺(jué)得這很糟糕,因?yàn)?MVC一直在教我們盡可能的隔離視圖和控制邏輯。但另一方,這種混合獲得另一個(gè)層面的 單一責(zé)任,他使得組件更加的靈活和可重用。
當(dāng)然,在React中你也可以使用純JS來(lái)編寫(xiě)你的組件:
render () {? ? return React.createElement("div", null, "Hello ",? ? ? ? this.props.name);}
是的,你會(huì)發(fā)現(xiàn)這很麻煩,沒(méi)有使用HTML來(lái)得直觀。因此React提供了JSX (JavaScript eXtension)語(yǔ)法讓你能夠在JS中書(shū)寫(xiě)HTML代碼。
render () {? ? return <div>Hello {this.props.name}</div>;}
什么是JSX
JSX在ECMAScript的基礎(chǔ)上提供了類似于XML的擴(kuò)展。 JSX和HTML有點(diǎn)像,但也有不一樣的地方。例如,HTML中的class屬性在JSX中 為className。其他不一樣的地方,你可以參考FB的HTML Tags vs. React Components?這篇文章。
但是由于瀏覽器原生并不支持JSX,因此我們需要將其編譯為JS,有很多方法能夠 完成這個(gè)任務(wù),后面我們會(huì)提到這些方法。此外,Babel也能夠講JSX編譯為JS。
一些參考資料:
Babel: How to use the react transformer
組件還應(yīng)該包括什么
每個(gè)組件都應(yīng)該包括一些內(nèi)部狀態(tài),處理邏輯,和事件處理器(例如按鈕點(diǎn)擊、輸入改變), 當(dāng)然也包括一些內(nèi)部的樣式。
你會(huì)遇到{this.props.name}這樣的代碼片段,這意味著你可以通過(guò)屬性的方式 先組件內(nèi)傳遞數(shù)據(jù),例如<MyComp name='weiwei sun' />。這讓組件變得可重用, 并且能夠自上而下的向嵌套的組件傳遞數(shù)據(jù)。
示例代碼如下:
class UserName extends React.Component {?
? render() {? ? return <div>name: {this.props.name}</div>;? }}class User extends React.Component {?
? render() {? ? return <div>? ? ? ? <h1>City: {this.props.user.city}</h1>? ? ? ? <UserName name={this.props.user.name} />? ? ? </div>;
? }
}
var user = { name: 'John', city: 'San Francisco' };?
React.render(<User user={user} />, document.body);
React擁抱ES6
在React中嘗試編寫(xiě)ES6是個(gè)非常不錯(cuò)的開(kāi)始,React并不是一開(kāi)始就支持ES6的, 而是從v0.13.0開(kāi)始支持的。你會(huì)經(jīng)常用到的ES6特性包括類、箭頭函數(shù)、consts 和模塊。例如,我們會(huì)經(jīng)常從繼承React.Component類開(kāi)始編寫(xiě)我們的組件。
還有一點(diǎn)需要注意的是,并不是每個(gè)瀏覽器都支持ES6,因此目前情況下,我們需要 使用一些工具將我們編寫(xiě)的ES6代碼轉(zhuǎn)換為ES5代碼,我推薦使用Babel。
一些參考資料:
組件生命周期
每個(gè)React組件在加載時(shí)都有特定的生命周期,在此期間不同的方法會(huì)被執(zhí)行。 下面簡(jiǎn)單介紹React組件的生命周期:
componentWillMount
該方法會(huì)在組件render之前執(zhí)行,并且永遠(yuǎn)只執(zhí)行一次。
componentDidMount
該方法會(huì)在組件加載完畢之后立即執(zhí)行。此時(shí),組件已經(jīng)完成了DOM結(jié)構(gòu)的渲染, 并可以通過(guò)this.getDOMNode()方法來(lái)訪問(wèn)。
componentWillReceiveProps
組件接收到一個(gè)新的prop時(shí)會(huì)被執(zhí)行,且該方法在初始render時(shí)不會(huì)被調(diào)用。
shouldComponentUpdate
在組件接收到新的props或state時(shí)被執(zhí)行。
componentWillUpdate
在組件接收到新的props或者state但還沒(méi)有render時(shí)被執(zhí)行。 在初始化時(shí)不會(huì)被執(zhí)行。
componentDidUpdate
在組件完成更新后立即執(zhí)行。在初始化時(shí)不會(huì)被執(zhí)行。 一般會(huì)在組件完成更新后被使用。
componentWillUnMount
在組件從DOM中unmount后立即執(zhí)行。該方法主要用來(lái)執(zhí)行一些必要的清理任務(wù)。
關(guān)于生命周期的具體內(nèi)容,你可以參考官方文檔。
在打包時(shí)使用Webpack和Babel
我們會(huì)經(jīng)常用到一些工具,首先一個(gè)是node.js的模塊系統(tǒng)和它的包管理工具npm。 我們會(huì)編寫(xiě)node風(fēng)格的代碼來(lái)require我們需要的東西。并且react本身也是一個(gè)獨(dú)立的?npm包。
通常你有兩種選擇,commonJS或者ES6:
var React = require('react/addon');var MyComponent = React.createClass({? ? // do something});module.exports = MyComponent;
或者
import React from 'react/addons';class MyComponent extends React.Component {? ? // do something use es6}export default MyComponent;
例如,我們會(huì)使用debug模塊來(lái)調(diào)試, 使用superagent模塊來(lái)編寫(xiě)請(qǐng)求。
現(xiàn)在,我們有了Node的依賴管理系統(tǒng),并且使用npm來(lái)提供模塊。下面我們需要做的 事:選擇一個(gè)合適的庫(kù)來(lái)打包我們的代碼,并且能夠讓其運(yùn)行在瀏覽器上。
因此我們需要一個(gè)打包器。目前最流行的解決方案包括兩個(gè),分別是Browserify和?Webpack。我們選擇使用Webpack,因?yàn)閃ebpack 更適合于React社區(qū)。
Webpack是如何工作的
Webpack用于打包我們的代碼,并且包含進(jìn)我們需要的包,然后輸出為瀏覽器可運(yùn)行的 文件。因?yàn)槲覀兪褂肑SX和ES6,因此我們需要相應(yīng)的工具來(lái)將其轉(zhuǎn)換為ES5。事實(shí)上, Babel能夠同時(shí)做這兩件事。使用Webpack能夠很輕松的完成這些任務(wù),因?yàn)閃ebpack 是面向配置的。
使用如下命令開(kāi)始:
npm init
npm install webpack --save-dev
npm install babel --save-dev
npm install babel-loader --save-dev
然后創(chuàng)建webpack.config.js文件,我們需要使用ES5來(lái)編寫(xiě)該文件,因?yàn)樗?webpack的配置文件。一個(gè)典型的配置方式如下:
var path = require('path');module.exports = {?
? entry: path.resolve(__dirname, '../src/client/scripts/client.js'),? output: {? ? path: path.resolve(__dirname, '../dist'),? ? filename: 'bundle.js'? },? module: {? ? loaders: [? ? ? {? ? ? ? test: /src\/.+.js$/,? ? ? ? exclude: /node_modules/,? ? ? ? loader: 'babel'? ? ? }? ? ]? }};
運(yùn)行webpack命令你可以執(zhí)行打包流程。這之后你可以只在頁(yè)面中包含bundle.js即可。 如下:
<script src='bundle.js'></script>
(提示:你可以使用node-static來(lái)存放你的靜態(tài)資源文件,使用npm install -g node-static?來(lái)安裝,并使用static .來(lái)啟動(dòng))。
項(xiàng)目結(jié)構(gòu)
一個(gè)典型的項(xiàng)目結(jié)構(gòu)你可以參考這個(gè)倉(cāng)庫(kù)。
config/?
? ? app.js
? ? webpack.js (js config over json -> flexible)src/?
? app/ (the React app: runs on server and client too)? ? components/? ? ? __tests__ (Jest test folder)? ? ? AppRoot.jsx
? ? ? Cart.jsx
? ? ? Item.jsx
? ? index.js (just to export app)? ? app.js
? client/? (only browser: attach app to DOM)? ? styles/? ? scripts/? ? ? client.js
? ? index.html
? server/? ? index.js
? ? server.js.gitignore.jshintrcpackage.json?
README.md
如何測(cè)試React組件
對(duì)于React組件的測(cè)試,這里推薦使用Jest, Jest也是由Facebook提供的測(cè)試框架,并且有很多強(qiáng)大的特性,但這里并會(huì)詳細(xì)的 介紹它們。
關(guān)于Jest,我推薦你閱讀和嘗試來(lái)自Facebook的Tutorial。
對(duì)于ES6代碼的測(cè)試,你可以參考?React ES6 Testing。
小結(jié)
本文簡(jiǎn)單介紹了React的基礎(chǔ)原理,一些相關(guān)的編程技術(shù)。后續(xù)還會(huì)整合一些資料 談一談Flux和同構(gòu)。