一、React性能优化SCU
React更新机制
- React渲染流程:
- 在render函数中返回一个jsx
- jsx创建出来对应的React.createElement
- 这些element最终会形成一个树结构(对应的话就是虚拟DOM)
- 最后React会根据虚拟DOM去渲染出来一个真实DOM
React更新流程:
- 当前props/state发生改变
- render函数重新执行
- 之后产生一棵新的DOM树(新的树结构)
- 在新旧树结构中进行diff算法来对比差异
- 之后计算出差异的点进行一系列的更新
- 最后把需要更新的地方更新到真实的DOM中
React更新流程
- React在props或state发生改变时,会调用React的render方法,会创建出一棵不同的树.
- React需要基于这两棵不同的树之间的差异来判断如何有效的更新UI:
- 如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法(算法的复杂程度为O(n²)),其中n是树中元素的数量
- 如果React中使用了该算法,那么展示1000个元素所需要执行的计算量将在亿的量级范围内.
- 这个开销很大,那么React更新性能将会变得十分低效.
- React对这个算法进行了优化,将其优化成了O(n),如何优化的呢?
- 同层节点之间相互比较,不会跨节点比较
- 不同类型的节点,产生不同的树结构
- 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定.
keys的优化
- 我们在之前遍历列表时,总是会提示一个警告,让我们加入一个key属性:
- 方式一:在最后位置插入数据
- 这种情况有没有key其实意义并不是很大
- 方式二:在前面插入数据
- 这种情况在没有key的情况下,所有的li都会进行修改
- 当子元素(li)拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素
- 在这种场景下,key为111和222的元素仅仅进行了位移,不需要进行任何的修改
- 将key为333的元素插入到最前面的位置即可
- key的注意事项
- key应该是唯一的
- key不要使用随机数(随机数会在下一次render时,重新生成一个新数)
- 使用index作为key,对性能是没有优化的
render函数被调用
- 嵌套案例:
- 在App中,我们增加了一个计数器的代码
- 当点击+1时,hUI重新调用App的render函数
- 而当App的render函数被调用时,所有的子组件的render函数都会被重新调用
那么,在之后的开发中,我们只是修改了App中的数据,是,所有的组件都需要重新render,进行diff算法,性能肯定非常低:
- 事实上,很多的组件是没有必要去重新render的
- 它们调用render应该有一个前提,就是依赖的数据(state,porps)发生改变,再去调用render方法;
- 那么怎么来控制render方法是否被调用?
- 通过shouldComponentUpdate方法即可
shouldComponentUpdate
- React提供了一个生命周期方法 shouldComponentUpdate(简称:SCU)这个方法接收参数,并且需要有返回值:
- 该方法有两个参数:
- nextProps => 修改之后,新的props属性
- nextState => 修改之后,新的state属性
- 该方法返回值是一个boolean类型:
- 返回值为true时,就需要调用render方法
- false则不需要调用
- 默认返回是true,也就是只要state/props发生改变,就会调用
- 比如我们在App中增加一个message属性:
- jsx中并没有依赖message,那么她的改变不应该引起重新渲染
- 但是因为render监听到了state中的改变,就会重新render,所以最后render方法还是被调用了.
PureComponent
- 如果所有的类都需要手动去实现 shouldComponentUpdate,那么会给开发带来很多的重复工作量
- 我们设想一下shouldComponentUpdate中的各种判断目的是什么?
- props/state中的数据是否发生了改变,来决定shouldComponentUpdate返回true/false
- 事实上React已经考虑到了这一点,所以React已经默认帮我们实现好了,那么如何实现的?
- 将class继承自PureComponent
shallowEqual方法
这个方法中,调用 !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState), 这个shallowEqual就是 进行浅层比较:
高阶组件memo
- 目前我们是针对类组件可以使用PureComponent,那么函数式组件呢?
- 事实上函数式组件我们在props没有改变时,也是不希望其重新渲染其DOM树结构的
- 我们需要使用一个高阶组件memo:
- 通过memo函数进行一层包裹
不可变数据的力量
二、获取DOM方式refs
如何使用ref
- 在React的开发模式中,通常情况下不需要,也不建议直接操作DOM原生,但是某些特殊的情况,确实需要获取到DOM进行某些操作:
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方 DOM 库
- 我们可以通过refs获取DOM
如何创建refs来获取对应的DOM呢?目前有三种方式:
- 一:传入字符串
- 使用时通过 this.refs.传入的字符串格式获取对应的元素
- 二:传入一个对象
- 对象是通过 React.createRef() 方式创建出来的
- 使用时获取到创建的对象其中有一个current属性就是对应的元素;
三:传入一个函数
- 该函数会在DOM被挂载时进行回调,这个函数会传入一个 元素对象,我们可以自己保存
- 使用时,直接拿到之前保存的元素对象即可
ref的类型
- ref 的值根据节点的类型而有所不同:
- 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性
- 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性
- 你不能在函数组件上使用 ref 属性,因为他们没有实例
- 函数式组件是没有实例的,所以无法通过ref获取他们的实例
- 但是某些时候,我们可能想要获取函数式组件中的某个DOM元素
- 这个时候我们可以通过 React.forwardRef.
三、受控和非受控组件
认识受控组件
- 在React中,HTML表单的处理方式和普通的DOM元素不太一样:表单元素通常会保存在一些内部的state
- 比如下面的HTML表单元素:
- 这个处理方式是DOM默认处理HTML表单的行为,在用户点击提交时会提交到某个服务器中,并且刷新页面
- 在React中,并没有禁止这个行为,它依然是有效的
- 但是通常情况下会使用JavaScript函数来方便的处理表单提交,同时还可以访问用户填写的表单数据
- 实现这种效果的标准方式是使用“受控组件"
受控组件的基本演练
- 在 HTML 中,表单元素(如 input 和 select)之类的表单元素通常自己维护 state并根据用户输入进行更新
- 而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新
- 将两者结合起来,使React的state成为“唯一数据源”
- 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作
- 被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”
- 由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源
- 由于 handleUsernameChange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新
Element |
Value property |
Change callback |
New value in the callback |
Input type="text" |
value="string" |
onChange |
e.target.value |
Input type="checkbox" |
checked={boolean} |
onChange |
e.target.checked |
Input type="radio" |
checked={boolean} |
onChange |
e.target.checked |
textarea |
value="string" |
onChange |
e.target.value |
select |
value="option value" |
onChange |
e.target.value |
受控组件的其他演练
- textarea标签
- texteare标签和input比较相似
- select标签
- select标签的使用也非常简单,只是它不需要通过selected属性来控制哪一个被选中.它可以匹配state的value来选中
- 处理多个输入
- 多处理方式可以像单处理方式那样进行操作,但是需要多个监听方法:
- 这里我们可以使用ES6的一个语法:计算属性名(Computed property names): []
非受控组件(不推荐使用)
- React推荐大多数情况下使用 受控组件 来处理表单数据:
- 一个受控组件中,表单数据是由 React 组件来管理的
- 另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理
- 如果要使用非受控组件中的数据,那么我们需要使用 ref 来从DOM节点中获取表单数据
- 在非受控组件中通常使用defaultValue来设置默认值
- 同样,input checkbox 和 input radio 支持defaultChecked, select和textarea支持defaultValue