PureComonent默认给类组件添加了一个shouldComponentUpdate的钩子函数,在这个钩子中,会对新旧状态及属性做一个浅比较,以此达到优化组件渲染的目的。
给需要获取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);
- }
类组件:获取当前组件的实例,通常用于父子组件传值及方法调用
- 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按钮
- }
- }
基于子组件中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>
- callback 发生在 componentDidUpdate 周期函数之后
- 特殊点:如果基于shouldComponentUpdate阻止了视图更新,componentDidUpdate钩子不会执行,但是callback会执行
tips:类似于Vue的$nextTick
在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会立即放入更新队列
- 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,就有可能被分开批处理。
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值。
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值,进行一次渲染。
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');
- ...
- }