Reactjs 踏坑指南2: JSX&&元件

艾倫先生發表於2017-12-14

JSX 知識準備

JSX 並不是一門全新的語言,僅僅是一個語法糖,允許開發者在javascript中編寫XML語言。這樣使用JavaScript來構建元件以及元件之間關係的應用,在程式碼層面顯得更加清晰,而不再是使用JavaScript操作DOM來建立元件以及元件之間的巢狀關係。

作為是React的核心部分,JSX使用XML標記的方式直接宣告頁面。在JavaScript程式碼裡直接寫XML的語法,每一個XML標籤都會被JSX轉換工具轉換成純JavaScript程式碼。HTML直接寫到JavaScript中不加任何分號,這就是JSX的語法奧義

JSX環境準備

JSX必須藉助ReactJS環境才能執行。在編寫JSX程式碼之前,需要先載入ReactJS檔案,比react.js.光有ReactJs還不行,還需要載入JSX解析器。下面大家一起準備好一個最基礎的ReactJS環境

  • 建立一個test.html檔案

  • <head>標籤中引入如下檔案

      <script src="http://cdn.bootcss.com/react/15.2.0/react.js"></script>
      <script src="http://cdn.bootcss.com/react/15.2.0/react-dom.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
    複製程式碼

這樣環境就搭建好了,然後按照慣例,雙手奉上一個Hello World 程式。

  <body>
    <div id="example"></div>
    <script type="text/babel">
      var HelloComponent = React.createClass({
        render: function(){
          return <span>Hello World !</span>
        }
      });
      
      ReactDOM.render(
        <HelloComponent />,
        document.getElementById('example')
      );
    </script>
  </body>
複製程式碼

執行結果

Reactjs 踏坑指南2: JSX&&元件

如上面程式碼,我們在JavaScript中書寫HTML標籤時,不再像以前那樣作為字串用引號引起來,而是想在XML檔案中書寫一樣,直接寫即可。

除了<span>Hello World !</span>這種直接使用的HTML標籤外,還有一個<HelloComponent />標籤。這個是ReactJS建立的元件類標籤,通過這種方式,把建立的HelloComponent元件引用進來。ReactJS約定,自定義的元件標籤首字母一定要大些,用來區分是元件標籤還是HTML標籤。

注意幾點

  • script 標籤的 type 屬性為 text/babel,這是React 獨有的 JSX 語法,跟 JavaScript 不相容。凡是在頁面中直接使用 JSX 的地方,都要加上 type="text/babel"。
  • 一共用了三個庫: react.js 、react-dom.js 和 browser.min.js ,它們必須首先載入。其中,react.js 是 React 的核心庫,react-dom.js 是提供與 DOM 相關的功能, browser.min.js的作用是將 JSX 語法轉為 JavaScript 語法。
  • 將 JSX 語法轉為 JavaScript 語法,這一步很消耗時間。現在前端專案,都會使用前端工程化,不會直接在html頁面中直接寫js程式碼,寫好的js程式碼都會使用工具進行編譯壓縮等。這樣的話,我們的jsx也會通過編譯直接轉化成js語法,讓瀏覽器直接使用。

JSX執行JavaScript表示式

JSX使用{}來執行JavaScript表示式。

var arr = [
 <h1>Hello world!</h1>,
 <h2>React is awesome</h2>,
];
ReactDOM.render(
 <div>{arr}</div>,
 document.getElementById('example')
);
複製程式碼

JSX定義屬性&&樣式使用

HTML中可以通過標籤上的屬性來改變當前元素的樣式,當然,這在JSX中也是可以的,只不過JSX在使用行內樣式的時候是有缺陷的。。使用{{}}而不是引號的方式,如下

React.render(
    <div style={{color:'red',marginTop:'20px'}}>
        xxxxx
    </div>,
    document.body
);
複製程式碼

注:直接在標籤上使用style屬性時,要寫成style={{}}是兩個大括號,外層大括號是告知jsx這裡是js語法,和真實DOM不同的是,屬性值不能是字串而必須為物件,需要注意的是屬性名同樣需要駝峰命名法。即margin-top要寫成marginTop,屬性之間以逗號間隔。還有class 屬性需要寫成 className ,for 屬性需要寫成 htmlFor ,這是因為 class 和 for 是 JavaScript 的保留字。

