React技術

React技術

注意:本章節運行環境在上一章節React項目構建之上。

簡介

  • React是Facebook開發並開源的前端框架。
  • 當時他們的團隊在市面上沒有找到合適的MVC框架,就自己寫了一個Js框架,用來架設Instagram(圖片分享社交 網絡)。2013年React開源。
  • React解決的是前端MVC框架中的View視圖層的問題。

Virtual DOM

  • DOM(文檔對象模型Document Object Model)
    001

  • 將網頁內所有內容映射到一棵樹型結構的層級對象模型上,瀏覽器提供對DOM的支持,用戶可以是用腳本調用DOM API來動態的修改DOM結點,從而達到修改網頁的目的,這種修改是瀏覽器中完成,瀏覽器會根據DOM的改變重繪改變的DOM結點部分。

  • 修改DOM重新渲染代價太高,前端框架爲了提高效率,儘量減少DOm的重繪,提出了Virtual DOM。Virtual DOM是一個JavaScript對象,性能更高。所有的修改都是先生成一個新的Virtual DOM,通過比較算法比較新舊Virtual DOM,得到差異化Virtual DOM,將這部分差異更新到瀏覽器DOM,瀏覽器只需要渲染這部分變化就行了。
    React實現了DOM Diff算法可以高效比對Virtual DOM。

支持JSX語法

JSX是一種JavaScript和XML混寫的語法,是JavaScript的擴展。

React.render(
    <div>
        <div>
            <div>content</div>
        <div/>
    </div>,
    document.getElementById('example')
)

測試程序

  • 替換/src/index.js爲先的代碼
import React from 'react';
import ReactDom from 'react-dom';

class Root extends React.Component{
  render(){
    return <div>hello xdd</div>
  }
}

ReactDom.render(<Root/>,document.getElementById("root"))
  • 保存文件後,會自動編譯,並重新裝載刷新瀏覽器端頁面。

002
react_005

程序解釋

  1. import React from 'react';導入react模塊
  2. import ReactDOM from 'react-dom';導入react的DOM模塊
  3. class Root extends React.Component組件類定義,從React.Component類上繼承。這個類生成JSXElement對象即React元素。
  4. render()渲染函數。返回組件中渲染的內容。注意,只能返回唯一一個頂級元素回去。
  5. ReactDom.render(<Root/>,document.getElementById('root'));第一個參數是JSXElement對象,第二個是DOM的Element元素。將React元素添加到DOM的Element元素中並渲染。
  • 還可以使用React.createElement創建react元素,第一個參數是React組件或者一個HTML的標籤名稱(例如div、span)。
return React.createElement('div',null,'hello hello xdd');
ReactDom.reader(React.createElement(Root),document.getElementById("root"));
  • 很明顯JSX更簡潔易懂,推薦使用JSX語法。

  • 增加一個子元素

import React from 'react';
import ReactDom from 'react-dom';

class SubEle extends React.Component{
  render(){
    return <div>Sub content</div>;
  }
}

class Root extends React.Component{
  render(){
    return(<div><h2>Welcome xdd.com</h2><br/> <SubEle/></div>);
  }
}

ReactDom.render(<Root />,document.getElementById('root'));

003

  • 注意:
    1. React組件的render函數return返回,只能是一個頂級元素
    2. JSX語法是XML,要求所有元素必須閉合,注意<br /> 不能寫成<br>

JSX規範

  • 約定標籤中首字母小寫就是html標記,首字母大寫就是組件
  • 要求嚴格的HTML標記,要求所有標籤都必須閉合。br也應該寫成<br />,/前留一個空格。
  • 單商省略小括號,多行請使用小括號
  • 元素有嵌套,建議多行,注意縮進
  • JSX表達式:表達式使用{}括起來,如果大括號內使用了引號,會當做字符串處理,例如<div>{'2>1?true:false'}</div>裏面的表達式成了字符串了。

組件狀態state

  • 每一個React組件都有一個狀態屬性state,它是一個JavaScript對象,可以爲它定義屬性來保存值。如果狀態變化了,會觸發UI的重新渲染。使用setState()方法可以修改state值。

  • 注意:state是每一個組件自己內部使用的,是組件自己的屬性。

  • 1.示例:依然修改/src/index.js

