热门组件库都在使用的usePropsValue怎么写usePropsValue旨在统一受控和非受控组件的行为。通常,开发
1、受控和非受控
大家可能经常会封装一些组件,纯UI组件也好,业务组件也好,但是不能脱离受控非受控的概念去写组件,这样很容易写出渲染次数过多的组件
1.1、受控
- 受控意味着受代码控制,例如一个Input的修改是你使用onChange将他的value修改为e.target.value
1.2、非受控
- 非受控意味着代码无法改变组件的值,组件往往只需要一个defaultValue,比如Input你可以不需要监听onChange,而是直接用form得到每个表单的输入,最后提交,所有组件的值是用户手动改变的,在react中往往体现为使用ref去拿到组件的引用。
1.3、理解这个对写组件有什么帮助?
- 你是否一股脑的接受用户的传入参数并手动set修改一次?
- 实际对于某些组件,往往我们不需要手动修改组件的值,手动的set往往导致多次渲染
- 例如表单,如果你只是需要表单的值,你没必要每个字段给一个value然后修改的时候set它
- 同理还有一些组件例如开关,日历,在我们不需要控制组件的值情况下,我们直接使用非受控模式即可,减少代码编写量的同时,还减少了渲染次数
1.4、什么是usePropsValue?
- 先来看看antd-mobile的使用场景
- 实际上我们写组件的时候,我们不希望去定死一个组件是受控还是非受控,因为就算当下他是非受控,在有一天迭代需求的时候,也可能变成受控模式
- 但是我们不可能未卜先知的去预计到这一天,过于提早的思考太多,反而影响代码的实现,usePropsValue就是为了这种场景而诞生的,将setState这一步放到了usePropsValue当中
- 所以受控和非受控的参数我们都收集起来:value,defaultValue,onChange,让usePropsValue去判断实际传入的是什么,如果只传递了defaultValue那么显然是非受控模式,如果同时传递了value和onChange那么显然是受控模式
- 简单看看usePropsValue的实现
2、usePropsValue编写思路
- 我们希望使用usePropsValue以后就和正常使用useState一样,但是这个setState的操作逻辑在usePropsValue中,我们在代码中只需要
const [selfValue,setSelfValue] = usePropsValue({ value: props.value, defaultValue: props.defaultValue, onChange: props.onChange })
- 入参只有defaultValue的时候,使用useRef修改current
- 入参数有value和onChange的时候要将onChange作为setState返回并修改value,这里要注意onChange
3、编写一个简单的usePropsValue
- 因为为了所有组件通用,入参用泛型
- useRef判断是defaultValue还是value
- 这里要注意因为非受控用的是useRef不会触发组件的更新渲染,所以这里我们使用ahooks更新
- 同理也可以自己写一个state去更新渲染状态
import { useUpdate } from 'ahooks' import { SetStateAction, useRef } from 'react' type Options<T> = { value?: T defaultValue: T onChange?: (v: T) => void } export const usePropsValue = <T>(options: Options<T>) => { const { value, defaultValue, onChange } = options const stateRef = useRef<T>(value !== undefined ? value : defaultValue) if (value !== undefined) { stateRef.current = value } const update = useUpdate() // setState用户传入的可能是函数,需要判断当是函数的时候直接更新为函数return的值 const setState = (v: SetStateAction<T>) => { const nextValue = typeof v === 'function' ? (v as (preState: T) => T)(stateRef.current) : v stateRef.current = nextValue update() return onChange?.(nextValue) } return [stateRef.current,setState] as const }
4、优化方案
- 因为同步写法会直接更新stateRef,所以当value变化的时候要对比一下,
if (nextValue === stateRef.current) return
- 使用
useCallBack
或者useMemoizedFn
包裹setState
- 避免不必要的重新渲染:如果
setState
函数在每次渲染时都被重新创建,那么它的引用就会发生变化,可能会导致依赖它的子组件或useEffect
钩子重新执行,这可能会引发不必要的重新渲染或副作用。而使用useMemoizedFn
可以确保函数的引用是稳定的,除非其依赖项发生变化。 - 性能优化:在某些场景下,函数可能会被频繁调用。如果每次都重新创建函数对象可能会增加内存和性能开销。使用
useMemoizedFn
可以减少这类开销。 - 稳定的回调函数:在某些情况下,组件可能会传递
setState
给子组件或其他钩子。如果这些子组件或钩子依赖于稳定的回调函数,那么使用useMemoizedFn
就可以确保它们收到的始终是同一个函数实例,避免因为函数引用变化而导致的不必要更新。
- 避免不必要的重新渲染:如果
5、结合组件实现
- 在代码仓库实现了一个Switch组件并使用usePropsValue
- 有兴趣可以看看github.com/Youngzx88/a…
6、总结
- 其实多看看这种开源的组件库,会发现有很多类似的hooks封装,这都是大家互相
借鉴+创新
,通过优秀的设计模式实现的 - 多了解了解这种hooks有助于写出高可维护的组件,像usePropsValue就可以让我们无脑去使用,不需要管组件本身是受控还是非受控,全看你的入参是什么
转载自:https://juejin.cn/post/7414024844263718975