React 解析

查找 React 中文文檔請往 https://doc.react-china.org/

原文: http://blog.reverberate.org/2...
做者: Josh Haberman
中文社區導航: http://nav.react-china.orgjavascript


這篇文章跟博客已有的其餘文章有一些分離, 博客大部分是語言解析和底層編程的,
最近我對一些 JavaScript 框架有了興趣, 包括 Facebook 的 React.
我最近閱讀的文章, 特別是 The Future of JavaScript MVC Frameworks,
讓我相信在 React 當中有一些深刻的強大的想法在裏邊,
然而我沒找到文章或者文檔能把它核心的抽象解釋到我滿意的.
就像我前一篇文章 LL and LR Parsing Demystified,
這篇文章嘗試解釋 React 裏對我有意義的思想.html

The 1000-Foot View

傳統的 Web app 當中, 你要花費高昂的代價和 DOM 進行交互, 一般是用 jQuery:html5

我把 DOM 標記成了紅色, 由於更新 DOM 開銷是很大的.
如今的不少 "App" 會有個 Model class 用來在內部表示狀態,
但在咱們這裏認爲只是 app 內部的實現細節.java

React 主要的目標是提供一套不一樣的, 高效的方案來更新 DOM.
不是經過直接把 DOM 變成可變的數據, 而是經過構建 "Virtual DOM", 虛擬的 DOM,
隨後 React 處理真實的 DOM 上的更新來進行模擬相應的更新:node

引入額外的一個層怎麼就更快了呢?
那不是意味着瀏覽器的 DOM 操做不是最優的, 若是在上邊加上一層能讓總體變快的話?react

是有這個意思, 只不過 virtual DOM 在語義上和真實的 DOM 有所差異.
最主要的是, virtual DOM 的操做, 不保證立刻就會產生真實的效果.
這樣就使得 React 可以等到事件循環的結尾, 而在以前徹底不用操做真實的 DOM.
在這基礎上, React 計算出幾乎最小的 diff, 以最小的步驟將 diff 做用到真實的 DOM 上.git

批量處理 DOM 操做和做用最少的 diff 是應用自身都能作到的.
任何應用作了這個, 都能變得跟 React 同樣地高效.
但人工處理出來很是繁瑣, 並且容易出錯. React 能夠替你作到.github

Components

我前面提到 virtual DOM 和真實的 DOM 有着不用的語義, 但同時也有明顯不一樣的 API.
DOM 樹上的節點被稱爲元素, 而 virtual DOM 是徹底不一樣的抽象, 叫作 components.web

component 的使用在 React 裏極爲重要, 由於 components 的存在讓計算 DOM diff 更高效,
比起完整通用的 tree-diff 算法消耗的 O(n^3) 高效多了.算法

想知道爲何, 就要深刻一點 components 的設計當中.
拿 React 首頁的 "Hello World" 作個例子:

/** @jsx React.DOM */
var HelloMessage = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;
  }
});
 
React.renderComponent(<HelloMessage name="John" />, mountNode);

這裏邊有多得可怕的運行細節沒有被解釋完全.
這個例子儘管小, 卻展現了一些宏大的想法, 因此這裏我花點時間慢慢講.

這個例子建立了 React component class HelloMessage,
而後建立了一個 virtual DOM, 包含 component,
(<HelloMessage>, 本質是是 HelloMessage class 的一個實例)
並掛載到真實的 DOM 元素裏的一個節點.

首先注意這個 virtual DOM 是由應用定義的 components 組成的(這裏是 <HelloMessage>).
這和瀏覽器真實的 DOM 有着顯著的不一樣, 那些都只是瀏覽器內建的好比 <p> <ul>.
真實的 DOM 不含應用特定的邏輯, 而僅僅是能夠託管事件回調的數據結構.
而 React 裏的 virtual DOM, 則是含有應用特定內在邏輯的, 專爲應用定製的 components.
這遠不止於一個 DOM 更新類庫. React 是一種新的抽象, 新的構建 View 的框架.

