【react】基础知识补充及原理【粗略版】

1.补充知识

1.PureComponent和React.Component的区别

PureComonent默认给类组件添加了一个shouldComponentUpdate的钩子函数,在这个钩子中,会对新旧状态及属性做一个浅比较,以此达到优化组件渲染的目的。

2.基于ref获取DOM元素的语法

1.给原生元素设置ref

给需要获取dom的元素设置ref='xxx',然后使用this.refs.xxx进行获取,不建议使用,在React.StrictMode模式下会报错

render() {
   return <div>
      <h2 className="title" ref="titleBox">温馨提示</h2>
   </div>;
}
componentDidMount() {
   console.log(this.refs.titleBox);
}

将ref设置为一个函数,并将ref的形参(dom元素)挂载到实例上

render() {
   return <div>
      <h2 className="title" ref={x => this.box2 = x}>友情提示</h2>
   </div>;
}
componentDidMount() {
   console.log(this.box2);
}

基于React.createRef()创建一个ref对象,初始化时为null 

box3 = React.createRef();

render() {
   return <div>
      <h2 className="title" ref={this.box3}>郑重提示</h2>
   </div>;
}
componentDidMount() {
   console.log(this.box3.current);
}

2.给组件设置ref

类组件:获取当前组件的实例,通常用于父子组件传值及方法调用

class Demo extends React.Component {
    render() {
        return <div>
            //子组件
            <Child1 ref={x => this.child1 = x} />
        </div>;
    }
    componentDidMount() {
        console.log(this.child1);
    }
}

 函数组件:获取函数组件内部某个元素。需要使用React.forwardRef()包裹子组件,这是函数子组件将拥有除props外的另一个形参:ref。通过把该形参设置给函数子组件中的某个元素,来达到获取函数子组件中dom元素的目的。

import React from "react";

const Child2 = React.forwardRef(function Child2(props, ref) {
    // 该ref形参为给子组件设置的ref值: x => this.child2 = x
    return <div>
        子组件2
        <button ref={ref}>按钮</button>
    </div>;
});

class Demo extends React.Component {
    render() {
        return <div>
            <Child2 ref={x => this.child2 = x} />
        </div>;
    }
    componentDidMount() {
       console.log(this.child2); //子组件内部的button按钮
    }
}

3.React中的插槽

基于子组件中props.children获取传递的插槽信息(子节点信息)。

  + 调用组件时,基于双闭合调用方式把插槽信息(子节点信息)传递给组件,组件内部进行渲       染

const DemoOne = function Demo(props) {
    let { title, children } = props;
    
    return <div>
      <h2>{title}</h2>
      <br/>
      {children}
    </div>;
};

//传递时
<Demo title="demo">
  //编译为vdom后作为props.children的值
  <div>我是插槽内容</div>
</Demo>

具名插槽

import React from 'react';
const DemoOne = function Demo(props) {
    let { title, children } = props;
    // 对children的类型做处理
    // 可以基于 React.Children 对象中提供的方法,对props.children做处理: 
    count\forEach\map\toArray... 在这些方法的内部,已经对children的各种形式做了处理
  
    children = React.Children.toArray(children);
    let headerSlot = [],
        footerSlot = [],
        defaultSlot = [];
    children.forEach(child => {
        // 传递进来的插槽信息,都是编译为virtualDOM后传递进来的,而不是传递的标签
        let { slot } = child.props;
        if (slot === 'header') {
            headerSlot.push(child);
        } else if (slot === 'footer') {
            footerSlot.push(child);
        } else {
            defaultSlot.push(child);
        }
    });

    return <div>
        {headerSlot}
        <br />
        <h2>{title}</h2>
        <br />
        {footerSlot}
    </div>;
};


<Demo title="学插槽">
  <span slot="footer">我是页脚</span>
  <span>我是匿名的</span>
  <span slot="header">我是页眉</span>
</Demo>

4.setState(partialState, callback)

  • callback 发生在 componentDidUpdate 周期函数之后
  • 特殊点:如果基于shouldComponentUpdate阻止了视图更新,componentDidUpdate钩子不会执行,但是callback会执行

tips:类似于Vue的$nextTick

5.setState的异步(react18)

在react18中,setState是异步的,无论是合成事件,周期函数,定时器等等。这时setState实现对状态的批处理,即将需更新的状态放入更新队列【updater】中进行处理。批处理有效减少更新次数,降低性能消耗

