004@ReactJS入門了解.md

React 入門實例教程

轉(zhuǎn)載(加入了自己的一些東西,推薦看原文):
一看就懂的ReactJs入門教程(精華版)
React 入門實例教程

現(xiàn)在最熱門的前端框架有AngularJS、React、Bootstrap等。自從接觸了ReactJS,ReactJs的虛擬DOM(Virtual DOM)和組件化的開發(fā)深深的吸引了我,下面來跟我一起領(lǐng)略
ReactJS的風采吧~~ 章有點長,耐心讀完,你會有很大收獲哦~

一、ReactJS簡介

React 起源于 Facebook 的內(nèi)部項目,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設(shè) Instagram 的網(wǎng)站。做出來以后,發(fā)現(xiàn)這套東西很好用,就在2013年5月開源了。由于 React 的設(shè)計思想極其獨特,屬于革命性創(chuàng)新,性能出眾,代碼邏輯卻非常簡單。所以,越來越多的人開始關(guān)注和使用,認為它可能是將來 Web 開發(fā)的主流工具。

ReactJS官網(wǎng)地址:http://facebook.github.io/react/
Github地址:https://github.com/facebook/react

二、對ReactJS的認識及ReactJS的優(yōu)點

首先,對于React,有一些認識誤區(qū),這里先總結(jié)一下:

  • React不是一個完整的MVC框架,最多可以認為是MVC中的V(View),甚至React并不非常認可MVC開發(fā)模式;
  • React的服務(wù)器端Render能力只能算是一個錦上添花的功能,并不是其核心出發(fā)點,事實上React官方站點幾乎沒有提及其在服務(wù)器端的應(yīng)用;
  • 有人拿React和Web Component相提并論,但兩者并不是完全的競爭關(guān)系,你完全可以用React去開發(fā)一個真正的Web Component;
  • React不是一個新的模板語言,JSX只是一個表象,沒有JSX的React也能工作。

1、ReactJS的背景和原理

在Web開發(fā)中,我們總需要將變化的數(shù)據(jù)實時反應(yīng)到UI上,這時就需要對DOM進行操作。而復(fù)雜或頻繁的DOM操作通常是性能瓶頸產(chǎn)生的原因(如何進行高性能的復(fù)雜DOM操作通常是衡量一個前端開發(fā)人員技能的重要指標)。React為此引入了虛擬DOM(Virtual DOM)的機制:在瀏覽器端用Javascript實現(xiàn)了一套DOM API。基于React進行開發(fā)時所有的DOM構(gòu)造都是通過虛擬DOM進行,每當數(shù)據(jù)變化時,React都會重新構(gòu)建整個DOM樹,然后React將當前整個DOM樹和上一次的DOM樹進行對比,得到DOM結(jié)構(gòu)的區(qū)別,然后僅僅將需要變化的部分進行實際的瀏覽器DOM更新。而且React能夠批處理虛擬DOM的刷新,在一個事件循環(huán)(Event Loop)內(nèi)的兩次數(shù)據(jù)變化會被合并,例如你連續(xù)的先將節(jié)點內(nèi)容從A變成B,然后又從B變成A,React會認為UI不發(fā)生任何變化,而如果通過手動控制,這種邏輯通常是極其復(fù)雜的。盡管每一次都需要構(gòu)造完整的虛擬DOM樹,但是因為虛擬DOM是內(nèi)存數(shù)據(jù),性能是極高的,而對實際DOM進行操作的僅僅是Diff部分,因而能達到提高性能的目的。這樣,在保證性能的同時,開發(fā)者將不再需要關(guān)注某個數(shù)據(jù)的變化如何更新到一個或多個具體的DOM元素,而只需要關(guān)心在任意一個數(shù)據(jù)狀態(tài)下,整個界面是如何Render的。

如果你像在90年代那樣寫過服務(wù)器端Render的純Web頁面那么應(yīng)該知道,服務(wù)器端所要做的就是根據(jù)數(shù)據(jù)Render出HTML送到瀏覽器端。如果這時因為用戶的一個點擊需要改變某個狀態(tài)文字,那么也是通過刷新整個頁面來完成的。服務(wù)器端并不需要知道是哪一小段HTML發(fā)生了變化,而只需要根據(jù)數(shù)據(jù)刷新整個頁面。換句話說,任何UI的變化都是通過整體刷新來完成的。而React將這種開發(fā)模式以高性能的方式帶到了前端,每做一點界面的更新,你都可以認為刷新了整個頁面。至于如何進行局部更新以保證性能,則是React框架要完成的事情。

