1. 程式人生 > >react(二):用例項認識react

react(二):用例項認識react

昨天在認識了什麼是react之後,今天開始用程式碼來寫一些demo,跟著慕課網的視訊一起寫demo發現執行不出來,找其原因是視訊太老了,react已經更新換代了,摔!!
然後跟著阮一峰老師一起寫12個demo,阮一峰老師的教程:React 入門例項教程,希望大家可以看一下老師寫的demo
github地址,求fork,求star,(~ ̄▽ ̄)~

HTML模板

這是一個最簡單的react模板

<!DOCTYPE html>
<html>
<head>
    <script src="../build/react.js"></script
>
<script src="../build/react-dom.js"></script> <script src="../build/browser.min.js"></script> </head> <body> <div id="container"></div> <script type="text/babel"> ReactDOM.render( <div> hello world </div
>
,document.getElementById('container') )
</script> </body> </html>

不同版本的react之間的差異

jsx和babel

在之前我所觀看的慕課網的初識react的視訊中的模板和這個有些出入,發現在慕課網上敲出來的程式碼沒有效果,原因是react已經更新,其實最主要的變化主要是 之前使用jsxTransformer來編譯JSX語法的,現在是使用babel來編譯的,使用時的區別是:
1. script標籤由原來的”text/jsx”變為”text/babel”
2. 所引入的js庫有原來的react.js和jsxTransformer.js變為browser.js

  1. JSX是什麼JSX其實是JavaScript的擴充套件,React為了程式碼的可讀性更方便地建立虛擬DOM等原因,加入了一些類似XML的語法的擴充套件。
  2. 編譯器——jsxTransformerJSX程式碼並不能直接執行,需要將它編譯成正常的JavaScript表示式才能執行,jsxTransformer.js就是這一編譯器的角色。
  3. 第二代編譯器——babel
    React官方部落格釋出了一篇文章,宣告其自身用於JSX語法解析的編譯器JSTransform已經過期,不再維護,React JS和React Native已經全部採用第三方Babel的JSX編譯器實現。原因是兩者在功能上已經完全重複,而Babel作為專門的JavaScript語法編譯工具,提供了更為強大的功能。而browser.js是babel編譯器的瀏覽器版本。

React.reader和ReactDOM.render

另一個變更的地方就是React.reader和ReactDOM.render,或許你也在網上看到有的程式碼用的是react,有的用的是reactDOM

這個是react最新版api,也就是0.14版本做出的改變。主要是為了使React能在更多的不同環境下更快、更容易構建。於是把react分成了react和react-dom兩個部分。這樣就為web版的react和移動端的React Native共享元件鋪平了道路。也就是說我們可以跨平臺使用相同的react元件。

新的react包包含了React.createElement,.createClass,.Component,.PropTypes,.children以及其他元素和元件類。這些都是你需要構建元件時助手。

而react-dom包包括ReactDOM.render,.unmountComponentAtNode和.findDOMNode。在 react-dom/server ,有ReactDOMServer.renderToString和.renderToStaticMarkup伺服器端渲染支援。

總的來說,兩者的區別就是:ReactDom是React的一部分。ReactDOM是React和DOM之間的粘合劑,一般用來定義單一的元件,或者結合ReactDOM.findDOMNode()來使用。更重要的是ReactDOM包已經允許開發者刪除React包新增的非必要的程式碼,並將其移動到一個更合適的儲存庫。

通過以上的介紹,你應該就清楚了這個demo個行程式碼的意思了
script載入的三個庫:react.js:React的核心庫, react-dom.js:react中和dom有關的庫,Browser.js將將jsx轉化為js

ReactDOM.render(componments,containerName)函式是將componments插入到containerName中,這個方法有兩個引數,第一個是要插入的dom,後一個是插入到什麼地方。上面的程式碼是將一個內容為hello world的div插入到id位container的div中。

react中可以直接在js中寫html標籤,這裡運用的是jsx語法。但是這裡並不是真正的dom節點,只是虛擬的dom,因為直接修改html的dom代價太大,所以我們操作虛擬dom,然後react有自己的diff演算法,對比改動的地方,再完成對dom的操作(自己膚淺的理解)

