原创 Youngzx 2025-01-05 09:03 重庆
点击关注公众号,“技术干货”及时达!
点击关注公众号,“技术干货” 及时达!
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
有兴趣可以看看https://github.com/Youngzx88/article-demo/tree/main/use-props-value
6、总结
其实多看看这种开源的组件库,会发现有很多类似的hooks封装,这都是大家互相借鉴+创新
,通过优秀的设计模式实现的
多了解了解这种hooks有助于写出高可维护的组件,像usePropsValue就可以让我们无脑去使用,不需要管组件本身是受控还是非受控,全看你的入参是什么
点击关注公众号,“技术干货” 及时达!