借用Facebook介紹React的視頻中聊天應(yīng)用的例子,當一條新的消息過來時,傳統(tǒng)開發(fā)的思路如上圖,你的開發(fā)過程需要知道哪條數(shù)據(jù)過來了,如何將新的DOM結(jié)點添加到當前DOM樹上;而基于React的開發(fā)思路如下圖,你永遠只需要關(guān)心數(shù)據(jù)整體,兩次數(shù)據(jù)之間的UI如何變化,則完全交給框架去做。可以看到,使用React大大降低了邏輯復(fù)雜性,意味著開發(fā)難度降低,可能產(chǎn)生Bug的機會也更少。

2、組件化

JSX語法,像是在Javascript代碼里直接寫XML的語法,實質(zhì)上這只是一個語法糖,每一個XML標簽都會被JSX轉(zhuǎn)換工具轉(zhuǎn)換成純Javascript代碼,React 官方推薦使用JSX, 當然你想直接使用純Javascript代碼寫也是可以的,只是使用JSX,組件的結(jié)構(gòu)和組件之間的關(guān)系看上去更加清晰。

虛擬DOM(virtual-dom)不僅帶來了簡單的UI開發(fā)邏輯,同時也帶來了組件化開發(fā)的思想,所謂組件,即封裝起來的具有獨立功能的UI部件。React推薦以組件的方式去重新思考UI構(gòu)成,將UI上每一個功能相對獨立的模塊定義成組件,然后將小的組件通過組合或者嵌套的方式構(gòu)成大的組件,最終完成整體UI的構(gòu)建。例如,F(xiàn)acebook的instagram.com整站都采用了React來開發(fā),整個頁面就是一個大的組件,其中包含了嵌套的大量其它組件,大家有興趣可以看下它背后的代碼。

如果說MVC的思想讓你做到視圖-數(shù)據(jù)-控制器的分離,那么組件化的思考方式則是帶來了UI功能模塊之間的分離。我們通過一個典型的Blog評論界面來看MVC和組件化開發(fā)思路的區(qū)別。

對于MVC開發(fā)模式來說,開發(fā)者將三者定義成不同的類,實現(xiàn)了表現(xiàn),數(shù)據(jù),控制的分離。開發(fā)者更多的是從技術(shù)的角度來對UI進行拆分,實現(xiàn)松耦合。

對于MVC開發(fā)模式來說,開發(fā)者將三者定義成不同的類,實現(xiàn)了表現(xiàn),數(shù)據(jù),控制的分離。開發(fā)者更多的是從技術(shù)的角度來對UI進行拆分,實現(xiàn)松耦合。

對于React而言,則完全是一個新的思路,開發(fā)者從功能的角度出發(fā),將UI分成不同的組件,每個組件都獨立封裝。

在React中,你按照界面模塊自然劃分的方式來組織和編寫你的代碼,對于評論界面而言,整個UI是一個通過小組件構(gòu)成的大組件,每個組件只關(guān)心自己部分的邏輯,彼此獨立。


React認為一個組件應(yīng)該具有如下特征:

  • (1)可組合(Composeable):一個組件易于和其它組件一起使用,或者嵌套在另一個組件內(nèi)部。如果一個組件內(nèi)部創(chuàng)建了另一個組件,那么說父組件擁有(own)它創(chuàng)建的子組件,通過這個特性,一個復(fù)雜的UI可以拆分成多個簡單的UI組件;
  • (2)可重用(Reusable):每個組件都是具有獨立功能的,它可以被使用在多個UI場景;
  • (3)可維護(Maintainable):每個小的組件僅僅包含自身的邏輯,更容易被理解和維護;

三、下載ReactJS,編寫Hello,world

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="../build/react.js"></script>
    <script src="../build/react-dom.js"></script>
    <script src="../build/browser.min.js"></script>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="example"></div>

