antd表单性能改进实践 debounce 高阶组件

阿里云幸运券

摘要
随着es2015、babel等技术在前端迅速普及,前端开发效率大幅提升。vue、react之类的框架也被广泛采用。在使用基于react的antd构建后台系统的过程中遇到了明显的性能问题。在上期文章发表后收到了许多赞。随着使用场景的变化,又有许多性能问题暴露出来。因此继续研究antd的性能问题,寻找最终解决方案。

antd简介
antd是由阿里旗下的 蚂蚁金服体验技术部 开发的一套UI设计语言,包括了设计样式、规范、组件库等内容。antd基于React开发,并且是起步较早的框架,因此功能上比较完善,使用者较多,社区资源也多。因此在去年技术选型中选中了antd作为DA后台系统前端的主要框架之一。

性能问题
所有的框架在降低开发成本的同时必然会牺牲一部分的性能,antd 也不例外。虽然 antd 基于的 React 生态一直在积极的优化性能,然而在极端情况下依然会出现性能问题。

名词解释
受控状态 / 非受控状态
有交互的 React 组件通常都会有成对的控制参数和回调方法,例如 Input 组件里的 value 和 onChange 。当使用组件的开发者设置了 value 时组件就处于“受控”状态( Controlled ),反之不设置 value 组件就处于“不受控”状态( Uncontrolled )。受控状态的组件在发生交互的时候并不会直接更新自己的状态,而是将变化的值返回给父组件处理,再经过父组件设置 value,受控组件才会发生相应变化。

受控模式:

非受控模式:

debounce
防抖动(Debounce)和节流阀(Throttle)在 javascript 中已经是广泛应用的方法,主要用于用户操作实时提交到服务器时限制提交频率。 Debounce 会在最后一次触发的一段时间后开始执行,Throttle 则是会在上一次执行一段时间后才会开始下一次执行。在它们两次执行间的触发事件则会被忽略。

问题分析
上一次的方案对付性能问题,得到了不错的效果。不过使用复杂组件时依然需要定制组件,所以笔者就寻找更方便的方法。

在处理 Form 表单过大导致 Input 输入框卡顿的问题上,上次的做法相当于将 Input 输入框和其他复杂组件隔离以避免用户输入时感到卡顿。受此启发,如果将 Input 输入框与 Form 隔离,则用户输入啥都不会感到卡顿了。

在使用复杂组件(例如 antd 的 Tree ,加载了数万个节点)时,开发者通常情况下并不能修改组件本身的代码。这种组件本身的性能就很拙计。不过细心的开发者会发现 antd 的 Tree 在受控状态和非受控状态性能差距比较明显。这是因为非受控状态下组件本身可以使用方便计算的格式保存结果,受控状态下则必须在每次变化时转换为输出格式,在控制变量变化时再转换为内部计算格式。如果转换过程比较耗时就会感到卡顿。

1.选择合适的数据格式转换时机
后端返回的数据有时候并不符合端的组件要求。 在大多数例子中会在Render里做数据转换,这也是可读性最高的形式,也是性能最差的形式。 遇到性能间题时,官方提示使用 sholdCompomenmUpdate ,但是 sholdCompomenmUpdate 通常是比较 nextProps 和 this.props 是否变化而控制更新。

如果 redux 中存储了一个用字符串表示的日期参数,在父组件的 render 里转换成 moment 传入日期控件,那父组件每次执行 render 后都会生成一个新的 moment 传入子组件中,子组件每次都会得到一个新的对象,导致 sholdCompomenmUpdate 无法按预期的生效。

那能否在这个参数变化时才进行转换,而其他时候传入的是一个缓存呢?答案是可以。在父组件的 constructor 和 componentWillReceiveProps 里进行格式转换并缓存在 state 里,再将 state 里的结果传入子组件中。当这个参数变化时就会执行转换,而其他时候子组件会得到同一个转换结果,所以子组件的 sholdCompomenmUpdate 可以正常工作。

(例子) 使用生命周期 + state 缓存参数虽然可以提高性能,但是会降低代码可读性。因此应该根据页面的复杂度选择使用。

