React 之實戰總結

寫這篇文章初衷是整理一下本身這近幾個月的心路歷程,從剛開始(19年10月份) 「入坑」react時的一臉懵,不知如何下手,到如今能夠寫簡單的業務。講述本身從徹底不瞭解這個框架 ,而後又一步步上道兒,我這裏就稱之爲「爬坑」,哈哈。因此想把本身的學習過程以及爬坑記錄下來,給本身往後翻閱,若有也在寫react ,想交流的小夥伴,文末有微信哦,哈哈。其實從很早就想研究下react了,只是時間上的不容許,如今終於能夠騰出時間來(其實平時工做較飽和,也只能擠業餘時間了)。html

------文中的示例都是本身通過實踐的,如理解有誤,還請告知哦!😂------vue

環境介紹:

項目使用umi腳手架配合dva搭建,ui組件是ant-design,模版使用的是antdesign的pro-layout現成的模版,較多使用aHooks來實現網絡請求、節流等操做,是一個使用了都說真香的Hooks庫,參考個人另外一篇文章:aHooksreact

具體版本號以下:ios

"@ant-design/pro-layout": "4.7.0",
    "@antv/g2": "^3.5.11",
    "antd": "^3.25.2",
    "array-move": "^2.2.0",
    "umi": "2.12.3",
    "ahooks": "^2.0.1",
    "umi-plugin-react": "1.14.7",
    "uuid": "^3.3.3",
    "axios": "^0.19.0",
    "bizcharts": "^3.5.6",
    "classnames": "^2.2.6",
    "copy-to-clipboard": "^3.2.0",
    "dayjs": "^1.8.17",
    "immutable": "^4.0.0-rc.12",
    "lodash": "^4.17.15",
    "moment": "^2.24.0",
    "mz-modules": "^2.1.0",
    "parameter": "^3.6.0",
    "prop-types": "^15.7.2",
    "qrcode": "^1.4.4",
    "qs": "^6.9.1",
    "rc-form-hooks": "^0.0.1-alpha.22",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "swr": "^0.1.12",
    
複製代碼

關於react的生命週期

如同vue同樣,react 也是有本身的生命週期,方便咱們根據加載順序來執行相應的操做。但因爲Hooks的出現,彷佛react 已經不在須要關心這些生命週期的問題,HooksReact 16.8 新增的特性,在你不須要寫class 組件的狀況下,就賦予了函數式組件 state 狀態管理及生命週期函數的特性,固然下面也會詳細介紹hooks將如何使用。json

經常使用的生命週期以下:axios

  1. 在渲染前調用:componentWillMountapi

  2. 在第一次渲染後調用:componentDidMount數組

  3. 在組件完成更新前調用:componentWillUpdate瀏覽器

  4. 在組件完成更新後當即調用:componentDidUpdate緩存

  5. 在組件接收到一個新的 prop (更新後)時被調用:componentWillReceiveProps

  6. 在組件從 DOM 中移除以前馬上被調用:componentWillUnmount

react的父組件和子組件生命週期執行順序:

加載渲染過程

父 componentWillMount => 父 render => 子 componentWillMount =>子 render => 子 componentDidMount => 父componentDidMount
複製代碼

子組件經過props取值並更新過程:

子 componentWillUpdate => 父 render => 子 componentWillReceiveProps => 父 componentWillUpdate => 子 render => 子 componentDidUpdate => 父 componentDidUpdate
複製代碼

單一父 / 子組件(不依賴props)更新過程:

componentWillUpdate => render => componentDidUpdate 
複製代碼

銷燬過程:

componentWillUnmount
複製代碼

1、 編譯

let root =  document.getElementById('example');

  /* 
  * 相似vue的template ,第一個參數是插入的模版,第二個是插入的根元素
  * 在react中樣式中的 class 要重命名爲 className 
  * render 是必不可少的,用來渲染到頁面上
  */

  ReactDOM.render(

    <h1 className="box">Hello, world!</h1>,

   root

  );

複製代碼

效果展現:


2、 定義變量

在react中定義變量是很方便的,能夠定義數組,數組中能夠包含咱們的html 標籤,而後能夠將變量直接帶入到頁面上。

<body>
    <div id="example"></div>
    <script type="text/babel">
      let root =  document.getElementById('example')
      let arr = [
        <h1 >Hello world!</h1>,
        <h2 >Hello React!</h2>,
      ];
      // 注意,react 的變量使用的是單花括號 {}
      ReactDOM.render(
        <div>{arr}</div>,
        root
      
      );
    </script>

複製代碼

效果展現:


3、組件

React裏組件起初都是用class來寫的(雖然如今都在用hooks,但這部分仍是保留,就當是記錄下它的發展史,hooks來寫組件無疑是很爽的,下面會介紹到。)。

