React Hooks 父组件获取子组件的方法

发布于 2022-09-12 00:17:58 字数 3947 浏览 17 评论 0

以下讨论父子组件都为函数组件。

只有父子两个层级

1、 父组件创建一个ref作为一个属性传入子组件。子组件根据内部方法的变化动态更改ref(useEffect)

const loopFn = () => {};

function Child(props) {
  const { innerRef } = props;
  const innerFn = useCallback(() => {
    console.error('inner function...');
  }, deps);

  useEffect(() => {
    innerRef.current.innerFn = innerFn;
  }, [innerFn]);
}

function Parent(props) {
  const ref = useRef({});

  const onClick = useCallback(() => {
    ref.current.innerFn && ref.current.innerFn()
  }, [])
  return [
    <Child innerRef={ref} />,
    <button onClick={onClick}>call inner function</button>
  ];
}

这种写法到是可以实现,但是感觉不够优雅。尤其是自定义一个属性传入ref。

而且还有引用值清除的问题:

如果此时 Child 组件被更换成 Child1 组件,此时可能 Child1 组件上并没有对此进行处理,则点击按钮就会调用 ChildinnerFn 方法。

useEffect(() => {
  const preInnerFn = innerRef.current.innerFn
  innerRef.current.innerFn = innerFn;
  return () => {
    innerRef.current.innerFn = preInnerFn
  }
}, [innerFn]);

2、 父组件获取函数子组件的方法,通过useImplerative

useImplerative 在销毁的时候会将 current 置为 null 。当然在设置值的时候也进行 null 值的容错。

const loopFn = () => {};

function Child(props, ref) {
  const { innerRef } = props;
  const innerFn = useCallback(() => {
    console.error('inner function...');
  }, deps);

  useImperativeHandle(ref, () => ({
    innerFn,
  }), [innerFn]);
}

Child = forwardRef(Child)

function Parent(props) {
  const ref = useRef({});

  const onClick = useCallback(() => {
    ref.current.innerFn && ref.current.innerFn()
  }, [])
  return [
    <Child ref={ref} />,
    <button onClick={onClick}>call inner function</button>
  ];
}

第二种方式使用了forwardRef, useImperativeHandle,看起来比较符合我们的优雅要求,理解上也比较好理解容易。

多个层级

有 A B C三个组件, A 为父组件,C为最下级

此时有个要求, A获取C的内部方法 acFun ,B获取C的内部方法 bcFun

方案一:

A 组件传入一个 refacRef 流经 B 组件,B 组件也传入一个 bcRef, 使用 useimperativehandle去挂上内部方法。

单若 A B为公共组件的话,就存在一个命名上的约定。 而且A组件获取C组件的内部方法就需要一级级通过props传入

const loopFn = () => {};

function C(props) {
  const { acRef, bcRef } = props
  const acFun = useCallback(() => {
    console.error('acFun... ')
  }, deps)

  const bcFun = useCallback(() => {
    console.error('bcFun... ')
  }, deps)

  useImperativeHandle(acRef, () => ({
    acFun,
  }), [acFun])

  useImperativeHandle(bcFun, () => ({
    bcFun,
  }), [bcFun])
}

function B(props) {
  const bcRef = useRef({})
  const onClick = useCallback(() => {
    bcRef.current.bcFun && bcRef.current.bcFun()
  }, [])
  return [
    <C acRef={props.acRef} bcRef={bcRef} />,
    <button onClick={onClick}>call inner fun</button>
  ]
}

function A(props) {
  const acRef = useRef({})
  const onClick = useCallback(() => {
    acRef.current.acFun && acRef.current.acFun()
  }, [])
  return [
    <B acRef={acRef} />,
    <button onClick={onClick}>call inner fun</button>
  ]
}

考虑到在实际过程中 A B, B C, 之间可能不止一级嵌套。

方案二:

针对于层层传递ref的问题 可不可以使用 createContext 尼?看一下createContext用法

const RefContext = React.createContext({})

function A() {
  const ref = useRef({})
  return (
    <RefContext.Provider value={ref}>
      <B />
    </RefContext.Provider>
  );
}

function C() {
  const acRef = React.useContext(RefContext)
  useImperativeHandle(acRef, () => {
    ....//
  })
}

但考虑到 ref 是一个引用值且 A 组件会有多个实例,所以这个方案不大可行。

问题/讨论

说出你对多个层级获取下级组件的看法/方案,或者对上述几种方案的看法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文