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
复制代码
相关文章
相关标签/搜索