React 高級內容

一. React developer tools安裝及使用

這是React項目的調試工具,方便我們在開發過程中對React項目進行調試。

安裝步驟:

1. 去github下載react-devtools壓縮包,也可以通過git bash下載:

 git clone https://github.com/facebook/react-devtools.git

2. 下載成功後,進入react-devtools文件夾,通過git bash安裝依賴,安裝時間會比較長:

npm --registry https://registry.npm.taobao.org install

3. 安裝成功後,我們便可以打包一份擴展程序出來:

npm run build:extension:chrome

4. 進入react-devtools\shells\chrome\build目錄下,將裏面的unpacked文件夾拖進chrome擴展程序裏面即可。

注意:此方法不太好使,建議使用別的方法。


二. PropTypes與DefaultProps的應用

1. PropTypes,子組件可以對父組件傳過來的屬性或者方法做一個類型校驗,不符合時會警告。

使用方法:

在TodoItem組件中引入PropTypes

在class的後面使用PropTypes來對父組件傳過來的值做類型校驗:

當傳過來的值類型不符合時,控制檯會警告。

知識點:

當我們需要某個屬性必須傳的時候,我們可以這麼寫:

TodoItem.propTypes = {
  test: PropTypes.string.isRequired,  //此處要求test時必須傳,且是字符串類型
  content: PropTypes.string,
  deleteItem: PropTypes.func,
  index: PropTypes.number
}

 

2. DefaultProps, 默認值,當我們子組件要求父組件傳某個屬性,但是父組件卻沒有傳,我們可以自己定義一個默認值。

使用方法:

還有一些關於props的功能,可以自行查閱react官網。

 


三. props,state與render函數的關係

1. React中數據與頁面之間互相聯動的底層運行機理。

當組件的state或者props發生改變的時候,render函數就會重新執行。

  • 拿我們前面做的TodoList爲例。當我們往input框輸入內容的時候,onchange事件就會被執行,而onchange事件對應的方法就會改變state,一旦state發生改變,那麼render函數就會重新地被執行,那麼頁面就會重新渲染,渲染的數據也是最新的。我們可以通過在render函數中console.log()內容進行驗證,可以發現每次輸入都會打印。
  • props改變的場景:當我們父組件向子組件傳值的時候,子組件通過props接收。父組件向子組件傳遞的值發生了改變,那麼props也就發生了改變,相應地,子組件中的render函數就會重新執行。還有一種情況子組件的render函數也會被重新執行,當父組件的render函數重新執行的時候,由於子組件也是在父組件的render函數中被調用的,所以也會導致子組件被重新渲染,也就是子組件的render函數被重新執行。

 


四. React中的虛擬DOM

假如我們需要自己實現一個React數據重新渲染的功能,我們需要做如下準備:

1、初始方案

之所以上面的準備耗性能,是因爲我們上面每次都是把整個dom重新生成,而不是隻生成數據改變了的那部分dom,這消耗了性能,另一方面替換掉完整的dom,這也會消耗性能。

2、第一次改進

上面的改進方案並不直接替換全部的原始dom,這節約了一部分性能。但是新的dom和原始dom進行比對又消耗了一部分性能,所以性能的提升並不明顯。

 

3、React提出了虛擬DOM的方案

上面便是React的虛擬DOM,在這裏需要知道一個點:js創建或者操作對象比創建或者操作真實的dom節省了很大的性能,這就是React提出虛擬DOM的原因。

 


五. 深入瞭解虛擬DOM

1. 在實際過程中, React的虛擬dom和上面有一些區別,上面是爲了方便用戶理解。實際上是這樣的:

 

2. 我們項目中render函數裏面的標籤內容不是真實的dom,它只是模板,當render函數執行的時候模板與數據相結合,生成虛擬的dom。

過程: JSX模板-------> createElement-----> 虛擬dom(JS對象)------->真實的dom

原理:

render() {
  return (
    <div>item</div>
  )
}

上面的jsx語法執行以後實際上是調用了下面的方法

render() {
  return React.createElement('div', {}, 'item');
}

這兩次代碼在頁面中的效果是一樣的,只不過第二次的代碼調用了更底層的實現方法,沒有使用jsx語法。實際開發中之所以不採用createElement方法,是因爲jsx語法更簡潔,更方便我們編寫。Vue框架中的虛擬dom機制和React是一致的。

 

3. 虛擬DOM的優點。

  • 性能提升了
  • 它使得跨端應用得以實現。React Native

React Native之所以能做app,依靠的就是虛擬DOM。當我們運行在瀏覽器端的時候,虛擬DOM就會生成真實的DOM,當我們運行在原生應用平臺的時候,虛擬DOM就會生成相應的原生應用組件。

 


六. 虛擬DOM中的Diff算法

兩個虛擬DOM比對的方式我們就稱爲Diff算法。