<body>
    <div id="example"></div>
    <script type="text/babel">
    
      let root = document.getElementById('example');
      
      // class 的名字必須大寫,並繼承自 React.Component
      class HelloMessage extends React.Component {
        constructor(...args){
          super(...args);
          
          this.name=this.props.name
          this.job=this.props.job
          this.age = this.props.age
        }
        
        fn(){
          return "Aaa"
        }
        render() {
            // 變量還能夠直接定義標籤,style後面需跟一個{},而裏面的內容須要是一個json,因此此處看起來是兩個{{}}
            let div =<div style={{color:'red'}}>我是div</div>
          return (
            <div>
                // 花括號中的值還能夠參與計算
                姓名: {this.name}<br/>
                工做: {this.job}<br/>
                年齡: {this.age+3}<br/>
                
                // 花括號不只能夠輸出變量,還能夠輸出方法
                {this.fn()} <br/>
                
                // 將標籤輸出到頁面
                {div}
            </div>
            );
        }
      }

      ReactDOM.render(
        <HelloMessage name="John" job="teacher" age="18"/>,
        root
      );
    </script>
  </body>

複製代碼

4、循環

JSX語法容許咱們把htmljs 穿插來寫,咱們來看看最經常使用的循環怎麼寫。

<body>
    <div id="example"></div>
    <script type="text/babel">
      let root = document.getElementById('example');

      
      class HelloMessage extends React.Component {
        constructor(...args){
          super(...args)
        }
        
        
        render() {
        let root =  document.getElementById('example')
        let names = ['Alice', 'Emily', 'Kate'];
          return (
            <div>
                names.map(function (item) {
                // 循環中須要添加key值,用來保證惟一性
                return <div key={item}>Hello, {item}!</div>
                })
            </div>
          
            );
        }
      }

      ReactDOM.render(
       <div>
          <HelloMessage />
        </div>,
        root
      );
    </script>
  </body>


複製代碼

效果展現:


5、組件嵌套

咱們日常的開發中,有時候須要用到些公共組件,那咱們就應對其進行封裝提取出來,如下是粗略版的父子組件嵌套寫法

<body>
        <div id="example"></div>
        <script type="text/babel">
        
     // 父組件   
     class Parent extends React.Component{
       constructor(...args){
           super(...args)
       }
       render(){
           return(
            <ul>
            // 將寫好的子組件嵌套進來便可
                    <Child/>
                    <Child/>
                    <Child/>
                    <Child/>
            </ul>
           )
       }
     }

    // 子組件
     class Child extends React.Component{
        constructor(...args){
            super(...args)
        }
        render(){
            return(
               <li>111</li> 
            )
        }
     }

      ReactDOM.render(
       
        <Parent />,
        document.getElementById('example')
      );
    </script>
    </body>

複製代碼

通常狀況下,render 裏面只會有一個最大的標籤包含,若是你有兩個標籤,請在外面添加一個包裹標籤,正確寫法:

ReactDOM.render(
       <div>
            <Parent></Parent>
            <Child></Child>
       </div>,
        document.getElementById('example')
      );
複製代碼

錯誤寫法:

ReactDOM.render(
 
       <Child></Child>
       
        <Parent></Parent>
       ,
        document.getElementById('example')
      );
複製代碼

在腳手架中還可用 react 中的空標籤<></> 去充當咱們最外層的包裹層,它的好處是不會多生成一個div

import { Component } from 'react'
class ConfigContent extends Component {
    constructor(...args){
        super(...args)
    }
    render(){
        return(
            <>
                <Parent></Parent>
                <Child></Child>
            </>
        )
    }
}

export default ConfigContent
複製代碼

組件能夠寫成單標籤或者雙標籤兩種形式。以下:

// 雙標籤
  <Parent></Parent>
  
 // 單標籤
 
 <Parent/>
複製代碼

6、 父子組件傳參 props

父組件給子組件傳遞參數,子組件接收,並渲染子組件: 父組件=>子組件

父組件

import { PureComponent } from "react";
import Child from "./child";

class Parent extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { id: 1 };
  }

  render() {
    return (
        <Child id={this.state.id} />
    );
  }
}

export default Parent;

複製代碼

子組件:

import { PureComponent } from "react";

class Child extends PureComponent {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
        <h1>child-page</h1>
        <p>{this.props.id}</p>
      </div>
    );
  }
}

export default Child;

複製代碼

效果展現:

子組件經過事件將子組件的值傳到父組件: 子組件=>父組件

子組件:

import { Button } from "antd";
import { PureComponent } from "react";

class Child extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { a: 1 };
  }

  action = {
    handleChange: () => {
      this.props.changeEvent(`子組件定義的值:${this.state.a}`);
    }
  };

  render() {
    return (
      <div>
        <h1>child-page</h1>
        <Button type="primary" onClick={this.action.handleChange}>
          改變父組件
        </Button>
      </div>
    );
  }
}

export default Child;

複製代碼

父組件:

import { PureComponent } from "react";
import { Button } from "antd";
import Child from "./child";

class Parent extends PureComponent {
  constructor(props) {
    super(props);
  }

  action = {
    changeEvent: mode => {
      console.log("父組件收到的值:", mode);
    }
  };

  render() {
    return (
      <div>
        <Child changeEvent={mode => this.action.changeEvent(mode)} />
      </div>
    );
  }
}

export default Parent;

複製代碼

點擊後的效果展現:

7、 添加事件

