React 部分原理
# setState 的说明
注意:setState({})
方法是异步更新数据的!如果同时调用 setState()
方法多次,后面的 setState()
不会依赖于前面的 setState()
结果。
不管同时调用多少次 setState()
,都只会触发一次重新渲染
推荐使用:setState((state, props) => {})
- 通过该语法形式,就可以做到:后面的
setState()
可以依赖于上面setState()
的值
setState()
方法由两个参数:
- 1 第一个参数用来更新状态
- 2 第二个参数是一个回调函数,表示组件状态更新后,立即出发的操作
// 修改状态:
// setState() 是异步更新数据的
handleClick = () => {
console.log('setState前的状态:', this.state.count)
// 回调函数的参数:
// 1 state 表示最新的状态
// 2 props 表示最新的 props
// 通过回调函数的返回值,来更新状态
this.setState((state, props) => {
return {
count: state.count + 2
}
}, () => {})
console.log('setState后的状态:', this.state.count)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# JSX 语法的转化过程
- JSX -> React.createElement() -> JS对象(用来告诉 React 最终要渲染在页面中的内容)
# React 组件更新机制
- 特点:只要父组件更新了,那么,父组件自己以及它的所有子组件(后代组件),全部会被更新。
# 组件性能优化
# 减轻state
只把更组件渲染内容相关的数据放在 state
中。不要把跟组件渲染无关的数据,放在 state
中,而把这些数据直接放在 this
中即可
# shouldComponentUpdate
使用场景:阻止不必要的更新。作用:通过返回值来决定是否重新渲染组件,如果返回 true
,就会更新组件;如果返回 false
,就不会更新组件。参数表示最新的 props
和 state
,可以通过 this.props
和 this.state
来获取到上一次(更新前的)的值
// 第一个参数:表示最新的props值
// 第二个参数:表示最新的state值
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.count % 2 === 0) {
// 此时为 偶数
return false
}
return true
}
2
3
4
5
6
7
8
9
# 使用纯组件
用法:只要将 React.Component
替换为 React.PureComponent
即可。原理:内部会自动实现 shouldComponentUpdate
钩子函数,会分别对比 更新前后的 props
和 state
,只要有一个发生变化了,就会更新组件;换句话说,也就是:如果 props
或 state
,都没有改变,此时,组件就不会更新了。
class Child2 extends React.PureComponent {
render() {
console.log('Child2 组件重新渲染了')
return <h1>随机数:{this.props.number}</h1>
}
}
2
3
4
5
6
# 纯组件内部的原理
原理:shallow compare
。对于值类型,直接修改即可,没有坑,但是对于引用类型来说只比较对象的地址。
- 如果直接修改当前对象中属性的值,那么,在更新状态的时候,即便数据变化了,组件也不会被重新渲染
- 应该创建新的引用类型值,再更新状态
// 1 不要这么做:
// 在 PureComponent 中,不会让组件重新渲染
const { obj } = this.state
const newObj = obj
newObj.number = number
this.setState({
obj: newObj
})
2
3
4
5
6
7
8
9
// 2 正确做法:
const { obj } = this.state
// 注意:此处创建了一个新的对象 newObj
const newObj = { ...obj }
newObj.number = number
this.setState({
obj: newObj
})
2
3
4
5
6
7
8
9
# 更新组件的原则
不要直接修改当前状态的值(引用类型),而是创建一个新的对象或数组
// 推荐修改数组值的方式:
this.setState({
list: [...this.state.list, { ..省略对象结构 }]
})
// ES5:
// const newList = list.concat([ ... ])
// 推荐修改对象值的方式:
this.setState({
obj: {...this.state.obj, number: ...}
})
// ES5:
// Object.assign(目标对象, 原来的状态对象, 新的状态对象)
// 当前例子中:const newObj = Object.assign({}, this.state.obj, { number: 9 })
2
3
4
5
6
7
8
9
10
11
12
13
14
# 虚拟DOM 和 Diff算法
React
中一个组件的更新方式:只要调用 setState()
当前这个组件就会被更新。也就是会调用 render()
方法。但是:不是 render()
方法被调用,整个组件的内容,就会被全部更新!!!实际上,React
内部会实现:部分更新,也就是,哪个地方需要更新,只会把这个地方对应的 DOM
重新渲染
# Diff 算法的说明 - 1
如果两棵树的根元素类型不同,React
会销毁旧树,创建新树
// 旧树
<div>
<Counter />
</div>
// 新树
<span>
<Counter />
</span>
执行过程:destory Counter -> insert Counter
2
3
4
5
6
7
8
9
10
11
# Diff 算法的说明 - 2
对于类型相同的 React DOM
元素,React
会对比两者的属性是否相同,只更新不同的属性。当处理完这个 DOM
节点,React
就会递归处理子节点。
// 旧
<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属性
2
3
4
5
6
7
8
9
10
11
# Diff 算法的说明 - 3
当在子节点的后面添加一个节点,这时候两棵树的转化工作执行的很好
// 旧
<ul>
<li>first</li>
<li>second</li>
</ul>
// 新
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
执行过程:
React会匹配新旧两个<li>first</li>,匹配两个<li>second</li>,然后添加 <li>third</li> tree
2
3
4
5
6
7
8
9
10
11
12
13
14
15
但是如果你在开始位置插入一个元素,那么问题就来了:
// 旧
<ul>
<li>1</li>
<li>2</li>
</ul>
// 新
<ul>
<li>3</li>
<li>1</li>
<li>2</li>
</ul>
执行过程:
React将改变每一个子节点,而非保持 <li>Duke</li> 和 <li>Villanova</li> 不变
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# key 属性
为了解决以上问题,React
提供了一个 key
属性。当子节点带有 key
属性,React
会通过 key
来匹配原始树和后来的树。
// 旧
<ul>
<li key="2015">1</li>
<li key="2016">2</li>
</ul>
// 新
<ul>
<li key="2014">3</li>
<li key="2015">1</li>
<li key="2016">2</li>
</ul>
执行过程:
现在 React 知道带有key '2014' 的元素是新的,对于 '2015' 和 '2016' 仅仅移动位置即可
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意
key 只需要保持与他的兄弟节点唯一即可,不需要全局唯一
尽可能的减少数组 index 作为 key,数组中插入元素的等操作时,会使得效率底下
# 组件的极简模型
(state, props) => UI