另外, 若是你一直關心 HTML 的消息, 你應該知道HTML 自定義標籤很快會有瀏覽器支持.
這將帶給真實的 DOM 類似的功能: 根據應用特定邏輯定製應用須要的 DOM 元素.
不過 React 不須要等待官方的自定義標籤支持, 由於 virtual DOM 不是真實的 DOM.
這使得 React 能提早應用, 嵌入相似自定義標籤和 Shadow DOM 的功能,
而不用等到瀏覽器加上了全部這些功能才能被使用.

回到例子裏, 已經能肯定, 其中建立了一個叫作 <HelloMessage> 的 component 掛載到了節點上.
我想用圖把最初的狀態表示爲下面幾種形式. 先來展現 virtual DOM 和真實 DOM 之間的關係.
先假定掛載點是文檔的 <body> 標籤:

裏邊的箭頭表示 virtual 標籤掛載到了真實的 DOM 元素上, 很快能夠看到結果.
同時看一下如今應用的 view 的邏輯說明:

這裏是說, 整張網頁內容是經過咱們定製的 <HelloMessage> component 展現的.
不過, 一個 <HelloMessage> 看起來是什麼樣子呢?

component 的渲染經過 render() 函數定義.
React 沒有明確說明何時或者多頻繁他會去調用 render(),
只是會儘可能調用, 使得正確的界面更新能看清.
render() 方法返回的內容, 表示了瀏覽器裏真實的 DOM 看起來應該怎樣.

這裏例子當中, render() 返回了 <div>, 裏面還有一些內容.
React 調用了 render() 函數, 獲得 <div>, 並相應到真實的 DOM 作更新.
因此如今圖片更像是:

這裏不只更新了 DOM, 還保存了 component 過去被更新了怎麼樣.
因此 React 才能進行在後面進行快速的 diff.

我掩蓋了一件事, render() 函數爲何可以返回 DOM 節點.
這是經過 JSX 完成的, 不是經過單純 JavaScript. 看 JSX 的編譯結果更有好處:

/** @jsx React.DOM */
var HelloMessage = React.createClass({displayName: 'HelloMessage',
  render: function() {
    return React.DOM.div(null, "Hello ", this.props.name);
  }
});
 
React.renderComponent(HelloMessage( {name:"John"} ), mountNode);

因此 return 的不是真實的 DOM 元素, 而是 React 相似 Shadow DOM 的實現,
(好比說是 React.DOM.div) 對應到真實的 DOM 元素.
因此 React 的 shadow DOM 實際上沒有真實的 DOM 節點.

表示狀態和改變

到上面爲止, 我跳過了很大一段故事, 就是 comonent 是怎樣被改變的.
若是 component 不容許改變, React 頂多只是個靜態渲染框架,
像是純粹的模板引擎, 好比 Mustache 或者 HandlebarsJS.
而 React 的要點是快速進行更新. 要更新, component 就須要能更改.

React 將其 state 做爲 component 的 state 屬性建模存儲.
這在 React 頁面上的第二個例子裏闡述了:

/** @jsx React.DOM */
var Timer = React.createClass({
  getInitialState: function() {
    return {secondsElapsed: 0};
  },
  tick: function() {
    this.setState({secondsElapsed: this.state.secondsElapsed + 1});
  },
  componentDidMount: function() {
    this.interval = setInterval(this.tick, 1000);
  },
  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  render: function() {
    return (
      <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
    );
  }
});
 
React.renderComponent(<Timer />, mountNode);

回調函數 getInitialState(), componentDidMount(), componentWillUnmount()
都會被 React 在對應的時機觸發, 他們的命名根據前面提到的應該寫的很清楚了.

而 component 和 state 改變背後的基本理解是這樣:

  1. render() 僅僅是一個返回 component state 和 props 的函數
  2. state 只有在 setState() 調用時才改變
  3. props 不會改變, 除非父級 component 從新調用了渲染, 傳入新的 props