事件是咱們在交互過程當中必不可少的,那麼咱們試試看,在react中如何添加事件。

(1)咱們原生的添加事件的方式,採用的是小寫onclick

<button onclick="activateLasers()">
  Activate Lasers
</button>
複製代碼

(2)react的添加事件,採用駝峯的方式定義onClick

<button onClick={activateLasers}>
  Activate Lasers
</button>
複製代碼

下面介紹幾種在項目中添加事件的方式,你們可根據狀況選擇:

1. 在標籤中添加方法及函數體,也是和初始的事件最接近的方式:

<body>
        <div id="example"></div>
        <script type="text/babel">
 
           class Child extends React.Component {
            constructor(...args){
              super(...args)
              this.a=[123]
            }
           // 直接在標籤上添加
            render() {
              return (
                <div onClick={function(){
                  console.log("eee")
                }}>{this.a}</div>
              )
            }
            
           }
           ReactDOM.render(
           <Child/>,
             document.getElementById('example')
           )
    </script>
    </body>
複製代碼

2. 在class組件中添加方法(須要從新綁定this):

<body>
        <div id="example"></div>
        <script type="text/babel">
 

     class Cmp1 extends React.Component{
        constructor(...args){
            super(...args)
        }
        fn(){
          // props只讀的,這裏的值是不可改的,是從外面傳進來的
          console.log(this.props.a)  // 0 
        }
        // onClick 相似原生事件,此處bind就是把咱們的fn的this緊緊綁在組件上,此時的內部也能拿到咱們組件的this
        render(){
            return(
               <div>
                {this.props.a}
               <input type="button" value="+1" onClick={this.fn.bind(this)}/>
               </div>
            )
        }
    }

      ReactDOM.render(
       <div>
        
        <Cmp1 a={0}/>
       
       </div>,
        document.getElementById('example')
      );
    </script>
    </body>

複製代碼

3. 定義屬性,在其內部添加方法(簡單,this的綁定不會變):

<body>
        <div id="example"></div>
        <script type="text/babel">
 
           class Child extends React.Component {

            constructor(...args){
              super(...args)
              this.a=[123]
            }
            // 定義變量
            action ={
              fn(){
                console.log(23)
              }
            }
            // 在這裏直接調用,就不用綁定this了
            render() {
              return (
                <div onClick={this.action.fn}>{this.a}</div>
              )
            }
            

           }
           ReactDOM.render(
           <Child/>,
             document.getElementById('example')
           )
    </script>
    </body>
複製代碼

4. 第四種添加事件方式

<body>
        <div id="example"></div>
        <script type="text/babel">
 
           class Child extends React.Component {

            constructor(...args){
              super(...args)
              this.a=[123]
            }
         
              fn=()=>{
                console.log(23)
              }
    
            render() {
              return (
                <div onClick={this.fn}>{this.a}</div>
              )
            }
            
           }
           ReactDOM.render(
           <Child/>,
             document.getElementById('example')
           )
    </script>
    </body>

複製代碼

5. React Hooks 中的事件添加

當咱們瞭解到React Hooks的事件添加後,咱們終於鬆了口氣,終於再也不考慮this綁定的問題了

import React from 'react';
import { Button } from 'antd';

const App = () => {
  const handleClick = () => {
    console.log('按鈕點擊');
  };

  return <Button onClick={handleClick}>按鈕</Button>;
};

export default App;

複製代碼

備註一下,關於事件傳參的寫法

import { PureComponent } from "react";
import { Button } from "antd";

class App extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { arr: [1, 2, 3, 4] };
  }

  action = {
    handleClick: i => {
      console.log(i.target.innerHTML);
    }
  };

  render() {
    return (
      <div>
        {this.state.arr.map((item, index) => {
          return (
            <Button
              key={index}
              onClick={index => this.action.handleClick(index)}
            >
              {item}
            </Button>
          );
        })}
      </div>
    );
  }
}

export default App;

複製代碼

效果展現:

8、利用state製做一個 input ++ 功能

state 構造函數是惟一可以初始化 this.state 的地方,接收一個對象,是可變的,能夠是內部加的,也能夠從外部傳進來,this.setSate是惟一改變state的方式。

// 製做一個input ++ 功能
<body>
        <div id="example"></div>
        <script type="text/babel">
 

     class Cmp1 extends React.Component{
        constructor(...args){
            super(...args)
           
            this.state={a:0}
        }
       
        fn(){
          // this.setSate是惟一改變state的方式
        this.setState({a:this.state.a+1})
        }
        render(){
            return(
               <div>
               {this.state.a}
               <input type="button" value="+1" onClick={this.fn.bind(this)}/>
               </div>
            )
        }
    }

      
      ReactDOM.render(
       <div>
            <Cmp1/>
       </div>,
        document.getElementById('example')
      );
    </script>
    </body>
複製代碼

頁面效果:


9、路由傳參 && 獲取參數

咱們在跳轉路由時,有時須要給跳轉到到頁面攜帶一些參數來定位頁面的顯示或回顯數據,此小節咱們就來看看如何傳參,以及入股取參。

