手写React useImperativeHandle,理解useImperativeHandle原理

162 阅读1分钟

一. useImperativeHandle方法介绍

useImperativeHandle方法接收三个参数,第一个参数是ref对象,第二个参数是create方法,会调用create方法将返回值赋值给ref.current属性,第三个参数是依赖deps,当依赖deps发生变化时会重新调用create获取新的返回值赋值给ref.current

例如下面这段代码,通过useImperativeHandle方法将setCountgetCount方法赋值给ref.current,提供给外部组件调用

function HelloWorld({ ref }) {
  const [count, setCount] = useState(0)

  useImperativeHandle(
    ref,
    () => ({
      setCount(value: number) {
        setCount(value)
      },
      getCount() {
        return count
      },
    }),
    [count],
  )
  
  return <h1>Hello World</h1>
}

function App() {
  const ref = useRef()
  
  return <HelloWorld ref={ref} />
}

二. 实现useImperativeHandle

useImperativeHandle内部实现依赖useLayoutEffect,可以参考文章手写React useLayoutEffect,理解useLayoutEffect原理

2.1 首次调用useImperativeHandle方法

在更新DOM阶段,会执行imperativeHandleEffect方法,调用create方法获取返回值赋值给ref.current

function imperativeHandleEffect(ref, create) {
  ref.current = create()

  return () => {
    ref.current = null
  }
}

function mountImperativeHandle(ref, create, deps) {
  // 创建Layout类型的Effect对象
  mountLayoutEffect(imperativeHandleEffect.bind(null, ref, create), deps)
}

2.2 更新调用useImperatvieHandle方法

在更新DOM阶段,会先执行imperativeHandleEffect方法返回的destroy方法,然后再调用create方法获取返回值赋值给ref.current

function imperativeHandleEffect(ref, create) {
  ref.current = create()

  return () => {
    ref.current = null
  }
}

function updateImperativeHandle(ref, create, deps) {
  // 创建Layout类型的Effect对象
  updateLayoutEffect(imperativeHandleEffect.bind(null, ref, create), deps)
}

2.3 定义useImperativeHandle方法

如果新节点不存在旧FiberNode节点,说明是首次调用函数组件方法,则调用mountImperativeHandle方法,否则调用updateImperativeHandle方法

function useImperativeHandle(ref, create, deps = null) {
  const current = currentlyRenderingFiber.alternate
  if (current === null) {
    mountImperativeHandle(ref, create, deps)
  } else {
    updateImperativeHandle(ref, create, deps)
  }
}

三. 往期文章推荐

3.1 React原理系列总结

四. 参考文档

4.1 React useImperativeHandle官方文档