使用memo,useMemo,useCallback进行性能优化
根据React官网介绍,我们可以通过memo,useMemo以及useCallback组合使用进行React的性能优化,当然了官方也指出这是非必需的,并且后期react的更新迭代可能会在框架内部去处理性能和缓存的问题,但是就目前而言,如有遇到性能问题还是需要我们自行借助React提供的api去解决的。
场景
在实际项目中,有遇到一个包含多种不同表单类型表单域组件的组件,我们使用formik做表单数据的收集与验证,页面大概长这样:
这个页面是有滚动条的,所以这里展示的表单域只是一部分,这种情况如果我们不用memo以及useMemo和useCallback做依赖缓存和性能优化就比较容易出现性能问题,比如我们在输入框一直按下一个字码按键进行输入不放开,他有可能并不会像我们所预想的那样一个接一个出现在输入框中,而是隔一会儿后全部出现在输入框,而且页面也可能会有一定的不流畅的感觉,这一种体验是不太友好的。
通过react devtools也可以查看我们在输入框变化的时候哪些组件刷新了(react devtools插件的设置中有Highlight updates when components render.这个选项,开启后react组件刷新的组件会有短暂高亮)
这是优化之前的渲染图,我在输入网络名称的时候旁边的select框的内容也在跟着重新渲染,这显然是有很大的性能开销的
这是优化之后的效果,我在输入网络名称的时候,并没有触发select框的重新渲染,这样的输入的时候及时是一只按在一个字母按键不松开,输入框那个也会跟着变化而不是上面提到了过一会儿才批量响应我的操作,用户体验提升很多。
解决方案
上面是描述场景和需求,接下来说一下我的解决方案
原因
首先先说上面问题出现的原因:
整个表单组件由各个不同的表单域组成,而每个组件都会传入 {...getFieldProps("minPasswordLength")}
这样的一个props,而getFieldProps这个方法会在表单组件每次刷新的时候都生成一次,因此对于子组件来说,这个方法每次都是一个新的prop,因此子组件每次都会更新。
解决
因此我们要做的就是:
-
用useMemo或者useCallback将该方法缓存起来:
const getFieldProps = useCallback((paramName: string) => formik.getFieldProps(paramName), [formik]);
-
然后在每个表单域组件(子组件)中使用memo方法:
import { FormControl, InputBaseProps, TextField } from "@mui/material"; import { memo } from "react"; import { MatFormItemProps } from "../../../models/base.model"; interface MatInputProps extends MatFormItemProps { multiline?: boolean; maxRows?: number; inputProps?: InputBaseProps["inputProps"]; } export default memo(function MatInput(props: MatInputProps) { const { width } = props; return ( <FormControl error={props.error} sx={{ maxWidth: width || 1 / 1 }} fullWidth> <TextField {...props} maxRows={props.maxRows}></TextField> </FormControl> ); })
这样就可以避免很多不必要的页面更新,优化整个应用的性能。