首先咱們先拿一下props,看看都有哪些參數:

console.log(this.props);
複製代碼

參數以下:

咱們來具體解析一下:

history:包含了路由push replace goBack 等方法,以及可拿到query state 等參數

history:{
   
    location:{
        pathname:'/dashboard/workplace', // url地址
        search:'?name='xiaoxiao', // 拿到的是完整的參數字符串 hash:'', query:{name:'xiaoxiao'}, // 拿到參數的對象格式 state:undefined // 拿到經過state傳入的參數 }, push:function push(path,state){} , // 跳轉到指定路徑 replace:function replace(path,state){} , // 跳轉到指定路徑,不會保留history goBack:function goBack(path,state){} , // 返回上一個路由地址 } 複製代碼

這個location 同上面的location,可拿到query參數 以及state 參數

location:{
    pathname:'/dashboard/workplace',
    search:'?name='xiaoxiao', query:{name:'xiaoxiao'}, state:undefined } 複製代碼

包含了具體的 url 信息,並能夠拿到params的參數的值。

match:{
    path:'/dashboard/workplace',
    url:'/dashboard/workplace',
    params:{}
}
複製代碼

接下來咱們就使用:this.props.history.push 來模擬跳轉,並攜帶參數:

1. query

經過url來進行傳參,地址欄是可見的

⚠️注意️:刷新頁面後,參數不會丟失,可傳對象

A 頁面:

this.props.history.push({
    pathname: "/dashboard/workplace",
    query: { name: "xiaoqiu" }
});

複製代碼

B頁面 (參數在url裏問號後顯示)

http://localhost:8000/#/dashboard/workplace?name=xiaoqiu
複製代碼

打印一下咱們接收到的參數:

2. state

state傳參,同query差很少,只是屬性不同,並且state傳的參數是加密的,不會在地址欄顯示

注意️:刷新頁面後,參數就會丟失,可傳對象

A頁面:

this.props.history.push({
    pathname: "/dashboard/workplace",
    state: { name: "xiaoqiu" }
});
複製代碼

B頁面: (參數並無出如今地址欄哦!)

http://localhost:8000/#/dashboard/workplace
複製代碼

打印一下咱們接收到的參數:


此時咱們刷新一下頁面,看看是否還能夠拿到state的值:

這時state的值已經丟失,評論中有位掘友反應,刷新後在history.state 裏面有state的值,我想說,history沒有直接的state屬性,state是在historylocation的屬性中,不知道是否能解釋那位掘友的問題🤔,歡迎你們一塊兒討論哦。

追加:獲得了掘友新的反饋:刷新頁面state的值會存在瀏覽器的history.state中,接下來咱們作一下驗證: 首先在接到參數頁面打印:

history.state
複製代碼

通過上面的查詢,拿到的結果是null,我去查閱了相關api

history.pushState({page: 1}, "title 1", "?page=1")

複製代碼

使用pushState賦值的能夠拿到history.state:

3. search

searchquery參數同樣是經過url進行傳輸

⚠️注意️:刷新頁面後,參數不會丟失,但只可傳字符串,不能傳輸對象

A頁面:

this.props.history.push({
    pathname: "/dashboard/workplace",
    search: "a=1&b=2"
});
複製代碼

B頁面

http://localhost:8000/#/dashboard/workplace?a=1&b=2
複製代碼

打印一下咱們接收到的參數:


總結一下:

此時咱們注意到不管是經過query或是search 傳參,返回參數時二者也同時都能取到,只是展示方式不一樣。query是以對象形式展示,search是以字符串展示,若是你想在地址欄顯示,那就用query,若是想加密傳輸,那就用state,但要注意使用state傳遞參數刷新後就會丟失哦!

10、ref獲取組件實例

經過給組件綁定ref 能夠獲取到整個子組件的實例,進而可傳參數並調用其方法

1. 傳統的ref,獲取當前組件的參數以及事件

import { Button } from "antd";
import { PureComponent } from "react";

class Child extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { a: 1 };
  }

  action = {
    handleChange: () => {
      console.log(this.refs["button"]);
    }
  };

  render() {
    return (
      <div>
        <h1>child-page</h1>
        <Button type="primary" onClick={this.action.handleChange} ref="button">
          改變父組件按鈕
        </Button>
      </div>
    );
  }
}

export default Child;

複製代碼

控制檯打印ref拿到的組件參數以及方法:

2. reactcreateRef,拿到子組件的參數和方法

import { PureComponent, createRef } from "react";
import { Button } from "antd";
import Child from "./child";

class Parent extends PureComponent {
  constructor(props) {
    super(props);
    this.children = createRef();
    this.state = { id: 1, arr: [1, 2, 3, 4] };
  }

  action = {
    handleClick: () => {
        console.log(this.children);
    }
  };

  render() {
    return (
      <div>
        <Button onClick={() => this.action.handleClick()}>
            按鈕
        </Button>
        子組件:
        <Child ref={this.children} />
      </div>
    );
  }
}

export default Parent;

複製代碼

控制檯輸出子組件的值:

3. useRef 父組件-函數組件,獲取子組件-class組件的實例

父組件-函數組件:

import { useRef, useEffect } from 'react'
import Child from './testChild'

const Parent = () => {
  const divRef = useRef()

  useEffect(() => {
    console.log('divRef', divRef.current)
  },[])

  return (
    <div>
      hello hooks
      <Child ref={divRef} text={'子組件'} />
    </div>
  )
}
export default Parent
 
複製代碼

子組件-class組件

import { PureComponent } from 'react'

class Child extends PureComponent {
  constructor(props) {
    super(props)
    this.state = { count: 12 }
  }
  render() {
    return <div>子組件</div>
  }
}
export default Child
複製代碼

控制檯輸出:

⚠️ 注意: ️若是想拿到子組件的實例,子組件必須是一個class類組件:參考react官網

11、createContext: 跨越子組件,實現祖孫數據傳輸

咱們在日常的開發中,若是遇到孫組件須要使用到爺爺組件的數據,咱們通常會通過中間的父組件,一層一層向下傳播,但有時中間的父組件不須要用到這些給孫子組件的值,再或者中間父組件層級過多,或者邏輯業務變更,那就須要咱們從頭至尾進行一系列的修改,有點得不償失,今天咱們就來實現一個從爺爺組件,跨越父組件,直通孫子組件的方法:

Provider 顧名思義,參數的提供者,將須要傳遞的參數經過它來暴露出來 Consumer 參數的接收者,用在須要接收參數的組件

首先將引入以及建立context,並拋出 Provider、Consumer

/**
* 建立 context 並導出Provider、Consumer
* Provider:通常用於提供參數的組件
* Consumer:通常用在接受參數的組件
*  */

import React from 'react'
let { Provider, Consumer } = React.createContext()
export { Provider, Consumer }

複製代碼

導入Provider:父/祖父組件,要位於Consumer的上層組件:

import { Provider } from '@@/utils/context'

class Parent extends PureComponent {
  constructor(props) {
    super(props)
    this.state = { name: 'zhangsan' }
  }
  render() {
    return (
    <Provider value={this.state.name}>
        <div>
            <p>父組件定義的值:{name}</p>
                 <Son></Son>
        </div>
    </Provider>
)
  }
}
export default Parent
複製代碼

拋出:Consumer 子/孫組件,要位於Provider的下層的組件,首先測試第一層的兒子組件:

import { Consumer } from '@@/utils/context'

class Son extends PureComponent {
  constructor(props) {
    super(props)
  }
  render() {
    return (
    <Consumer>
        {
            name=>(
                <div>
                    <p>子組件接收的值:{name}</p>
                     <Grandson></Grandson>
                </div>
            )
        }
    </Consumer>
)
  }
}
export default Son

複製代碼

接下來測試第N層的孫子組件,寫法同上:

import { Consumer } from '@@/utils/context'

class Grandson extends PureComponent {
  constructor(props) {
    super(props)
  }
  render() {
    return (
    <Consumer>
        {
            name=>(
                <div>
                    <p>孫子組件接收的值:{name}</p>
                </div>
            )
        }
    </Consumer>
)
  }
}
export default Grandson

複製代碼

當多個參數須要傳遞時,就直接放在一個對象({})裏就能夠,子組件收到的也時一個對象

父組件:

import React from 'react';
import { Provider } from '@xb/utils/context';
import Child from './Child1';

const App = () => {
  const state = false;
  const params = {
    name: 'zhangsan',
    age: 18,
    job: 'teacher',
  };
 
  return (
    <Provider value={{ params, state }}>
         
        子組件:<Child />
          
    </Provider>
  );
};

export default App;

複製代碼

Child:

import React from 'react';
import { Consumer } from '@xb/utils/context';

const Child1 = () => {
  return (
    <Consumer>
      {data => (
        <div>
          <p>
            子組件接收的值:{data.params.age}--{data.state}
          </p>
        </div>
      )}
    </Consumer>
  );
};

export default Child1;

複製代碼

總結:從上層往下傳值,剛剛咱們實驗的是字符串,對象,其實還可傳輸數組。基本知足上層無需層層傳遞,下層也可拿到上層的數據。當咱們的項目須要在多處使用context,那也時沒有問題的,每一個Consumer只會去找它的上級傳來的值,而不會去兄弟組件尋找。

12、React.Hooks函數式組件實踐

上面劇透了不少次,這一講來說一下使人心動💓 的hooks。那麼什麼是 hooks ?

借用官網的解釋:

HookReact 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。

import React, { useState } from 'react';

function Example() {
  // 聲明一個新的叫作 「count」 的 state 變量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
複製代碼

1. useState : 存儲state,更新state

咱們都知道hooks的一大亮點就是在不編寫 class 的狀況下使用 state,那麼hooksstateclassstate的區別是什麼呢?該怎麼使用呢?

定義state

const [state, setState] = useState(initialState); 
複製代碼
  • 接收一個默認參數
  • 返回一個 state,以及更新 state 的函數

更新state

setState(newState);
複製代碼

在初始渲染期間,返回的狀態 (state) 與傳入的第一個參數 (initialState) 值相同。setState 函數用於更新 state。它接收一個新的 state 值並將組件的一次從新渲染加入隊列。

下面上一個簡單數字 + +的例子:

import React, { useState } from 'react';
import { Button, Card } from 'antd';

const App = () => {
  const [state, setState] = useState<number>(0);

  return (
    <Card>
      <p>count: {state}</p>

      <Button
        type="primary"
        onClick={() => {
          setState(state + 1);
        }}
      >
        +
      </Button>
    </Card>
  );
};

export default App;

複製代碼

2. useEffect : 監聽參數變化,執行的函數

useEffect不管是否有參數的監聽,React 都會等待瀏覽器完成畫面渲染以後纔會延遲調用 。若是你不想讓它首次就執行,能夠考慮使用:useUpdateEffect -- 它是一個只在依賴更新時執行的 useEffect hook

  1. 默認狀況下,useEffect 會在每輪組件渲染完成後執行。這樣的話,一旦 useEffect 的依賴發生變化,它就會被從新建立。
useEffect(() => {
    handleSubmit();
  }, []);
複製代碼
  1. 當有依賴的參數時,以下:tabkey發生變化後,會自動執行函數體內的handleSubmit方法
useEffect(() => {
    handleSubmit();
  }, [tabKey]);
複製代碼
  1. 能夠監聽多個參數的變化,以下:當tabKey或者state改變都會執行函數體內的handleSubmit方法:
useEffect(() => {
    handleSubmit();
  }, [tabKey,state]);
複製代碼
  1. 當組件銷燬時執行的一些內容,能夠在useEffectreturn一個函數,這樣return的函數體內的內容會在銷燬時執行:
useEffect(() => {
    handelColumns(tabVal || 'intention');
    // 須要是一個函數哦!!!
    return () => {
    // 如下內容只會在銷燬的時候執行
      console.log(1);
    };
  }, []);
複製代碼

將來可期:依賴項數組不會做爲參數傳給 effect 函數。雖然從概念上來講它表現爲:全部 effect 函數中引用的值都應該出如今依賴項數組中。將來編譯器會更加智能,屆時自動建立數組將成爲可能。

3. useReducer : useState的替代方案,存儲state,更新state

useReducer做爲useState的替代方案。在某些場景下,useReducer 會比 useState 更適用,例如: state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於以前的 state 等。

接收參數:

  • 第一個參數是一個形如 (state, action) => newStatereducer函數;
  • 第二個參數是state的初始值;
  • 第三個參數是可選參數,值爲一個函數,能夠用來惰性提供初始狀態。

返回值:

  • useReducer 返回一個數組,數組中包含一個 statedispathstate 是返回狀態中的值,而 dispatch 是一個能夠發佈事件來更新 state 的函數。

有兩種不一樣初始化 useReducer state 的方式,你能夠根據使用場景選擇其中的一種。將初始 state 做爲第二個參數傳入 useReducer 是最簡單的方法,下面咱們使用【 input ++、--】的例子來看一下它如何使用:

第一種:指定初始化,將初始 state 做爲第二個參數傳入

import React, { useReducer } from 'react';
import { Button } from 'antd';
import CardLayout from '@xb/layouts/CardLayout';

const initialState = { count: 0 };

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};
const IntendedList = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <CardLayout>
      <p>count: {state.count}</p>

      <Button
        type="primary"
        onClick={() => {
          dispatch({ type: 'increment' });
        }}
        style={{ marginRight: 30 }}
      >
        +
      </Button>

      <Button
        type="primary"
        onClick={() => {
          dispatch({ type: 'decrement' });
        }}
      >
        -
      </Button>
    </CardLayout>
  );
};

