React入門


@React入門與實戰
@@React入門與實戰
@@@第一章 React簡介
@@@@React由來
###React由來
<p>
<img src="img/one.png" style="border-radius:50%">
<div>相信對於React大家或多或少都知道一些關於它的消息吧,確實React在這最近的<em>2-3</em>年時間里面迅速的流傳開來。不管是作為web端開發的同學,還是作為App端開發的同學,都有必要要去了解、學習它。因為它已經成為一種既可以跨平台、而且又可以快速開發、性能特別棒的寵兒了。下面列出幾條React的由來:</div><br />
<li>React是一個用於構建用戶界面的<em>JAVASCRIPT</em>庫</li>
<li>React主要用於構建UI,很多人認為React是MVC中的V(視圖)</li>
<li>React起源於Facebook的內部項目,用於<em>構建Instagram的網站</em>,並於2013年5月開源</li>
<li>React擁有較高的性能,代碼邏輯非常簡單,越來越多的人已開始關注和使用它</li><br />

<div>相信看完上面列出來的特點,有些同學會有疑問了,比如什么是聲明式設計、什么是單向響應的數據流,且先別着急,讓我們先把好奇心先放放,跟着課程的進度,相信你能夠一一了解到。</div><br />
**React學習需具備的知識**
<br />
<div>在開始學習React之前,您需要具備以下基礎知識:</div>
<li><em>HTML5</em></li>
<li><em>CSS</em></li>
<li><em>JavaScript</em></li>
<br />
**練習**
<div>
在這里給大家留道練習題,目前市場上面,還有哪些流行的前端框架,它們與React項目有哪些區別呢?
</div>
<br />
</p>

@@@@React下載
###React下載
<p>
<div>由於React主要是提供一套JS框架,所以在使用它之前我們首先需要得到一份React的js庫,目前主流的四種方法:</div>
<li>通過<em>npm</em>進行下載,這種下載方式需要你本地首先配置好npm環境,然后通過npm指令從遠程倉庫里面下載一份JS庫</li>
<li>通過他人已經下載好的js得到</li>
<li>通過<em>CDN服務器</em>直接進行引用,這種方式我們不需要事先下載React js庫,而是提供一個遠程的CDN服務器的鏈接,當代碼進行鏈接編譯的時候,將會自動從遠程下載JS庫。優點是我們不需要隨着JS庫的升級更新本地JS庫,缺點就是必須保證遠程CDN服務器的穩定可用</li>
<li>4.由於React是開源的,所以我們可以通過源碼自己編譯得到</li>
<div>由於篇幅限制,所以這一小節我們就不詳細介紹npm配置下載了,有興趣的同學可以自行研究,相信也不會太難。以后的課程里面,我們將采取第三條方法,通過cdn引用的方法,這種方法也是正規項目里面最常采用的方法了。</div><br />

**React JS CDN**
<li><code><script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script></code></li>
<li><code><script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script></code></li>
<li><code><script src="http://static.runoob.com/assets/react/browser.min.js"></script></code></li>
<div>下面大致講一講上面三個JS庫的各自作用,三個庫都帶有.min后綴,相信具有web開發經驗的同學應該知道js庫是經過壓縮的。<em>react.min.js是React核心js庫</em>,<em>react-dom.min.js則是React與dom操作相關的js庫<em>,<em>browser.min.js的作用是將JSX語法轉換為JavaScript語法</em>,當然這一步是很耗時間的。</div><br />

<img src="img/img14.png" width="600" style="border-radius:50%">
<br />
**練習**
<div>
通過本節學習相信你已經對React有一定的了解了,最起碼能夠知道它大概應該是怎樣使用的。本節最后給大家留一個練習題吧,這個練習題我覺得還是很有必要試一下,因為對你以后學習其他新技術還是很有幫助的,比如<em>nodejs</em>。大家可以自己在本地電腦上面搭建一下npm環境,同時用這種方法下載一下React JS庫。
</div>
</p>

@@@@特點與Hello World
###特點與Hello World
<p>
<img src="img/seven.jpg">
**React的特點**
<div>作為一款知名度這么高的前端JS框架,何以能夠有如此魅力,下面我們詳細列出React的幾條主要特點:</div><br />
<li>聲明式設計-React采用申明范式,可以輕松描述應用。</li>
<li>高效-React通過對<em>DOM的模擬</em>,最大限度地減少與DOM的交互。</li>
<li>靈活-React可以與已知的庫或框架很好地配合。</li>
<li><em>JSX-JSX是javascript語法的擴展</em>,React開發不一定使用JSX,但我們推薦使用它。</li>
<li>組件-通過React構建組件,使得代碼更加容易得到復用,能夠很好地應用在大項目的開發中。</li>
<li>單向響應的數據流-React實現了單向響應的數據流,從而減少了重復代碼,這也是它為什么比傳統數據綁定更簡單。</li><br />

**Hello World**
<div>在這一節里面,讓我們庸俗一把,通過一個Hello World的demo來快速了解一下React的代碼規范與結構。</div>

<pre>
<div id="example"></div>
<script type="text/babel">
   ReactDOM.render(
     <h1>Hello, world!</h1>,
     document.getElementById('example')
   );
</script>
</pre>
<div>
下面我們來大致講一下React代碼結構,從上面的代碼可以看到首先需要加載三個JS庫,所以我們將script標簽放置在head里面。在主體的body部分我們簡單的定義了一個名為"example"的div,然后通過script標記定義了一個js代碼塊,其中的type特別重要,這也是react所獨有並且能夠識別的。通過<em>type="text/babel"來告訴react的解析庫</em>,下面這段代碼是JSX了,請幫我解析成正規的javascript代碼吧。最后在script里面,使用了<em>render函數</em>,該函數是React的最基本方法,用於將模板轉為HTML語言,並插入指定的DOM節點。上面代碼將一個h1標題,插入example節點。
</div><br />

<div class='cw-exe'>
同學可以寫個demo,將自己的名字輸出到屏幕上面。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/babel">
      ReactDOM.render(
        <h1>Hello, world!</h1>,
        document.getElementById('example')
      );
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@@JSX語法
###JSX語法
<p>
<img src="img/three.png" height="300" width="600">
<div>好了,進入這一節,請大家一定要仔細閱讀,同時盡可能的完成后面的練習題。因為這一節是React學習中的重中之重,你只有學好了JSX語法才能游刃有余的用好React,就像<em>java/c#</em>一樣,只有了解了基本語法,才能構建上層的繁華。通過上一節的代碼,其實大家已經一窺JSX語法了,只是還不夠全面而已。HTML語言直接寫在JavaScript語言之中,不加任何引號,這就是<a href="https://facebook.github.io/react/docs/rendering-elements.html">JSX的語法</a>,它允許HTML與JavaScript的混寫。</div>

<pre>
<div id="example"></div>
<script type="text/babel">
  var names = ['Alice', 'Emily', 'Kate'];

  ReactDOM.render(
    <div>
    {
      names.map(function (name) {
        return <div>Hello, {name}!</div>
      })
     }
    </div>,
    document.getElementById('example')
   );
</script>
</pre>
<div>
上面代碼體現了JSX的基本語法規則:<em>遇到HTML標簽(以<開頭)</em>,就用HTML規則解析;<em>遇到代碼塊(以{開頭)</em>,就用JavaScript規則解析。同時JSX允許直接在模板插入JavaScript變量。如果這個變量是個數組,則會展開這個數組的所有成員,請看下面的代碼片段:
<pre>
var arr = [
  <h1>Hello world!</h1>,
  <h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);
</pre>
我們發現通過JSX語法,Facebook給我們提供很靈活的js和html交互方式。相信有過java web開發經驗的同學對於SSH框架一定不陌生吧,但是它里面的struts框架在js與html操作的過程顯得特別的笨拙,用起來經常會顯得束手束腳。
</div><br />

**其他JSX語法**
<li>
<div>
render an element into the dom
<pre>
<div id="root"></div>

const element = <h1>Hello, world</h1>;
ReactDOM.render(
  element,
  document.getElementById('root')
);
</pre>
</div>
</li>

<li>
<div>
updating the rendered element
<pre>
function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);
</pre>
</div>
</li><br />

<div class="cw-exe">
通過上面的學習,同學可以試着自己寫一個根據用戶輸入款的內容更新下面的label的內容,即兩者是同步更新的。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
  <body>
   <div id="example"></div>
   <script type="text/babel">
       var names=['xiao','cai','hello'];
       ReactDOM.render(
          <div>
        {
          names.map(function (name) {
            return <div>Hello, {name}!</div>
          })
        }
        </div>,
document.getElementById('example')
       );
   </script>
</body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@第二章 組件簡介
@@@@組件事件
###組件事件
<p>
<img src="img/eight.png" width="600">
<div>
進入本章之后,我們將開始學習React里面的精華知識,相對於第一章的內容,相對來說難一點。但是如果你有一定的語言基礎,相信理解起來也不會有太大的難度。好了,讓我們進去正題。首先就是為什么我們需要組件,第一章的入門篇,我們就提到過React具有:高效、靈活、組件等特點。當我們在項目里面需要多次使用同一個UI片段的時候,我們就可以考慮將其以組件的形式獨立出來了。這樣將可以很好地實現代碼復用,便於日后的維護,同時很好的符合設計里面內聚性。
</div><br />