1. 爲什麼React中要把setstate設置成爲異步

比如說我們連續調用了三次setstate,React就會把三次合併成一次setstate,只去做一次虛擬DOM的比對,然後去更新一次dom,這樣就可以省去額外的兩次DOM比對帶來的性能耗費。這麼設置本質上是爲了提高React底層的性能。

2. 爲什麼不建議用index來做key值

React中虛擬DOM比對的Diff算法是同級比對的,當數據發生改變了以後,又會生成一個新的虛擬DOM,兩者進行比對,找到差異以後就會生成新真實的DOM。首先比對最頂層的虛擬DOM,假設他倆一致就會去比對第二層。如果他倆不一致,於是他就會把原始頁面上的虛擬DOM,對應的下面的所有DOM,全部都刪除掉。重新生成一遍節點下的所有的DOM,用重新生成的這些DOM,替換掉原始頁面上的DOM。也就是它如果比對的這層虛擬DOM不一致,下面幾層就不去比了,整個的就對原始的DOM進行替換了。這麼比對有些可重複利用的dom還是會被銷燬掉,消耗了一些性能,但是好處是算法簡單,這樣帶來的好處就是比對的速度會非常的快,大大減少了比對算法的性能上的消耗。

我們平時給數組列表設置key值,使得兩個虛擬DOM之間有名字,方便兩兩進行比對。而我們不推薦使用index值來作爲key值,因爲比如說第二次的虛擬DOM是我們刪除了數組的某一項,此時一部分元素的index值就發生了改變,這樣我們算法根據key值進行比對的就發生了錯誤。這就是爲什麼我們一般不推薦使用index來作爲key值得原因。

 


七. React中ref的使用

1、希望content的類型可以是字符串或者是數組

 

2、 ref的使用

在React中除了可以使用e.target來獲取對應的dom元素以外,還可以使用ref來獲取dom元素。

1. 之前的TodoList中handleInputChange方法裏面的e.target可以用ref替換。方法:

首先給render函數裏面的input添加ref(注意:inputDom是一個參數,可以自己隨便定義)

接着將handleInputChange方法裏面的e.target替換即可。實際效果一樣:

 

但是React是數據驅動的框架,我們不推薦使用ref直接操作dom,除非遇到很複雜的業務必須操作dom。

 

2、使用ref遇到的一個問題:別忘了setState是異步的。

我們希望在TodoList中實現一個功能,點擊提交按鈕,打印出ul裏面div的長度。

首先:

接着:

我們通過控制檯發現結果不對:每次打印都會少一個。

原因是React的setState方法是異步的,每次打印出來以後setState方法纔可能會執行。解決方法:

使用setState方法的第二個參數,該參數裏面的函數會等setState異步執行完成之後纔會被執行。

結果:

 


八、React的生命週期鉤子

React的生命週期主要經歷了四個階段:

Initialization: 初始化階段

Mounting:組件掛載到頁面階段

Updating:組件內容更新階段

Unmounting:把組件從頁面去除階段

1、初始化階段:

        該階段的生命週期函數在組件剛被創建的那個時刻就會調用。由於constructor這個函數並非React所獨有,ES6中也存在,所以React並沒有將它歸爲生命週期函數,但是它就是在該階段調用的。在該階段會定義state,會接收props。

2、掛載階段:

        該階段有三個生命週期函數被調用

  • componentWillMount   在組件即將第一次被掛載到頁面的時刻自動執行
  • render  組件掛載到頁面的時刻自動執行
  • componentDidMount   組件第一次被掛載到頁面之後,自動地執行

我們分別在不同階段打印相應的生命週期函數,結果如下:

我們在輸入框輸入文字,發現只有render函數被調用,這是因爲另外兩個只在第一次被掛載的時候會調用,之後就不會被調用了:

 

3、組件內容更新階段

        有兩種情況會進入該階段,一種是props發生改變,另一種是state發生改變。從上面的圖可以發現兩者的區別是props比state開始多調用一個生命週期函數(因爲歸根結底props改變的原因是因爲父組件的state改變)。

兩者都有的:

  • shouldComponentUpdate    組件被更新之前,他會自動被執行。有三種情況

1. 

  shouldComponentUpdate() {
    console.log('shouldComponentUpdate');
  }

然後在輸入框中輸入,但是輸入框卻不現實文字,結果:

這是因爲這個生命週期函數最後需要return一個布爾值。

 

2. return ture

  shouldComponentUpdate() {
    console.log('shouldComponentUpdate');
    return true;
  }

爲了方便我們記憶,上面代碼可以這麼理解:應該讓組件更新嗎?return true的話就讓組件更新,相應的輸入框也能顯示文字,組件更新也就能夠調用rener函數了。結果頁面:

 

3. return false

  shouldComponentUpdate() {
    console.log('shouldComponentUpdate');
    return false;
  }