import React from 'react';
import ReactDom from 'react-dom';

class Root extends React.Component{
  //定義一個對象
  state = {p1:'www.hao123',p2:'.com'};//構造函數中定義state
  render(){
    this.state.p1 = 'python.xdd';// 可以修改屬性值
    // this.setState(p1:'python.xddabc');//不可用對還在更新中的state使用setState。會觸發遞歸調用render函數
    // Warning: setState(...): Cannot update during an existing state transition (such as within render). 
    setTimeout(()=> this.setState({p1:'python.xdd'}),1000);
    return(
      <div>
        <div>Welcome to {this.state.p1}{this.state.p2}</div>
      </div>
    );
  }
}

ReactDom.render(<Root />,document.getElementById('root'));
  • 2.複雜例子

先看一個網頁

<html>

<head>
    <script type="text/javascript">
        function getEventTrigger(event) {
            x = event.target; // 從事件中獲取元素
            alert("觸發的元素的id是:" + x.id);
        }
    </script>
</head>

<body>
    <div id="t1" onmousedown="getEventTrigger(event)"> 點擊這句話,會觸發一個事件,並彈出一個警示框 </div>
</body>

</html>
  • div的id是t1,鼠標按下事件捆綁了一個函數,只要鼠標在對象上按下就會觸發調用getEventTrigger函數,瀏覽器 會送給它一個參數event。event是事件對象,當事件觸發時,event包含觸發這個事件的對象。

  • HTML DOM的JavaScript事件

屬性 此事件發生在何時
onabort 圖像加載被中斷
onblur 元素失去焦點
onchange 域的內容被改變
onclick 當用戶點擊某個對象時調用的事件句柄
ondblclick 當用戶雙擊某個對象時調用的事件句柄
onerror 在加載文檔或圖像時發生錯誤
onfocus 元素獲得焦點
onkeydown 某個鍵盤按鍵被按下
onkeypress 某個鍵盤按鍵被按下並鬆開
onkeyup 某個鍵盤按鍵被鬆開
onload 一張頁面或一幅圖像完成加載
onmousedown 鼠標按鈕被按下
onmousemove 鼠標被移動
onmouseout 鼠標從某元素移開
onmouseover 鼠標移到某元素之上
onmouseup 鼠標按鍵被鬆開
onreset 重置按鈕被點擊
onresize 窗口或框架被重新調整大小
onselect 文本被選中
onsubmit 確認按鈕被點擊
onunload 用戶退出頁面
  • 使用React實現上面的傳統的HTML
import React from 'react';
import ReactDom from 'react-dom';

class Toggle extends React.Component{
  state = {flag:true}; //類中定義state

  handleClick(event){
    console.log(event.target.id);
    console.log(event.target === this);
    console.log(this);
    console.log(this.state);
    this.setState({flag:!this.state.flag});
  }

  render(){
    /*注意一定要綁定this onClick寫成小駝峯 */
    return <div id='xdd' onClick={this.handleClick.bind(this)}>
      點擊這句話會觸發一個事件。{this.state.flag.toString()}
    </div>;
  }
}

class Root extends React.Component {
  //定義一個對象
  state = {p1:'www.xdd',p2:'.com'};//構造函數中定義state
  render(){
    return(
      <div>
        <div>Welcome to {this.state.p1}{this.state.p2}</div>
        <br />
        <Toggle />
      </div>
    )
  }
}

ReactDom.render(<Root />,document.getElementById('root'))

004

  1. Toggle類分析
    1. 它有自己的state屬性
    2. 當render完成後,網頁上有一個div標籤,div標籤對象捆綁了一個click事件的處理函數,div標籤內有文本內容。如果通過點擊左鍵,就觸發了click方法關聯的handleClick函數,在這個函數裏將狀態值改變。
      • 使用this.state.flag = !this.state.flag方式改變,不會觸發render函數執行
      • 使用this.setSatte({flag:!this.state.flag})方式,會觸發該組件和其子組件render函數的執行
    3. 狀態值state的改變將引發render重繪。
    4. 如果組件自己的state變了,只會觸發自己的render方法重繪。
  • 注意:

    1. {this.handleClick.bind(this)},不能外加引號
    2. this.handleClick.bind(this)一定要綁定this,否則當觸發捆綁的函數時,this是函數執行的上下文決定的,this已經不是觸發事件的對象了。
    3. console.log(event.target.id),取回的參數事件的對象的id,但是這不是我們封裝的組件對象。所以,console.log(event.target===this)是false。所以這裏一定要用this,而這個this是通過綁定來的。
    4. this寫在類中,始終指的是React組件示例本身。
  • React中的事件

    1. 使用小駝峯命名
    2. 使用JSX表達式,表達式中指定事件處理函數
    3. 不能使用return false,如果要阻止事件默認行爲,使用event.preventDefault()

