react基础--原理揭秘:
目录
3.2 避免不必要的重新渲染--shouldComponentUpdate(nextProps, nextState)
一、setState
1.1 setState() 是异步更新数据的
-
注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()
1. 当你调用 setState 的时候,React.js 并不会马上修改 state (为什么) 2. 而是把这个对象放到一个更新队列里面 3. 稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
-
可以多次调用 setState() ,只会触发一次重新渲染
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
class App extends Component {
state = {
count: 20,
money: 100
}
render() {
return (
<div>
<h4>APP组件</h4>
<div>点击次数:{this.state.count}--{this.state.money}</div>
<button onClick={this.handleClick}>点击</button>
</div>
)
}
handleClick = () => {
// 异步
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
money: 200
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
// 后面覆盖前面,上面的一堆就等价于
// this.setState({
// count: this.state.count + 1,
// money: 200
// })
console.log(this.state.count);
}
}
ReactDom.createRoot(document.getElementById('root')).render(<App />)
在使用 React.js 的时候,并不需要担心多次进行 setState
会带来性能问题。
1.2 推荐语法(setState的第一个参数)
使用 setState((preState) => {})
语法
参数preState: React.js 会把上一个 setState
的结果传入这个函数
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
class App extends Component {
state = {
count: 20,
money: 100
}
render() {
return (
<div>
<h4>APP组件</h4>
<div>点击次数:{this.state.count}--{this.state.money}</div>
<button onClick={this.handleClick}>点击</button>
</div>
)
}
handleClick = () => {
// state -- 能够保证是最新的 state
this.setState((state) => {
return {
count: state.count + 1
}
})
this.setState((state) => {
return {
count: state.count + 1
}
})
// 或者下面这种写法
// 箭头函数返回一个对象的时候要用小括号包起来
this.setState((state) => ({ count: state.count + 1})
)
}
}
ReactDom.createRoot(document.getElementById('root')).render(<App />)
这种语法依旧是异步的,但是state可以获取到最新的状态,适用于需要调用多次setState
1.3 setState的第二个参数
-
场景:在状态更新(页面完成重新渲染)后立即执行某个操作
-
语法:
setState(updater[, callback])
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
class App extends Component {
state = {
count: 20,
money: 100
}
render() {
return (
<div>
<h4>APP组件</h4>
<div>点击次数:{this.state.count}--{this.state.money}</div>
<button onClick={this.handleClick}>点击</button>
</div>
)
}
handleClick = () => {
// state -- 能够保证是最新的 state
this.setState((state) => {
return {
count: state.count + 1
}
})
this.setState((state) => {
return {
count: state.count + 1
}
})
// 或者下面这种写法
// 箭头函数返回一个对象的时候要用小括号包起来
this.setState((state) => ({ count: state.count + 1})
)
// 拿到的还是修改之前的
console.log(this.state.count);
this.setState((state) => ({ count: state.count + 1}),
() => {
console.log(this.state.count);
}
)
}
}
ReactDom.createRoot(document.getElementById('root')).render(<App />)
1.4 综合示例
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
class App extends Component {
state = {
isShow: false
}
inputRef = React.createRef()
// 点击按钮,显示输入框,并且让输入框有焦点
render() {
return (
<div>
<h4>APP组件</h4>
{
this.state.isShow ? (<input type="text" ref={this.inputRef} placeholder='请输入你的评论' />) : (<button onClick={this.handleClick}>发表评论</button>)
}
</div>
)
}
handleClick = () => {
this.setState({
isShow: true
}, () => {
this.inputRef.current.focus()
})
}
}
ReactDom.createRoot(document.getElementById('root')).render(<App />)
二、组件更新机制
setState() 的两个作用: 1. 修改 state 2. 更新组件(UI)
过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
import Parent from './Parent'
class App extends Component {
render() {
console.log('App render');
return (
<div>
<h4>APP组件</h4>
<Parent></Parent>
<button onClick={this.handleClick}>渲染</button>
</div>
)
}
handleClick = () => {
this.setState({})
}
}
ReactDom.createRoot(document.getElementById('root')).render(<App />)
Parent.js
import React, { Component } from 'react'
import Son from './Son';
import Son2 from './Son2'
export default class Parent extends Component {
render() {
console.log('parent render');
return (
<div>
<p>Parent</p>
<Son></Son>
<Son2></Son2>
</div>
)
}
}
Son.js
import React, { Component } from 'react'
export default class Son extends Component {
render() {
console.log('son1 render');
return (
<div>
<p>Son组件</p>
</div>
)
}
}
Son2.js
import React, { Component } from 'react'
export default class Son2 extends Component {
render() {
console.log('son2 render');
return (
<div>
<p>Son2组件</p>
</div>
)
}
}
三、组件性能优化
遵循的原则:
- 功能第一
- 性能优化第二
3.1 减轻state
减轻 state:只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading 等)
注意:不用做渲染的数据不要放在 state 中,比如定时器 id等
对于这种需要在多个方法中用到的数据,应该直接放在 this 中
-
this.xxx = 'bbb'
-
this.xxx
class Hello extends Component {
componentDidMount() {
// timerId存储到this中,而不是state中
this.timerId = setInterval(() => {}, 2000)
}
componentWillUnmount() {
clearInterval(this.timerId)
}
render() { … }
}
vue中同样不要把和渲染无关的数据放到data中
3.2 避免不必要的重新渲染--shouldComponentUpdate(nextProps, nextState)
不是很推荐这种方法,而是推荐使用纯组件
-
组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
-
问题:子组件没有任何变化时也会重新渲染 (接收到的props没有发生任何的改变)
-
如何避免不必要的重新渲染呢?
-
解决方式:使用钩子函数
shouldComponentUpdate(nextProps, nextState)
-
作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
-
触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate => render)
第一个参数:
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
import Parent from './Parent'
class App extends Component {
render() {
console.log('App render');
return (
<div>
<h4>APP组件</h4>
<Parent></Parent>
</div>
)
}
}
ReactDom.createRoot(document.getElementById('root')).render(<App />)
Parent.js
import React, { Component } from 'react'
import Son from './Son';
import Son2 from './Son2'
export default class Parent extends Component {
state = {
money: 100,
car: '小黄车'
}
render() {
console.log('Parent render');
const { money, car } = this.state
return (
<div>
<p>Parent</p>
<Son money={money}></Son>
<Son2 car={car}></Son2>
<button onClick={() => this.setState({money: 200})}>加钱</button>
<button onClick={() => this.setState({car: '镚子'})}>改车</button>
</div>
)
}
}
Son.js
import React, { Component } from 'react'
export default class Son extends Component {
render() {
console.log('Son render');
return (
<div>
<p>Son组件--{this.props.money}</p>
</div>
)
}
shouldComponentUpdate(nextProps){
// console.log(this.props);
// console.log(nextProps);
if(this.props.money === nextProps.money){
// 返回 true(默认) 或 false
return false; // 让组件不更新
}else {
return true;
}
}
}
Son2.js
import React, { Component } from 'react'
export default class Son2 extends Component {
render() {
console.log('Son2 render');
return (
<div>
<p>Son2组件--{this.props.car}</p>
</div>
)
}
}
第二个参数:
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
class App extends Component {
state = {
list: ['1', '2', '3'],
current: 0
}
render() {
console.log('APP render');
return (
<div>
<h4>APP组件</h4>
<div>渲染:{this.state.list[this.state.current]}</div>
<button onClick={this.random}>随机</button>
</div>
)
}
random = () => {
this.setState({
current: parseInt(Math.random() * this.state.list.length)
})
}
shouldComponentUpdate(nextProps, nextState){
if(this.state.current === nextState.current){
// 没有变化
return false
} else {
return true
}
}
}
ReactDom.createRoot(document.getElementById('root')).render(<App />)
四、纯组件
做组件性能优化推荐用纯组件去做 --做性能优化的时候
纯组件内部用的是浅层对比,也就是不能改原来的对象或数组,而是给一个新的对象或数组
React.PureComponent
与 React.Component
功能相似
区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件
4.1 纯组件的基本使用
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { PureComponent } from 'react';
class App extends PureComponent {
state = {
list: ['1', '2', '3'],
current: 0
}
render() {
console.log('APP render');
return (
<div>
<h4>APP组件</h4>
<div>渲染:{this.state.list[this.state.current]}</div>
<button onClick={this.random}>随机</button>
</div>
)
}
random = () => {
this.setState({
current: parseInt(Math.random() * this.state.list.length)
})
}
}
ReactDom.createRoot(document.getElementById('root')).render(<App />)
只有在性能优化的时候可能会用到纯组件,不要所有的组件都使用纯组件,因为纯组件需要消耗性能进行对比
4.2 纯组件的浅层对比说明
值类型:
-
说明:纯组件内部的对比是 shallow compare(浅层对比)
-
对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
let number = 0
let newNumber = number
newNumber = 2
console.log(number === newNumber) // false
state = { number: 0 }
setState({
number: Math.floor(Math.random() * 3)
})
// PureComponent内部对比:
最新的state.number === 上一次的state.number // false,重新渲染组件
引用类型:
-
说明:纯组件内部的对比是 shallow compare(浅层对比)
-
对于引用类型来说:只比较对象的引用(地址)是否相同
const obj = { number: 0 }
const newObj = obj
newObj.number = 2
console.log(newObj === obj) // true
state = { obj: { number: 0 } }
// 错误做法
state.obj.number = 2
setState({ obj: state.obj })
// PureComponent内部比较:
最新的state.obj === 上一次的state.obj // true,不重新渲染组件
纯组件的最佳实践:
注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!
// 正确!创建新数据
const newObj = {...state.obj, number: 2}
setState({ obj: newObj })
// 正确!创建新数据
// 不要用数组的push / unshift 等直接修改当前数组的的方法
// 而应该用 concat 或 slice 等这些返回新数组的方法
this.setState({
list: [...this.state.list, {新数据}]
})