<script type="text/babel">
    ReactDOM.render (
            <h1>Hello, world!</h1>,
            document.getElementById('example')
    );
</script>

</body>
</html>

ReactDOM.render 是 React 的最基本方法,用于將模板轉(zhuǎn)為 HTML 語言,并插入指定的 DOM 節(jié)點。

上面代碼有兩個地方需要注意。首先,最后一個 <script> 標簽的 type 屬性為 text/babel 。這是因為 React 獨有的 JSX 語法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"

其次,上面代碼一共用了三個庫: react.jsreact-dom.jsBrowser.js ,它們必須首先加載。其中,react.js 是 React 的核心庫,react-dom.js 是提供與 DOM 相關(guān)的功能,Browser.js 的作用是將 JSX 語法轉(zhuǎn)為 JavaScript 語法,這一步很消耗時間,實際上線的時候,應(yīng)該將它放到服務(wù)器完成。

$ babel src --out-dir build

上面命令可以將 src 子目錄的 js 文件進行語法轉(zhuǎn)換,轉(zhuǎn)碼后的文件全部放在 build 子目錄。

1、ReactDOM.render()

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('example')
);

上面代碼將一個 h1 標題,插入 example 節(jié)點(查看 demo01)。

這里需要注意的是,react并不依賴jQuery,當然我們可以使用jQuery,但是render里面第二個參數(shù)必須使用JavaScript原生的getElementByID方法,不能使用jQuery來選取DOM節(jié)點。

2、JSX 語法

上一節(jié)的代碼, HTML 語言直接寫在 JavaScript 語言之中,不加任何引號,這就是 JSX 的語法,它允許 HTML 與 JavaScript 的混寫(查看 Demo02 )。


    var names = ['Alice', 'Emily', 'Kate'];

    ReactDOM.render(
      <div>
      {
        names.map(function (name) {
          return <div>Hello, {name}!</div>
        })
      }
      </div>,
      document.getElementById('example')
    );

上面代碼體現(xiàn)了 JSX 的基本語法規(guī)則:遇到 HTML 標簽(以 < 開頭),就用 HTML 規(guī)則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規(guī)則解析。上面代碼的運行結(jié)果如下。

Hello ,Alice!
Hello ,Emily!
Hello ,Kate!

3、組件

React 允許將代碼封裝成組件(component),然后像插入普通 HTML 標簽一樣,在網(wǎng)頁中插入這個組件。React.createClass 方法就用于生成一個組件類(查看 demo04)。


    var HelloMessage = React.createClass({
      render: function() {
        return <h1>Hello {this.props.name}</h1>;
      }
    });

    ReactDOM.render(
      <HelloMessage name="John" />,
      document.getElementById('example')
    );

上面代碼中,變量 HelloMessage 就是一個組件類。模板插入 <HelloMessage />時,會自動生成 HelloMessage 的一個實例(下文的"組件"都指組件類的實例)。所有組件類都必須有自己的 render 方法,用于輸出組件。

注意,組件類的第一個字母必須大寫,否則會報錯,比如HelloMessage不能寫成helloMessage。另外,組件類只能包含一個頂層標簽,否則也會報錯。


    var HelloMessage = React.createClass({
      render: function() {
        return <h1>
          Hello {this.props.name}
        </h1><p>
          some text
        </p>;
      }
    });

上面代碼會報錯,因為HelloMessage組件包含了兩個頂層標簽:h1和p。

組件的用法與原生的 HTML 標簽完全一致,可以任意加入屬性,比如 <HelloMessage name="John">,就是 HelloMessage 組件加入一個 name屬性,值為 John。組件的屬性可以在組件類的 this.props 對象上獲取,比如 name 屬性就可以通過 this.props.name讀取。

添加組件屬性,有一個地方需要注意,就是 class 屬性需要寫成 classNamefor 屬性需要寫成 htmlFor ,這是因為 classforJavaScript 的保留字。

4、this.props.children

this.props 對象的屬性與組件的屬性一一對應(yīng),但是有一個例外,就是 this.props.children 屬性。它表示組件的所有子節(jié)點(查看 demo05)。


    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
    );

上面代碼的 NoteList 組件有兩個 span 子節(jié)點,它們都可以通過 this.props.children 讀取