return false組件不會更新,所以輸入框不會顯示我們輸入的文字,但是控制檯不會報錯,因爲我們return 了一個布爾值。結果頁面:

 

  • componentWillUpdate    
  //組件被更新之前,它會自動執行。但是它在shouldComponentUpdate之後執行
  //如果shouldComponentUpdate返回true它纔會執行
  //如果返回false,這個函數就不會被執行了
  componentWillUpdate() {
    console.log('componentWillUpdate');
  }

下面是return true的情況:

 

  • render
  • componentDidUpdate    組件更新完畢之後,它會被執行
  componentDidUpdate() {
    console.log('componentDidUpdate')
  }

 結果:

 

props獨有的:

  • componentWillReceiveProps     前提是一個組件要從父組件接收參數。如果這個組件第一次存在於父組件中不會被執行,如果這個組件已經存在於父組件中了,纔會被執行。

該生命週期函數是props獨有的,它執行的前提是該組件必須有props參數,也就是父組件給該組件傳值。所以我們把這個生命週期函數寫在TodoItem這個子組件中。

效果演示:

TodoItem:

  componentWillReceiveProps() {
    console.log('child componentWillReceiveProps');
  }

結果:我們輸入完畢第一次點擊提交以後,不會打印出來,再次輸入以後,此時父組件的render函數重新執行後,纔會執行該生命週期函數,即打印出來。

 

4、把組件從頁面去除階段

  • componentWillUnmount    當這個組件即將被從頁面剔除的時候執行

TodoItem:

  componentWillUnmount() {
    console.log('child componentWillUnmount');
  }

結果:當我們點擊列表項時,該列表項被刪除,此時該生命週期函數被執行

 


九、生命週期函數的使用場景

1. 注意: render這個生命週期函數必須存在。

這是因爲React的組件是繼承自Component這個組件的,該組件默認內置了所有的生命週期函數,唯獨沒有內置render這個生命週期函數的實現。

 

2. 利用生命週期函數提高性能

案例:

TodoItem中的render函數中添加console.log('child render');

結果:

實際中上面每次輸入導致TodoItem這個子組件都會重新被渲染是耗性能的,我們可以利用shouldComponentUpdate這個生命週期函數來實現。

TodoItem:

  shouldComponentUpdate(nextProps, nextState) {
    //nextProps表示組件如果更新後的Props
    //nextState表示組件如果更新後的state
    if(nextProps.content !== this.props.content) {
      return true;
    } else {
      return false;
    }
  }

效果:

可以發現,此時當我們只輸入沒有提交的時候,TodoItem這個組件不會重新被渲染,提高了性能。

 

3. 在哪個生命週期函數中發送ajax請求?

不要放在render函數中,因爲它在頁面中經常會被反覆地執行。推薦放在componentDidMount這個生命週期函數中。

 

 


10、使用 mockjs 實現本地數據 mock

1、課程講的 charles 折騰了一下午也用不了,應該是版本更新後的問題。查找資料後使用 mockjs 成功實現了 

  • 安裝 mockjs : npm install mockjs ;
  • 在 src 目錄下建立 mock 文件夾,裏面建立 mockdata.js 文件:
import Mock from 'mockjs';

Mock.mock('/todolist', {
  data: ['a', 'b', 'c']
})   //第一個參數是自己寫的,到時候 axios 也寫這個路徑即可, 第二個參數放數據
  • TodoList.js 文件引入 mockdata.js , 然後添加代碼:
  componentDidMount() {
    axios.get('/todolist')
      .then((res) => {
        const data = res.data.data;
        this.setState(() => ({
          list: [...data]
        }))
      })
      .catch(() => {console.log('error')});
  }

此時頁面就渲染出模擬的數據了。


11、React中實現CSS過渡動畫

刪除之前的 TodoList 後的目錄結構:

index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

App.js:

import React, { Component, Fragment } from 'react';
import './style.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      show: true
    };
    this.handleToggle = this.handleToggle.bind(this);
  }
  render() {
    return (
      <Fragment>
        <div className={this.state.show ? 'show' : 'hide'}>hello world</div>
        <button onClick={this.handleToggle}>toggle</button>
      </Fragment>
    )
  }
  handleToggle() {
    this.setState({
      show: this.state.show ? false : true
    })
  }
}

export default App;

style.css:

.show {
  opacity: 1;
  transition: all 1s ease-in;   /*所有的屬性都用 1s 的時間來切換*/
}

.hide {
  opacity: 0;
  transition: all 1s ease-in;   /*所有的屬性都用 1s 的時間來切換*/
}

啓動項目就實現了一個切換顯示與否的漸變效果。


12、React中使用CSS動畫效果

之前代碼不變,就改變 style.css 的效果:

.show {
  animation: show-item 2s ease-in forwards;
}