屬性props

  • props就是組件的屬性properties。
  • 把React組件當做標籤使用,可以爲其增加屬性。如:<Toggle name="school" parent={this} />
    1. 爲上面的Toggle元素增加屬性:
      • name = "school",這個屬性會作爲一個單一的對象傳遞給組件,加入到組件的props屬性中
      • parent = {this},注意這個this是在Root元素中,指的是Root組件本身
      • 在Root中爲使用JSX語法爲Toggle增加子元素,這些子元素也會被加入Toggle組件的props.children中
import React from 'react';
import ReactDom from 'react-dom';

class Toggle extends React.Component{
  state = {flag:true}; //類中定義state

  handleClick(event){
    console.log(event.target.id);
    console.log(event.target === this);
    console.log(this);
    console.log(this.state);
    this.setState({flag:!this.state.flag});
  }

  render(){
    /*注意一定要綁定this onClick寫成小駝峯 */
    return <div id='xdd' onClick={this.handleClick.bind(this)}>
      點擊這句話會觸發一個事件。{this.state.flag.toString()}<br />
      顯示props<br />
      {this.props.name}:{this.props.parent.state.p1}{this.props.parent.state.p2}
      {this.props.children}
    </div>;
  }
}

class Root extends React.Component {
  //定義一個對象
  state = {p1:'www.xdd',p2:'.com'};//構造函數中定義state
  render(){
    return(
      <div>
        <div>Welcome to {this.state.p1}{this.state.p2}</div>
        <br />
        <Toggle name= "school" parent={this} >{/*自定義2個屬性通過props傳給Togle組件對象 */}
          <hr />{/*子元素通過props.children訪問 */}
          <span>我是Toggle元素的子元素</span>{/*子元素通過props.children訪問 */}
        </Toggle>
      </div>
    )
  }
}

ReactDom.render(<Root />,document.getElementById('root'))

005

注意:props中的屬性值不能被修改,否則會拋出 TypeError: Cannot assign to read only property 'name' of object '# <Object>異常。也就是說Props在組件內部不能修改,只讀。但可以通過重繪組件重新定義props內的屬性。

應該說state是私有private的,屬於組件自己的屬性,組件外無法直接訪問。可以修改state,但是建議使用setState方法。
props是共有public屬性,組件外也可以訪問,但組件內只讀。
props是一種組件外部傳入向組件內部傳入數據的一種方式,只不過採用標籤屬性的方式。

構造器constructor

  • 使用ES6的構造器,要提供一個參數props,並把這個參數使用super傳遞給父類
import React from 'react';
import ReactDom from 'react-dom';

class Toggle extends React.Component{
  // state = {flag:true}; //類中定義state

  constructor(props){
    super(props);//一定要調用super父類構造器,否則會報錯
    this.state = {flag:true}; //類中定義state
  }

  handleClick(event){
    console.log(event.target.id);
    console.log(event.target === this);
    console.log(this);
    console.log(this.state);
    this.setState({flag:!this.state.flag});
  }

  render(){
    /*注意一定要綁定this onClick寫成小駝峯 */
    return <div id='xdd' onClick={this.handleClick.bind(this)}>
      點擊這句話會觸發一個事件。{this.state.flag.toString()}<br />
      顯示props<br />
      {this.props.name}:{this.props.parent.state.p1}{this.props.parent.state.p2}
      {this.props.children}
    </div>;
  }
}

