V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Leviathann
V2EX  ›  React

react 组件的 ref 到底是什么类型?

  •  
  •   Leviathann · 2021-09-06 19:35:51 +08:00 · 1845 次点击
    这是一个创建于 1175 天前的主题,其中的信息可能已经有所发展或是发生改变。

    版本是 15,所以没法用 React.createRef 所以写了 callback ref,但是一旦想抽成组件内部函数怎么写都写不对

    class A extends Component<{}, {}> {
    
      createRef = (element: HTMLDivElement | null) => this.rootElement = element
     
      render() {
        return (
          <B ref={this.createRef}/>
        )
      }
    }
    
    type Bprops = {
      ref: React.Ref<HTMLDivElement>
    }
    
    class B extends Component<Bprops> {
      render() {
        const {ref} = this.props
        return <div ref={ref}/>
      }
    }
    

    然后就报错

    TS2322: Type '(element: HTMLDivElement | null) => HTMLDivElement | null' is not assignable to type '(Ref<PopUpAd> | undefined) & Ref<HTMLDivElement>'. Type '(element: HTMLDivElement | null) => HTMLDivElement | null' is not assignable to type 'string & ((instance: HTMLDivElement | null) => any)'. Type '(element: HTMLDivElement | null) => HTMLDivElement | null' is not assignable to type 'string'.

    完全不知道报错的这些东西哪来的,react 的定义里就一句

    type Ref<T> = string | { bivarianceHack(instance: T | null): any }["bivarianceHack"];
    

    而且定义里的 string 是或,而报错里的是交

    想去掉报错只能把 createRef 的参数的类型声明去掉,实际上就相当于是 any 把

    4 条回复    2021-09-07 20:40:46 +08:00
    2i2Re2PLMaDnghL
        1
    2i2Re2PLMaDnghL  
       2021-09-07 10:23:50 +08:00   ❤️ 1
    我先解释下报错里的交,为写得段我就把 PopUpAd 和 HTMLDivElement 写成 A 和 B 了

    (Ref<A> | undefined) & Ref<B>
    = (Ref<A> & Ref<B>) | (undefined & Ref<B>) // 分配律
    = Ref<A> & Ref<B> // undefined 交非 undefined 任何为空
    = (string | (A|null) => any) & (string | (B|null) => any) // Ref<T> 展开
    = (string & string) | (((A|null) => any) & string) | (string & ((B|null) => any)) | (((A|null) => any) & ((B|null) => any)) // 分配律
    2i2Re2PLMaDnghL
        2
    2i2Re2PLMaDnghL  
       2021-09-07 10:30:42 +08:00   ❤️ 1
    你这边 (Ref<PopUpAd> | undefined) & Ref<HTMLDivElement> 在不考虑二者有包含关系的情况下就只能是 string 了。

    再考虑到 Ref<T> 对 T 逆变,也可以是 Ref<PopUpAd | HTMLDivElement> ?这个部分我验算算不清。
    也就是说你的 createRef 应当符合 (PopUpAd | HTMLDivElement | null) => any
    至于 PopUpAd 是哪来的,我就没有头绪了
    Leviathann
        3
    Leviathann  
    OP
       2021-09-07 12:00:34 +08:00 via iPhone
    @2i2Re2PLMaDnghL 额,忘了改报错信息
    PopUpAd 就是这里的 B 组件
    也就是说 A 组件想要持有一个 B 组件中某个 HTMLElement 的 ref
    那 A 组件的持有的 ref 就要定义为 HTMLElement | B | null 吗
    2i2Re2PLMaDnghL
        4
    2i2Re2PLMaDnghL  
       2021-09-07 20:40:46 +08:00
    或者公共父类。看上去 Component 就可以了?

    猜想,
    因为你的 createRef 传送过程中先后通过了 B.ref 和 Div.ref ,所以必须都符合。
    ```<B ref={this.createRef}/>``` 这一段可能检查引擎蕴含了 createRef 必须是 Ref<B>,或者说 Component 的定义里包含了这一限定,你的 Bprops 实际是与之相交而非覆盖。
    你尝试下把这个 ref 的名字改成 myref 试试?
    当然理论上也可以让 B 同时 extends Div,但引入的问题可能比解决的多。

    然后发现其实是双变的,bivarianceHack 这名字看上去就是为了让 TS 允许双变,不清楚如何办到的。范畴论推理不是我的强项。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4026 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 05:14 · PVG 13:14 · LAX 21:14 · JFK 00:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.