JSX中的延展屬性

在JSX中我們可以使用ES6中的最新語法...來遍歷物件

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

  var Lucy = {
    name: "feng",
    votes: "23"
  }

  ReactDOM.render(
  <HelloMessage {...Lucy} />,
    document.getElementById('example')
  );
複製程式碼

JSX中的事件繫結

JSX支援事件的繫結,語法類似於HTML中事件的繫結,不同的是這裡事件的名稱必須按照駝峰式,下面給個例子大家參考一下。

var HelloComponent = React.createClass({
    testClick: function (str) {
      alert('hello ' +  str)
    },
    render: function() {
      return (
          <p
          onClick={this.testClick.bind(this, 'feng')} style={{olor:'#ff6600',width:'200px', height:'100p'}}
          >
          Click me
          </p>
          )
        
    }
  })

  
  ReactDOM.render(
  <HelloComponent  />,
    document.getElementById('example')
  );
複製程式碼

Reactjs 踏坑指南2: JSX&&元件

知曉了JSX的基本使用之後,開始介紹ReactJS的元件的概念和使用方法

元件初步

ReactJS的基礎就是例項化,即按功能封裝成一個又一個的元件,各個元件維護自己的狀態和UI,當狀態改變時,會自動重新渲染整個元件,多個元件一起協作構成了整個ReactJS應用。

以一個簡單的Hello World開始

<head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="http://cdn.bootcss.com/react/15.2.0/react.js"></script>
    <script src="http://cdn.bootcss.com/react/15.2.0/react-dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
<head>
<body>
    <div id="example"></div>
    <script type="text/babel">
          var HelloMessage = React.createClass({
            render: function() {
                  return <div>Hello,{this.props.name}</div>
            }
          })
          ReactDOM.render(<HelloMessage name="World" />, document.getElementById('example'))
    </script>
</body>
複製程式碼

執行結果:

執行結果

審查元素髮現DOM結構如下:

Reactjs 踏坑指南2: JSX&&元件

上面的程式碼中,我們呼叫React提供的工廠方法來建立元件,其中:

  • React是全域性物件,ReactJS的所有頂級API都掛在這個物件下。
  • React.createClass是ReactJS用來建立元件類的方法,這個物件必須包含一個render方法和若干可選的生命週期(詳情請見下文)方法,其中
  • render是一個元件級的API,是React.createClass內部最常用的API,該方法主要用來返回元件結構
  • React.render(這裡我用的是ReactDOM.render)是最常用的方法,用於將模板轉換成HTML語言,並將轉換後的DOM結構插入到制定的DOM節點上
  • 簡而言之就是:先通過React.createClass來建立一個元件類,呼叫元件的render方法輸出元件的DOM結構,最後呼叫React.render將組建插入到id為example的節點上

這裡我們需要保證render函式是純函式:即同樣的輸入始終返回相同的輸出,並且執行過程中沒有副作用(和DOM互動或者發Ajax請求)。但一個元件要和DOM互動或者發Ajax請求需求是很正常的,那麼就要用到其他生命週期方法了。

##元件的狀態與屬性

元件本質上是狀態機,輸入確定,輸出一定確定。元件把狀態與結果一一對應起來,元件中有state與prop(狀態與屬性)。

  • 屬性(props)是由父元件傳遞給子元件的;
  • 狀態(state)是子元件內部維護的資料,當狀態發生變化的同時,元件也會進行更新。當狀態發生轉換時會觸發不同的鉤子函式(詳見下文中生命週期),從而讓開發者有機會做出相應。

props屬性的用法

