1. 特殊说明
- 没有被子组件使用的
props会被丢弃,比如传递了style,但是子组件没有操作style属性,或者没有restProps,则style无效- React 就像函数一样,不使用就没有效果
- Vue 会自动将多余属性挂载在 dom 上
- 若父组件发生了
re-render,默认情况下(不进行特殊处理),则子组件也会re-render- 默认不会对对 props 进行一致性判定来减少 re-render,就像内部函数一样
- 可以结合 React.memo 来减少 re-render
以下内容都是聚焦于组件 props,或者是传递 props 给组件之前
2. 普通属性
- 基本类型:
numberstringbooleannullundefinedsymbolbigint - 非基本类型,要么要求父组件使用useMemo包裹(不友好);要么在组件外层套
React.memo,然后第二个参数进行比较,偷懒的方案是直接用react-fast-compare
import React from 'react';
import isEqual from 'react-fast-compare';
...
const UserSelector = () => {
...
}
export default React.memo(UserSelector, isEqual)
- react 默认采用
浅比较(Object.is)来判定数据是否发生变化- 非基本类型使用比较内存地址来判定是否变更,所以【直接修改对象的属性】或者【直接调用数组方法】 修改内存地址,不会触发变更。
- 对象可以监听内部基本类型属性,或者序列化
- 数组重新定义数组方法,或者序列化
3. 回调函数属性
-
使用匿名箭头函数
- 可以传递额外参数
- 父组件 re-render,子组件也 re-render 的性能问题
<TimePicker onChange={(value, valueStr) => handleChange(valueStr, 'minValue')} value={searchValue.minValue ? moment(searchValue.minValue, 'HH:mm:ss') : null} /> -
不使用匿名箭头函数
- 子组件触发时传递参数
- 拆成多个事件
- 使用
useMemoizedFn+React.memo来做到,在父组件发生 re-render 时,子组件的参数没有发生改变,子组件不 re-render
// 父组件中 const handleEdit = useMemoizedFn((id: string) => { ... // 其内可以使用本组件的外部值 }); <FormAction ... onEdit={handleEdit} ... /> // 子组件中 const FormAction = (props: IFormActionProps) => { const { ..., onEdit, ... } = props; ... const handleClick = () => { onEdit?.(id) } } export default React.memo(FormAction, deepEqual);
4. 解构对象
- 不使用解构的
- 每一次从大对象
props中获取属性需要消耗性能 - 每一次的值异常都要判定,比如【问号判空】、【感叹号断言】、【默认值赋予】等
- 代码会不简洁,且会导致编译后的代码中出现很多es5的判定内容
- 每一次从大对象
const control = getControl(controlId, controls);
const originControl = getControl(control?.componentSettings?.srcColumnId || '', props.relatedForm?.template?.controls) || {};
return basicInputComponents(originControl.controlId, originControl.type, props.relatedForm?.template?.controls);
对比
const { relatedForm } = props;
const { template } = relatedForm || {};
// 别名
const { controls: relatedControls = [] } = template || {};
const control = getControl(controlId, controls);
const { componentSettings } = control || {};
const { srcColumnId = '' } = componentSettings || {};
const originControl = getControl(srcColumnId, relatedControls) || {};
return basicInputComponents(originControl.controlId, originControl.type, relatedControls);
- 解构赋予默认值是在属性值为
undefined的时候才生效,null不会生效
const targetObj = { a: 0, b: undefined, c: null, d: [], e: {} };
const { a = '默认值', b = '默认值', c = '默认值', d = '默认值', e = '默认值' } = targetObj;
console.log('a:', a); // 0
console.log('b:', b); // 默认值
console.log('c:', c); // null
console.log('d:', d); // []
console.log('e:', e); // {}
- 若解构时赋予了默认【非基本类型】值,则在结合
useEffect的过程中,有可能产生死循环
const { dataList = [] } = props;
const [dataObj, setDataObj] = useState({count: 0});
useEffect(() => {
...
setCount({
...dataObj,
count: dataList.length
});
...
}, [dataList]);
5. React.memo包裹组件
- 类似 class component 中的
shouldComponentUpdate的逻辑 - 组件被渲染时,若返回 ,则会进行此次 re-render;返回 ,则会忽略此次 re-render。可以有效避免父组件渲染导致的无效子组件渲染
- 默认使用 Object.is浅比较的方式,来判定 props 前后是否发生了变化
- props 中有函数时,需要结合
useCallback(最好用useMemorizedFn替代)来保证回调函数的不变性useCallback无法有效处理有外部依赖的函数
- 使用第二个参数来设置自定义逻辑(可以采用
react-fase-compare插件)。逻辑同第二点
React.memo(someComponent, (prev, next) => {
if (prev.id === next.id) {
return true; // 忽略此次渲染
}
return false; // 进行此次渲染
});
- 没有特殊判定,直接可以使用
react-fast-compare
import isEqual from 'react-fast-compare';
...
export default React.memo(MyComponent, isEqual);