<div>
React允許將代碼封裝成<em>組件(Component)</em>,然后像插入普通HTML標簽一樣,在網頁中插入這個組件,React.createClass方法就用於生成一個組件類。<br />
<pre>
var HelloMessage = React.createClass({
  render: function() {
    return <h1>Hello {this.props.name}</h1>;
  }
});

ReactDOM.render(
  <HelloMessage name="John" />,
  document.getElementById('example')
);
</pre>
</div><br />
<div>
上面代碼我們就定義了一個HelloMessage組件,但是同學可能會有幾個地方覺得很陌生。首先就是this.props.name這個東西是什么意思?還有render:function這種寫法是不是必須的呢?模板插入<HelloMessage />時,會自動生成HelloMessage的一個實例(下文的“組件”都指組件類的實例)。所有組件類都必須有自己的render方法,用於輸出組件。
</div><br />

**注意(tip)**
<li>
組件類的<em>第一個字母必須大寫</em>,否則會報錯,比如HelloMessage不能寫成helloMessage。
</li>
<li>
組件類只能包含一個頂層標簽,否則也會報錯。
</li>
<pre>
var HelloMessage = React.createClass({
  render: function() {
    return <h1>
      Hello {this.props.name}
    </h1><p>
      some text
    </p>;
  }
});
</pre>
<div>
上面的代碼就會報錯,<em>因為包含兩個頂層標簽:h1和p</em>。
</div><br />

<div class="cw-exe">
學完這一小節,是不是有種想創建自己組件的沖動呢?同學可以試着創建一個以自己命名的組件。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/babel">
      var HelloMessage = React.createClass({
        render: function() {
          return <h1>Hello {this.props.className}</h1>;
        }
      });

      ReactDOM.render(
        <HelloMessage className="John" />,
        document.getElementById('example')
      );
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@@組件屬性
###組件屬性
<div>組件的用法與原生的HTML標簽完全一致,可以任意加入屬性,比如<HelloMessage name="cai">,就是HelloMessage組件加入一個name屬性,值為cai。組件的屬性可以在組件類的 <em>this.props</em> 對象上獲取,比如 name 屬性就可以通過 this.props.name 讀取。
</div>
<br />
**注意**
<li>添加組件屬性,有一個地方需要注意,就是 <em>class 屬性需要寫成 className</em> ,<em>for 屬性需要寫成 htmlFor</em> ,這是因為 class 和 for 是 JavaScript 的保留字。</li><br />

**this.props.children**
<div>
this.props 對象的屬性與組件的屬性一一對應,但是有一個例外,就是 this.props.children 屬性。它表示組件的所有子節點:<br />
<pre>
var NotesList = React.createClass({
  render: function() {
    return (
      <ol>
      {
        React.Children.map(this.props.children, function (child) {
          return <li>{child}</li>;
        })
      }
      </ol>
    );
  }
});

ReactDOM.render(
  <NotesList>
    <span>hello</span>
    <span>world</span>
  </NotesList>,
  document.body
);
</pre>
</div>
<div>
上面代碼的 NoteList 組件有兩個 span 子節點,它們都可以通過 <em>this.props.children</em> 讀取,但是這里需要注意,this.props.children的值有三種情況:
<li>如果當前組件沒有子節點,它就是 undefined;</li>
<li>如果有一個子節點,數據類型是 object;</li>
<li>如果有多個子節點,數據類型就是 array;</li>
所以,處理 this.props.children 的時候要小心。<br />
React 提供一個工具方法 React.Children 來處理 this.props.children 。我們可以用 React.Children.map 來遍歷子節點,而不用擔心 this.props.children 的數據類型是 undefined 還是 object。
</div>
<br />
**PropTypes**
<div>
組件的屬性可以接受任意值,字符串、對象、函數等等都可以。有時,我們需要一種機制,驗證別人使用組件時,提供的參數是否符合要求。組件類的PropTypes屬性,就是用來驗證組件實例的屬性是否符合要求:<br />
<pre>
var MyTitle = React.createClass({
  propTypes: {
    title: React.PropTypes.string.isRequired,
  },

  render: function() {
     return <h1> {this.props.title} </h1>;
   }
});</pre><br />
上面的Mytitle組件有一個title屬性。PropTypes 告訴 React,這個 title 屬性是必須的,而且它的值必須是字符串。<br />
<pre>
var data = 123;

ReactDOM.render(
  <MyTitle title={data} />,
  document.body
);</pre><br />
這樣一來,title屬性就通不過驗證了。<br />

此外,<em>getDefaultProps</em> 方法可以用來設置組件屬性的默認值。<br />
<pre>
var MyTitle = React.createClass({
  getDefaultProps : function () {
    return {
      title : 'Hello World'
    };
  },

  render: function() {
     return <h1> {this.props.title} </h1>;
   }
});

ReactDOM.render(
  <MyTitle />,
  document.body
);
</pre>
</div>

<div class='cw-exe'>
這一節的內容比較多,這里留個同學一個比較完整的練習,寫一個完整的登錄頁面,要求有完整的驗證流程(用戶名6-20位字母數字,密碼智能為字母,且都不能為空)。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/babel">
      var NotesList = React.createClass({
        render: function() {
          return (
            <ol>
              {
                React.Children.map(this.props.children, function (child) {
                  return <li>{child}</li>;
                })
              }
            </ol>
          );
        }
      });

      ReactDOM.render(
        <NotesList>
          <span>hello</span>
          <span>world</span>
        </NotesList>,
        document.getElementById('example')
      );
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@@組件狀態
###組件狀態
<p>
<img src="img/six.jpg" height="220" width="600">
<div>組件免不了要與用戶互動,React 的一大創新,就是將組件看成是一個狀態機,一開始有一個初始狀態,然后用戶互動,導致狀態變化,從而觸發重新渲染 UI。</div>
<br />
**Demo實例**
<pre>
  <body>
    <div id="example"></div>
    <script type="text/babel">