2.使用高阶组件为现有组件增加 debounce
首先我们来看看 React 的生命周期。如果很熟悉可以跳过下面一段话。
React 生命周期包括 constructor、componentWillMount、componentDidMount、shouldComponentUpdate、componentWillReceiveProps、render、componentWillUpdate、componentDidUpdate、render、componentWillUnmount。关于它们的含义,可以看 《React组件生命周期小结》

点我查看 示例页面

从父组件触发form更新,主要是从服务端获取数据填充到表单里触发更新,可以看见整个表单的组件都更新了。

从表单域触发form更新,主要是用户修改表单内容触发更新。整个表单又更新了。

可以看见在这两种主要场景中都会触发整个组件的更新。特别是用户在输入框中输入的时候,每输入一次就会执行这么多的流程,这包括了表单和表单所有子组件的更新。那为什么不能只更新用户输入的表单域组件呢?这是 React 的模式和 antd 的实现造成的。

如果不使用 antd 的 Form 而表单直接保存到 state 中,是这样的
腾讯云代金券

使用了 antd 的 Form 后,是这样的

因为 antd 将表单数据和处理逻辑都放在父组件里,并且表单组件有许多联动功能,再加上 React 里避免使用 shouldComponentUpdate 的原则,造成了这样的结果。 很多表单域组件,拥有一对 value 和 onChange 控制组件状态、获取结果。特别是表单中用到的组件,几乎都是这种类型。但是当表单中包含很多表单域组件的时候就会遇到性能问题。

受到在实时搜索中使用 debounce 的启发,我想到用户输入时先将表单域组件更新,延迟一段时间后再触发 onChange 事件更新表单中的数据,这样就能立刻响应用户输入,对原有的组件组合方式也不会造成很大变化。
而且当组件发现 onChange 输出的数据和接下来传入 value 的数据一样,则不会触发更新,这样就能避免子组件的二次更新。 先进行效果测试,对比的是使用 debounce 高阶组件之前和之后的效果

使用前

使用后

测试地址:assweeecan/react_component_debounce_test

测试页面:assweeecan/react_component_debounce_test

可以看见使用前,用户输入很卡顿,体验极差。使用后,用户输入会立刻渲染出来,然后才会更新表单中的数据,用户体验有着质的飞跃。

并且使用后 checkbox 的选中和删除速度都大幅度提高。因为使用前会等待整个 group 生命周期执行过去才会显示 checkbox 状态改变,而对每个 checkbox 使用后则会在用户点击时立刻显示变化,再更新 group 里的数据。

要实现这个功能,就要介入表单和表单域的生命周期,在 componentWillReceiveProps 和 onChange 事件中做处理。笔者参考了 react-redux 的实现方式,就是在表单域组件外面套一层高阶组件再放入表单中,就可以介入它们之间。

import reactComponentDebounce from ‘react-component-debounce’;
import { Checkbox } from ‘antd’;

const CheckboxA = reactFormFieldDebounce({
valuePropName: ‘checked’,
triggerMs: 250,
})(Checkbox);

高阶组件中,将父组件传入的 onChange 做包装,加上 debounce 后传入子组件。子组件触发 onChange 后,高阶组件会立刻设置 value 值,之后再延迟调用父组件的 onChange 将值传回到表单中。如果连续发生变化,则会在最后一次 onChange 才会将值传到表单中。

当高阶组件收到 value 的变化,会延迟一小段时间才将 value 保存到自己的 state 中,再将 state 中的 value 传到子组件中,实现了延迟设置 value。

再加上 shouldComponentUpdate 的控制,就能有效减少输入框、选择框等等表单域组件的更新次数,提高流畅度。 不过,使用 debounce 高阶组件也是有限制的,必须是 onChange / value 成对出现的, onChange 传出的值与 value 传入的值一样的,onChange 没有过滤 value 值的组件才能使用。否则就只能使用单纯的 shouldComponentUpdate 控制更新了。
再对组件增加一些附属功能,例如自定义 value、onChange 参数名称、自定义延迟时间等等,就完成了这个高阶组件。

腾讯云代金券

原文链接

https://zhuanlan.zhihu.com/p/28980381

服务推荐

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值