一、setState()
更新数据
setState()更新数据是异步的- 注意:使用该语法,后面的
setState不要依赖前面setState的值 - 多次调用
setState,只会触发一次render重新渲染(为了性能)
import React from 'react'
import ReactDOM from 'react-dom'
/*
setState() 异步更新数据
*/
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
// 此处更新state是异步更新数据的(会更新但不是马上更新,点击一次,控制台的两个输出都是1)
this.setState({
count: this.state.count + 1 // 1 + 1
})
console.log('count:', this.state.count) // 1
this.setState({
count: this.state.count + 1 // 1 + 1
})
console.log('count:', this.state.count) // 1
}
render() {
console.log('render') //多次调用setState,只会触发一次render
return (
<div>
<h1>计数器:{this.state.count}</h1> {/*点击一次,虽然调用了两次setState,但显示2 */ }
<button onClick={this.handleClick}>+1</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
推荐语法
使用 setState((state,props) => {}) 语法,参数是个回调函数,在该函数里返回更新后的状态
- 参数state: 表示最新的state
- 参数props: 表示最新的props
import React from 'react'
import ReactDOM from 'react-dom'
/*
setState() 推荐语法
*/
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
// 注意:这种语法也是异步更新state的:先打印 'count:1',再打印 '第二次调用:{ count:2 }'
this.setState((state, props) => { //state:1
return {
count: state.count + 1 // 1 + 1
}
})
this.setState((state, props) => { //参数state表示最新的state:2
console.log('第二次调用:', state) // 2
return {
count: state.count + 1 // 2 + 1
}
})
console.log('count:', this.state.count) // 1 这种语法也是异步更新state的
}
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1> {/*点击一次,调用了两次setState,显示3 */}
<button onClick={this.handleClick}>+1</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
setState的第二个参数
- 场景:在状态更新(页面DOM完成重新渲染)后立即执行某个操作(类似componentDidUpdate)
- 语法:
setState(update[,callback])
import React from 'react'
import ReactDOM from 'react-dom'
/*
setState() callback
*/
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
this.setState(
(state, props) => {
return {
count: state.count + 1
}
},
// 状态更新后并且DOM重新渲染后,立即执行:
() => {
console.log('状态更新完成:', this.state.count) // 2
console.log(document.getElementById('title').innerText) // 计数器:2
}
)
console.log(this.state.count) // 1 先打印1,在状态更新完后,调用第二个参数的回调函数
}
render() {
return (
<div>
<h1 id="title">计数器:{this.state.count}</h1>
<button onClick={this.handleClick}>+1</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
二、JSX语法的转化过程
- JSX仅仅是
createElement()方法的语法糖(简化语法) - JSX语法被 @babel/preset-react 插件编译为
createElement()方法 - React 元素: 是一个对象,用来描述你希望在屏幕上看到的内容
三、组件更新机制
-
setState() 的两个作用
- 修改state
- 更新组件
-
过程:父组件Parent2重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件及其所有子组件)
-
根组件更新时,所有的子组件都会更新
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件更新机制
*/
import './index.css'
// 根组件
class App extends React.Component {
state = {
color: '#369'
}
getColor() {
return Math.floor(Math.random() * 256) //获取随机的颜色值
}
changeBG = () => {
this.setState(() => {
return {
color: `rgb(${this.getColor()}, ${this.getColor()}, ${this.getColor()})` //拼接成背景色
}
})
}
render() {
console.log('根组件')
return (
<div className="app" style={{ backgroundColor: this.state.color }}>
<button onClick={this.changeBG}>根组件 - 切换颜色状态</button>
<div className="app-wrapper">
<Parent1 />
<Parent2 />
</div>
</div>
)
}
}
// ------------------------左侧---------------------------
class Parent1 extends React.Component {
state = {
count: 0
}
handleClick = () => {
this.setState(state => ({ count: state.count + 1 }))
}
render() {
console.log('左侧父组件')
return (
<div className="parent">
<h2>
左侧 - 父组件1
<button onClick={this.handleClick}>点我({this.state.count})</button>
</h2>
<div className="parent-wrapper">
<Child1 />
<Child2 />
</div>
</div>
)
}
}
class Child1 extends React.Component {
render() {
console.log('左侧子组件 - 1')
return <div className="child">子组件1-1</div>
}
}
class Child2 extends React.Component {
render() {
console.log('左侧子组件 - 2')
return <div className="child">子组件1-2</div>
}
}
// ------------------------右侧---------------------------
class Parent2 extends React.Component {
state = {
count: 0
}
handleClick = () => {
this.setState(state => ({ count: state.count + 1 }))
}
render() {
console.log('右侧父组件')
return (
<div className="parent">
<h2>
右侧 - 父组件2
<button onClick={this.handleClick}>点我({this.state.count})</button>
</h2>
<div className="parent-wrapper">
<Child3 />
<Child4 />
</div>
</div>
)
}
}
class Child3 extends React.Component {
render() {
console.log('右侧子组件 - 1')
return <div className="child">子组件2-1</div>
}
}
class Child4 extends React.Component {
render() {
console.log('右侧子组件 - 2')
return <div className="child">子组件2-2 </div>
}
}
ReactDOM.render(<App />, document.getElementById('root'))
四、组件性能优化
1. 减轻state
- state只存储跟组件渲染相关的数据(比如:count/ 列表数据 /loading等)
- 注意:不用做渲染的数据不要放在state中,如定时器id等
- 对于这种需要在多个方法中用到的数据(跟渲染无关的),应该放到this中
2. 避免不必要的重新渲染
组件更新机制:父组件更新会引起子组件也被更新,导致子组件没有任何变化时也会重新渲染
避免不必要的重新渲染:
A. 使用钩子函数 shouldComponentUpdate(nextProps, nextState)
-
其返回值(写个条件判断)决定是否重新渲染
- nextProps:最新的props
- nextState:最新的状态
-
作用:这个函数有返回值,如果返回true,代表需要重新渲染,如果返回false,代表不需要重新渲染
-
触发时机:它是更新阶段的钩子函数,在组件重新渲染前执行
执行顺序:shouldComponentUpdate => render(调用setState更新组件,先执行shouldComponentUpdate,若返回false,render就不会再执行,避免重新渲染)
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件性能优化:
*/
// 根组件
class App extends React.Component {
state = {
count: 0
}
handleClick = () => {
this.setState(state => {
return {
count: state.count + 1
}
})
}
// 钩子函数
shouldComponentUpdate(nextProps, nextState) {
// 返回false,阻止组件重新渲染,不会执行render
// return false
// 最新的状态:控制台输出的值和页面显示的值相同
console.log('最新的state:', nextState)
// 更新前的状态:
console.log('this.state:', this.state)
return true
}
render() {
console.log('组件更新了')
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={this.handleClick}>+1</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
随机数案例
需求:随机生成数字,显示在页面,如果生成的数字与当前显示的数字相同,那么就不需要更新UI,反之更新UI。
1.状态是自己的:
- 利用nextState参数来判断当前组件是否需要更新
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件性能优化:比较state
*/
class App extends React.Component {
state = {
number: 0
}
// 点击事件,每次点击生成一个随机数
handleClick = () => {
this.setState(() => {
return {
number: Math.floor(Math.random() * 3) //Math.random()*3: 0~3之间的随机数,Math.floor()取整,最后取值为0,1,2
}
})
}
// 将要更新UI的时候会执行这个钩子函数
// 因为两次生成的随机数可能相同,如果相同,不重新渲染(不执行render)
shouldComponentUpdate(nextProps, nextState) {
console.log('最新状态:', nextState, ', 当前状态:', this.state)
return nextState.number !== this.state.number //前后不相等,返回true
}
render() {
console.log('render')
return (
<div>
<h1>随机数:{this.state.number}</h1>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
2.状态不是自己的:
- 利用props参数来判断是否需要进行更新 用props,组件APP得接收props(把展示数据的h1处改成单独的子组件NumberBox,让该子组件接收一个叫number的props)
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件性能优化:
*/
// 生成随机数
class App extends React.Component {
state = {
number: 0
}
handleClick = () => {
this.setState(() => {
return {
number: Math.floor(Math.random() * 3)
}
})
}
render() {
return (
<div>
<NumberBox number={this.state.number} />
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
class NumberBox extends React.Component {
// 将要更新UI的时候会执行这个钩子函数
shouldComponentUpdate(nextProps) {
console.log('最新props:', nextProps, ', 当前props:', this.props)
// 如果当前生成的值 与 页面的值 number相同,就返回false,不更新组件
return nextProps.number !== this.props.number
}
render() {
console.log('子组件中的render') //查看是否有不必要的渲染
return <h1>随机数:{this.props.number}</h1>
}
}
ReactDOM.render(<App />, document.getElementById('root'))
B.使用纯组件PureComponent
与 React.Component 功能相似
- 区别: PureComponent 内部自动实现了 shouldComponentUpdate钩子,不需要手动比较
- 原理:纯组件内部通过分别比对前后两次props和state的值,来决定是否重新渲染组件
只要props和state其中有一个变化了,就重新渲染;都相同就不重新渲染
实现原理
纯组件内部的对比是 shallow compare(浅层对比)
- 对于值类型来说:浅层对比是比较两个值是否相同(直接给值类型赋值即可)
- 引用类型:只比较对象的引用(地址)是否相同
注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件性能优化:
*/
// 引用类型:
const obj = { number: 0 }
const newObj = obj
newObj.number = 2
console.log(newObj === obj) // true
// 生成随机数
class App extends React.PureComponent {
state = {
obj: {
number: 0
}
}
handleClick = () => {
// 正确做法:创建新对象
const newObj = { ...this.state.obj, number: Math.floor(Math.random() * 3) }
this.setState(() => {
return {
obj: newObj
}
})
// 错误演示:直接修改原始对象中属性的值
/* const newObj = this.state.obj
newObj.number = Math.floor(Math.random() * 3)
this.setState(() => {
return {
obj: newObj
}
}) */
}
render() {
console.log('父组件重新render')
return (
<div>
<h1>随机数:{this.state.obj.number}</h1>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
随机数案例
- state:
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件性能优化:
*/
// 生成随机数
class App extends React.PureComponent {
state = {
number: 0
}
handleClick = () => {
this.setState(() => {
return {
number: Math.floor(Math.random() * 3)
}
})
}
render() {
console.log('父组件中的render')
return (
<div>
<h1>随机数:{this.state.number}</h1>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
- props:
import React from 'react'
import ReactDOM from 'react-dom'
/*
组件性能优化:
*/
// 生成随机数
class App extends React.Component {
state = {
number: 0
}
handleClick = () => {
this.setState(() => {
return {
number: Math.floor(Math.random() * 3)
}
})
}
render() {
return (
<div>
<NumberBox number={this.state.number} />
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
class NumberBox extends React.PureComponent {
render() {
console.log('子组件中的render')
return <h1>随机数:{this.props.number}</h1>
}
}
ReactDOM.render(<App />, document.getElementById('root'))
五、虚拟DOM和Diff算法(react高效的原因)
- React更新视图的思想是:只要state变化就重新渲染视图
- 问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染吗? 不是这样的
- 理想状态:部分更新,只更新变化的地方
虚拟DOM
React元素是虚拟DOM,本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)
使用虚拟DOM,操作真实DOM之前,会把两个有差异的虚拟DOM通过Diff算法进行比较,比较出有差异的部分再去更新页面。
Diff算法:最小化页面重绘
执行过程
- 初次渲染时,React会根据初始化的state(model),创建一个虚拟DOM对象(树)
- 根据虚拟DOM生成真正的DOM,渲染到页面
- 若有数据变化了(红色部分setState()),会重新根据新的数据,创建新的虚拟DOM对象(树)
- 与上一次得到的虚拟DOM对象,使用Diff算法比对前后两个虚拟DOM(找不同),得到需要更新的内容
- 最终,React只将变化的内容更新(patch)到真正的DOM中,重新渲染到页面
代码演示
组件的render()调用后,根据render里的 状态和JSX结构 生成虚拟DOM对象 (render()方法的调用并不意味着浏览器进行渲染,render方法调用时意味着Diff算法开始比对了)
import React from 'react'
//引入react-dom,用于支持react操作dom
import ReactDOM from 'react-dom'
// 生成随机数
class App extends React.PureComponent {
state = {
number: 0
}
handleClick = () => {
this.setState(() => {
return {
number: Math.floor(Math.random() * 2)
}
})
}
// render方法的每一次调用并不都意味着浏览器中的全部重新渲染
// render方法调用仅仅说明要执行diff算法将变化了的内容更新
render() {
const el = (
<div>
<h1>随机数:</h1>
<p>{this.state.number}</p>
//点击按钮后状态发生变化,会重新生成虚拟DOM
<button onClick={this.handleClick}>重新生成</button>
</div>
)
return el
}
}
//ReactDOM.render的第一个参数:虚拟dom对象,第二个参数:要渲染到的目标容器
ReactDOM.render(<App />, document.getElementById('root'))