var LikeButton = React.createClass({
  getInitialState: function() {
    return {liked: false};
  },
  handleClick: function(event) {
    this.setState({liked: !this.state.liked});
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);
    </script>
</pre>

<div>
上面代碼是一個 LikeButton 組件,它的 getInitialState 方法用於定義初始狀態,也就是一個對象,這個對象可以通過 this.state 屬性讀取。當用戶點擊組件,導致狀態變化,this.setState 方法就修改狀態值,每次修改以后,自動調用 this.render 方法,再次渲染組件。<br />
由於 <em>this.props 和 this.state </em>都用於描述組件的特性,可能會產生混淆。一個簡單的區分方法是,this.props 表示那些一旦定義,就不再改變的特性,而 this.state 是會隨着用戶互動而產生變化的特性。
</div><br />

<div class='cw-exe'>
同學們可以寫一個,每隔3秒刷新頁面button透明度的demo。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/babel">
var LikeButton = React.createClass({
  getInitialState: function() {
    return {liked: false};
  },
  handleClick: function(event) {
    this.setState({liked: !this.state.liked});
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@@組件生命周期
###組件生命周期
<p>
<img src="img/nine.png" height="300" width="600">
<div>首先我想來解釋一下,什么是生命周期。相信如果同學有一定的App端開發經驗的話,那么對於生命周期這個詞一定不會覺得陌生。不過不熟悉也沒關系,且聽筆者慢慢道來。在App開發過程中,對於每一個頁面都會有一個生命周期,比如當頁面在創建的時候,我們可以加載一些資源。當頁面銷毀的時候,我們可以清理一些大文件數據等。同理拿到React里面,它也會有對應的三個狀態:
<li>Mounting:已插入<em>真實 DOM</em>;</li>
<li>Updating:正在被重新渲染;</li>
<li>Unmounting:已移出真實 DOM;</li>
</div>

<br />
**React對應的生命周期處理函數**
<div>
React 為每個狀態都提供了兩種處理函數,will 函數在進入狀態之前調用,did 函數在進入狀態之后調用,三種狀態共計五種處理函數。
<li>componentWillMount():將插入(加載)頁面;</li>
<li>componentDidMount():已經加載(渲染)完成頁面;</li>
<li>componentWillUpdate(object nextProps, object nextState):將更新DOM對象;</li>
<li>componentDidUpdate(object prevProps, object prevState):已經更新DOM對象;</li>
<li>componentWillUnmount():將銷毀DOM對象;</li>
<div>此外,React 還提供兩種特殊狀態的處理函數:</div>
<li>componentWillReceiveProps(object nextProps):已加載組件收到新的參數時調用;</li>
<li>shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時調用;</li>
</div>

<br />
**Demo實例**
<pre>
    <div id="example"></div>
    <script type="text/babel">
      var Hello = React.createClass({
        getInitialState: function () {
          return {
            opacity: 1.0
          };
        },

        componentDidMount: function () {
          this.timer = setInterval(function () {
            var opacity = this.state.opacity;
            opacity -= .05;
            if (opacity < 0.1) {
              opacity = 1.0;
            }
            this.setState({
              opacity: opacity
            });
          }.bind(this), 100);
        },

        render: function () {
          return (
            <div style={{opacity: this.state.opacity}}>
              Hello {this.props.name}
            </div>
          );
        }
      });

      ReactDOM.render(
        <Hello name="world"/>,
        document.getElementById('example')
      );
    </script>
</pre>
上面代碼在hello組件加載以后,通過 <em>componentDidMount 方法設置一個定時器</em>,每隔100毫秒,就重新設置組件的透明度,從而引發重新渲染。

<div class='cw-exe'>
同學可以加載一張圖片,在加載之前提示“正在加載...”,然后在加載完成之后提示“加載完成...”。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
   <body>
    <div id="example"></div>
    <script type="text/babel">
      var Hello = React.createClass({
        getInitialState: function () {
          return {
            opacity: 1.0
          };
        },

        componentDidMount: function () {
          this.timer = setInterval(function () {
            var opacity = this.state.opacity;
            opacity -= .05;
            if (opacity < 0.1) {
              opacity = 1.0;
            }
            this.setState({
              opacity: opacity
            });
          }.bind(this), 100);
        },

        render: function () {
          return (
            <div style={{opacity: this.state.opacity}}>
              Hello {this.props.name}
            </div>
          );
        }
      });

      ReactDOM.render(
        <Hello name="world"/>,
        document.getElementById('example')
      );
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@第三章 React其他功能
@@@@訪問DOM
###訪問DOM
<p>
<img src="img/ten.png" height="300" width="600">
<div>組件並不是真實的 DOM 節點,而是存在於內存之中的一種數據結構,叫做<em>虛擬 DOM (virtual DOM)</em>。只有當它插入文檔以后,才會變成真實的 DOM 。根據 React 的設計,所有的 DOM 變動,都先在虛擬 DOM 上發生,然后再將實際發生變動的部分,反映在真實 DOM上,這種算法叫做 <a href="http://calendar.perfplanet.com/2013/diff/">DOM diff</a> ,它可以極大提高網頁的性能表現。</div><br />
<div>但是,有時需要從組件獲取真實 DOM 的節點,這時就要用到 ref 屬性。</div>
<br />

**Demo實例**
<pre>
    <div id="example"></div>
    <script type="text/babel">
var MyComponent = React.createClass({
  handleClick: function() {
    this.refs.myTextInput.focus();
  },
  render: function() {
    return (
      <div>
        <input type="text" ref="myTextInput" />
        <input type="button" value="Focus the text input" onClick={this.handleClick} />
      </div>
    );
  }
});

ReactDOM.render(
  <MyComponent />,
  document.getElementById('example')
);
    </script>
</pre>
<div>
上面代碼中,組件 MyComponent 的子節點有一個文本輸入框,用於獲取用戶的輸入。這時就必須獲取真實的 DOM 節點,虛擬 DOM 是拿不到用戶輸入的。為了做到這一點,文本輸入框必須有一個 ref 屬性,然后 this.refs.[refName] 就會返回這個真實的 DOM 節點。<br />
需要注意的是,由於 <em>this.refs.[refName] 屬性獲取的是真實 DOM</em> ,所以必須等到虛擬 DOM 插入文檔以后,才能使用這個屬性,否則會報錯。上面代碼中,通過為組件指定 Click 事件的回調函數,確保了只有等到真實 DOM 發生 Click 事件之后,才會讀取 this.refs.[refName] 屬性。<br />
</div>

<div class='cw-exe'>
同學可以將上面的demo改造一下,當按鈕點擊的時候,獲取用戶輸入的內容。進而將上面的登錄頁面完善,驗證用戶名和密碼不為空。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
    <body>
    <div id="example"></div>
    <script type="text/babel">
var MyComponent = React.createClass({
  handleClick: function() {
    this.refs.myTextInput.focus();
  },
  render: function() {
    return (
      <div>
        <input type="text" ref="myTextInput" />
        <input type="button" value="Focus the text input" onClick={this.handleClick} />
      </div>
    );
  }
});

ReactDOM.render(
  <MyComponent />,
  document.getElementById('example')
);
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@@使用表單
###使用表單
<p>
<div>如果同學有了解java web開發的話,那么對於表單這個詞一定不會覺得陌生。因為在jsp里面專門有個form標簽用於表單的定義,然后在form標簽里面有個“method”屬性用於定義不同的方法提交表單。當然這樣做的結果就導致表單用起來非常的麻煩,必須嚴格的按照form標簽的方式進行提交表單。但是在React里面就將表單設計的非常靈活,可以以一種動態綁定的方式來獲取表單里面的數據。用戶在表單填入的內容,屬於用戶跟組件的互動,所以不能用 this.props 讀取。</div><br />

**Demo實例**
<div>
<pre>
    <div id="example"></div>
    <script type="text/babel">
      var Input = React.createClass({
        getInitialState: function() {
          return {value: 'Hello!'};
        },
        handleChange: function(event) {
          this.setState({value: event.target.value});
        },
        render: function () {
          var value = this.state.value;
          return (
            <div>
              <input type="text" value={value} onChange={this.handleChange} />
              <p>{value}</p>
            </div>
          );
        }
      });

      ReactDOM.render(<Input/>, document.getElementById('example'));
    </script>
</pre>
上面代碼中,文本輸入框的值,不能用 this.props.value 讀取,而要定義一個 onChange 事件的回調函數,通過<em>event.target.value</em> 讀取用戶輸入的值。textarea 元素、select元素、radio元素都屬於這種情況
</div>

<div class='cw-exe'>
同學可以利用表單的方式,獲取一下select元素的值。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
    <body>
    <div id="example"></div>
    <script type="text/babel">
      var Input = React.createClass({
        getInitialState: function() {
          return {value: 'Hello!'};
        },
        handleChange: function(event) {
          this.setState({value: event.target.value});
        },
        render: function () {
          var value = this.state.value;
          return (
            <div>
              <input type="text" value={value} onChange={this.handleChange} />
              <p>{value}</p>
            </div>
          );
        }
      });

      ReactDOM.render(<Input/>, document.getElementById('example'));
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@@使用上下文對象
###使用上下文對象
<p>
<div>context 是在 react @ 0.14 版本以后發布的一個高級且實驗性的功能,有可能在未來做出更改。不推薦頻繁使用,如果使用的話盡量保持在小范圍內並且避免直接使用 context 的 API,為了以后升級框架更加容易。</div><br />

**使用 Context 的原因**
<div>
為了有時候想傳遞數據通過組件樹,但是不想給每一層級的組件手動傳遞屬性,那么 context 就能幫你 <em>"越級" 傳遞數據</em>到組件樹中你想傳遞到的深層次組件。<br />

有時候 A組件 為了給 B組件 中的 C組件 傳遞一個 prop ,而需要把參數在組件中傳遞兩次才能最終將 A組件 中的 prop 傳遞給 C組件 。請看下面的官方實例代碼,使用原來的兩層方式傳遞:
<pre>
var Button = React.createClass({
  render: function() {
    return (
      <button style={{background: this.props.color}}>
        {this.props.children}
      </button>
    );
  }
});

var Message = React.createClass({
  render: function() {
    return (
      <div>
        {this.props.text} <Button color={this.props.color}>Delete</Button>
      </div>
    );
  }
});

var MessageList = React.createClass({
  render: function() {
    var color = "purple";
    var children = this.props.messages.map(function(message) {
      return <Message text={message.text} color={color} />;
    });
    return <div>{children}</div>;
  }
});
</pre>
現在我們使用 context 來完成參數的傳遞試試。
<pre>
var Button = React.createClass({
  // 必須指定context的數據類型
  contextTypes: {
    color: React.PropTypes.string
  },
  render: function() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
});

var Message = React.createClass({
  render: function() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
});

var MessageList = React.createClass({
  //
  childContextTypes: {
    color: React.PropTypes.string
  },
  getChildContext: function() {
    return {color: "purple"};
  },
  render: function() {
    var children = this.props.messages.map(function(message) {
      return <Message text={message.text} />;
    });
    return <div>{children}</div>;
  }
});
</pre>

示例代碼中通過添加 childContextTypes 和 getChildContext() 到 MessageList ( context 的提供者),<em>React 自動向下傳遞數據然后在組件樹中的任意組件</em>(也就是說任意子組件,在此示例代碼中也就是 Button )都能通過定義 contextTypes 訪問 context 中的數據。
</div>

<div class='cw-exe'>
同學們可以再好好比較一下這兩種方式傳遞的優缺點,同時做一下這樣的demo:利用Context的方式,拿到ul列表標簽里面的自列表li里面的Text值。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
    <body>
    <div id="example"></div>
    <script type="text/babel">
      var Button = React.createClass({
  // 必須指定context的數據類型
  contextTypes: {
    color: React.PropTypes.string
  },
  render: function() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
});

var Message = React.createClass({
  render: function() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
});

var MessageList = React.createClass({
  //
  childContextTypes: {
    color: React.PropTypes.string
  },
  getChildContext: function() {
    return {color: "purple"};
  },
  render: function() {
    var children = this.props.messages.map(function(message) {
      return <Message text={message.text} />;
    });
    return <div>{children}</div>;
  }
});
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@@動畫效果
###動畫效果
<p>
<img src="img/img11.gif" height="300" width="600">
<div>
React為動畫提供一個ReactTransitonGroup插件組件作為一個底層的API,一個ReactCSSTransitionGroup來簡單地實現基本的CSS動畫和過渡。</div>
<br />
**高級API:ReactCSSTransitionGroup**
<div>
ReactCSSTransitionGroup是基於ReactTransitionGroup的,在React組件進入或者離開DOM的時候,它是一種簡單地執行CSS過渡和動畫的方式。這個的靈感來自於優秀的<em>ng-animate庫</em>。<br />
ReactCSSTransitionGroup是ReactTransitions的接口。這是一個簡單的元素,包含了所有你對其動畫感興趣的組件。這里是一個例子,例子中我們讓列表項淡入淡出。
<pre>
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

var TodoList = React.createClass({
  getInitialState: function() {
    return {items: ['hello', 'world', 'click', 'me']};
  },
  handleAdd: function() {
    var newItems =
      this.state.items.concat([prompt('Enter some text')]);
    this.setState({items: newItems});
  },
  handleRemove: function(i) {
    var newItems = this.state.items;
    newItems.splice(i, 1);
    this.setState({items: newItems});
  },
  render: function() {
    var items = this.state.items.map(function(item, i) {
      return (
        <div key={item} onClick={this.handleRemove.bind(this, i)}>
          {item}
        </div>
      );
    }.bind(this));
    return (
      <div>
        <button onClick={this.handleAdd}>Add Item</button>
        <ReactCSSTransitionGroup transitionName="example">
          {items}
        </ReactCSSTransitionGroup>
      </div>
    );
  }
});
</pre>
<div>在這個組件當中,當一個新的項被添加到ReactCSSTransitionGroup,它將會被添加example-enter類,然后在下一時刻被添加example-enter-active CSS類。這是一個基於<em>transitionName prop的約定</em>。</div><br />
<div>為了能夠給它的子級應用過渡效果,ReactCSSTransitionGroup必須已經掛載到了DOM。下面的例子不會生效,因為ReactCSSTransitionGroup被掛載到新項,而不是新項被掛載到ReactCSSTransitionGroup里。將這個與上面的快速開始部分比較一下,看看有什么差異。
<pre>
render: function() {
    var items = this.state.items.map(function(item, i) {
      return (
        <div key={item} onClick={this.handleRemove.bind(this, i)}>
          <ReactCSSTransitionGroup transitionName="example">
            {item}
          </ReactCSSTransitionGroup>
        </div>
      );
    }, this);
    return (
      <div>
        <button onClick={this.handleAdd}>Add Item</button>
        {items}
      </div>
    );
  }
</pre>
</div>
</div>
<br />
**禁用動畫**
<div>
如果你想,你可以禁用入場或者出場動畫。例如,有些時候,你可能想要一個入場動畫,不要出場動畫,但是ReactCSSTransitionGroup會在移除DOM節點之前等待一個動畫完成。你可以給ReactCSSTransitionGroup添加transitionEnter={false}或者transitionLeave={false} props來禁用這些動畫。<br />
當使用ReactCSSTransitionGroup的時候,沒有辦法通知你在過渡效果結束或者在執行動畫的時候做一些復雜的運算。如果你想要更多細粒度的控制,你可以使用底層的ReactTransitionGroup API,該API提供了你自定義過渡效果所需要的函數。
</div>
<br />
**ReactTransitionGroup生命周期**
<div>ReactTransitionGroup是動畫的基礎。它可以通過<em>React.addons.TransitionGroup得到</em>。當子級被添加或者從其中移除(就像上面的例子)的時候,特殊的生命周期函數就會在它們上面被調用。</div>
<li>
<pre>
componentWillEnter(callback)
在組件被添加到已有的TransitionGroup中的時候,該函數和componentDidMount()被同時調用。這將會阻塞其它動畫觸發,直到callback被調用。該函數不會在TransitionGroup初始化渲染的時候調用。
</pre>
</li>
<li>
<pre>
componentDidEnter()
該函數在傳給componentWillEnter的callback函數被調用之后調用。
</pre>
</li>
<li>
<pre>
componentWillLeave(callback)
該函數在子級從ReactTransitionGroup中移除的時候調用。雖然子級被移除了,ReactTransitionGroup將會使它繼續在DOM中,直到callback被調用。
</pre>
</li>
<li>
<pre>
componentDidLeave()
該函數在willLeave callback被調用的時候調用(與componentWillUnmount是同一時間)。
</pre>
</li>

<div class='cw-exe'>
同學們可以利用本節的動畫內容,做一個簡單的圖片輪播demo。
</div>
</p>

<hr>
~@ $v.event.type == "scene.init"

var html = function(){/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
    <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
    <script src="http://static.runoob.com/assets/react/browser.min.js"></script>
  </head>
  <body>
    <script type="text/babel">
      var ReactCSSTransitionGroup = require('react-addons-css-transition-group');
    var React = require('react');
    var ReactDOM = require('react-dom');
    var data = ['one','two','three'];
    var Control = React.createClass({
        getInitialState:function(){
            return {
                'items':this.props.data
            }
        },
        addItem:function(){
            var newItems = this.state.items.concat('four');
            this.setState({
                'items':newItems
            });
        },
        removeItem:function(i){
            var newItems = this.state.items;
            newItems.splice(i,1);
            this.setState({
                'items':newItems
            });
        },
        render:function(){
            var $this = this;
            var List = this.state.items.map(function(value,index){
                return <div key={value} onClick = {$this.removeItem.bind($this,index)}> { value}</div>
            });
            return (
                <div>
                    <button onClick={this.addItem}>add Item</button>
                    <ReactCSSTransitionGroup
                        transitionName='example'
                        transitionEnterTimeout={500}
                        transitionLeaveTimeout={300}>
                        {List}
                    </ReactCSSTransitionGroup>
                </div>
            )
        }
    });
    ReactDOM.render(<Control data={data}/> ,document.getElementById('content'));
    </script>
  </body>
</html>
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@@性能優化
###性能優化
<p>
<div>當大家考慮在項目中使用 React 的時候,第一個問題往往是他們的應用的速度和響應是否能和非 React 版一樣,每當狀態改變的時候就重新渲染組件的整個子樹,讓大家懷疑這會不會對性能造成負面影響。React 用了一些黑科技來<em>減少 UI </em>更新需要的花費較大的 DOM 操作。</div>
<br />
**使用 production 版本**
<div>
如果你在你的 React app 中進行性能測試或在尋找性能問題,一定要確定你在使用 <em>minified production build</em>。開發者版本包括額外的警告信息,這對你在開發你的 app 的時候很有用,但是因為要進行額外的處理,所以它也會比較慢。
</div>
<br />
**避免更新 DOM**
<div>
React 使用虛擬 DOM,它是在瀏覽器中的 DOM 子樹的渲染描述,這個平行的描述讓 React 避免創建和操作 DOM 節點,這些遠比操作一個 JavaScript 對象慢。當一個組件的 props 或 state 改變,React 會構造一個新的虛擬 DOM 和舊的進行對比來決定真實 DOM 更新的必要性,只有在它們不相等的時候,React 才會使用盡量少的改動更新 DOM。
<br />
在此之上,React 提供了生命周期函數 shouldComponentUpdate,在重新渲染機制回路(虛擬 DOM 對比和 DOM 更新)之前會被觸發,賦予開發者跳過這個過程的能力。這個函數默認返回 true,讓 React 執行更新。
<pre>
shouldComponentUpdate: function(nextProps, nextState) {
  return true;
}
</pre>
一定要記住,React 會非常頻繁的調用這個函數,所以要確保它的執行速度夠快。
假如你有個帶有多個對話的消息應用,如果只有一個對話發生改變,如果我們在 ChatThread 組件執行 shouldComponentUpdate,React 可以跳過其他對話的重新渲染步驟。
<pre>
shouldComponentUpdate: function(nextProps, nextState) {
  // TODO: return whether or not current chat thread is
  // different to former one.
}
</pre>
因此,總的說,React 通過讓用戶使用 <em>shouldComponentUpdate 減短重新渲染回路</em>,避免進行昂貴的更新 DOM 子樹的操作,而且這些必要的更新,需要對比虛擬 DOM。
</div>
<br />
**Immutable-js 來救贖**
<div>
Immutable-js 是 Lee Byron 寫的 JavaScript 集合類型的庫,最近被 Facebook 開源,它通過結構共享提供不可變持久化集合類型。一起看下這些特性的含義:
<li>Immutable: 一旦創建,集合就不能再改變;</li>
<li>Persistent: 新的集合類型可以通過之前的集合創建,比如 set 產生改變的集合。創建新的集合之后源集合仍然有效;</li>
<li>Structural Sharing: 新的集合會使用盡量多的源集合的結構,減少復制來節省空間和性能友好。如果新的集合和源集合相等,一般會返回源結構;</li>
不可變讓跟蹤改變非常簡單,每次改變都是產生新的對象,所以我們僅需要對象的引用是否改變,比如這段簡單的 JavaScript 代碼:
<pre>
var x = { foo: "bar" };
var y = x;
y.foo = "baz";
x === y; // true
</pre>
盡管 y 被改變,因為它和 x 引用的是同一個對象,這個對比返回 true。然而,這個代碼可以使用 immutable-js 改寫如下:
<pre>
var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'bar'  });
var y = x.set('foo', 'baz');
x === y; // false
</pre>

這個例子中,因為改變 x 的時候返回了新的引用,我們就可以安全的認為 x 已經改變。
<br />
臟檢測可以作為另外的可行的方式追蹤改變,給 setters 一個標示。這個方法的問題是,它強制你使用 setters,而且要寫很多額外的代碼,影響你的類。或者你可以在改變之前深拷貝對象,然后進行深對比來確定是不是發生了改變。這個方法的問題是,深拷貝和深對比都是很花性能的操作。
<br />
因此,不可變數據結構給你提供了一個高效、簡潔的方式來跟蹤對象的改變,而<em>跟蹤改變</em>是實現 shouldComponentUpdate 的關鍵。所以,如果我們使用 immutable-js 提供的抽象創建 props 和 state 模型,我們就可以使用 PureRenderMixin,而且能夠獲得很好的性能增強。
</div>
<br />
**練習**
對比一下Immutable-js 和 Flux,看看它們之間有什么異同點?
</p>

~@ $v.event.type == "scene.init"

var html = function(){/*
*/}.tpl();

var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";

$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();

@@@第四章 Redux
@@@@Redux介紹
###Redux介紹
<p>
<img src="img/img13.png" height="300" width="600">
<br />
**為什么使用Redux**
<div>
首先明確一點,Redux 是一個有用的架構,但不是非用不可。事實上,大多數情況,你可以不用它,只用 React 就夠了。當滿足下面這些情況的時候,你可以考慮使用Redux:
<li>用戶的使用方式復雜;</li>
<li>不同身份的用戶有不同的使用方式(比如普通用戶和管理員);</li>
<li>多個用戶之間可以協作;</li>
<li>與服務器大量交互,或者使用了WebSocket;</li>
<li>View要從多個來源獲取數據;</li>
上面這些情況才是 Redux 的適用場景:多交互、多數據源。<br />
從組件角度看,如果你的應用有以下場景,可以考慮使用 Redux:
<li>某個組件的狀態,需要共享;</li>
<li>某個狀態需要在任何地方都可以拿到;</li>
<li>一個組件需要改變全局狀態;</li>
<li>一個組件需要改變另一個組件的狀態;</li>
發生上面情況時,如果不使用 Redux 或者其他狀態管理工具,不按照一定規律處理狀態的讀寫,代碼很快就會變成一團亂麻。你需要一種機制,可以在同一個地方查詢狀態、改變狀態、傳播狀態的變化。<br />
總之,<em>不要把 Redux 當作萬靈丹,如果你的應用沒那么復雜,就沒必要用它</em>。另一方面,Redux 只是 Web 架構的一種解決方案,也可以選擇其他方案。
</div>
<br />
**設計思想**
<br />
<div>
<li>(1)Web 應用是一個狀態機,視圖與狀態是一一對應的;</li>
<li>(2)所有的狀態,保存在一個對象里面;</li>
</div>

<br />
**練習**
<div>有興趣的同學,可以查閱更多的資料,深入理解Redux的設計思想。</div>
</p>

@@@@使用Redux管理視圖模型
###使用Redux管理視圖模型
<p>
<img src="img/four.png" height="300" width="600">
**Store**
<div>
Store 就是保存數據的地方,你可以把它看成一個容器。整個應用只能有一個 Store。<br>
Redux 提供<em>createStore</em>這個函數,用來生成 Store。
</div>
<pre>
import { createStore } from 'redux';
const store = createStore(fn);
</pre>
<div>
上面代碼中,createStore函數接受另一個函數作為參數,返回新生成的 Store 對象。
</div>
<br />

**State**
<br />
<div>
Store對象包含所有數據。如果想得到某個時點的數據,就要對 Store 生成快照。這種時點的數據集合,就叫做 State。
當前時刻的 State,可以通過<em>store.getState()</em>拿到。
</div>
<pre>
import { createStore } from 'redux';
const store = createStore(fn);

const state = store.getState();
</pre>
<div>
Redux 規定, 一個 State 對應一個 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么樣,反之亦然。
</div>
<br />

**Action**
<div>
State 的變化,會導致 View 的變化。但是,用戶接觸不到 State,只能接觸到 View。所以,State 的變化必須是 View 導致的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。<br />
Action 是一個對象。其中的type屬性是必須的,表示 Action 的名稱。其他屬性可以自由設置,社區有一個規范可以參考。
</div>
<pre>
const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};
</pre>
<div>
上面代碼中,Action 的名稱是ADD_TODO,它攜帶的信息是字符串Learn Redux。<br />
可以這樣理解,Action 描述當前發生的事情。改變 State 的唯一辦法,就是使用 Action。它會運送數據到 Store。
</div>
<br />
**store.dispatch()**
<div>
store.dispatch()是 View 發出 Action 的唯一方法。
</div>
<pre>
import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});
</pre>
上面代碼中,store.dispatch接受一個 Action 對象作為參數,將它發送出去。
結合 Action Creator,這段代碼可以改寫如下:
<pre>
store.dispatch(addTodo('Learn Redux'));
</pre>
<br />
**store.subscribe()**
<div>
Store 允許使用store.subscribe方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。
</div>
<pre>
import { createStore } from 'redux';
const store = createStore(reducer);

store.subscribe(listener);
</pre>
顯然,只要把 View 的更新函數(對於 React 項目,就是組件的render方法或setState方法)放入listen,就會實現 View 的自動渲染。<br />
store.subscribe方法返回一個函數,調用這個函數就可以解除監聽:
<pre>
let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();
</pre>
<br/>
**Store 的實現**
<div>
上一節介紹了 Redux 涉及的基本概念,可以發現 Store 提供了三個方法:
<li><code>store.getState()</code></li>
<li><code>store.dispatch()</code></li>
<li><code>store.subscribe()</code></li>
</div>
<pre>
import { createStore } from 'redux';
let { subscribe, dispatch, getState } = createStore(reducer);
</pre>
createStore方法還可以接受第二個參數,表示 State 的最初狀態。這通常是服務器給出的。
<pre>
let store = createStore(todoApp, window.STATE_FROM_SERVER)
</pre>
上面代碼中,<em>window.STATE_FROM_SERVER</em>就是整個應用的狀態初始值。注意,如果提供了這個參數,它會覆蓋 Reducer 函數的默認初始值。<br />
下面是createStore方法的一個簡單實現,可以了解一下 Store 是怎么生成的。
<pre>
const createStore = (reducer) => {
  let state;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    }
  };

  dispatch({});

  return { getState, dispatch, subscribe };
};
</pre>
<br />
**練習**
<div>
同學們可以利用上面講解到Redux知識點,寫一個HelloWorld,從加強理解Action、State、Store概念。
</div>
<br />
</p>