.hide {
  animation: hide-item 2s ease-in forwards;   /*forwards是讓動畫結束的時候保存最後一幀的樣式,不加它的話就不會隱藏*/
}

@keyframes hide-item {
  0% {
    opacity: 1;
    color: red;
  }
  50% {
    opacity: .5;
    color: green;
  }
  100% {
    opacity: 0;
    color: blue;
  }
}
@keyframes show-item {
  0% {
    opacity: 0;
    color: red;
  }
  50% {
    opacity: .5;
    color: green;
  }
  100% {
    opacity: 1;
    color: blue;
  }
}

此時運行後就有了動畫的效果。


13、使用react-transition-group實現動畫(一)

簡單的動畫我們可以自己寫,但是比較複雜的我們可以藉助這個第三方模塊 : react-transition-group

  • 首先進行安裝 : 
    npm install react-transition-group --save
  • App.js :
import React, { Component, Fragment } from 'react';
import { CSSTransition } from 'react-transition-group';
import './style.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      show: true
    };
    this.handleToggle = this.handleToggle.bind(this);
  }
  render() {
    return (
      <Fragment>
        <CSSTransition
          in={this.state.show}  //根據show的值來判斷是出場動畫還是入場動畫
          timeout={300}  //動畫的時間
          classNames='fade'  //給類添加前綴名
        >
          <div>hello world</div>
        </CSSTransition>
        <button onClick={this.handleToggle}>toggle</button>
      </Fragment>
    )
  }
  handleToggle() {
    this.setState({
      show: this.state.show ? false : true
    })
  }
}

export default App;
  • style.css :
/*執行入場動畫的時候,也就是show從false變成true的時候。在入場動畫執行的第一個時刻CSSTransition這個組件會往div上掛載一個樣式,名字就是fade-enter*/
.fade-enter {
  opacity: 0;
}

/*入場動畫執行的第二個時刻到入場動畫完成之前的時刻,這段時間div上面一直會有fade-enter-active這個class*/
.fade-enter-active {
  opacity: 1;
  transition: opacity 1s ease-in;
}
/*當整個入場動畫執行完以後,這個class就會被增加到div上面*/
.fade-enter-done {
  opacity: 1;
}

/*出場的第一個時刻,該class會被加上去*/
.fade-exit {
  opacity: 1;
}
/*執行出場動畫的這段時間都會存在這個class*/
.fade-exit-active {
  opacity: 0;
  transition: opacity 1s ease-in;
}
/*整個出場動畫完成之後*/
.fade-exit-done {
  opacity: 0;
}

此時我們就實現了和之前一樣的動畫效果。

雖然看起來我們實現一樣的效果,使用這個模塊來做更復雜,但是這個第三方的庫有着很多特別實用的地方:

  • 給 CSSTransition 這個組件添加 unmountOnExit ,可以發現動畫結束後 div 元素就不存在,動畫開始它又出來了。
  • 這個模塊還有很多鉤子函數(類似生命週期函數),我們可以使用這些鉤子函數實現特定的動畫。
  render() {
    return (
      <Fragment>
        <CSSTransition
          in={this.state.show}  //根據show的值來判斷是出場動畫還是入場動畫
          timeout={300}  //動畫的時間
          classNames='fade'  //給類添加前綴名
          unmountOnExit
          onEntered={(el) => {el.style.color='red'}}  //el 指的就是內部的 div 元素,意思是當入場動畫執行結束之後,給元素添加紅色的字體顏色
        >
          <div>hello world</div>
        </CSSTransition>
        <button onClick={this.handleToggle}>toggle</button>
      </Fragment>
    )

添加配置:

appear={true}   //第一次展示的時候就有動畫效果

一般這個組件都能滿足我們 react 開發中需要的動畫效果,到時候查文檔即可。


14、使用react-transition-group實現動畫(二)

1、實現多個元素的動畫效  App.js:

import React, { Component, Fragment } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './style.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: []
    };
    this.handleAddItem = this.handleAddItem.bind(this);
  }
  render() {
    return (
      <Fragment>
        <TransitionGroup>
          {
            this.state.list.map((item, index) => {
              return (
                <CSSTransition
                  key={index}
                  timeout={300}
                  classNames='fade'
                  unmountOnExit
                  onEntered={(el) => {el.style.color='red'}}
                  appear={true}
                >
                  <div>{item}</div>
                </CSSTransition>
              )
            })
          }
        </TransitionGroup>
        <button onClick={this.handleAddItem}>toggle</button>
      </Fragment>
    )
  }
  handleAddItem() {
    this.setState((prevState) => {
      return {
        list: [...prevState.list, 'item']
      }
    })
  }
}

export default App;

其實就是用 TransitionGroup 包起來, 內部每一項還是使用 CSSTransition 。

 

 

 

 

 

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