虛擬DOM

元件並不是真實的 DOM 節點,而是存在於記憶體之中的一種資料結構,叫做虛擬 DOM (virtual DOM)。只有當它插入文件以後,才會變成真實的 DOM 。根據 React 的設計,所有的 DOM 變動,都先在虛擬 DOM 上發生,然後再將實際發生變動的部分,反映在真實 DOM上,這種演算法叫做 DOM diff ,它可以極大提高網頁的效能表現。

這裡有幾點需要注意的:
1. componments只能是一個dom節點,你可以插入兩個標籤,但是一定要用一個標籤包裹在外面,也就是說最外層一定要是一個標籤

至此,我們的第一個react工程就寫好了,可以在瀏覽器中檢視效果,效果如下image

JXL語法

JSX是React的核心組成部分,它使用XML標記的方式去直接宣告介面,介面元件之間可以互相巢狀。可以理解為在JS中編寫與XML類似的語言,一種定義帶屬性樹結構(DOM結構)的語法,它的目的不是要在瀏覽器或者引擎中實現,它的目的是通過各種編譯器將這些標記編譯成標準的JS語言。

利用 JSX 編寫 DOM 結構,可以用原生的 HTML 標籤,也可以直接像普通標籤一樣引用 React 元件。這兩者約定通過大小寫來區分,小寫的字串是 HTML 標籤,大寫開頭的變數是 React 元件。

使用 HTML 標籤:

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

var myDivElement = <div className="foo" />;
render(myDivElement, document.getElementById('mountNode'));

HTML 裡的 class 在 JSX 裡要寫成 className,因為 class 在 JS 裡是保留關鍵字。同理某些屬性比如 for 要寫成 htmlFor

使用元件:

import React from 'react';
import { render } from 'react-dom';
import MyComponent from './MyComponet';

var myElement = <MyComponent someProperty={true} />;
render(myElement, document.body);

更多關於使用jsx的方法請見使用 JSX

component

component在react裡面可以認為是元件的意思,是react中最小也是最重要的組成部分,在概念上類似於模組

將程式碼封裝成元件之後我們就可以像插入普通html標籤一樣插入這個元件,React.createClass 方法就用於生成一個元件類(檢視 demo04)。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="container"></div>

<script type="text/babel">
var HellOMessage = React.createClass({
    render:function () {
        return <h1>Hello {this.props.name}</h1>
    }
});

    ReactDOM.render(
            <HellOMessage name="John" />
            ,document.getElementById('container')
    )
</script>
</body>
</html>

**因為這裡是插入的元件,所以HellOMessage首字母需要大寫

上面程式碼中,變數 HelloMessage 就是一個元件類。模板插入 <HelloMessage /> 時,會自動生成 HelloMessage 的一個例項(下文的”元件”都指元件類的例項)。所有元件類都必須有自己的 render 方法,用於輸出元件。

元件的用法與原生的 HTML 標籤完全一致,可以任意加入屬性,比如 <HelloMessage name="John"> ,就是 HelloMessage 元件加入一個 name 屬性,值為 John。元件的屬性可以在元件類的 this.props 物件上獲取,比如 name 屬性就可以通過 this.props.name 讀取。上面程式碼的執行結果如下。
image

this.props 物件的屬性與元件的屬性一一對應,但是有一個例外,就是 this.props.children 屬性。它表示元件的所有子節點(檢視 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
);

這裡需要注意, this.props.children 的值有三種可能:如果當前元件沒有子節點,它就是 undefined ;如果有一個子節點,資料型別是 object ;如果有多個子節點,資料型別就是 array 。所以,處理 this.props.children 的時候要小心。
React 提供一個工具方法 React.Children 來處理 this.props.children 。我們可以用 React.Children.map 來遍歷子節點,而不用擔心 this.props.children 的資料型別是 undefined 還是 object。更多的 React.Children 的方法,請參考官方文件

PropTypes