在这段代码中,三句setState代码不会立即更新状态及视图,代码顺次执行,遇到setState则将其加入更新队列中,最后让更新队列中的任务统一更新/渲染一次(批处理)。故上述代码render只执行一次,而三个console.log也只是打印更新之前的值。 

handle = () => {
        let { x, y, z } = this.state;
        this.setState({ x: x + 1 });
        console.log(this.state.x);

        this.setState({ y: y + 1 });
        console.log(this.state.y);

        this.setState({ z: z + 1 });
        console.log(this.state.z);
    };

   render() {
     console.log('我是render');
     ...
   }

tips:批处理的机制是微任务吗?如果不是,那么和微任务有什么区别?或者说是一种类异步操作

在定时器中时,批处理将如何进行?

这里将不等待setTimeout执行,而是先将setTimeout之外的两个setState先放入更新队列批处理渲染一次;然后1000ms后setTimeout执行,再将定时器内部的setState放入更新队列中批处理渲染

handle = () => {
        let { x, y, z } = this.state;
        this.setState({ x: x + 1 });
        this.setState({ y: y + 1 });
        console.log(this.state);

        setTimeout(() => {
            this.setState({ z: z + 1 });
            console.log(this.state);
        }, 1000);
    };

render() {
     console.log('我是render');
     ...
   }

tips:如果setTimeout不传入时间呢?

在多个时间差距不大或者时间差距极小的定时器中,分别有setState,那么批处理如何进行

在当前相同时间段内【浏览器此时可以处理的事情中】,遇到setState会立即放入更新队列

handle = () => {
        let { x, y, z } = this.state;

        setTimeout(() => {
            this.setState({ x: x + 1 });
            console.log(this.state);
        }, 1000);

        setTimeout(() => {
            this.setState({ y: y + 1 });
            console.log(this.state);
        }, 1000);

        setTimeout(() => {
            this.setState({ z: z + 1 });
            console.log(this.state);
        }, 1000);
    };

render() {
     console.log('我是render');
     ...
   }

上述代码中,由于setTimeout同时执行,时间差距极小,故setState批处理渲染一次。那么在不断更改三个setTimeout的时间中,时间差距大于2ms,就有可能被分开批处理。

6.setState的同步(react18以下)

 react18以下,在合成事件【jsx元素中基于onXxx绑定的事件】,周期函数中,setState的操作是异步的;而在定时器,手动获取DOM元素事件绑定【元素.addEventListener()】等,它将变为同步操作(立即更新状态)

handle = () => {
        let { x, y, z } = this.state;
        this.setState({ x: x + 1 });
        this.setState({ y: y + 1 });
        console.log(this.state);

        setTimeout(() => {
            this.setState({ z: z + 1 });
            console.log(this.state);
        }, 1000);
    };

render() {
     console.log('我是render');
     ...
   }

该段代码先执行打印【我是render】(此结果由setTimeout外的两个setState批处理执行得到);1s后,打印z为处理后的值,即说明定时器执行时,setState为同步,此时console.log能打印最新的z值。

7.setState的第一个参数为函数时 

setState((prevState) => { //prevState: 存储之前的状态值

   return {

      xxx: xxx

   }

})

多次修改同一个state值时,可以通过该方式获取上一轮的state值

handle = () => {
        //x为0
        for(let i=0;i<20;i++){
          //a代码
          this.setState({x: x+1});

          //b代码
          this.setState(prevState => {
             return {
                x: prevState + 1
             }
          });
        }
    };

render() {
     console.log('我是render');
     ...
   }

上述a代码由于批处理机制的原因会打印1;而b代码则是将setState的第一个参数:函数放入更新队列,把函数依次执行,最终得到叠加后的x值,进行一次渲染。

8.flushSync

 flushSync 可以刷新【updater】更新队列,即让修改状态的任务立即批处理一次。

这是一个实验性的api,不利于性能,谨慎使用。

import { flushSync } from 'react-dom';

handle = () => {
        let { x, y, z } = this.state;

        this.setState({ x: x + 1 });
        console.log(this.state.x);
        
        //此处去刷新队列,会得到最新的x和y
        flushSync(() => {
          this.setState({ y: y + 1 });
          console.log(this.state.y);
        });
        
        //此时的z会是最新的x值和y值之和
        this.setState({ z: z + 1 });
        console.log(this.state.z);
    };

   render() {
     console.log('我是render');
     ...
   }

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值