export default IntendedList;

複製代碼

第二種:惰性初始化,你能夠選擇惰性地建立初始 state。爲此,須要將 init 函數做爲 useReducer 的第三個參數傳入,這樣初始 state 將被設置爲 init(initialArg)

import React, { useReducer } from 'react';
import { Button } from 'antd';
import CardLayout from '@xb/layouts/CardLayout';

const initialCount = 0;

const init = initialCount => {
  return { count: initialCount };
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
};
const IntendedList = () => {
  const [state, dispatch] = useReducer(reducer, initialCount, init);

  return (
    <CardLayout>
      <p>count: {state.count}</p>

      <Button
        type="primary"
        onClick={() => {
          dispatch({ type: 'reset', payload: initialCount });
        }}
        style={{ marginRight: 30 }}
      >
        reset
      </Button>

      <Button
        type="primary"
        onClick={() => {
          dispatch({ type: 'increment' });
        }}
        style={{ marginRight: 30 }}
      >
        +
      </Button>

      <Button
        type="primary"
        onClick={() => {
          dispatch({ type: 'decrement' });
        }}
      >
        -
      </Button>
    </CardLayout>
  );
};

export default IntendedList;

複製代碼

頁面的展示(gif圖沒作成,先看這個把😄 ):

4.useCallback 緩存回調函數,僅在某個依賴項改變時纔會更新