class Root extends React.Component {
  //定義一個對象
  state = {p1:'www.xdd',p2:'.com'};//構造函數中定義state
  render(){
    return(
      <div>
        <div>Welcome to {this.state.p1}{this.state.p2}</div>
        <br />
        <Toggle name= "school" parent={this} >{/*自定義2個屬性通過props傳給Togle組件對象 */}
          <hr />{/*子元素通過props.children訪問 */}
          <span>我是Toggle元素的子元素</span>{/*子元素通過props.children訪問 */}
        </Toggle>
      </div>
    )
  }
}

ReactDom.render(<Root />,document.getElementById('root'))

組件的聲明週期

  • 組件的生命週期可分成三個狀態:
    1. Mounting:已插入真實DOM
    2. Updating: 正在被重新渲染
    3. Unmounting: 已移出真實DOM
  • 組件的生命週期狀態,說明在不同時機訪問組件,組件正處在生命週期的不同狀態上。
  • 在不同的生命週期狀態訪問,就產生不同的方法。
  • 生命週期方法如下:
    1. 裝載組件時觸發
      • componentWillMount在渲染前調用,在客戶端也在服務端。只會在裝載之前調用一次。
      • componentDidMount:在第一次渲染後調用,只在客戶端。之後組件已經生成了對應的DOM結構,可以通過this.getDOMNode()來進行訪問。如果你想和其他JavaScript框架一起使用,可以在這個方法中調用setTimeout,setInterval或者發送AJAX請求等操作(防止異步操作阻塞UI)。只在裝載完成後調用一次,在render之後。
    2. 更新組件觸發。這些方法不好在首次render組件的週期調用。
      • componentWillReceiveProps(nextProps)在組件接收到一個新的prop時被調用。這個方法在初始化render時補回被調用。
      • shouldComponentUpdate(nextProps,nextState)返回一個布爾值。在組件接收到新的props或者state時被調用。初始化時或者使用forceUpdate時不被調用。
        • 可以在你確認不需要更新組件時使用。
        • 如果設置爲false,就是不允許更新組件,那麼componentWillUpdate、componentDidUpdate不會執行。
      • componentWillUpdate(nextProps,nextState)在組件接收到新的props或者state但還沒有render時被調用。在初始化時補回被調用。
      • componentDidUpdate(prevProps,prevState)在組件完成更新後立即調用。在初始化時不會被調用。
    3. 卸載組件觸發
      • componentWillUnmount在組件從DOM中移除的時候立刻被調用。

006

由圖可知:

  • constructor構造器是最早執行的函數。
  • 組件構建好之後,如果更新組件的state或props(注意在組件內props是隻讀的),就會在render渲染前觸發一系列的更新聲明周期函數
  • 重新編寫/src/index.js構造兩個組件,在子組件Sub中,加入所有生命週期函數。下面例子添加是裝載、卸載的生命週期函數
import React from 'react';
import ReactDom from 'react-dom';

class Sub extends React.Component{
  constructor(props){
    console.log('Sub constructor');
    super(props);
    this.state = {count:0};
  }

  handleClick(){
    this.setState({count:this.state.count+1});
  }

  /* 組件實例化後被調用。。 */
  componentWillMount(){
    // constructor之後,第一次render之前
    console.log('---------渲染前被調用了componentWillMount------')
  }

  /**第一次render調用後 */
  componentDidMount(){
    // 第一次render後
    console.log('------第一次render調用後 componentDidMount----')
  }

  /* Props被改變,將要更新才調用 */
  componentWillReceiveProps(nextProps){
    // props變更時,接到新props了,交給shouldComponentUpdate
    // props組件內只讀,只能從外部改變
    console.log('---------------componentWillReceiveProps start-------------------')
    console.log(this.props);
    console.log(nextProps);
    console.log("--------Props變更了,componentWillReceiveProps end----------",this.state.count)
  }

  /**將要更新,同意更新後,真正更新前,之後調用render */
  componentWillUpdate(nextProps,nextState){
    // 是否組件更新,props或state方式改變時,返回布爾值,true纔會更新
    console.log('------state或props更新前-----------',this.state.count,nextState)
  }