props是一個物件,是組建用來接收外面傳來的引數,元件內部是不允許修改自己的props屬性的,只能夠通過父元件來修改。具體的使用方法如下

  • 接收鍵值對資料

    <HelloWorld name= ? />

    1. 字串:"XiaoFeng"

    2. 求值表示式 {123}、{"XiaoFeng"}

    3. 陣列{[1,2,3]}

    4. 變數{variable}

    5. 函式求值表示式{function}(不推薦,如果需要函式可以單獨把函式提取出來然後單獨呼叫函式)

       var HelloWorld =React.createClass({
        render:function () {
             console.log(this.props)
            return <p>Hello,{this.props.name ? this.props.name : " World"}</p>;
        },
       });
       var HelloUniverse = React.createClass({
          getInitialState:function () {
              return {
                  name:''
              };
          },
          handleChange: function (event) {
              this.setState({name: event.target.value});
          },
          render: function () {
              return (<div>
              <HelloWorld name={this.state.name}></HelloWorld>
              <br/>
              <input type="text" onChange={this.handleChange} />
              </div>
              );
          }
       });
       ReactDOM.render(<HelloUniverse />,example);
      複製程式碼

      Reactjs 踏坑指南2: JSX&&元件

  • setProps

    已過時,即將廢棄

  • propTypes

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

      var MyTitle = React.createClass({
        propTypes: {
          title: React.PropTypes.string.isRequired,
        },
        render: function() {
           return <h1> {this.props.title} </h1>;
         }
      });
    複製程式碼

    上面的Mytitle元件有一個title屬性。PropTypes 告訴 React,這個 title 屬性是必須的,而且它的值必須是字串。現在,我們設定 title 屬性的值是一個數值。

      var data = 123;
      ReactDOM.render(
        <MyTitle title={data} />,
        document.body
      );
    複製程式碼

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

  • 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);
    複製程式碼
  • this.props.children

    this.props 物件的屬性與元件的屬性一一對應,但是有一個例外,就是 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>,example
      );
    複製程式碼

    上面程式碼的 NoteList 元件有兩個 span 子節點,它們都可以通過 this.props.children 讀取。這裡需要注意, this.props.children 的值有三種可能:

    1. 如果當前元件沒有子節點,它就是 undefined;
    2. 如果有一個子節點,資料型別是 object;
    3. 如果有多個子節點,資料型別就是 array

    React 提供一個工具方法 React.Children 來處理 this.props.children 。我們可以用 React.Children.map 來遍歷子節點,而不用擔心 this.props.children 的資料型別是 undefined 還是 object。

state屬性的用法

React把元件當成狀態機,一旦使用者互動導致狀態發生變化,就會觸發重新渲染UI。this.state會隨著使用者的互動而發生變化。

  • 工作原理

    常用的通知 React 資料變化的方法是呼叫 setState(data, callback)。這個方法會合並(merge) data 到 this.state,並重新渲染元件。渲染完成後,呼叫可選的 callback 回撥。大部分情況下不需要提供 callback,因為 React 會負責把介面更新到最新狀態。

  • getInitialState

    object getInitialState()

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

  • setState

    setState(object nextState[, function callback])

    this.setState 方法用於修改狀態值,每次修改以後,自動呼叫 this.render 方法,再次渲染元件。

    注意:

    1. 絕對不要直接改變 this.state,因為在之後呼叫 setState() 可能會替換掉你做的更改。把 this.state 當做不可變的。
    2. setState()不會立刻改變 this.state,而是建立一個即將處理的state轉變。在呼叫該方法之後獲取 this.state 的值可能會得到現有的值,而不是最新設定的值。
    3. 不保證 setState()呼叫的同步性,為了提升效能,可能會批量執行state轉變和DOM 渲染。
    4. setState ( ) 將總是觸發一次重繪,除非在 shouldComponentUpdate()中實現了條件渲染邏輯。如果使用可變的物件,但是又不能在shouldComponentUpdate()中實現這種邏輯,僅在新 state 和之前的 state 存在差異的時候呼叫 setState()可以避免不必要的重新渲染。

    常用的模式是建立多個只負責渲染資料的無狀態(stateless)元件,在它們的上層建立一個有狀態(stateful)元件並把它的狀態通過 props 傳給子級。這個有狀態的元件封裝了所有使用者的互動邏輯,而這些無狀態元件則負責宣告式地渲染資料。

元件的生命週期

生命週期各階段

