1.目标
A. 能够知道setState()更新数据是异步的
B. 能够知道JSX语法的转化过程
C. 能够说出React组件的更新机制
D. 能够对组件进行性能优化
E. 能够说出虚拟DOM和Diff算法
2.目录
A. setState()的说明
B. JSX语法的转化过程
C. 组件更新机制
D. 组件性能优化
E. 虚拟DOM和Diff算法
3.setState()的说明
3.1 更新数据
A. setState() 是异步更新数据的
B. 注意:使用该语法时,后面的setState()不能依赖于前面的setState()
C. 可以多次调用setState(),只会触发一次重新渲染
1setState.js
import React from "react";class App31 extends React.Component {state = {count: 0,};handleClick = () => {//异步更新操作this.setState({count: this.state.count + 1,});console.log("count1:" + this.state.count);this.setState({count: this.state.count + 1,});console.log("count2:" + this.state.count);};render() {//数据更新了就会调用一次render//但是,如果数据变化一样的,render只调用一次console.log("render");return (<div><h1>计数器:{this.state.count}</h1><button onClick={this.handleClick}>点击</button></div>);}
}export default App31;
index,js
import App31 from "./1setState";
ReactDOM.createRoot(document.getElementById("root")).render(<App31></App31>);
3.2 推荐语法
A. 推荐:使用setState((state,props)=>{})语法
B. 参数state:表示最新的state
C. 参数props:表示最新的props
import React from "react";class App31 extends React.Component {state = {count: 1,};handleClick = () => {// //异步更新操作// this.setState({// count: this.state.count + 1,// });// console.log("count1:" + this.state.count);// this.setState({// count: this.state.count + 1,// });// console.log("count2:" + this.state.count);//推荐语法//注意:这种语法也是异步更新state的,但是会记录最新的state的数据,所以在页面显示为3this.setState((state, props) => {console.log("state1:", state, "props1:", props);return {count: state.count + 1,};});console.log("count:", this.state.count); // 1this.setState((state, props) => {console.log("state2:", state, "props2:", props);return {count: state.count + 1,};});console.log("count:", this.state.count); // 1};render() {//数据更新了就会调用一次render//但是,如果数据变化一样的,render只调用一次console.log("render");return (<div><h1>计数器:{this.state.count}</h1><button onClick={this.handleClick}>点击</button></div>);}
}export default App31;
3.3 第二个参数
A. 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
B. 语法:setState(update [,callback])
import React from "react";class App31 extends React.Component {state = {count: 1,};handleClick = () => {// //异步更新操作// this.setState({// count: this.state.count + 1,// });// console.log("count1:" + this.state.count);// this.setState({// count: this.state.count + 1,// });// console.log("count2:" + this.state.count);//推荐语法//注意:这种语法也是异步更新state的,但是会记录最新的state的数据,所以在页面显示为3// this.setState((state, props) => {// console.log("state1:", state, "props1:", props);// return {// count: state.count + 1,// };// });// console.log("count:", this.state.count); // 1// this.setState((state, props) => {// console.log("state2:", state, "props2:", props);// return {// count: state.count + 1,// };// });// console.log("count:", this.state.count); // 1// 第二个参数,在状态更新(页面完成重新渲染)后立即执行某个操作this.setState((state, props) => {return {count: state.count + 1,};},() => {console.log("状态更新完成,当前count值:", this.state.count);});};render() {//数据更新了就会调用一次render//但是,如果数据变化一样的,render只调用一次console.log("render");return (<div><h1>计数器:{this.state.count}</h1><button onClick={this.handleClick}>点击</button></div>);}
}export default App31;
4.JSX语法的转化过程
A. JSX仅仅是createElement()方法的语法糖(简化语法)
B. JSX语法被@babel/preset-react插件编译为createElement()方法
C. React元素:是一个对象,用来描述你希望的屏幕上看到的内容
const element=<h1 className='greeting'>Hello JSX!</h1>
// const element1=React.createElement('h1',{
// className:'greeting'
// },'Hello JSX!!!')
console.log(element);
// console.log(element1);
ReactDOM.render(element,document.getElementById('root'))
效果图:
5.组件更新机制
A. setState()的两个作用:1.修改state 2.更新组件(UI)
B. 过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)
首次加载时渲染
点击根组件会触发所有组件,点击左侧父组件1时会触发局部更新,只更新当前组件与子组件,不会触发父组件
2comUpdate.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./comUpdate.css";class App50 extends React.Component {state = {bgColor: "#369",};getBgColor = () => {return Math.floor(Math.random() * 256);};toggleBgColor = () => {this.setState({bgColor: `rgb(${this.getBgColor()},${this.getBgColor()},${this.getBgColor()})`,});};render() {console.log("根组件");return (<divclassName="rootParent"style={{ backgroundColor: this.state.bgColor }}>根组件<button onClick={this.toggleBgColor}>切换根组件颜色</button><div className="app-wrapper"><Parent1></Parent1><Parent2></Parent2></div></div>);}
}
// 左边
class Parent1 extends React.Component {state = {count: 0,};handleClick = (state) => {this.setState((state) => {return {count: state.count + 1,};});};render() {console.log("左侧父组件 1");return (<div className="Parent1"><span>左侧-父组件</span><button onClick={this.handleClick}>点击({this.state.count})</button><div className="parentWrapper1"><Child1></Child1><Child2></Child2></div></div>);}
}
class Child1 extends React.Component {render() {console.log("左侧子组件 1-1");return <div className="child1">左侧子组件1-1</div>;}
}
class Child2 extends React.Component {render() {console.log("左侧子组件 1-2");return <div className="child2">左侧子组件1-2</div>;}
}// 右边
class Parent2 extends React.Component {state = {count: 0,};handleClick = (state) => {this.setState((state) => {return {count: state.count + 2,};});};render() {console.log("右侧父组件 2");return (<div className="Parent1"><span>右侧-父组件</span><button onClick={this.handleClick}>点击({this.state.count})</button><div className="parentWrapper1"><Child3></Child3><Child4></Child4></div></div>);}
}
class Child3 extends React.Component {render() {console.log("右侧子组件 2-1");return <div className="child1">右侧子组件2-1</div>;}
}
class Child4 extends React.Component {render() {console.log("右侧子组件 2-2");return <div className="child2">右侧子组件2-2</div>;}
}
export default App50;
index.js
import App50 from "./2comUpdate";
ReactDOM.createRoot(document.getElementById("root")).render(<App50></App50>);
comUpdate.css
.rootParent {width: 800px;height: 600px;margin: 0 auto;font-weight: 700;font-size: 22px;
}.app-wrapper {display: flex;justify-content: space-between;margin-top: 20px;
}.Parent1 {width: 40%;height: 400px;background-color: pink;
}.parentWrapper1 {display: flex;justify-content: space-between;margin-top: 30px;
}.child1 {width: 40%;height: 300px;background-color: lightgreen;
}.child2 {width: 40%;height: 300px;background-color: lightgrey;
}.Parent2 {width: 40%;height: 400px;background-color: salmon;
}
6.组件性能优化
6.1 减轻state
A. 减轻state:只存储跟组件渲染相关的数据(比如:count/列表数据/loading等)
B. 注意:不用做渲染的数据不要放在state中,比如定时器id等
C. 对于这种需要在多个方法中用到的数据,应该放在this中
6.2 避免不要的重新渲染
A. 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
B. 问题:子组件没有任何变化时也会重新渲染
C. 如何避免不必要的重新渲染吗?
D. 解决方案:使用钩子函数shouldComponentUpdate(nextProps,nextState)
E. 作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
F. 触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate->render)
3performanceOptimize.js
import React from "react";class App62 extends React.Component {state = {count: 0,};handleClick = () => {this.setState((state) => {return {count: state.count + 1,};});};//钩子函数shouldComponentUpdate(nextProps, nextState) {//返回false,阻止组件重新渲染// return false;//最新的状态console.log("最新的state:", nextState);//更新前的状态console.log("this.state:", this.state);return true;}render() {console.log("组件更新了");return (<div><h1>count:{this.state.count}</h1><button onClick={this.handleClick}>点击</button></div>);}
}export default App62;
index.js
import App62 from "./3performanceOptimize";
ReactDOM.createRoot(document.getElementById("root")).render(<App62></App62>);
6.2.1 案例:随机数(nextState)
4App621Random.js
import React from "react";class App621Random extends React.Component {state = {number: 0,};getRandom = () => {this.setState((state) => {return {number: Math.floor(Math.random() * 3),};});};//因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染shouldComponentUpdate(nextProps, nextState) {if (nextState.number === this.state.number) {return false;}return true;}render() {console.log("触发渲染");return (<div><h1>随机数:{this.state.number}</h1><button onClick={this.getRandom}>获取随机数</button></div>);}
}export default App621Random;
index.js
import App621Random from "./4App621Random";
ReactDOM.createRoot(document.getElementById("root")).render(<App621Random></App621Random>
);
6.2.2 案例:随机数(NextProps)
4App621Random.js
import React from "react";class App621Random extends React.Component {state = {number: 0,};getRandom = () => {this.setState((state) => {return {number: Math.floor(Math.random() * 3),};});};//因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染// shouldComponentUpdate(nextProps, nextState) {// if (nextState.number === this.state.number) {// return false;// }// return true;// }render() {console.log("触发父组件的render");return (<div>{/* <h1>随机数:{this.state.number}</h1> */}<ChildNumber number={this.state.number}></ChildNumber><button onClick={this.getRandom}>获取随机数</button></div>);}
}
class ChildNumber extends React.Component {shouldComponentUpdate(nextProps, nextState) {console.log("最新props:", nextProps, ",当前props:", this.props);return nextProps.number !== this.props.number;}render() {console.log("子组件中的render");return <h1>子组件的随机数:{this.props.number}</h1>;}
}export default App621Random;
index.js
import App621Random from "./4App621Random";
ReactDOM.createRoot(document.getElementById("root")).render(<App621Random></App621Random>
);
6.3 纯组件
A. 纯组件:PureComponent与React.Component功能相似
B. 区别:PureComponent内部自动实现了shouldComponentUpdate钩子,无需手动比较
C. 原理:纯组件内部通过对比前后两次props和state的值,来决定是否重新渲染组件
5pure.js
import React from "react";class PureApp extends React.PureComponent {state = {number: 0,};getRandom = () => {this.setState((state) => {return {number: Math.floor(Math.random() * 2),};});};render() {console.log("父组件render");return (<div><h1>父组件随机数:{this.state.number}</h1><ChildPure number={this.state.number}></ChildPure><button onClick={this.getRandom}>获取随机数</button></div>);}
}class ChildPure extends React.PureComponent {render() {console.log("子组件render");return <h1>子组件随机数:{this.props.number}</h1>;}
}
export default PureApp;
index.js
import PureApp from "./5pure";
ReactDOM.createRoot(document.getElementById("root")).render(<PureApp></PureApp>
);
6.3.1 浅层对比
A. 说明:纯组件内部的对比是shallow compare(浅层对比)
B. 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
C.对于引用类型来说:只比较对象的引用(地址)是否相同
D.注意:state或props中属性值为引用类型时,应该创建新数据,不要直接修改数据!
shallow.js
import React from "react";class ShallowRandom extends React.PureComponent {state = {obj: {number: 0,},};getRandom = () => {// 错误演示:直接修改原始对象中属性的值// const newObj = this.state.obj;// newObj.number = Math.floor(Math.random() * 2);// this.setState((state) => {// return {// obj: newObj,// };// });//正确做法:创建新对象const newObj = { ...this.state.obj, number: Math.floor(Math.random() * 2) };this.setState(() => {return {obj: newObj,};});};render() {console.log("父组件render");return (<div><h1>父组件随机数的值:{this.state.obj.number}</h1><ChildRandom number={this.state.obj.number}></ChildRandom><button onClick={this.getRandom}>重新生成随机数</button></div>);}
}class ChildRandom extends React.PureComponent {render() {console.log("子组件render");return <h1>子组件随机数:{this.props.number}</h1>;}
}export default ShallowRandom;
import ShallowRandom from "./6shallow";
ReactDOM.createRoot(document.getElementById("root")).render(<ShallowRandom></ShallowRandom>
);
7.虚拟DOM和DIFF算法
A. React更新视图的思路是:只是state变化就重新渲染视图
B. 特点:思路非常清晰
C. 问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?不是
D. 理想状态:部分更新,只更新变化的地方
E. 问题:React是如何做到部分更新的?虚拟DOM配合Diff算法
F. 虚拟DOM:本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)
7.1 执行过程
A. 初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)。
B. 根据虚拟DOM生成真正的DOM,渲染到页面中
C. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)
A. 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容
B. 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面
7.2 代码演示
A. 组件render()调用后,根据状态和JSX结构生成虚拟DOM对象
B. 示例中,只更新P元素的文本节点内容
7.vdom.js
import React from "react";class Vdom extends React.PureComponent {state = {number: 0,};getRandom = () => {this.setState(() => {return {number: Math.floor(Math.random() * 3),};});};render() {return (<div><h1>随机数</h1><h1>{this.state.number}</h1><button onClick={this.getRandom}>重新生成</button></div>);}
}export default Vdom;
index.js
import Vdom from "./7vdom";
ReactDOM.createRoot(document.getElementById("root")).render(<Vdom></Vdom>);
8.总结
A. 工作角度:应用第一,原理第二
B. 原理有助于更好的理解React的自身运行机制
C. SetState()异步更新数据
D. 父组件更新导致子组件更新,纯组件提升性能
E. 思路清晰简单为前提,虚拟DOM和Diff保效率
F. 虚拟DOM->state+JSX
G. 虚拟DOM的真正价值从来都不是性能
H. 虚拟DOM使react脱离了浏览器的束缚