V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Sponsored by
LinkedIn
2000 个不用坐班的远程好工作在召唤你 · 弹性上班不打卡,工作和生活都能拥有
2000 个不用坐班的全球远程工作,帮助 V2EX 的小伙伴开启全新的工作方式。
Promoted by LinkedIn
FaiChou
V2EX  ›  React

Functional Component 如何像 Class Component 一样通过 ref 获取 instance 呢?

  •  
  •   FaiChou ·
    FaiChou · 124 天前 · 1075 次点击
    这是一个创建于 124 天前的主题,其中的信息可能已经有所发展或是发生改变。

    一个修改身高的控件, 可滚动的尺子是用 ScrollView 来实现的, 在父组件中有个单位切换的按钮, 切换单位时会调整默认的身高数值并且滚动尺子到指定位置. 比如用户调整 ft 到 cm, 数值会变成 170 并且尺子也会滚动到 170 (包括下面的单位数值也会变).

    img

    如果这是一个 class component:

    <ChangeUnitButtons onClick={()=> { rullerRef.current.changeUnit() }} />
    <Ruller ref={rullerRef} />
    
    class Ruller extends React.Component {
        changeUnit() {
        	scrollViewRef.current.scrollTo(someCalculatedValue);
        }
        render() {
          return (
        	<ScrollView ref={scrollViewRef} />
          );
        }
    }
    

    但这个控件是用 Functional Component 实现的, 不能通过 ref 来获取 instance 然后 instance.someMethod 来调用内部方法.

    查了下 React 的文档, 可以通过 useImperativeHandle 这个 hook 配合 forwardRef 来实现这种需求.

    useImperativeHandle 是用来做这个事情的吗? 虽然是可以用来做这个事情, 但查了下文档, 其设计目的不是用来做这个事情的:

    useImperativeHandle customizes the instance value that is exposed to parent components when using ref

    所以上面这种需求应该怎么处理? 有什么 workaround?

    考虑过:

    1. 不同的单位渲染不同的 Ruller 控件, 比如 {unit === 'cm' ? <Ruller /> : <Ruller />}
    2. 将单位也封装到控件中
    8 条回复    2022-05-23 21:54:06 +08:00
    iamzuoxinyu
        1
    iamzuoxinyu  
       124 天前
    不是有`useRef`么?
    iamzuoxinyu
        2
    iamzuoxinyu  
       124 天前
    噢,抱歉,没仔细看。简单地,将单位和数值当作 props 传进去不行么?
    towry
        3
    towry  
       124 天前
    函数式组件暴露一些 API 。

    ruller:

    `props.ref && props.ref({ api1: () => {} });`

    使用:

    ```
    const rullerRef = useRef(null);

    if (rullerRef) { rullerRef.current.api1() }

    <ruller ref={(ref) => rullerRef.current = ref } />
    ```
    towry
        4
    towry  
       124 天前
    ```
    useEffect(() => {
    // set ref to outer on mounted
    props.ref && props.ref({ api1: () => { // do something })
    }, []);
    ```
    vace
        5
    vace  
       124 天前
    很多方式。
    0. 直接 props/provider 传值
    1. scrollViewef.current 是 dom 的话(比如 div ),可以用 forwardRef ,父组件直接用 ref 引用子组件的 dom 方法。
    2. useImperativeHandle 是自定义暴露给父组件的实例值,也就是子组件可以让父组件引用任意自定义的数据或者方法。(没有不推荐吧,看场景, 少写 ref 的命令式代码)
    3. 父组件创建自定义 ref ,用 props 传递
    yuankui
        6
    yuankui  
       124 天前
    ```
    interface RuleProps {
    value: number;
    type: 'cm' | 'ft';
    }
    ```
    楼上正解
    fayetitus
        7
    fayetitus  
       124 天前
    如果你真的真的真的真的真的很确信你需要这样做,那么 useImperativeHandle 就是用来做这个事情的:你希望子组件向父组件暴露一个实例的 value ,而 method 显然是 value 。不论是文档,样例,社区讨论,你的用法都符合设计意图。例如 https://stackoverflow.com/questions/37949981/call-child-method-from-parent 的高赞答案。

    “如果你真的真的真的真的真的很确信你需要这样做”的意思是:你很忙,没空重塑自己的心智,没空思考其他的办法,你一定想要——让父组件调用子组件的函数。
    fayetitus
        8
    fayetitus  
       124 天前   ❤️ 1
    彩蛋:useImperativeHandle 的曾用名是 useImperativeMethods ,这个曾用名听上去是不是更像你想要的东西?
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2614 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 12:05 · PVG 20:05 · LAX 05:05 · JFK 08:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.