元件的屬性可以接受任意值,字串、物件、函式都可以。所以我們react提供了一種機制,驗證別人在使用我們的元件時提供的引數是否符合要求

元件類的PropTypes屬性,就是用來驗證元件例項的屬性是否符合要求(檢視 demo06)。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</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="container"></div>
<script type="text/babel">
var data = "123";

    var TestPropsType = React.createClass({
        propsType:{
            title:React.propsType.string.isRequired,
        },
        render: function () {
            return <h1>{this.props.title}</h1>
        }
    })

    ReactDOM.render(
            <TestPropsType title={data}/>


            ,document.getElementById('container')
    )
</script>
</body>
</html>

這就是驗證了在使用TestPropsType這個元件時title屬性是必須的並且傳入的引數是字串,如果將data 改為var data = 123title屬性就通不過驗證了,會報錯

獲取真實的DOM節點

從元件獲取真實 DOM 的節點,就要用到 ref 屬性(檢視 demo07 )。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</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="container"></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="text" value="Focus the text input" onClick={this.handleClick}/>
                    </div>
            )
        }
    })

    ReactDOM.render(
            <MyComponent/>
            ,document.getElementById('container')
    )
</script>
</body>
</html>

這個demo中,元件MyConmponent的子節點有一個文字輸入框,用於獲取使用者的輸入。所以我們必須獲取真實的DOM節點,所以在文字框的input標籤有一個ref屬性,然後this.refs.[redName]就會返回這個真實的DOM節點。

state

元件免不了要與使用者互動,React的一大創新就是jiang將元件看成是一個狀態機,一開始就有一個初始狀態,然後使用者互動,導致狀態的改變,從而重新渲染UI(檢視 demo08

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</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="container"></div>
<script type="text/babel">
    var LikeBtn = 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(
            <LikeBtn/>
            ,document.getElementById('container')
    )

</script>
</body>
</html>

在這個demo中,在元件LikeBtn中,首先為其定義了InitialState:liked:false,就是liked初始時的狀態為false,然後當用戶點選時,導致狀態的變化,this.setState方法就修改state,將liked修改,每次修改以後,自動呼叫 this.render 方法,再次渲染元件。

React state與props區別

stateprops都是描繪元件的特性的,所以有時候會容易混淆,所以來說一下兩者的區別

state可以理解為狀態,可以認為是元件自帶的性質,當發生互動效果的時候可以通過鉤子檢測到然後判斷是否需要改變狀態,狀態改變時元件也會進行相應的更新。比如在demo8裡面,初始的狀態為false,然後當我們點選元件(發生了互動,觸發事件),然後狀態改變liked的狀態變為true,因為狀態的改變,所以p中的文字就會做出相應的改變

props可以理解為屬性,是由父元件繼承來的,所以一旦定義就不能改變

props屬性的用法

鍵值對:值可以有多種形式

  • 字串:”XiaoWang”;
  • 求值表示式 {123}、{“XiaoWang”};
  • 陣列{[1,2,3]};
  • 變數{variable};
  • 函式求值表示式{function}(不推薦,如果需要函式可以單獨把函式提取出來然後單獨呼叫函式);

展開語法{…props}:

React會自動把物件中的屬性和值當做屬性的賦值

var HelloWorld =React.createClass({
     rencer:function () {
         return <p>Hello,{this.props.name1 + ' 'this.props.name2}</p>;
     },
 });
 var HelloUniverse = React.createClass({
     getInitialState:function () {
         return {
             name1:'Tim',
             name2:'John',
         };
     },
     handleChange: function (event) {
         this.setState({name: event.target.value});
     },
     render: function () {
         return <div>
         <HelloWorld name={...this.state}></HelloWorld>
         <br/>
         <input type="text" onChange={this.handleChange} />
         </div>
     },
 });
 ReactDom.render(<HelloUniverse />,document.body);

getDefaultProps

getDefaultProps 方法可以用來設定元件屬性的預設值。

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

  render: function() {
     return <h1> {this.props.title} </h1>;
   }
});
ReactDOM.render(<MyTitle />,document.body);

state狀態的用法

getInitialState

object getInitialState()