  /**是否組件更新,props或者state方式改變時,返回布爾值,true纔會更新Dom界面。阻止不了值的變化 */
  shouldComponentUpdate(nextProps,nextState){
    // 是否組件更新,props或state方式改變時,返回布爾值,true纔會更新
    console.log('---有組件發生了更新----shouldComponentUpdate----------',this.state.count,nextState);
    return true; //return false將攔截Dom的更新
  }

  componentDidUpdate(prevProps,prevState){
    //同意更新後,真正更新後,在render之後調用
    console.log('------state或props更新後-----------',this.state.count,prevState)
  }

  /**移除才調用 */
  componentWillUnmount(){
    //清理工作
    console.log('-------移除前被調用componentWillUnmount----------')
  }

  render(){
    console.log('Sub render被調用---');
    return (
      <div id="sub"  style={{ height: 200 + 'px', color: 'red', backgroundColor: '#f0f0f0', padding:'20px' }} 
      onClick={this.handleClick.bind(this)}>
        <a style={{backgroundColor: 'white'}} onClick = {this.handleClick.bind(this)}>Sub's count = {this.state.count}</a>
      </div>
    )
  }
}

class Root extends React.Component{
  constructor(props){
    console.log('Root Construnctor');
    super(props);
    this.state = {remot:false,xdd:1,name:'xdd'};
  }

  remot(){
    this.setState({remot:!this.state.remot})
    console.log('-------remot--'+this.state.remot.toString()+'----')
  }

  handleClick(){
    this.setState({xdd:this.state.xdd +1});
  }

  handleClick2(){
    this.setState({name:this.state.name+1})
  }

  render(){
    if (this.state.remot){
      return <div>
        <h1>Root</h1><a onClick={this.remot.bind(this)}>點我重新加載</a>
      </div>
    }
    else{
      return (<div onDoubleClick = {this.remot.bind(this)}>
        <h1>Root</h1>
        <div onClick={this.handleClick.bind(this)}>點我改變重繪Sub並改變props  xdd={this.state.xdd}</div>
        <a onClick={this.handleClick2.bind(this)}>點我只改變父組件中的屬性值 name={this.state.name}</a>
        <Sub xdd={this.state.xdd} prent={this} />{/**父組件的render,會引起下以及組件的更新流程,導致props重新發送,即使子組件props沒有改變過。 */}
        </div>)
    }
  }
}

ReactDom.render(<Root />,document.getElementById('root'))

007

  • componentWillMount 第一次裝載,在首次render之前。例如控制state、props
  • componentDidMount 第一次裝載結束,在首次render之後。例如控制state、props
  • componentWillReceiveProps 在組件內部,props是隻讀不可變的,但是這個函數可以接收到新的props,可以對 props做一些處理,this.props = {name:‘roooooot’};這就是偷樑換柱。componentWillReceiveProps觸發,也會 走shouldComponentUpdate。
  • shouldComponentUpdate 判斷是否需要組件更新,就是是否render,精確的控制渲染,提高性能。
  • componentWillUpdate 在除了首次render外,每次render前執行,componentDidUpdate在render之後調用。
  • 不過,大多數時候,用不上這些函數,這些鉤子函數是爲了精確的控制。
  • 如果子組件和父組件使用了相同的事件,可以認爲點擊子組件也是點擊了父組件,父組件重繪,就會把子組件 props更新,引起子組件組件更新流程,就會從componentWillReceiveProps開始執行。如果子組件自己修改自己 的state,不會執行componentWillReceiveProps。

函數試組件

  • React從15.0開始支持函數式組件,定義如下:
import React from 'react';
import ReactDom from 'react-dom';

function Root(props){
  return <div>{props.scholName}</div>
}

ReactDom.render(<Root scholName='xdd' />,document.getElementById("root"))
  • 開發中,很多情況下,組件其實很簡單,不需要state狀態,也不需要使用生命週期函數。
  • 函數式組件的函數應該提供一個參數props,返回一個React元素。
  • 函數式組件的函數本身就是render函數。
  1. 改寫上面示例代碼
import React from 'react';
import ReactDom from 'react-dom';

let Root = props => <div>{props.scholName}</div>

ReactDom.render(<Root scholName='xdd' />,document.getElementById("root"))

以前函數式組件還有名字叫stateless components無狀態組件。當前React發佈了16.8,已經可以在函數式組件中使用state了,所有官方建議叫函數式組件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章