0%

React-深入原理

setState() 的说明

更新数据

  • setState()是异步更新数据的

  • 注意:使用改语法时,后面的setState() 不要依赖前面的setState()

  • 可以多次调用setState(), 只会触发一次重新渲染

    1
    2
    3
    4
    5
    this.state = {count:1}
    this.setState({
    count:this.state.count+1
    })
    console.log(this.state.count) // 1

推荐语法

  • 推荐:使用 setState((state,props)=>{}) 语法

  • 参数state:表示最新的state

  • 参数props:表示最新的props

    1
    2
    3
    4
    5
    6
    xthis.setState((state,props)=>{
    return{
    conut:state.count +1
    }
    })
    console.log(this.state.count) // 1

第二个参数

  • 场景:在状态更新(页面完成重新渲染)后立即执行某个操作

  • 语法:setState(updater,[callback])

    1
    2
    3
    4
    this.setState(
    (state,props)=>{},
    ()=>{conslole.log('这个回调函数会在状态更新后立即执行')}
    )
    1
    2
    3
    4
    5
    6
    this.setState(
    (state,props)=>{},
    ()=>{
    document.title = '更新state后的标题:'+this.state.count
    }
    )

JSX 语法的转化过程

  • JSX 仅仅是 createElement()方法的语法糖(简化语法)
  • JSX 语法被 @babel/preset-react 插件编译为 createElement() 方法
  • React 元素:是一个对象,用来描述你希望在屏幕上看到的内容

JSX语法转换过程

组件更新机制

  • setState()的两个作用:1.修改 state 2.更新舰(UI)
  • 过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)

组件更新机制

组件性能优化

减轻 state

  • 减轻 state:只存储跟组件渲染相关的数据(比如:count/列表数据/loading)等
  • 注意:不用做渲染的数据不要放在state中,比如定时器 id 等
  • 对于这种需要在多个方法中用到的数据,应该放在this中
1
2
3
4
5
6
7
8
9
10
class Hello extends Component {
componentDidMount(){
// timerId 存储到this中,而不是 state中
this.timerId = setInterval(()=>,2000)
}
componentWillUnmount(){
clearInterval(this.timerId)
}
render(){...}
}

避免不必要的重新渲染

  • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
  • 问题:子组件没有任何变化时也会被重新渲染
  • 如何避免不必要的重新渲染呢?
  • 解决方式:使用钩子函数 shouldComponentUpdate(nextProps,nextState)
  • 作用:通过返回值决定组件是否被重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate–>redner)
1
2
3
4
5
6
7
class Hello extends Component{
shouldComponentUpdate(){
// 根据条件,决定是否重新渲染组件
return false
}
render(){...}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
/**
* shouldComponentUpdate(nextProps,nextState)
*/
class App extends React.Component {

state = {
number: 0
}
handleClick = () => {
this.setState((state, props) => {
return {
number: Math.floor(Math.random() * 3)
}
})
}
shouldComponentUpdate(nextProps, nextState) {
let oldArr = [];
let newArr = [];
// 判断当前状态
for (let key in this.state) {
oldArr.push(this.state[key]);
newArr.push(nextState[key]);
}
return newArr.every((item, index) => newArr[index] !== oldArr[index]);

}

render() {
let { number } = this.state;
return (
<div>
<NumberBox number={number} />
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
class NumberBox extends React.Component {
render() {
console.log("子组件 render");
return (
<h1>随机数:{this.props.number}</h1>
)
}
shouldComponentUpdate(nextProps, nextState) {
let oldArr = [];
let newArr = [];
// 判断当前状态
for (let key in this.props) {
oldArr.push(this.props[key]);
newArr.push(nextProps[key]);
}
return newArr.every((item, index) => newArr[index] !== oldArr[index]);

}
}

ReactDOM.render(<App />, document.getElementById('root'));

纯组件

  • 纯组件:时通过控制shouldComponentUpdate生命周期函数,减少render调用次数来减少性能损耗的.

  • 说明:纯组件内部的对比是 shallow compate(浅层对比)

  • 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)

    1
    2
    3
    4
    let number = 0;
    let newNumber = number;
    newNumber =2
    console.log(number===newNumber) // false
    1
    2
    3
    4
    5
    6
    state = {number:0}
    setState({
    number: Math.floor(Math.random() * 3)
    })
    // PureComponent 内部对比
    最新的 state.number === 上一次 state.number // false,重新渲染
  • 对于引用类型来说:只比较对象的引用(地址)是否相同

    1
    2
    3
    4
    const obj = {number:0}
    const newObj = obj
    newObj.number = 2
    console.log(newObj===obj) // true
    1
    2
    3
    4
    5
    6
    state = {obj : {number:0}}
    // 错误做法
    state.obj.number = 2
    setState({obj:state.obj})
    // PureComponet 内部比较
    最新的state.obj === 上次的state.obj // true 不重新渲染
  • 注意:state 或 props 中属性值为引用累时,应该创建新数据,不要直接修改原始数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 正确!创建新数据
    const newObj = {...state.obj,number:2}
    setState({obj:newObj})

    // 正确!创建新数据
    // 不要用数组的push/ unshift 等直接修改当前数据的方法
    // 而应该应用 oncat 或 slice 等这些返回数组的方法
    this.setState({
    list: [...this.state.list,{新数据}]
    })

虚拟DOM 和 Diff算法

  • React 更新视图的思想是:只要 state 变化重新渲染试图
  • 特点:思路非常清晰
  • 问题:组件中只有一个DOM元素需要更新时,也得把整个组件内容重新渲染到页面中?不是
  • 理想状态:部分更新,只更新变化的地方
  • 问题:React 是如何做到部分更新的?虚拟DOM 配合 Diff算法

虚拟DOM :本质就是一个 JS 对象,用来描述你希望在屏幕上看到的内容(UI)

虚拟DOM

执行过程

1.初次渲染时,React 会更具初始化state(Model),创建一个虚拟DOM对象(树)

2.根据虚拟 DOM 生成真正的 DOM,渲染到页面中

3.当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)

4.与上一次得到的虚拟 DOM 镀锡,使用 Diff 算法对比(找不同),得到需要更新的内容

5.最终,React 只将变化的内容更新(patch)到DOM中,重新渲染到页面。

执行过程

代码演示

  • 组件 render() 调用后,根据状态JSX结构生成虚拟DOM对象
1
2
3
4
5
6
7
8
9
{
type:'div'
props:{
children:{
{type:'h1', props:{children:'随机数'}},
{type:'p', props:{children:0}}
}
}
}
1
2
// ... 省略其他结构
{type:'p', props:{children:2}}

总结

1.工作角度:应用第一,原理第二

2.原理有助于更好的地理解 React 的自身运行机制

3.setState() 异步更新数据

4.父组件更新导致子组件更新,纯组件提升性能。

5.思路清晰简单为前提,虚拟DOM 和 Diff 保证效率

6.虚拟 DOM-> state+JSX

7.虚拟 DOM 的真正价值从来都不是性能,而是让React脱离了浏览器的束缚,因为虚拟DOM并不是一个真正的DOM,换句话说只要能够运行JS 的地方就能运行 React(移动端开发、VR开发)

谢谢老板