在整個ReactJS的宣告週期中,主要會經歷這四個階段:建立階段、例項化階段、更新階段、銷燬階段。下面通過一段程式碼,來深入瞭解元件各個環節的運作流程

  var OneComponent = React.createClass({
    //1.建立階段
    getDefaultProps: function() {
      //在建立類的時候被呼叫
      console.log('getDefaultProps');
      return {}
    },

    //2.例項化階段
    getInitialState: function() {
      //獲取this.state的預設值
      console.log('getInitialState');
      return {};
    },
    componentWillMount: function() {
      //在render之前呼叫此方法
      //業務邏輯的處理都應該放在這裡,比如對state的操作等
      console.log('componentWillMount')
    },
    render: function() {
      //渲染並返回一個虛擬DOM
      console.log('render');
      return (
          <div> hello <b> {this.props.name} </b></div>
        )
    },
    componentDidMount: function() {
      //該方法發生在render方法之後
      //在該方法中,ReactJS會使用render方法返回的虛擬DOM物件來建立真實DOM結構
      console.log('componentDidMount');
    },

    //3.個更新階段
    componentWillReceieveProps: function() {
      //該方法發生在this.props被修改或富元件呼叫setProps()方法之後
      console.log('componentWillRecieveProps');
    },
    shouldComponentUpdate: function() {
      //是否需要更新
      console.log('shouldComponentUpdate');
      return true;
    },
    componentWillUpadate: function() {
      //將要更新
      console.log('componentWillUpadate');
    },
    componentDidUpdate: function() {
      //更新完畢
      console.log('componentDidUpdate');
    },
    //4.銷燬階段
    componentWillUnmout: function() {
      //銷燬時被呼叫
      console.log('componentWillUnmout');
    }
  })

  
  ReactDOM.render(
  <OneComponent  name="World "/>,
    document.getElementById('example')
  );
複製程式碼

建立階段:

該階段主要發生在建立元件類的時候,即在呼叫React.createClass的時候。這個階段只會觸發一個getDefaultProps方法,該方法會返回一個物件,並快取下來。然後與富元件制定的props物件合併,最後賦值給this.props作為該元件的預設屬性。

例項化階段

該階段主要發生在例項化元件類的時候,也就是元件類被呼叫的時候。

  ReactDOM.render(
  <OneComponent  name="World "/>,
    document.getElementById('example')
  );
複製程式碼

該元件被呼叫的時候,這個階段會觸發一系列的流程,具體的執行順序如下所示。

  • getInitialState。初始化元件的state的值,其返回值會賦值給元件的this.state屬性。

  • componentWillMount 在渲染(掛載)之前呼叫一次。

    此時this.refs物件為空。如果在此時呼叫this.setState()則會更新this.state物件,而render只會呼叫一次。

  • render 根據state的值,生成頁面需要的虛擬DOM結構,並返回該結構

  • componentDidMount 在渲染之後呼叫一次。根據虛擬DOM結構而生成的真實DOM進行相應的處理,元件內部可以通過this.getDOMNode()來獲取當前元件的節點,然後就可以像在web開發中那樣操作裡面的DOM元素了。

      componentDidMount () {
        const textbox = React.findDOMNode(this.refs.text)
        if (this.props.autoFocus) textbox.focus()
      }
    複製程式碼

更新階段

主要發生在使用者操作或者元件有更新的時候,此時會根據使用者的操作行為進行相應的頁面結構的調整。該階段也會觸發一系列的流程,具體的執行順序如下所示。

  • componentWillReceiveProps 在將要接收props時呼叫。在該函式中,通常可以呼叫this.setState方法來完成對state的修改。

    props是父元件傳遞給資組建的。父元件發生render的時候子元件就會呼叫componentWillReceiveProps(不管props有沒有更新,也不管父子元件之間有沒有資料交換)。

      componentWillReceiveProps (nextProps) {
        if (this.props.disabled !== nextProps.disabled) {
      // disabled這個屬性改變了
        }
      }
    複製程式碼
  • shouldComponentUpdate:該方法用來攔截新的props或state,然後根據事先設定好的判斷邏輯,返回值決定元件是否需要update。

    元件掛載之後,每次呼叫setState後都會呼叫shouldComponentUpdate判斷是否需要重新渲染元件。預設返回true,需要重新render。在比較複雜的應用裡,有一些資料的改變並不影響介面展示,可以在這裡做判斷,優化渲染效率。

      shouldComponentUpdate (nextProps, nextState) {
          // 比較props或者states,返回true則更新照常,返回false則取消更新,且不會呼叫下面的兩個生命週期函式
      }
    複製程式碼
  • componentWillUpdate :元件更新之前呼叫一次。當上一部中shouldComponentUpdate方法返回true時候,就可以在該方法中做一些更新之前的操作。

  • render 根據一系列的diff演算法,聲稱需要更心動額虛擬DOM資料。實際表明,在render中,最好只做資料和模板的結合,而不進行state等邏輯的修改,這樣元件結構更清晰。

  • componentDidUpdate 元件的更新已經同步到DOM中後出發。

    除了首次render之後呼叫componentDidMount,其它render結束之後都是呼叫componentDidUpdate。