(props 屬性在前面沒有明確說, 他們是渲染時從父級元素傳進來的屬性.)

前面我是 React 會調用渲染函數"足夠頻繁",
意味着 React 不會再去調用 render(), 直到 component 的 setState() 被調用,
或者被父級元素傳入不一樣的 props 屬性從新渲染.

把全部信息聚集到一塊兒, 能夠闡釋 app 初始化時 virtual 改變的數據流
(好比, 響應一個 Ajax 請求):

從 DOM 當中獲取數據

上面只討論了怎麼把數據的更新傳播到 DOM.
實際的應用是, 也要從 DOM 獲取數據, 由於咱們須要那樣從用戶獲取數據
要看是如何工做的, 能夠看第三個 React 主頁上的例子:

/** @jsx React.DOM */
var TodoList = React.createClass({
  render: function() {
    var createItem = function(itemText) {
      return <li>{itemText}</li>;
    };
    return <ul>{this.props.items.map(createItem)}</ul>;
  }
});
var TodoApp = React.createClass({
  getInitialState: function() {
    return {items: [], text: ''};
  },
  onChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var nextItems = this.state.items.concat([this.state.text]);
    var nextText = '';
    this.setState({items: nextItems, text: nextText});
  },
  render: function() {
    return (
      <div>
        >h3<TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.onChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
      </div>
    );
  }
});
React.renderComponent(<TodoApp />, mountNode);

簡單說, 手動操做 DOM (像 onChange() 方法裏寫的),
事件回調能夠調用 setState() 來更新 UI.
若是你的應用裏有 model 的 class, 那麼你的事件回調是應該去相應更新 model,
還有就是調用 setState() 讓 React 知道數據有更新.
若是你已經習慣了一些自動進行雙向綁定的框架,
model 和 view 的數據兩個方向相互傳播, 這裏可能有點落後了.

這個例子裏有不少一眼能看見之外的東西. 雖然例子看起來是這樣的,
React 實際上沒有在真實的 <input> 元素上綁定 handler.
而是在整個文檔的級別綁定了 handler 等待事件冒泡, 再分發到 virtual DOM 對應的元素.
這帶來的好處有速度(在真實的 DOM 上綁定大量的 handler 會很慢),
還有是一致的跨瀏覽器兼容(即使瀏覽器行爲遵循標準, 或者屬性不全).

全部這些放在一塊兒, 終於能看到整個圖景裏的數據流動,
從用戶事件(好比說鼠標點擊)開始, 最終完成 DOM 的更新:

結論

經過寫這篇文章我學到了很多關於 React 的東西. 下面是我主要的收穫.

React 是一個 View 的類庫
React 沒有影響到你使用任何 model.
React 的 component 是一個 view 級別的概念, 其中 state 對應這個 UI 部分的狀態.
你能夠把任何 model 類庫結合到 React 來使用
(固然有些 model 的處理使得更新被優化得更深刻, 好比 Om 的文章裏寫的).

React 的 component 抽象很適合把更改做用到 DOM 上去.
component 的抽象是條理化的, 適合被複合, 這個設計帶來了 DOM 更新的高效.

React component 從 DOM 上獲取更新相對不那麼方便
手寫 event handler 讓 React 看起來明顯比一些自動更新 view 更改到 model 的類庫低級.

React 的抽象是有漏洞的.
大多數時間你只是對 virtual DOM 進行編程, 但有時你須要能直接操做真的 DOM.
React 文檔裏關於這個講了不少, 這在他們的Working With the Browser 章節是必需的.

根據個人理解, 我傾向認爲在 The Future of JavaScript MVC Frameworks 裏說的內容,
須要更深刻去審視. 但這個不大同樣, 我要等到另外一篇文章寫.

我不是 React 方面專家, 若是有錯了好心提醒我一下.

相關文章
相關標籤/搜索