React 入門實戰

學習 React 不是一蹴而就的事情,須要一步一步來,長期積累。毫無疑問,如今前端框架最火的 Angular、React 和 VUE。我做爲一個小白,其實幾個月以前就已經接觸到 React,不過那時候只是簡單的實現了幾個小 Demojavascript

photo

React 給我最大的吸引,就是虛擬DOM 和組件化開發,它起源於 Facebook 內部項目,在 2013 年 5 月開源。這套 MVC 框架性能和代碼邏輯都有很大的優點,愈來愈多的人開始關注。html

原理和背景

如今的 Web 開發,就是將實時變化的數據渲染到 UI 上,對 DOM 進行操做,JQuery 爲何這麼火,就是它提供了一系列方便的 DOM 操做,容易選擇,無需考慮瀏覽器兼容等等問題。前端

複雜的 DOM 操做一般是性能瓶頸產生的緣由,React 引入了一套 Virtual DOM,在瀏覽器端的 JavaScript 實現了一套 DOM API。如今的開發變得簡單,開發者編寫虛擬 DOM,每當數據變化時,React 會從新繪製整個虛擬 DOM 樹,而後採用高效的 Diff 算法,只把須要變化的部分渲染到瀏覽器中java

在之前,渲染數據都是直接使用 innerHTMl,使用很方便,可是帶來的弊端就是一些沒有改變的數據也要從新渲染。在更古老的瀏覽器時代,那個時候後臺的數據稍有變化,直接都說從新渲染整個 HTML,至關於從新打開一次頁面。react

之前 MVC 的思想,是作到數據-視圖-控制器的分離,如今組件化的思想是作到 UI 功能模塊之間的分離,之前的 MVC 開發者須要定義三個類來實現數據、視圖和控制器的分離,開發者更多的從技術的角度對 UI 進行拆分,實現鬆耦合。webpack

對於 React ,徹底是一個新的作法,開發者從功能的角度出發,將 UI 分紅不一樣的組件,每一個組件都用虛擬 DOM 獨立封裝,至於渲染,就交給 API 去處理,開發者只需將數據和視圖綁定到一塊兒。以一個簡單的評論系統 UI 爲例,將各個評論組件和數據綁定到一塊兒,至於更新,只須要考慮全部的數據,只要兩次數據發生變化,就告訴 UI 去從新渲染,DOM API 渲染更新的組件。git

JSX 語法

React 須要將虛擬 DOM 和真實 DOM 綁定起來,是經過 ReactDOM.render 函數,它是 React 中最基本的用法,github

<head>
  <script src="react.js"></script>
  <script src="react-dom.js"></script>
  <script src="JSX.js"></script>
</head>
<body>
  <div id="example"></div>
  <script type="text/jsx">
    ReactDOM.render(
      <h1>Hello World!</h1>,
      document.getElementById("example")
    )
  </script>
<body>

當在瀏覽器裏打開這個 HTML 的時候,就會看到在 id 爲 example 的元素下有一個 h1 的 Hello World 標籤,這就是將數據綁定。web

這裏直接使用了 JSX 語法,我以爲這個語法最大的特色就是你能夠直接在 JS 裏直接寫 HTML,而不用加引號。若是你用 Sublime 編輯器,還能夠安裝 babel 或 jsx 的插件,寫起來更方便。ajax

jsx 還有其餘的功能,好比模版,官方就提供了幾個例子,

const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;

const element2 = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

還能夠插入數組:

const arr = [
  <h1>Hello World!</h1>,
  <h1>Hello React!</h1>
]
ReactDOM.render(
  <div>{arr}</div>,
  document.getElementById("example")
)
// Hello World!
// Hello React!

還能夠混合着寫:

const names = ['world', 'react', 'jsx']
ReactDOM.render(
  <div>
  {
    names.map(name => <h1>Hello {name}</h1>)
  }
  </div>,
  document.getElementById("example")
)
// Hello world
// Hello react
// Hello jsx

組件

React 提供虛擬 DOM,咱們能夠手動構造本身的組件,這個經過 React.createClass 函數來實現,或者繼承於 Component 類:

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

// 或者 ES6 寫法,繼承於 Component 類
class Hello extends Component{
  render() {
    return <h1>Hello {this.props.name}</h1>;
  }
}

// 使用
ReactDOM.render(
  <Hello name="React" />,
  document.getElementById('example')
);

Hello 就是 Component,能夠和 HTML 元素同樣使用,可是組件首字母必須大寫,且頂層標籤只能有一個this.props 是組件的一個對象,能夠在組件使用的地方,把值傳給定義的地方,這裏的 this.props.name 就是指 "React"

由於 class 和 for 是 JavaScript 的保留字,因此 class 要寫成 className,for 要寫成 htmlFor。

this.props.children

this.props.children 表示組件的全部子節點,能夠用來構造:

var List = React.createClass({
  render() {
    return (
      <ol>
      {React.Children.map(this.props.children, child => <li>{child}</li>)}
      </ol>
    )
  }
});

ReactDOM.render(
  <List>
    <h1>hello world</h1>
    <h1>hello react</h1>
  </List>,
  document.getElementById('example')
);
//1. hello world
//2. hello react

this.props.children 有三種狀況,若是沒有子節點,返回 underfined,若是有一個子節點,返回 Object,若是有多個子節點,返回 array,具體狀況,具體分析,最好加個類型判斷。

propTypes

這個是 React 提供的用來驗證組件的屬性是否符合要求,好比組件的名稱要求是 string 型,當給它 number 型的時候會提示錯誤。