@@@@Reducer 的拆分
###Reducer 的拆分
<p>
<img src="img/five.jpg" height="300" width="600">
<br />
Reducer 函數負責生成 State。由於整個應用只有一個 State 對象,包含所有數據,對於大型應用來說,這個 State 必然十分龐大,導致 Reducer 函數也十分龐大,請看下面的例子。
<pre>
const chatReducer = (state = defaultState, action = {}) => {
  const { type, payload } = action;
  switch (type) {
    case ADD_CHAT:
      return Object.assign({}, state, {
        chatLog: state.chatLog.concat(payload)
      });
    case CHANGE_STATUS:
      return Object.assign({}, state, {
        statusMessage: payload
      });
    case CHANGE_USERNAME:
      return Object.assign({}, state, {
        userName: payload
      });
    default: return state;
  }
};
</pre>
上面代碼中,三種 Action 分別改變 State 的三個屬性。
<li><em>ADD_CHAT:chatLog屬性</em></li>
<li><em>CHANGE_STATUS:statusMessage屬性</em></li>
<li><em>CHANGE_USERNAME:userName屬性</em></li>
這三個屬性之間沒有聯系,這提示我們可以把 Reducer 函數拆分。不同的函數負責處理不同屬性,最終把它們合並成一個大的 Reducer 即可。
<pre>
const chatReducer = (state = defaultState, action = {}) => {
  return {
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action)
  }
};
</pre>
上面代碼中,Reducer 函數被拆成了三個小函數,每一個負責生成對應的屬性,這樣一拆,Reducer 就易讀易寫多了。而且,這種拆分與 React 應用的結構相吻合:一個 React 根組件由很多子組件構成。這就是說,子組件與子 Reducer 完全可以對應,Redux 提供了一個<em>combineReducers</em>方法,用於 Reducer 的拆分。你只要定義各個子 Reducer 函數,然后用這個方法,將它們合成一個大的 Reducer。
<pre>
import { combineReducers } from 'redux';

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