相似useEffect,把內聯回調函數及依賴項數組做爲參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將很是有用。

參數:

  • 第一個參數是一個回調函數
  • 第二個參數是一個數組,存放回調函數的依賴,當這些依賴變化時,從新渲染
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
複製代碼

5. useMemo : 緩存變量,僅在某個依賴項改變時纔會更新

把建立函數和依賴項數組做爲參數傳入 useMemo,它僅會在某個依賴項改變時才從新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。

const compute = useMemo(() => {
    const count = 222;
    // 這裏使用 a或b 針對 count 作一些很複雜的計算
    // 只有當依賴的 a 或者 b 的值發生改變時纔會從新計算,不然返回的是以前緩存的值
    return count * a * b;
  }, [a, b]);

複製代碼

5. 自定義hook

有些知識真的是須要必定的積累後,才能理解,而且要多多學而時習之,就好比這個自定義hook,如今終於能模仿着寫出一些小例子了,分享給你們。


首先介紹一下,自定義 Hooks 容許建立自定義 Hook,只要函數名遵循以 use 開頭,且返回非 JSX 元素,就是 Hooks 啦!自定義 Hooks 內還能夠調用包括內置 Hooks 在內的全部自定義 Hooks

這是一個經過監聽參數值來判斷當前的登陸狀態的hook,先使用 手動的 + 、-來模擬一個動態監聽狀態,當數值大於0爲在線狀態,反之爲離線狀態,下面是代碼:

hook頁面:

import { useState, useEffect } from 'react';

/**
 * @param value {number}
 * @return {boolean}
 */
const useOnlineState = (value: number): boolean => {
  const [state, setState] = useState(false);

  useEffect(() => {
    if (value > 0 && Boolean(value)) {
      setState(true);
      return;
    }
    setState(false);
  }, [value]);

  return state;
};

export default useOnlineState;

複製代碼

調用頁面:

import React, { useState } from 'react';
import { Button, Card } from 'antd';
import useOnlineState from './test';

const App = () => {
  const [count, setCount] = useState(0);
  const onlineState = useOnlineState(count);

  return (
    <Card>
      <p> {count}</p>
    
      <Button
        type="primary"
        onClick={() => {
          setCount(count + 1);
        }}
      >
        +
      </Button>
      <Button
        style={{ margin: '0px 0px 20px 30px' }}
        type="primary"
        onClick={() => {
          setCount(count - 1);
        }}
      >
        -
      </Button>

      <p>當前狀態{onlineState ? '在線' : '離線'}( 數值大於0爲在線狀態,反之爲離線狀態 )</p>
    </Card>
  );
};

export default App;

複製代碼

頁面展示:

  • 離線:

  • 在線:

上面咱們使用自定義組件導出了一個online狀態,咱們還可使用導出函數方法,以下導出狀態和顯示隱藏的方法:

Hook:

import { useCallback, useState } from 'react';

export type IHook = [
  boolean,
  {
    hide: () => void;
    show: () => void;
  },
];

/**
 * 分享
 * @param {string} value
 * @return {IHook}
 */

const useShowinfo = (value: boolean): IHook => {
  const [state, setState] = useState<boolean>(value);

  const hide = useCallback(() => {
    setState(false);
  }, []);

  const show = useCallback(() => {
    setState(true);
  }, []);

  return [state, { hide, show }];
};

export default useShowinfo;


複製代碼

調用頁面:

import React from 'react';
import { Button, Card } from 'antd';
import useShowinfo from './test';

const App = () => {
  const [state, { show, hide }] = useShowinfo(false);

  return (
    <Card>
      <Button
        type="primary"
        onClick={() => {
          show();
        }}
      >
        show
      </Button>
      <Button
        style={{ margin: '0px 0px 20px 30px' }}
        type="primary"
        onClick={() => {
          hide();
        }}
      >
        hide
      </Button>
      <p>{state ? 'show' : 'hide'}</p>
    </Card>
  );
};

export default App;

複製代碼

頁面展示:

  • show

  • hide

十3、React.memo當子組件依賴的props未發生改變,緩存子組件,不進行渲染

咱們日常開發項目時,通常狀況下都本着一個模塊負責一起業務,獲取數據和邏輯通常放在父組件,渲染放在子組件。那麼當咱們的父組件頁面數據發生變化後,不管是否傳給子組件props,子組件就會從新渲染,固然這並非咱們想要的結果,咱們期待的結果:只有當子組件依賴父組件當props發生變化後,再次渲染子組件,下面就開始咱們的改造:


未改造前

父組件:

import React, { useState } from 'react';
import useForm from 'rc-form-hooks';
import { Form, Input, Radio } from 'antd';
import Child from './Child';

const Parent = () => {
  const form = useForm();
  const { getFieldDecorator } = form;
  const [sourceType, setSourceType] = useState<number>(0);

  // 切換資源分類
  const onChange = e => {
    const { value } = e.target;
    setSourceType(value);
  };

  return (
    <Form>
      <Form.Item label="資源名稱">
        {getFieldDecorator('title', {
          rules: [{ required: true, message: '請輸入資源名稱' }],
        })(<Input placeholder="請輸入資源名稱" maxLength={45} />)}
      </Form.Item>

      <Form.Item label="資源分類">
        {getFieldDecorator('type', {
          rules: [{ required: true }],
          initialValue: 0,
        })(
          <Radio.Group onChange={onChange}>
            <Radio value={0}>A類</Radio>
            <Radio value={1}>B類</Radio>
          </Radio.Group>,
        )}
      </Form.Item>
      <Child sourceType={sourceType} />
    </Form>
  );
};