getDefaultProps 能夠用來表示一些默認的值,使用組件的時候沒有設置,就使用默認值。

var Hello = React.createClass({
  propTypes: {
    // name 必須爲 string 類型
    name: React.PropTypes.string.isRequired
  }
  getDefaultProps(){
    return {
      // title 設置成默認值
      title: 'React'
    }
  }
  render() {
    return <h1>Hello {this.props.name}, title is {title}</h1>;
  }
});

const num = 123
ReactDOM.render(
  <Hello name={num} />,
  document.getElementById('example')
);
// Warning 能夠顯示,但會報 warning

數據操做

當虛擬 DOM 插入到 HTML 中就變成真實 DOM,但如何對真實 DOM 進行操做呢?React 也是提供一套解決方案的,經過 ref 能夠找到真實 DOM:

var MyComponent = React.createClass({
  handleClick() {
    this.refs.mySpan.textContent = 'have clicked!'
  }
  render() {
    return (
      <div>
        <span ref="mySpan">not click</span>
        <button onClick={this.handleClick}></button>
      <div>
    )
  }
})

經過在虛擬 DOM 中定義 ref 值,而後在 this.refs 屬性中調用,該對象和真實 DOM 操做相同。關於 React 提供的完整事件,能夠查看官方文檔

state

其實前面介紹了不少,仍是沒有和數據扯上關係。前面已經提到,開發者只須要關心總體的數據,具體是經過什麼來設置的呢?React 提供了 state 狀態集,開始有一個初始狀態,當後面因爲某些操做致使狀態改變的時候,便會自動的渲染 DOM,觸發 UI 層的改變,這其中的一些 diff 算法都是對開發者不可見的。

var MyComponent = React.createClass({
  getInitialState() {
    return {clickedNum: 0}
  }
  handleClick() {
    this.setState({clickedNum: this.state.clickedNum + 1})
  }
  render() {
    var text = `You have clicked this button ${this.state.clickedNum} times!`
    return (
      <div>
        <span>{text}</span>
        <button onClick={this.handleClick}></button>
      <div>
    )
  }
})

當每一次點擊 button 的時候,會觸發 handleClick 事件,致使狀態變化,React 會調用 render() 事件從新渲染改變的 DOM。

組件的生命週期

一個組件也是有生命週期短,從大的範圍來說,組件有三種狀態,mount(已插入真實 DOM),update(更新插入的 DOM),unmount(移除真實 DOM),可是函數有五個時間段:

  1. componentWillMount()

  2. componentDidMount()

  3. componentWillUpdate(object nextProps, object nextState)

  4. componentDidUpdate(object prevProps, object prevState)

  5. componentWillUnmount()

will 表示還沒執行,did 表示已經執行。

var Hello = React.createClass({
  getInitialState: function () {
    return {
      opacity: 1.0
    };
  },
  componentWillMount: function(){
    console.log('component will mount')
  },
  componentDidMount: function () {
    console.log('component did mount')
    this.timer = setInterval(() => {
      var opacity = this.state.opacity;
      opacity -= .05;
      if (opacity < 0.1) {
        opacity = 1.0;
      }
      this.setState({
        opacity: opacity
      });
    }, 100);
  },
  componentWillUpdate: function(){
    console.log('component will update')
  },
  conponentDidUpdate: function(){
    console.log('component did update')
  },
  render: function () {
    return (
      <div style={{opacity: this.state.opacity}}>
        Hello {this.props.name}
      </div>
    );
  }
});

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

從輸出的結果來看,執行的順序就是按照上面的順序,並且會發現 componentWillUpdatecomponentDidUpdate函數會重複執行,由於 100 毫秒透明度就變化了一次。

component will mount
component did mount
component will update
component did update
component will update
component did update
component will update
.....

ajax 操做

經過 ajax 能夠獲取來自服務器的數據,好比:

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

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

  componentWillUnmount: function(){
    this.getData.abort();
  },

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

以前使用 isMounted() 函數,貌似後來被取消了,而後使用 componentWillUnmount 函數來 abort 以前的 ajax 請求,由於 componentWillUnmount 表示的就是組件即將被刪除,若是異步獲取 ajax 尚未完成的話,就把它取消掉。

不過感受 ES6 提供了 Promise 以後,貌似方便多了。

var PromiseDemo = React.createClass({
  getInitialState: function(){
    return {
      loading: true,
      data: null,
      error: null
    }
  },
  componentDidMount: function(){
    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 <div>loading...</div>
    }
    else if(this.state.error != null){
      return <div>error...{this.state.error.message}</div>
    }
    else{
      var p = this.state.data[0].html_url
      return <div>
        <h1>the URL is:</h1>
        <p>{p}</p>
      </div>
    }
  }
});
ReactDOM.render(
  <PromiseDemo promise={$.getJSON('https://api.github.com/users/octocat/gists')} />,
  document.getElementById('example2')
);

學好 ES6 真的很重要。固然,React 也是一把好槍,ES6 是一把好布,好布擦好槍,這才能摩擦出高效率。

總結

這只是單純的介紹 React,一些簡單的操做,一點也不復雜,其實把 React 和 webpack 結合發揮的做用更大,後面的文章會繼續涉及到,同時還有 react-router 和 react-redux、react-flux。共勉!

參考

React 入門實例教程
一看就懂的ReactJs入門教程(精華版)
React虛擬DOM淺析
http://reactjs.cn/react/docs/tutorial.html

歡迎來個人博客交流。

相關文章
相關標籤/搜索