銷燬階段

  • componentWillUnmount 會在元件即將從掛載點移去時呼叫,用來取出去除即將被銷燬的DOM節點的引用,或者是清除計時器,取消監聽的時間等等。

注意:

絕對不要在componentWillUpdate和componentDidUpdate中呼叫this.setState方法,否則將導致無限迴圈呼叫。

componentWillMount、componentDidMount和componentWillUpdate、componentDidUpdate可以對應起來。區別在於,前者只有在掛載的時候會被呼叫;而後者在以後的每次更新渲染之後都會被呼叫。

元件生命週期流程圖

Reactjs 踏坑指南2: JSX&&元件

圖片來自網路文章

元件之間的通訊

先建立一個父類元件Parent,它內部呼叫一個叫做Child的子元件,並將接收到的外部引數name傳遞給子元件Child。

var Parent = React.createClass({
    handleClick: function() {
      this.refs.myTextInput.focus();
    },
    render: function() {
      return (
          <div onClick={this.handleClick}>
            <input type="text" ref="myTextInput" />
            Parent is:
              <Child name={this.props.name}></Child>
          </div>
        )
    }
});
複製程式碼

在建立一個子類元件Child

  var Child = React.createClass({
    render: function() {
      return <span>{this.props.name}</span>
    }
  });
複製程式碼

自後通過React.render方法將元件渲染到頁面上

  ReactDOM.render(
    <Parent name="React" />,document.getElementById('example')
  );
複製程式碼

執行結果

Reactjs 踏坑指南2: JSX&&元件

整個應用的功能是父元件接收傳入的使用者名稱稱,並將用於名稱傳給子元件,最後再由子元件渲染顯示到頁面上。這些元件直接按是怎麼通訊的呢?下面通過兩方面介紹一下

  • 子元件呼叫父元件的方法。從上面的例子可以看出,子元件要拿到父元件的屬性,需要通過this.props方法。所以,如果子元件想要呼叫父元件的方法,只需要父元件把要被呼叫的方法以屬性的方式放在子元件上,子元件內部便可通過"this.props.被呼叫的方法"這樣的方式拿到name屬性的。然後,每次父元件修改了傳入的name屬性,子元件便會得到通知,然後自動獲取新的name屬性
  • 父元件呼叫子元件的方法。子元件呼叫父元件是通過prop屬性,而反過來,父元件呼叫子元件通過的就是 ref 屬性。子元件被設定了ref屬性之後,父元件便可以通過this.ref.xxx來獲取到子元件了,其中xx為子元件的ref值。

元件實踐

1. 靜態元件

var MyComponent=React.createClass({
  render: function() {
   return <h1 className="my-h1" style={{color:'red'}}>Hello world!</h1>;
 }
});
ReactDOM.render(
 <MyComponent />,
 document.getElementById('example')
);
複製程式碼

執行結果

執行結果

2. 動態元件

var Counter = React.createClass({
    incrementCount: function(){
        this.setState({
            count: this.state.count + 1
        });
    },
    getInitialState: function(){
        return {
            count: 0
        }
    },
    render: function(){
        return (
            <div className="my-component">
                <h1>Count: {this.state.count}</h1>
                <button type="button" onClick={this.incrementCount}>{this.props.name}</button>
            </div>
        );
    }
});

ReactDOM.render(
    <Counter name="遞增按鈕" />,
    document.getElementById('mount-point')
);
複製程式碼

執行結果:

遞增按鈕

參考內容

相關文章