5、PropTypes

組件的屬性可以接受任意值,字符串、對象、函數(shù)等等都可以。有時,我們需要一種機制,驗證別人使用組件時,提供的參數(shù)是否符合要求

組件類的PropTypes屬性,就是用來驗證組件實例的屬性是否符合要求(查看 demo06)。


    var MyTitle = React.createClass({
      propTypes: {
        title: React.PropTypes.string.isRequired,
      },

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

上面的Mytitle組件有一個title屬性。PropTypes 告訴 React,這個 title 屬性是必須的,而且它的值必須是字符串。現(xiàn)在,我們設(shè)置 title 屬性的值是一個數(shù)值。


    var data = 123;

    ReactDOM.render(
      <MyTitle title={data} />,
      document.body
    );

這樣一來,title屬性就通不過驗證了。控制臺會顯示一行錯誤信息。

Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.

更多的PropTypes設(shè)置,可以查看官方文檔。

此外,getDefaultProps 方法可以用來設(shè)置組件屬性的默認值。


var MyTitle = React.createClass({
  getDefaultProps : function () {
    return {
      title : 'Hello World'
    };
  },

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

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

上面代碼會輸出"Hello World"。

6.獲取真實的DOM節(jié)點

組件并不是真實的 DOM 節(jié)點,而是存在于內(nèi)存之中的一種數(shù)據(jù)結(jié)構(gòu),叫做虛擬 DOM (virtual DOM)。只有當它插入文檔以后,才會變成真實的 DOM 。根據(jù) React 的設(shè)計,所有的 DOM 變動,都先在虛擬 DOM 上發(fā)生,然后再將實際發(fā)生變動的部分,反映在真實 DOM上,這種算法叫做 DOM diff ,它可以極大提高網(wǎng)頁的性能表現(xiàn)。

但是,有時需要從組件獲取真實 DOM 的節(jié)點,這時就要用到 ref 屬性(查看 demo07 )。


    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')
    );

上面代碼中,組件 MyComponent 的子節(jié)點有一個文本輸入框,用于獲取用戶的輸入。這時就必須獲取真實的 DOM 節(jié)點,虛擬 DOM 是拿不到用戶輸入的。為了做到這一點,文本輸入框必須有一個 ref 屬性,然后 this.refs.[refName] 就會返回這個真實的 DOM 節(jié)點。

需要注意的是,由于 this.refs.[refName] 屬性獲取的是真實 DOM ,所以必須等到虛擬 DOM 插入文檔以后,才能使用這個屬性,否則會報錯。上面代碼中,通過為組件指定 Click 事件的回調(diào)函數(shù),確保了只有等到真實 DOM 發(fā)生 Click 事件之后,才會讀取 this.refs.[refName] 屬性。

React 組件支持很多事件,除了 Click 事件以外,還有 KeyDown 、Copy、Scroll 等,完整的事件清單請查看官方文檔

7.this.state

組件免不了要與用戶互動,React 的一大創(chuàng)新,就是將組件看成是一個狀態(tài)機,一開始有一個初始狀態(tài),然后用戶互動,導(dǎo)致狀態(tài)變化,從而觸發(fā)重新渲染 UI (查看 demo08 )。


    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')
    );

上面代碼是一個 LikeButton 組件,它的 getInitialState 方法用于定義初始狀態(tài),也就是一個對象,這個對象可以通過 this.state 屬性讀取。當用戶點擊組件,導(dǎo)致狀態(tài)變化,this.setState 方法就修改狀態(tài)值,每次修改以后,自動調(diào)用 this.render 方法,再次渲染組件。

由于 this.props 和 this.state 都用于描述組件的特性,可能會產(chǎn)生混淆。一個簡單的區(qū)分方法是,this.props 表示那些一旦定義,就不再改變的特性,而 this.state 是會隨著用戶互動而產(chǎn)生變化的特性。

8.表單

用戶在表單填入的內(nèi)容,屬于用戶跟組件的互動,所以不能用 this.props 讀取(查看 demo9 )。


    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.body);

上面代碼中,文本輸入框的值,不能用 this.props.value 讀取,而要定義一個 onChange 事件的回調(diào)函數(shù),通過 event.target.value 讀取用戶輸入的值。textarea 元素、select元素、radio元素都屬于這種情況,更多介紹請參考官方文檔。

9.組件的生命周期

組件的生命周期分成三個狀態(tài):

  • Mounting:已插入真實 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真實 DOM

React 為每個狀態(tài)都提供了兩種處理函數(shù),will 函數(shù)在進入狀態(tài)之前調(diào)用,did 函數(shù)在進入狀態(tài)之后調(diào)用,三種狀態(tài)共計五種處理函數(shù)。

  • componentWillMount()
  • componentDidMount()
  • componentWillUpdate(object nextProps, object nextState)
  • componentDidUpdate(object prevProps, object prevState)
  • componentWillUnmount()

此外,React 還提供兩種特殊狀態(tài)的處理函數(shù)。

  • componentWillReceiveProps(object nextProps):已加載組件收到新的參數(shù)時調(diào)用
  • shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時調(diào)用

這些方法的詳細說明,可以參考官方文檔。下面是一個例子(查看 demo10 )。


    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.body
    );