export default todoApp;
</pre>
上面的代碼通過combineReducers方法將三個子 Reducer 合並成一個大的函數。
這種寫法有一個前提,就是 State 的屬性名必須與子 Reducer 同名。如果不同名,就要采用下面的寫法。
<pre>
const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})

// 等同於
function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}
</pre>
總之,<em>combineReducers()</em>做的就是產生一個整體的 Reducer 函數。該函數根據 <em>State 的 key</em> 去執行相應的子 Reducer,並將返回結果合並成一個大的 State 對象,下面是combineReducer的簡單實現。
<pre>
const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](state[key], action);
        return nextState;
      },
      {}
    );
  };
};
</pre>
<div>
你可以把所有子 Reducer 放在一個文件里面,然后統一引入。
</div>
<br />
**練習**
<br />
<div>
同學們可以試着做一個計算器,完整的示例
<a href="https://github.com/reactjs/redux/blob/master/examples/counter/public/index.html">代碼</a>。
</div>
</p>

@@@@Redux的React綁定(一)
###Redux的React綁定(一)
<p>
<img src="img/img12.jpg" height="300" width="600">
**UI 組件**
<div>
React-Redux 將所有組件分成兩大類:UI 組件(presentational component)和容器組件(container component)。
UI 組件有以下幾個特征:
<li>只負責 UI 的呈現,不帶有任何業務邏輯;</li>
<li>沒有狀態(即不使用this.state這個變量);</li>
<li>所有數據都由參數(this.props)提供;</li>
<li>不使用任何 Redux 的 API;</li>
下面就是一個 UI 組件的例子。
<pre>
const Title =
  value => <h1>{value}</h1>;