getInitialState方法用於定義初始狀態,也就是一個物件,這個物件可以通過 this.state 屬性讀取。在元件掛載之前呼叫一次。返回值將會作為 this.state 的初始值。

setState

setState(object nextState[, function callback])
合併 nextState 和當前 state。這是在事件處理函式中和請求回撥函式中觸發 UI 更新的主要方法。另外,也支援可選的回撥函式,該函式在 setState 執行完畢並且元件重新渲染完成之後呼叫。this.setState 方法用於修改狀態值,每次修改以後,自動呼叫 this.render 方法,再次渲染元件。

replaceState

replaceState(object nextState[, function callback])
類似於 setState(),但是刪除之前所有已存在的 state 鍵,這些鍵都不在 nextState 中。

表單

使用者在表單填入的內容,屬於使用者跟元件的互動,所以不能用 this.props 讀取(檢視 demo9 )。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</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="container"></div>
<script type="text/babel">
var InputTest = 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(
            <InputTest/>
            ,document.getElementById('container')
    )
</script>
</body>
</html>

上述程式碼的效果是有一個輸入框,輸入框下面是一個p標籤,第一次開啟輸入框和下面的文字都是Hello!然後改變輸入框的內容就會在下面顯示。

文字輸入框的值,不能用 this.props.value 讀取,而要定義一個 onChange 事件的回撥函式,通過 event.target.value 讀取使用者輸入的值。textarea 元素、select元素、radio元素都屬於這種情況

componments的生命週期

元件的生命週期分為三個狀態:
- Mounting:已插入真實DOM
- Updating: 正在被重新渲染
- Unmounting:已移除真實的DOM

React 為每個狀態都提供了兩種處理函式,will 函式在進入狀態之前呼叫,did 函式在進入狀態之後呼叫,三種狀態共計五種處理函式。
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()

此外,React 還提供兩種特殊狀態的處理函式。
- componentWillReceiveProps(object nextProps):已載入元件收到新的引數時呼叫
- shouldComponentUpdate(object nextProps, object nextState):元件判斷是否重新渲染時呼叫

這裡寫圖片描述

這裡寫圖片描述

下面是一個例子(檢視 demo10 )。

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

</head>
<body>
<div id="container"></div>
<script type="text/babel">
    var Hello = React.createClass({
        getInitialState:function () {
            alert('init')
            return{
                opacity:1.0
            }
        },

        componentWillMount:function () {
          alert('Will')
        },

        componentDidMount:function () {
            alert('Did')
            this.timer = setInterval(function () {
                var opacity = this.state.opacity;
                opacity -= 0.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('container')
    )
</script>
</body>
</html>

整理demo寫了三個函式:getInitialState、componentWillMount、componentDidMount可以通過彈出的內容來很直觀的知道到component的生命週期。

為什麼用bind函式,作為javascript初學者的我是這樣理解的,我們在componentDidMount函式中又增加了定時器函式setInterval,所以在定時器函式的內部使用this的話,this代表的是setInterval的例項物件,但是我們需要this指向componentDidMount的例項物件,所以需要用bind函式將this指向改變,還有一種寫法:

componentDidMount:function () {
            alert('Did')
            var _self  = this;
            _self.timer = setInterval(function () {
                var opacity = _self.state.opacity;
                opacity -= 0.05;
                if(opacity<0.1){
                    opacity = 1.0
                }
                _self.setState({
                    opacity: opacity
                });
            },100)
        },

這是在setInterval函式外,先將this對的複製給一個區域性變數 _self,然後在setInterval函式內使用這個區域性變數_self就可以在setInterval函式內使用componentDidMount中的this,這種好理解但是逼格不夠高,(  ̄ー ̄)(  ̄ー ̄)

AJAX

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

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

</head>
<body>
 <div id="container"></div>

 <script type="text/babel">
     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.lastGistUrl}>here</a>.
                 </div>
             )
         }
     })

     ReactDOM.render(
         <UserGist source="https://api.github.com/users/octocat/gists"/>
         ,document.getElementById('container')
     )
 </script>
</body>
</html>