react - 性能优化

setState

更新数据

  • setState() 是异步更新数据的
  • 注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()
1. 当你调用 setState 的时候,React.js 并不会马上修改 state (为什么)
2. 而是把这个对象放到一个更新队列里面
3. 稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
  • 可以多次调用 setState() ,只会触发一次重新渲染
this.state = { count: 1 }
this.setState({
	count: this.state.count + 1
})
console.log(this.state.count) // 1

在使用 React.js 的时候,并不需要担心多次进行 setState 会带来性能问题。

推荐语法

  • 推荐:使用 setState((preState) => {}) 语法

  • 参数preState: React.js 会把上一个 setState 的结果传入这个函数

this.setState((preState) => {
    return {
    	count: preState.count + 1
    }
})
console.log(this.state.count) // 1

这种语法依旧是异步的,但是state可以获取到最新的状态,适用于需要调用多次setState

第二个参数

  • 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
  • 语法:setState(updater[, callback])
this.setState(
	(state) => ({}),
	() => {console.log('这个回调函数会在状态更新后立即执行')}
)
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)
  • 过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)

组件性能优化

  1. 功能第一
  2. 性能优化

减轻state

  • 减轻 state(data):只存储跟组件渲染相关的数据(比如: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中, 放进去会造成很多不必要的渲染。

避免不必要的重新渲染

  • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰

  • 问题:子组件没有任何变化时也会重新渲染 (接收到的props没有发生任何的改变)

  • 如何避免不必要的重新渲染呢?

  • 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)

  • 作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染

  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate => render)

class Hello extends Component {
    shouldComponentUpdate() {
        // 根据条件,决定是否重新渲染组件
        return false
    }
    render() {}
}

纯组件

  • 纯组件:React.PureComponentReact.Component功能相似
  • 区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
  • 原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件
class Hello extends React.PureComponent {
    render() {
        return (
        	<div>纯组件</div>
        )
    }
}

只有在性能优化的时候可能会用到纯组件,不要所有的组件都使用纯组件,因为纯组件需要消耗性能进行对比

纯组件比较-值类型

  • 说明:纯组件内部的对比是 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, {新数据}]
})

虚拟DOM与diff算法

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

虚拟DOM

虚拟DOM(Virtual DOM):虚拟DOM就是使用javascript的对象来描述了DOM结构

  • 为什么要有虚拟DOM?
    • 因为操作真实DOM的代价是非常昂贵的,随便一个DOM对象,都有很多很多的属性
    • DOM中大部分的属性都不是我们需要关注的
  • 虚拟DOM:
    • 使用js对象来描述一个DOM结构,在react框架中,描述一个DOM的对象,使用的一个虚拟DOM
    • 页面的更新可以先全部反映在js对象上,操作内存中的js对象的速度显然要快多了,不需要重绘与回流
    • 等更新完后,再将最终的js对象映射成真实的DOM,交由浏览器去绘制。

DIFF算法

react中通过diff算法来决定如何更新视图中的内容

diff算法 - 中文文档

参考资料: 如何实现一个虚拟DOM

diif算法的举例

  • 如果两棵树的根元素类型不同,React会销毁旧树,创建新树
// 旧树
<div>
  张三
</div>

// 新树
<span>
  张三
</span>
  • 对于类型相同的React DOM 元素,如果仅仅是属性不同,只更新不同的属性
// 旧
<div className="before" title="stuff"></div>
// 新
<div className="after" title="stuff"></div>
只更新:className 属性

// 旧
<div style={{color: 'red', fontWeight: 'bold'}}></div>
// 新
<div style={{color: 'green', fontWeight: 'bold'}}></div>
只更新:color属性
  • 添加节点的情况
// 旧
<ul>
  <li>first</li>
  <li>second</li>
</ul>

// 新
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>
// 在虚拟DOM中添加 <li>third</li>



// 效率不高
// 旧 [a,b]
<ul>
  <li>a</li>
  <li>b</li>
</ul>
// 新  [c,a,b]
<ul>
  <li>c</li>
  <li>a</li>
  <li>b</li>
</ul>
// React将改变每一个子节点, 不会保证<li>a</li><li>b</li>不变化
  • 带key的情况
// 旧
<ul>
  <li key="2015">a</li>
  <li key="2016">b</li>
</ul>

// 新
<ul>
  <li key="2014">c</li>
  <li key="2015">a</li>
  <li key="2016">b</li>
</ul>

// react会根据唯一的key来移动元素
// 在遍历数据时,推荐在使用 key 属性

react中DOM的更新方式

  1. 使用虚拟DOM(JavaScript)对象结构表示 DOM 树的结构,根据虚拟DOM渲染出真正的DOM树

  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异(diff算法)

  3. 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值