</pre>
因為不含有狀態,<em>UI 組件又稱為"純組件"</em>,即它純函數一樣,純粹由參數決定它的值。
</div>
<br />
**容器組件**
<div>
容器組件的特征恰恰相反:
<li>負責管理數據和業務邏輯,不負責 UI 的呈現;</li>
<li>帶有內部狀態;</li>
<li>使用 Redux 的 API;</li>
總之,只要記住一句話就可以了:<em>UI 組件負責 UI 的呈現,容器組件負責管理數據和邏輯</em>。<br />
你可能會問,如果一個組件既有 UI 又有業務邏輯,那怎么辦?回答是,將它拆分成下面的結構:外面是一個容器組件,里面包了一個UI 組件。前者負責與外部的通信,將數據傳給后者,由后者渲染出視圖。<br />
React-Redux 規定,所有的 <em>UI 組件都由用戶提供</em>,容器組件則是由 React-Redux 自動生成。也就是說,用戶負責視覺層,狀態管理則是全部交給它。
</div>
<br />
**mapStateToProps()**
<div>
mapStateToProps是一個函數。它的作用就是像它的名字那樣,建立一個從(外部的)state對象到(UI 組件的)props對象的映射關系,作為函數,mapStateToProps執行后應該返回一個對象,里面的每一個鍵值對就是一個映射。請看下面的例子。
<pre>
const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}</pre>
上面代碼中,mapStateToProps是一個函數,它接受state作為參數,返回一個對象。這個對象有一個todos屬性,代表 UI 組件的同名參數,后面的getVisibleTodos也是一個函數,可以從state算出 todos 的值,下面就是getVisibleTodos的一個例子,用來算出todos。
<pre>
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
    default:
      throw new Error('Unknown filter: ' + filter)
  }
}
</pre>
<em>mapStateToProps會訂閱 Store</em>,每當state更新的時候,就會自動執行,重新計算 UI 組件的參數,從而觸發 UI 組件的重新渲染,mapStateToProps的第一個參數總是state對象,還可以使用第二個參數,代表容器組件的props對象。
<pre>
// 容器組件的代碼
//    <FilterLink filter="SHOW_ALL">
//      All
//    </FilterLink>

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}
</pre>
使用ownProps作為參數后,如果容器組件的參數發生變化,也會引發 UI 組件重新渲染。<br />
connect方法可以省略mapStateToProps參數,那樣的話,UI 組件就不會訂閱Store,就是說 Store 的更新不會引起 UI 組件的更新。
</div>
</p>

@@@@Redux的React綁定(二)
###Redux的React綁定(二)
<p>
**mapDispatchToProps()**
<div>
mapDispatchToProps是connect函數的第二個參數,用來建立 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操作應該當作 Action,傳給 Store。它可以是一個函數,也可以是一個對象。<br />
如果mapDispatchToProps是一個函數,會得到dispatch和ownProps(容器組件的props對象)兩個參數。
<pre>
const mapDispatchToProps = (
  dispatch,
  ownProps
) => {
  return {
    onClick: () => {
      dispatch({
        type: 'SET_VISIBILITY_FILTER',
        filter: ownProps.filter
      });
    }
  };
}
</pre>
從上面代碼可以看到,mapDispatchToProps作為函數,應該返回一個對象,該對象的每個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。<br />
如果mapDispatchToProps是一個對象,它的每個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被當作 Action creator ,返回的 Action 會由 Redux 自動發出。舉例來說,上面的mapDispatchToProps寫成對象就是下面這樣。
<pre>
const mapDispatchToProps = {
  onClick: (filter) => {
    type: 'SET_VISIBILITY_FILTER',
    filter: filter
  };
}
</pre>
</div>
<br />
**connect()**
<div>
React-Redux 提供connect方法,用於從 UI 組件生成容器組件。<em>connect的意思,就是將這兩種組件連起來</em>。
<pre>
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
</pre>
上面代碼中,TodoList是 UI 組件,<em>VisibleTodoList</em>就是由 React-Redux 通過connect方法自動生成的容器組件。
但是,因為沒有定義業務邏輯,上面這個容器組件毫無意義,只是 UI 組件的一個單純的包裝層。為了定義業務邏輯,需要給出下面兩方面的信息。
<li>(1)輸入邏輯:外部的數據(即state對象)如何轉換為 UI 組件的參數;</li>
<li>(2)輸出邏輯:用戶發出的動作如何變為 Action 對象,從 UI 組件傳出去;</li>
因此,connect方法的完整 API 如下。
<pre>
import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)
</pre>
上面代碼中,connect方法接受兩個參數:<em>mapStateToProps</em>和<em>mapDispatchToProps</em>。它們定義了 UI 組件的業務邏輯。前者負責輸入邏輯,即將state映射到 UI 組件的參數(props),后者負責輸出邏輯,即將用戶對 UI 組件的操作映射成 Action。
</div>
<br />
**Provider組件**
<div>
connect方法生成容器組件以后,需要讓容器組件拿到state對象,才能生成 UI 組件的參數。
一種解決方法是將state對象作為參數,傳入容器組件。但是,這樣做比較麻煩,尤其是容器組件可能在很深的層級,一級級將state傳下去就很麻煩。<br />
React-Redux 提供Provider組件,可以讓容器組件拿到state。
<pre>
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
</pre>
上面代碼中,Provider在根組件外面包了一層,這樣一來,App的所有子組件就默認都可以拿到state了。
它的原理是React組件的context屬性,請看源碼。
<pre>
class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object
}
</pre>
上面代碼中,store放在了上下文對象context上面。然后,子組件就可以從<em>context拿到store</em>,代碼大致如下。
<pre>
class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();
    // ...
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object
}
</pre>
React-Redux自動生成的容器組件的代碼,就類似上面這樣,從而拿到store。
</div>
<br />

**練習**
<div>
同學們學到這里,可以試着寫一個登錄頁面demo,要求有登錄相關的驗證流程。
</div>
</p>

