likes
comments
collection
share

热门组件库都在使用的usePropsValue怎么写usePropsValue旨在统一受控和非受控组件的行为。通常,开发

作者站长头像
站长
· 阅读数 19

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怎么写usePropsValue旨在统一受控和非受控组件的行为。通常,开发
  • 实际上我们写组件的时候,我们不希望去定死一个组件是受控还是非受控,因为就算当下他是非受控,在有一天迭代需求的时候,也可能变成受控模式
  • 但是我们不可能未卜先知的去预计到这一天,过于提早的思考太多,反而影响代码的实现,usePropsValue就是为了这种场景而诞生的,将setState这一步放到了usePropsValue当中
  • 所以受控和非受控的参数我们都收集起来:value,defaultValue,onChange,让usePropsValue去判断实际传入的是什么,如果只传递了defaultValue那么显然是非受控模式,如果同时传递了value和onChange那么显然是受控模式
  • 简单看看usePropsValue的实现 热门组件库都在使用的usePropsValue怎么写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
    1. 避免不必要的重新渲染:如果 setState 函数在每次渲染时都被重新创建,那么它的引用就会发生变化,可能会导致依赖它的子组件或 useEffect 钩子重新执行,这可能会引发不必要的重新渲染或副作用。而使用 useMemoizedFn 可以确保函数的引用是稳定的,除非其依赖项发生变化。
    2. 性能优化:在某些场景下,函数可能会被频繁调用。如果每次都重新创建函数对象可能会增加内存和性能开销。使用 useMemoizedFn 可以减少这类开销。
    3. 稳定的回调函数:在某些情况下,组件可能会传递 setState 给子组件或其他钩子。如果这些子组件或钩子依赖于稳定的回调函数,那么使用 useMemoizedFn 就可以确保它们收到的始终是同一个函数实例,避免因为函数引用变化而导致的不必要更新。

5、结合组件实现

6、总结

  • 其实多看看这种开源的组件库,会发现有很多类似的hooks封装,这都是大家互相借鉴+创新,通过优秀的设计模式实现的
  • 多了解了解这种hooks有助于写出高可维护的组件,像usePropsValue就可以让我们无脑去使用,不需要管组件本身是受控还是非受控,全看你的入参是什么
转载自:https://juejin.cn/post/7414024844263718975
评论
请登录