上面代碼在hello組件加載以后,通過 componentDidMount 方法設(shè)置一個定時器,每隔100毫秒,就重新設(shè)置組件的透明度,從而引發(fā)重新渲染。

另外,組件的style屬性的設(shè)置方式也值得注意,不能寫成

style="opacity:{this.state.opacity};"

而要寫成

style={{opacity: this.state.opacity}}

這是因為 React 組件樣式是一個對象,所以第一重大括號表示這是 JavaScript 語法,第二重大括號表示樣式對象。

10.Ajax

組件的數(shù)據(jù)來源,通常是通過 Ajax 請求從服務(wù)器獲取,可以使用 componentDidMount 方法設(shè)置 Ajax 請求,等到請求成功,再用 this.setState 方法重新渲染 UI (查看 demo11 )。


    var UserGist = React.createClass({
      getInitialState: function() {
        return {
          username: '',
          lastGistUrl: ''
        };
      },

      componentDidMount: function() {
        $.get(this.props.source, function(result) {
          var lastGist = result[0];
          if (this.isMounted()) {
            this.setState({
              username: lastGist.owner.login,
              lastGistUrl: lastGist.html_url
            });
          }
        }.bind(this));
      },

      render: function() {
        return (
          <div>
            {this.state.username}'s last gist is
            <a href={this.state.lastGistUrl}>here</a>.
          </div>
        );
      }
    });

    ReactDOM.render(
      <UserGist source="https://api.github.com/users/octocat/gists" />,
      document.body
    );

上面代碼使用 jQuery 完成 Ajax 請求,這是為了便于說明。React 本身沒有任何依賴,完全可以不用jQuery,而使用其他庫。

我們甚至可以把一個Promise對象傳入組件,請看Demo12。

ReactDOM.render(
  <RepoList
    promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}
  />,
  document.body
);

上面代碼從Github的API抓取數(shù)據(jù),然后將Promise對象作為屬性,傳給RepoList組件。

如果Promise對象正在抓取數(shù)據(jù)(pending狀態(tài)),組件顯示"正在加載";如果Promise對象報錯(rejected狀態(tài)),組件顯示報錯信息;如果Promise對象抓取數(shù)據(jù)成功(fulfilled狀態(tài)),組件顯示獲取的數(shù)據(jù)。


var RepoList = React.createClass({
  getInitialState: function() {
    return { loading: true, error: null, data: null};
  },

  componentDidMount() {
    this.props.promise.then(
      value => this.setState({loading: false, data: value}),
      error => this.setState({loading: false, error: error}));
  },

  render: function() {
    if (this.state.loading) {
      return <span>Loading...</span>;
    }
    else if (this.state.error !== null) {
      return <span>Error: {this.state.error.message}</span>;
    }
    else {
      var repos = this.state.data.items;
      var repoList = repos.map(function (repo) {
        return (
          <li>
            <a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description}
          </li>
        );
      });
      return (
        <main>
          <h1>Most Popular JavaScript Projects in Github</h1>
          <ol>{repoList}</ol>
        </main>
      );
    }
  }
});

參考資料

一看就懂的ReactJs入門教程(精華版)
React 入門實例教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容