@@@@Redux中間件介紹
###Redux中間件介紹
<p>
<img src="img/img15.jpg" height="300" width="600">
**為什么要使用中間件**
<div>
一個關鍵問題沒有解決:異步操作怎么辦?Action 發出以后,Reducer 立即算出 State,這叫做同步;Action 發出以后,過一段時間再執行 Reducer,這就是異步。<br />
怎么才能 Reducer 在異步操作結束后自動執行呢?這就要用到新的工具:中間件(middleware)。
</div>
<br />
**中間件的概念**
<br />
<div>
為了理解中間件,讓我們站在框架作者的角度思考問題:如果要添加功能,你會在哪個環節添加?
<li>Reducer:純函數,只承擔計算 State 的功能,不合適承擔其他功能,也承擔不了,因為理論上,純函數不能進行讀寫操作;
</li>
<li>View:與 State 一一對應,可以看作 State 的視覺層,也不合適承擔其他功能;</li>
<li>Action:存放數據的對象,即消息的載體,只能被別人操作,自己不能進行任何操作;</li>
想來想去,只有發送 Action 的這個步驟,即store.dispatch()方法,可以添加功能。舉例來說,要添加日志功能,把 Action 和 State 打印出來,可以對store.dispatch進行如下改造。
<pre>
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}
</pre>
上面代碼中,對store.dispatch進行了重定義,在發送 Action 前后添加了打印功能。這就是中間件的雛形。
中間件就是一個函數,對store.dispatch方法進行了改造,在發出 Action 和執行 Reducer 這兩步之間,添加了其他功能。
</div>
<br />
**中間件的用法**
<br />
<div>
本教程不涉及如何編寫中間件,因為常用的中間件都有現成的,只要引用別人寫好的模塊即可。比如,上一節的日志中間件,就有現成的<em>redux-logger模塊</em>。這里只介紹怎么使用中間件。
<pre>
import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);
</pre>
上面代碼中,redux-logger提供一個生成器<em>createLogger</em>,可以生成日志中間件logger。然后,將它放在<em>applyMiddleware</em>方法之中,傳入createStore方法,就完成了store.dispatch()的功能增強。
這里有兩點需要注意:
<li>createStore方法可以接受整個應用的初始狀態作為參數,那樣的話,applyMiddleware就是第三個參數了。
<pre>
const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(logger)
);</pre></li>
<li>中間件的次序有講究。
<pre>
const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);</pre></li>
上面代碼中,applyMiddleware方法的三個參數,就是三個中間件。有的中間件有次序要求,使用前要查一下文檔。比如,logger就一定要放在最后,否則輸出結果會不正確。
</div>
</p>

@@@@Redux中間件高級
###Redux中間件高級
<p>
**applyMiddlewares()**
<div>
看到這里,你可能會問,applyMiddlewares這個方法到底是干什么的?
它是 Redux 的原生方法,作用是將所有中間件組成一個數組,依次執行。下面是它的源碼。
<pre>
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}
</pre>
上面代碼中,所有中間件被放進了一個數組chain,然后嵌套執行,最后執行store.dispatch。可以看到,中間件內部(middlewareAPI)可以拿到getState和dispatch這兩個方法。
</div>
<br />
**redux-thunk 中間件**
<br />
<div>
異步操作至少要送出兩個 Action:用戶觸發第一個 Action,這個跟同步操作一樣,沒有問題;如何才能在操作結束時,系統自動送出第二個 Action 呢?奧妙就在 Action Creator 之中。
<pre>
class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    dispatch(fetchPosts(selectedPost))
  }

// ...
</pre>
上面代碼是一個異步組件的例子。加載成功后(componentDidMount方法),它送出了(dispatch方法)一個 Action,向服務器要求數據 <em>fetchPosts(selectedSubreddit)</em>。這里的fetchPosts就是 Action Creator。
下面就是fetchPosts的代碼,關鍵之處就在里面。
<pre>
const fetchPosts = postTitle => (dispatch, getState) => {
  dispatch(requestPosts(postTitle));
  return fetch(`/some/API/${postTitle}.json`)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(postTitle, json)));
  };
};

// 使用方法一
store.dispatch(fetchPosts('reactjs'));
// 使用方法二
store.dispatch(fetchPosts('reactjs')).then(() =>
  console.log(store.getState())
);
</pre>
上面代碼中,<em>fetchPosts是一個Action Creator(動作生成器)</em>,返回一個函數。這個函數執行后,先發出一個Action(requestPosts(postTitle)),然后進行異步操作。拿到結果后,先將結果轉成 JSON 格式,然后再發出一個 Action( receivePosts(postTitle, json))。
上面代碼中,有幾個地方需要注意。
<li>fetchPosts返回了一個函數,而普通的 Action Creator 默認返回一個對象;</li>
<li>返回的函數的參數是<em>dispatch和getState</em>這兩個 Redux 方法,普通的 Action Creator 的參數是 Action 的內容;</li>
<li>在返回的函數之中,先發出一個 <em>Action(requestPosts(postTitle))</em>,表示操作開始;</li>
<li>異步操作結束之后,再發出一個 <em>Action(receivePosts(postTitle, json))</em>,表示操作結束;</li>
這樣的處理,就解決了自動發送第二個 Action 的問題。但是,又帶來了一個新的問題,Action 是由store.dispatch方法發送的。而store.dispatch方法正常情況下,參數只能是對象,不能是函數,這時,就要使用中間件redux-thunk。
<pre>
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

// Note: this API requires redux@>=3.1.0
const store = createStore(
  reducer,
  applyMiddleware(thunk)
);
</pre>
上面代碼使用<em>redux-thunk中間件</em>,改造store.dispatch,使得后者可以接受函數作為參數。因此,異步操作的第一種解決方案就是,寫出一個返回函數的 Action Creator,然后使用redux-thunk中間件改造store.dispatch。
</div>
<br/>
**練習**
<br />
<div>
同學們可以利用這一節學習的知識,模擬一下http異步請求,更新UI界面。
</div>
</p>

@@@第五章 Router
@@@@使用Router管理多組件
###React Router安裝
<p>
<img src="img/img16.jpg" height="300" width="600">
<div>
本章開始介紹 React 體系的一個重要部分:<em>路由庫React-Router</em>。它是官方維護的,事實上也是唯一可選的路由庫。它通過管理 URL,實現組件的切換和狀態的變化,開發復雜的應用幾乎肯定會用到。
<br />
**基本用法**
<br />
<div>
React Router 安裝命令如下。
</div>
<pre>
$ npm install -S react-router
</pre>
使用時,路由器Router就是React的一個組件。
<pre>
import { Router } from 'react-router';
render(<Router/>, document.getElementById('app'));
</pre>
Router組件本身只是一個容器,真正的路由要通過Route組件定義。
<pre>
import { Router, Route, hashHistory } from 'react-router';