export default Parent;

複製代碼

子組件:

import React from 'react';

interface IProps {
  sourceType: number;
}

const Child: React.FC<IProps> = props => {
  console.log('子組件渲染', props.sourceType);
  return <></>;
};

export default Child;

複製代碼

頁面展現:

初次進入父組件頁面:

當更改了非子組件的props後:

當更改了子組件的props後:


改造後

父組件同上,子組件以下改變:

import React from 'react';

interface IProps {
  sourceType: number;
}
const Child: React.FC<IProps> = React.memo(props => {
  console.log('子組件渲染', props.sourceType);
  return <></>;
});
export default Child;

複製代碼

改造後從新刷新父組件,查看子組件渲染狀況,此時已經沒有其餘多餘的渲染:


props升級到多層嵌套參數

上面咱們試的都是單層的數據,React.memo默認作到淺比較,若是咱們的參數是相似對象的多層嵌套,那就須要使用到它的第二個參數了

父組件只改動一下這裏:

<Child sourceType={{ a: { b: sourceType } }} />
複製代碼

重點在子組件,React.memo的第二個參數,我當前只想到了JSON.stringify,各位掘友有其餘更優雅的方式能夠告訴我哦😄:

import React from 'react';

interface IProps {
  sourceType: {
    a: {
      b: number;
    };
  };
}
const Child: React.FC<IProps> = React.memo(
  props => {
    console.log('子組件渲染', props.sourceType.a.b);
    return <></>;
  },

  (precProps, nextProps) => JSON.stringify(precProps) === JSON.stringify(nextProps),
);
export default Child;

複製代碼

當切換與子組件無關當數據後,子組件再也不渲染:

只有當子組件依賴的props參數改變後,纔會執行從新渲染

哈哈,是否是很神奇,我上面的例子是用在函數組件hooks中,那class組件,推薦使用PureComponent,相同的效果

import { PureComponent, Fragment,  } from 'react'

@Form.create()
class Search extends PureComponent {
  constructor(props) {
    super(props)
   
  }

  render() {
    return (
      <Fragment>內容</Fragment>
    )
  }
}

export default Search

複製代碼

十4、React.lazy 動態加載子組件,組件的懶加載

今天在逛掘金時,發現本身居然漏了這個lazy的方法,說明本身看的仍是不夠多,還須要須要須要努力啊!!💪


今天就來講說這個懶加載,咱們開發的過程當中,父子組件嵌套的狀況通常比較頻繁,但有時咱們的子組件是須要在必定場景纔會顯示的,那麼咱們就儘可能不讓它渲染,減小父組件頁面的渲染的負載。

React.lazy必須經過調用動態的import()加載一個函數,此時會返回一個Promise, 並解析(resolve)爲一個帶有包含React組件的默認導出的模塊。 ---- Reactjs官網

這裏還須要說明兩點:

  1. 須要使用標識符Suspense 來包裹子組件,以此讓react 得知哪些內容是須要懶加載的;
  2. Suspense的屬性fallback不能爲空,fallback屬性存放等待子組件加載時呈現出的元素;

我先給你們上一下代碼:

父組件:

import React, { useState, lazy, Suspense } from 'react';
import { Button } from 'antd';
const Test = lazy(() => import('./test'));

const App = () => {
  const [visible, setVisible] = useState<boolean>(false);

  return (
    <>
      <Button
        type="primary"
        onClick={() => {
          setVisible(true);
        }}
      >
        切換
      </Button>
      {visible && (
        <Suspense fallback={<div>Loading...</div>}>
          <Test />
        </Suspense>
      )}
      
    </>
  );
};

export default App;
複製代碼

子組件(一個普通的子組件):

import React, { useEffect } from 'react';

const TestView = () => {
  useEffect(() => {
    console.log('子組件的渲染');
  }, []);

  return <div>我是測試的頁面</div>;
};

export default TestView;

複製代碼

正常的父組件加載完,能夠看到咱們的子組件並無渲染:

當咱們點擊「切換」,子組件異步加載出來了:

參考文章:

  1. 如何使用React.lazy和Suspense進行組件延遲加載
  2. react代碼分割

寫到此處,並無結束哦!接下來我還會持續追加,看文章的小夥伴們能夠添加一下關注哦!

做者:Christine    
出處:https://juejin.cn/post/6844903512493539341
版權全部,歡迎保留原文連接進行轉載:) 
複製代碼

若是你對我對文章感興趣或者有些建議想說給我聽👂,也能夠添加一下微信哦!

郵箱:christine_lxq@sina.com

若是親感受個人文章還不錯的話,能夠一下添加關注哦!

最後:
        祝各位工做順利!
                        -小菜鳥Christine
複製代碼
相關文章
相關標籤/搜索