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');
...
}