render((
  <Router history={hashHistory}>
    <Route path="/" component={App}/>
  </Router>
), document.getElementById('app'));
</pre>
上面代碼中,用戶訪問根路由/(比如<pre>http://www.example.com/</pre>),組件APP就會加載到document.getElementById('app')。
你可能還注意到,Router組件有一個參數history,它的值hashHistory表示,路由的切換由URL的hash變化決定,即URL的#部分發生變化。舉例來說,用戶訪問<pre>http://www.example.com/</pre>,實際會看到的是<pre>http://www.example.com/#/</pre>。
<em>Route組件定義了URL路徑與組件的對應關系</em>。你可以同時使用多個Route組件。
<pre>
<Router history={hashHistory}>
  <Route path="/" component={App}/>
  <Route path="/repos" component={Repos}/>
  <Route path="/about" component={About}/>
</Router>
</pre>
上面代碼中,用戶訪問/repos(比如<pre>http://localhost:8080/#/repos</pre>)時,加載Repos組件;訪問/about(<pre>http://localhost:8080/#/about</pre>)時,加載About組件。
</div>
<br />
</p>

@@@@React Router屬性
###React Router屬性
<p>
**Link**
<br />
<div>
Link組件用於取代<code><a></code>元素,生成一個鏈接,允許用戶點擊后跳轉到另一個路由。它基本上就是<code><a></code>元素的React 版本,可以接收Router的狀態。
<pre>
render() {
  return <div>
    <ul role="nav">
      <li><Link to="/about">About</Link></li>
      <li><Link to="/repos">Repos</Link></li>
    </ul>
  </div>
}
</pre>
如果希望當前的路由與其他路由有不同樣式,這時可以使用Link組件的activeStyle屬性。
<pre>
<Link to="/about" activeStyle={{color: 'red'}}>About</Link>
<Link to="/repos" activeStyle={{color: 'red'}}>Repos</Link>
</pre>
上面代碼中,當前頁面的鏈接會紅色顯示。
另一種做法是,使用activeClassName指定當前路由的Class。
<pre>
<Link to="/about" activeClassName="active">About</Link>
<Link to="/repos" activeClassName="active">Repos</Link>
</pre>
上面代碼中,當前頁面的鏈接的<em>class會包含active</em>。
在Router組件之外,導航到路由頁面,可以使用瀏覽器的History API,像下面這樣寫。
<pre>
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
</pre>
</div>
<br />
**path 屬性**
<br />
<div>
Route組件的path屬性指定路由的匹配規則。這個屬性是可以省略的,這樣的話,不管路徑是否匹配,總是會加載指定組件。
請看下面的例子。
<pre>
<Route path="inbox" component={Inbox}>
   <Route path="messages/:id" component={Message} />
</Route>
</pre>
上面代碼中,當用戶訪問<em><code>/inbox/messages/:id</code></em>時,會加載下面的組件。
<pre>
<Inbox>
  <Message/>
</Inbox>
</pre>
如果省略外層Route的path參數,寫成下面的樣子。
<pre>
<Route component={Inbox}>
  <Route path="inbox/messages/:id" component={Message} />
</Route>
</pre>
現在用戶訪問/inbox/messages/:id時,組件加載還是原來的樣子。
<pre>
<Inbox>
  <Message/>
</Inbox>
</pre>
</div>

<br />
**練習**
<br />
<div>
同學可以自己找一找<em>Restful Api</em>的文檔看看,結合兩者知識再理解一下<em>Link/to</em>屬性,path屬性,下一節我們講解的History屬性多多少少也是有聯系的。
</div>
</p>

@@@@路由器歷史策略
###路由器歷史策略
<p>
<img src="img/img18.jpg" height="300" width="600">
<div>詳細大家對於瀏覽器的歷史記錄都非常的熟悉吧,其實呢?Router里面的歷史記錄跟這個概念是差不多的。</div>
<br />
**histroy 屬性**
<br />
<div>
Router組件的history屬性,用來監聽瀏覽器地址欄的變化,並將URL解析成一個地址對象,供 React Router 匹配。
history屬性,一共可以設置三種值:
<li><em>browserHistory</em></li>
<li>hashHistory</li>
<li>createMemoryHistory</li>
如果設為hashHistory,路由將通過URL的hash部分(#)切換,URL的形式類似example.com/#/some/path。
<pre>
import { hashHistory } from 'react-router'

render(
  <Router history={hashHistory} routes={routes} />,
  document.getElementById('app')
)
</pre>
如果設為browserHistory,瀏覽器的路由就不再通過Hash完成了,而顯示正常的路徑<code>example.com/some/path</code>,背后調用的是瀏覽器的History API。
<pre>
import { browserHistory } from 'react-router'

render(
  <Router history={browserHistory} routes={routes} />,
  document.getElementById('app')
)
</pre>
但是,這種情況需要對服務器改造。否則用戶直接向服務器請求某個子路由,會顯示網頁找不到的404錯誤。
如果開發服務器使用的是<em>webpack-dev-server</em>,加上--history-api-fallback參數就可以了。
<br />
$ webpack-dev-server --inline --content-base . --history-api-fallback
createMemoryHistory主要用於服務器渲染。它創建一個內存中的history對象,不與瀏覽器URL互動。
<pre>
const history = createMemoryHistory(location)</pre>
</div>
<br />
**練習**
<br />
<div>
這節的知識點比較少,同學們可以再復習一下上面提到的三種歷史記錄的異同點。
</div>
</p>

@@@@路由匹配
###路由匹配
<p>
**通配符**
<br />
<div>
path屬性可以使用通配符。
<pre>
<Route path="/hello/:name">
// 匹配 /hello/michael
// 匹配 /hello/ryan

<Route path="/hello(/:name)">
// 匹配 /hello
// 匹配 /hello/michael
// 匹配 /hello/ryan

<Route path="/files/*.*">
// 匹配 /files/hello.jpg
// 匹配 /files/hello.html

<Route path="/files/*">
// 匹配 /files/
// 匹配 /files/a
// 匹配 /files/a/b

<Route path="/**/*.jpg">
// 匹配 /files/hello.jpg
// 匹配 /files/path/to/file.jpg
</pre>
通配符的規則如下:
<li>:paramName
:paramName匹配URL的一個部分,直到遇到下一個/、?、#為止。這個路徑參數可以通過this.props.params.paramName取出;</li>
<li>()
()表示URL的這個部分是可選的;</li>
<li>*
*匹配任意字符,直到模式里面的下一個字符為止。匹配方式是非貪婪模式;</li>
<li>**
** 匹配任意字符,直到下一個/、?、#為止。匹配方式是貪婪模式;</li>
path屬性也可以使用相對路徑(不以/開頭),匹配時就會相對於父組件的路徑,可以參考上一節的例子。嵌套路由如果想擺脫這個規則,可以使用絕對路由。路由匹配規則是從上到下執行,一旦發現匹配,就不再其余的規則了。
<pre>
<Route path="/comments" ... />
<Route path="/comments" ... />
</pre>
上面代碼中,路徑/comments同時匹配兩個規則,第二個規則不會生效。
設置路徑參數時,需要特別小心這一點。
<pre>
<Router>
  <Route path="/:userName/:id" component={UserPage}/>
  <Route path="/about/me" component={About}/>
</Router>
</pre>
上面代碼中,用戶訪問/about/me時,不會觸發第二個路由規則,因為它會匹配/:userName/:id這個規則。因此,帶參數的路徑一般要寫在路由規則的底部。
此外,URL的查詢字符串/foo?bar=baz,可以用<em>this.props.location.query.bar</em>獲取。
</div>
<br />

**練習**
<br />
<div>
同學可以自己找一些關於正則表達式的文檔看看,結合本節的知識相信,理解起來更加容易些。
</div>
</p>

@@@@服務器渲染
###服務器渲染
<p>
<img src="img/img19.jpg" height="300" width="600">
<div>
想讓搜索引擎抓取到你的站點,服務端渲染這一步不可或缺,服務端渲染還可以提升站點的性能,因為在加載JavaScript腳本的同時,瀏覽器就可以進行頁面渲染。<br />
React的虛擬DOM是其可被用於服務端渲染的關鍵。首先每個ReactComponent 在虛擬DOM中完成渲染,然后React通過虛擬DOM來更新瀏覽器DOM中產生變化的那一部分,虛擬DOM作為內存中的DOM表現,為React在Node.js這類非瀏覽器環境下的吮吸給你提供了可能,React可以從<em>虛擬DoM中生成一個字符串</em>。而不是跟新真正的DOM,這使得我們可以在客戶端和服務端使用同一個<em>React Component</em>。<br />
React 提供了兩個可用於服務端渲染組件的函數:<em>React.renderToString</em> 和<em>React.render-ToStaticMarkup</em>。 在設計用於服務端渲染的ReactComponent時需要有預見性,考慮以下方面:
<li>選取最優的渲染函數;</li>
<li>如何支持組件的異步狀態;</li>
<li>如何將應用的初始化狀態傳遞到客戶端;</li>
<li>哪些生命周期函數可以用於服務端的渲染;</li>
<li>如何為應用提供同構路由支持;</li>
<li>單例、實例以及上下文的用法;</li>

<br />
**React.renderToString**
<br />
<div>
React.renderToString是兩個服務端渲染函數中的一個,也是開發主要使用的一個函數,和React.render不同,該函數去掉了用於表示渲染位置的參數。取而代之,該函數只返回一個字符串,這是一個快速的同步(阻塞式)函數,非常快。
</div>
<pre>
var MyComponent = React.createClass({
render:fucniton(){
return <div> Hello World!</div>;
}
});
var world= React.renderToString (<MyComponent/>);
 
//這個示例返回一個單行並且格式化的輸出
<divdata-reactid=".fgvrzhg2yo"data-ract-checksum="-1663559667">
Hello World!
</div>
</pre>
你會注意到,React為這個<div>元素添加了兩個data前綴的屬性。在瀏覽器環境下,React使用data-reactid區分DOM節點。這也是每當組件的state及props發生變化時,React都可以精准的跟新制定DOM節點的原因。
<br />
<div>
data-react-checksum僅僅存在於服務端。顧名思義,它是已創建<em>DOM和校驗和</em>。這准許React在客戶端服用與服務端結構上相同點的DOM結構。該屬性只會添加到跟元素上。
</div>
<br />
**React.renderToStaticMarkup**
<br />
<div>
React.renderToStaticMarkup是第二個服務端渲染函數,除了不會包含React的data屬性外,它和React.renderToString沒有區別。
</div>
<pre>
varMyComponent=React.createClass({
render:function(){
return<div>Hello World!</div>;
}
});
varworld= React.renderToStaticMarkup(<MyCompoent/>);
 
//單行輸出
<div>HelloWorld!</div>
</pre>
<br />
**用React.renderToString還是React.renderToStaticMarkup**
<br />
<div>
每個渲染函數都有自己的用途,所以你必須明確自己的需求,再去決定使用哪個渲染函數。當且僅當你不打算在客戶端渲染這個React Component時,才應該選擇使用React.renderToStaticMarkup函數。
</div>
 <br />
下面有一些示例:
<li><em>生成HTML電子郵件;</em></li>
<li><em>通過HTML到PDF的轉化來生成PDF;</em></li>
<li><em>組件測試;</em></li>
 <br />
大多數情況下,我們都會選擇使用<em>React.renderToString</em>。這將准許React使用<em>data-react-checksum</em>在客戶端跟迅速的初始化同一個React Component因為React可以重用服務端提供的
DOM,所以它可以跳過生成DOM節點以及把他們掛載到文檔中這兩個昂貴的進程,對於復雜些的站點,這樣做就會顯著的減少加載時間,用戶可以更快的與站點進行交互。
確保React Component能夠在服務端和客戶端准確的渲染出一致的結構是很重要的。如果data-react-checksum不匹配,React會舍棄服務端提供的DOM,然后生成新的DOM節點,並且將它們更新到文檔中。此時,Ract也不胡iyongyou服務端渲染帶來的各種性能上的優勢。
 
</div>
</p>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM