1. 程式人生 > >Node.js從入門到實戰(六)React一頁紙總結(很大的一頁紙)

Node.js從入門到實戰(六)React一頁紙總結(很大的一頁紙)

一、React

React是一個JavaScript庫,是由FaceBook和Instagram開發的,主要用於使用者建立圖形化介面。

由於 React 的設計思想極其獨特,屬於革命性創新,效能出眾,程式碼邏輯卻非常簡單。所以,越來越多的人開始關注和使用,認為它可能是將來 Web 開發的主流工具。
這個專案本身也越滾越大,從最早的UI引擎變成了一整套前後端通吃的 Web App 解決方案。衍生的 React Native 專案,目標更是巨集偉,希望用寫 Web App 的方式去寫 Native App。如果能夠實現,整個網際網路行業都會被顛覆,因為同一組人只需要寫一次 UI ,就能同時執行在伺服器、瀏覽器和手機。

二、React元件

React 的核心思想是:封裝元件。各個元件維護自己的狀態和 UI,當狀態變更,自動重新渲染整個元件。基於這種方式的一個直觀感受就是我們不再需要不厭其煩地來回查詢某個 DOM 元素,然後操作 DOM 去更改 UI。
React 大體包含下面這些概念:

  1. 元件
  2. JSX
  3. Virtual DOM
  4. Data Flow
這裡通過一個簡單的元件來快速瞭解這些概念,以及建立起對 React 的一個總體認識。
import React, { Component } from 'react';
import { render } from 'react-dom';

class HelloMessage extends Component {
  render() {
    return <div>Hello {this.props.name}</div>;
  }
}

// 載入元件到 DOM 元素 mountNode
render(<HelloMessage name="John" />, mountNode);

元件

React 應用都是構建在元件之上。上面的 HelloMessage 就是一個 React 構建的元件,最後一句 render 會把這個元件顯示到頁面上的某個元素 mountNode 裡面,顯示的內容就是 <div>Hello John</div>。
props 是元件包含的兩個核心概念之一,另一個是 state。可以把 props 看作是元件的配置屬性,在元件內部是不變的,只是在呼叫這個元件的時候傳入不同的屬性(比如這裡的 name)來定製顯示這個元件。

JSX

從上面的程式碼可以看到將 HTML 直接嵌入了 JS 程式碼裡面,這個就是 React 提出的一種叫 JSX 的語法
,這應該是最開始接觸 React 最不能接受的設定之一,因為前端被“表現和邏輯層分離”這種思想“洗腦”太久了。但實際上元件的 HTML 是組成一個元件不可分割的一部分,能夠將 HTML 封裝起來才是元件的完全體,React 發明了JSX 讓 JS 支援嵌入 HTML 不得不說是一種非常聰明的做法,讓前端實現真正意義上的元件化成為了可能。

Virtual DOM

當元件狀態 state 有更改的時候,React 會自動呼叫元件的 render 方法重新渲染整個元件的 UI。
當然如果真的這樣大面積的操作 DOM,效能會是一個很大的問題,所以 React 實現了一個Virtual DOM,元件 DOM 結構就是對映到這個 Virtual DOM 上,React 在這個 Virtual DOM 上實現了一個diff 演算法,當要重新渲染元件的時候,會通過 diff 尋找到要變更的 DOM 節點,再把這個修改更新到瀏覽器實際的 DOM 節點上,所以實際上不是真的渲染整個 DOM 樹。這個 Virtual DOM 是一個純粹的 JS 資料結構,所以效能會比原生 DOM 快很多。

Data Flow

“單向資料繫結”是 React 推崇的一種應用架構的方式。當應用足夠複雜時才能體會到它的好處,雖然在一般應用場景下你可能不會意識到它的存在,也不會影響你開始使用 React,你只要先知道有這麼個概念。
使用 React 的網頁原始碼,結構大致如下。
<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8">
   <title>Hello world!</title>
   <script src = "../../build/react.js"></script>
   <script src = "../../build/react-dom.js"></script>
   <script src = "../../build/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>
上面程式碼有兩個地方需要注意。首先,最後一個 <script> 標籤的 type 屬性為text/babel 。這是因為 React 獨有的 JSX 語法,跟 JavaScript 不相容。凡是使用 JSX 的地方,都要加上 type="text/babel" 。
其次,上面程式碼一共用了三個庫: react.js 、react-dom.js 和 Browser.js ,它們必須首先載入。其中,react.js 是 React 的核心庫,react-dom.js 是提供與 DOM 相關的功能,Browser.js 的作用是將 JSX 語法轉為 JavaScript 語法,這一步很消耗時間,實際上線的時候,應該將它放到伺服器完成。
$ babel src --out-dir build
JSX的基本語法規則:遇到HTML標籤(以<開頭),就用HTML規則解析;遇到程式碼塊(以{開頭),就用JavaScript規則解析。上面命令可以將 src 子目錄的 js 檔案進行語法轉換,轉碼後的檔案全部放在 build 子目錄

三、React元件

3.1 ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用於將模板轉為 HTML 語言,並插入指定的 DOM 節點

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('example')
);
上面程式碼將一個 h1 標題,插入 example 節點(檢視 demo01),執行結果如下。



3.2 props和state

元件有兩個核心概念:

  1. props
  2. state
一個元件就是通過這兩個屬性的值在 render 方法裡面生成這個元件對應的 HTML 結構。

props 就是元件的屬性,由外部通過 JSX 屬性傳入設定,一旦初始設定完成,就可以認為 this.props 是不可更改的,所以不要輕易更改設定 this.props 裡面的值(雖然對於一個 JS 物件你可以做任何事)。
state 是元件的當前狀態,可以把元件簡單看成一個“狀態機”,根據狀態 state 呈現不同的 UI 展示。一旦狀態(資料)更改,元件就會自動呼叫 render 重新渲染 UI,這個更改的動作會通過 this.setState 方法來觸發。

this.props.children
this.props物件的屬性和元件的屬性一一對應,但是有個children除外,它表示的是元件的所有子節點

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 子節點,它們都可以通過 this.props.children 讀取,執行結果如下。
1. hello
2. world
這裡需要注意, this.props.children 的值有三種可能:如果當前元件沒有子節點,它就是 undefined ;如果有一個子節點,資料型別是object ;如果有多個子節點,資料型別就是 array 。所以,處理 this.props.children 的時候要小心。React 提供一個工具方法 React.Children 來處理 this.props.children 。我們可以用 React.Children.map 來遍歷子節點,而不用擔心 this.props.children 的資料型別是 undefined 還是 object。

3.3 事件處理

React 的一大創新,就是將元件看成是一個狀態機,一開始有一個初始狀態,然後使用者互動,導致狀態變化,從而觸發重新渲染 UI,如下的例子顯示了React中事件繫結和處理的機制: 
import React, { Component } from 'react';
import { render } from 'react-dom';

class LikeButton extends Component {
  constructor(props) {
    super(props);
    this.state = { liked: false };
  }

  handleClick(e) {
    this.setState({ liked: !this.state.liked });
  }

  render() {
    const text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick.bind(this)}>
          You {text} this. Click to toggle.
      </p>
    );
  }
}

render(
    <LikeButton />,
    document.getElementById('example')
);
上面程式碼是一個 LikeButton 元件,它的 getInitialState 方法用於定義初始狀態,也就是一個物件,這個物件可以通過 this.state屬性讀取。當用戶點選元件,導致狀態變化,this.setState 方法就修改狀態值,每次修改以後,自動呼叫 this.render方法,再次渲染元件。
由於 this.props 和 this.state 都用於描述元件的特性,可能會產生混淆。一個簡單的區分方法是,this.props 表示那些一旦定義,就不再改變的特性,而 this.state 是會隨著使用者互動而產生變化的特性。

3.4 元件的生命週期

元件的生命週期分成三個狀態:

  • Mounting:已插入真實 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真實 DOM
React 為每個狀態都提供了兩種處理函式,will 函式在進入狀態之前呼叫,did 函式在進入狀態之後呼叫,三種狀態共計五種處理函式。
  1. componentWillMount()
  2. componentDidMount()
  3. componentWillUpdate(object nextProps, object nextState)
  4. componentDidUpdate(object prevProps, object prevState)
  5. componentWillUnmount()

此外,React 還提供兩種特殊狀態的處理函式:

  1. componentWillReceiveProps(object nextProps):已載入元件收到新的引數時呼叫
  2. shouldComponentUpdate(object nextProps, object nextState):元件判斷是否重新渲染時呼叫

如下是狀態處理函式的使用例項,即一個100ms定時器:

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 方法設定一個定時器,每隔100毫秒,就重新設定元件的透明度,從而引發重新渲染。

3.5 元件的引用

ReactDOM.render 元件返回的是什麼?
它會返回對元件的引用也就是元件例項(對於無狀態狀態元件來說返回 null),注意 JSX 返回的不是元件例項,它只是一個ReactElement 物件(還記得我們用純 JS 來構建 JSX 的方式嗎),比如這種:

// A ReactElement
const myComponent = <MyComponent />

// render
const myComponentInstance = ReactDOM.render(myComponent, mountNode);
myComponentInstance.doSomething();

findDOMNode()
當元件載入到頁面上之後(mounted),你都可以通過 react-dom 提供的 findDOMNode() 方法拿到元件對應的 DOM 元素。
import { findDOMNode } from 'react-dom';

// Inside Component class
componentDidMound() {
  const el = findDOMNode(this);
}
Refs

另外一種方式就是通過在要引用的 DOM 元素上面設定一個 ref 屬性指定一個名稱,然後通過 this.refs.name 來訪問對應的 DOM 元素。比如有一種情況是必須直接操作 DOM 來實現的,你希望一個 <input/> 元素在你清空它的值時 focus,你沒法僅僅靠 state 來實現這個功能。

class App extends Component {
  constructor() {
    return { userInput: '' };
  }

  handleChange(e) {
    this.setState({ userInput: e.target.value });
  }

  clearAndFocusInput() {
    this.setState({ userInput: '' }, () => {
      this.refs.theInput.focus();
    });
  }

  render() {
    return (
      <div>
        <div onClick={this.clearAndFocusInput.bind(this)}>
          Click to Focus and Reset
        </div>
        <input
          ref="theInput"
          value={this.state.userInput}
          onChange={this.handleChange.bind(this)}
        />
      </div>
    );
  }
}
如果 ref 是設定在原生 HTML 元素上,它拿到的就是DOM 元素,如果設定在自定義元件上,它拿到的就是元件例項,這時候就需要通過 findDOMNode 來拿到元件的 DOM 元素。
因為無狀態元件沒有例項,所以 ref 不能設定在無狀態元件上,一般來說這沒什麼問題,因為無狀態元件沒有例項方法,不需要 ref 去拿例項呼叫相關的方法,但是如果想要拿無狀態元件的 DOM 元素的時候,就需要用一個狀態元件封裝一層,然後通過 ref 和 findDOMNode 去獲取。

3.6 元件間通訊

父子元件間通訊

這種情況下很簡單,就是通過 props 屬性傳遞,在父元件給子元件設定 props,然後子元件就可以通過 props 訪問到父元件的資料/方法,這樣就搭建起了父子元件間通訊的橋樑。

import React, { Component } from 'react';
import { render } from 'react-dom';

class GroceryList extends Component {
  handleClick(i) {
    console.log('You clicked: ' + this.props.items[i]);
  }

  render() {
    return (
      <div>
        {this.props.items.map((item, i) => {
          return (
            <div onClick={this.handleClick.bind(this, i)} key={i}>{item}</div>
          );
        })}
      </div>
    );
  }
}

render(
  <GroceryList items={['Apple', 'Banana', 'Cranberry']} />, mountNode
);
div 可以看作一個子元件,指定它的 onClick 事件呼叫父元件的方法。父元件訪問子元件?用 refs即可。

非父子元件間的通訊 
使用全域性事件 Pub/Sub 模式,在 componentDidMount 裡面訂閱事件,在componentWillUnmount 裡面取消訂閱,當收到事件觸發的時候呼叫setState 更新 UI。

3.7 Ajax請求

元件的資料通常是通過Ajax請求伺服器獲取的,可以使用componentDidMount方法來設定Ajax請求,等到請求成功,再用this.setState方法重新渲染UI:

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.getElementById('example')
);
上面程式碼使用 jQuery 完成 Ajax 請求,在DidMout中使用$.get實現HTTP請求。