近來React.js變得越來越流行,本文就來談一談React.js的入門實踐,通過分析一些常用的概念,以及提供一些入門 的最佳程式設計程式設計方式,僅供參考。
首先需要搞懂的是,React並不是一個框架,React提供了一些新穎的概念、庫 和程式設計原則讓你能夠同時在服務端和客戶端編寫快速、緊湊、漂亮的程式碼來構建 你的web應用。
如果你使用React,那麼可能會涉及到一些常用的概念或技術,包括:
- ES6 React
- 虛擬DOM(virtual DOM)
- 元件驅動開發(component-driven development)
- 不變性(immutability)
- 自上而下的渲染(top-down rendering)
- 渲染路徑和優化
- 打包工具, ES6, 構建請求, debugging, 路由等
- 同構React(isomorphic React)
什麼是React.js
React.js不是一個框架
在整個Web應用的MVC架構中,你可以將React看作為檢視層,並且是一個高效 的檢視。React提供了和以往不一樣的方式來看待檢視,它以元件開發為基礎。 對React應用而言,你需要分割你的頁面,使其成為一個個的元件。也就是說,你的 應用是由這些元件組合而成的。
你可以通過分割元件的方式去開發複雜的頁面或某個功能區塊,並且元件是可以 被複用的。這個過程大概類似於用樂高積木去瓶裝不同的物體。我們稱這種程式設計方式稱為 元件驅動開發。
React的一大特點是其所擁有的虛擬DOM,它讓頁面渲染變得非常的高效,並且比直接 操縱DOM變得更為可控。這兩大特點的組合使得React具有強大的自上而下的頁面渲染 能力。
好了,React的有兩個特點:元件化和高效的虛擬DOM,但是為什麼它這麼被看好呢? 因為React更多的是一種概念層面的東西,而庫是其次的。也有很多其他遵從了這些思想的第三方實現。和每一個程式設計概念一樣,React尤其 獨有的解決方案、工具和工具。但這裡並不會深入的去討論他們,而是關注React本身。
Virtual DOM
為了跟蹤模型層的變化,並且將其應用到DOM中(也就是渲染),我們需要注意兩個 重要的事情:
- 資料是什麼時候改變的
- 哪一個(些)DOM元素需要被更新
對於(1)而言,React提供了一個觀察者模型用於替代傳統的髒檢查(dirty checking), 也就是持續的檢查模型的變化。這也就是解釋了為什麼React不需要計算哪些發生 了改變的原因,因為它會立即知道。這個過程減少了計算量,並它應用程式變得 更平滑。但這裡真正有趣的是,React是如何管理DOM操縱的。
對於DOM改變(2)而言,React在記憶體中構建了DOM的樹形表示,並且計算出哪個 DOM元素應該被改變。對瀏覽器而言,DOM操縱是比較耗費效能的,因此我們更傾向於 讓其變得最小化。幸運的是,React檢視儘可能少的觸及到DOM元素。給予物件表示而言, 更少的DOM操縱意味著計算會更快,因此DOM改變也被儘可能的減少。
React在底層實現了一個diffing
演算法,該演算法使用DOM的樹形表示法,當某個 節點發生變化(標記為dirty)時它會重新計算整個子樹,你會注意到你的模型發生 了改變,因為整個子樹在之後會被重新渲染。關於該演算法的詳細分析可以參考這篇 文章。
如何在服務端渲染
因為React在DOM表示時使用了一個虛擬(假的)DOM,因此藉助於這種方式使得在服務端 渲染輸出HTML稱為可能(不借助於JSDom, PhantomJS等)。React還能智慧的識別出 服務端渲染出來的頁面標記,並在客戶端只為這些標記新增事件處理器,這對構建 同構web app非常有用。
有意思的是,React渲染出來的HTML標記都包含了data-reactid
屬性,這有助於 React中追蹤DOM節點。
一些閱讀資料
- React’s diff algorithm
- The Secrets of React’s virtual DOM
- Why is React’s concept of virtual DOM said to be more performant than dirty model checking?
- virtual-dom
元件驅動開發
對於component-driven development而言,你在一個模板中是看不到整個網站的。 雖然在一開始你可能會遇到一些困難,但是如果進一步的使用這種思路,你會發現 它易於理解,易於維護,並且容易測試。
如何使用React的方式來思考元件開發
下面我們來看如何實現元件驅動開發這一理念。我們看一個例子,這個例子來源於 thinking in react 這篇文章。對於構建一個可過濾的產品列表而言,通常其包括如下的元件結構:
一個元件應該包含什麼
首先,理想的,我們應該遵守單一責任原則 來設計你的元件。當你發下你的元件應該做的更多的時候,你可以考慮將其分割為 更小的元件集合。
因為我們在討論元件層級,因此在你的元件中也會使用到其他元件。我們首先看下 在ES5中元件程式碼是什麼樣子的:
1 2 3 4 5 |
var HelloComponent = React.createClass({ render: function() { return <div>Hello {this.props.name}</div>; } }); |
如果使用ES6,你的元件程式碼可以這樣寫:
1 2 3 4 5 |
class HelloComponent extends React.Component { render() { return <div>Hello {this.props.name}</div>; } } |
JS和JSX
正如你說看到的,我們的元件是JS和HTML程式碼的混合,你可能會覺得這很糟糕,因為 MVC一直在教我們儘可能的隔離檢視和控制邏輯。但另一方,這種混合獲得另一個層面的 單一責任,他使得元件更加的靈活和可重用。
當然,在React中你也可以使用純JS來編寫你的元件:
1 2 3 4 |
render () { return React.createElement("div", null, "Hello ", this.props.name); } |
是的,你會發現這很麻煩,沒有使用HTML來得直觀。因此React提供了JSX (JavaScript eXtension)語法讓你能夠在JS中書寫HTML程式碼。
1 2 3 |
render () { return <div>Hello {this.props.name}</div>; } |
什麼是JSX
JSX在ECMAScript的基礎上提供了類似於XML的擴充套件。 JSX和HTML有點像,但也有不一樣的地方。例如,HTML中的class
屬性在JSX中 為className
。其他不一樣的地方,你可以參考FB的HTML Tags vs. React Components 這篇文章。
但是由於瀏覽器原生並不支援JSX,因此我們需要將其編譯為JS,有很多方法能夠 完成這個任務,後面我們會提到這些方法。此外,Babel也能夠講JSX編譯為JS。
一些參考資料:
元件還應該包括什麼
每個元件都應該包括一些內部狀態,處理邏輯,和事件處理器(例如按鈕點選、輸入改變), 當然也包括一些內部的樣式。
你會遇到{this.props.name}這樣的程式碼片段,這意味著你可以通過屬性的方式 先元件內傳遞資料,例如 <MyComp name='weiwei sun' /> 。這讓元件變得可重用, 並且能夠自上而下的向巢狀的元件傳遞資料。
示例程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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中嘗試編寫ES6是個非常不錯的開始,React並不是一開始就支援ES6的, 而是從 v0.13.0 開始支援的。你會經常用到的ES6特性包括類、箭頭函式、consts 和模組。例如,我們會經常從繼承 React.Component 類開始編寫我們的元件。
還有一點需要注意的是,並不是每個瀏覽器都支援ES6,因此目前情況下,我們需要 使用一些工具將我們編寫的ES6程式碼轉換為ES5程式碼,我推薦使用Babel。
一些參考資料:
元件生命週期
每個React元件在載入時都有特定的生命週期,在此期間不同的方法會被執行。 下面簡單介紹React元件的生命週期:
componentWillMount
該方法會在元件render
之前執行,並且永遠只執行一次。
componentDidMount
該方法會在元件載入完畢之後立即執行。此時,元件已經完成了DOM結構的渲染, 並可以通過 this.getDOMNode() 方法來訪問。
componentWillReceiveProps
元件接收到一個新的prop時會被執行,且該方法在初始render
時不會被呼叫。
shouldComponentUpdate
在元件接收到新的props或state時被執行。
componentWillUpdate
在元件接收到新的props或者state但還沒有render時被執行。 在初始化時不會被執行。
componentDidUpdate
在元件完成更新後立即執行。在初始化時不會被執行。 一般會在元件完成更新後被使用。
componentWillUnMount
在元件從DOM中unmount後立即執行。該方法主要用來執行一些必要的清理任務。
關於生命週期的具體內容,你可以參考官方文件。
在打包時使用Webpack和Babel
我們會經常用到一些工具,首先一個是node.js
的模組系統和它的包管理工具npm
。 我們會編寫node風格的程式碼來require
我們需要的東西。並且react
本身也是一個獨立的 npm包。
通常你有兩種選擇,commonJS或者ES6:
1 2 3 4 5 6 7 |
var React = require('react/addon'); var MyComponent = React.createClass({ // do something }); module.exports = MyComponent; |
或者
1 2 3 4 5 |
import React from 'react/addons'; class MyComponent extends React.Component { // do something use es6 } export default MyComponent; |
例如,我們會使用debug模組來除錯, 使用superagent模組來編寫請求。
現在,我們有了Node的依賴管理系統,並且使用npm
來提供模組。下面我們需要做的 事:選擇一個合適的庫來打包我們的程式碼,並且能夠讓其執行在瀏覽器上。
因此我們需要一個打包器。目前最流行的解決方案包括兩個,分別是Browserify和 Webpack。我們選擇使用Webpack,因為Webpack 更適合於React社群。
Webpack是如何工作的
Webpack用於打包我們的程式碼,並且包含進我們需要的包,然後輸出為瀏覽器可執行的 檔案。因為我們使用JSX
和ES6
,因此我們需要相應的工具來將其轉換為ES5。事實上, Babel能夠同時做這兩件事。使用Webpack能夠很輕鬆的完成這些任務,因為Webpack 是面向配置的。
使用如下命令開始:
1 2 3 4 |
npm init npm install webpack --save-dev npm install babel --save-dev npm install babel-loader --save-dev |
然後建立 webpack.config.js 檔案,我們需要使用ES5來編寫該檔案,因為它是 webpack的配置檔案。一個典型的配置方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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' } ] } }; |
執行 webpack 命令你可以執行打包流程。這之後你可以只在頁面中包含 bundle.js 即可。 如下:
1 |
<script src='bundle.js'></script> |
(提示:你可以使用
node-static 來存放你的靜態資原始檔,使用
npm install -g node-static 來安裝,並使用
static .
來啟動)。
專案結構
一個典型的專案結構你可以參考這個倉庫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 .jshintrc package.json README.md |
如何測試React元件
對於React元件的測試,這裡推薦使用Jest, Jest也是由Facebook提供的測試框架,並且有很多強大的特性,但這裡並會詳細的 介紹它們。
關於Jest,我推薦你閱讀和嘗試來自Facebook的Tutorial。
對於ES6程式碼的測試,你可以參考 React ES6 Testing。
小結
本文簡單介紹了React的基礎原理,一些相關的程式設計技術。後續還會整合一些資料 談一談Flux和同構。
Statement
本文翻譯自:https://blog.risingstack.com/the-react-way-getting-started-tutorial/ 有增刪改。
References
- https://blog.risingstack.com/the-react-way-getting-started-tutorial/
- https://github.com/RisingStack/react-way-getting-started
- http://facebook.github.io/react/docs/component-specs.html