Definitelytyped: [@types/react] RefObject.current 不应再是只读的

创建于 2018-12-05  ·  48评论  ·  资料来源: DefinitelyTyped/DefinitelyTyped

现在可以分配给ref.current ,请参见示例: https: //reactjs.org/docs/hooks-faq.html#is -there-something-like-instance-variables

尝试为其赋值会出现错误: Cannot assign to 'current' because it is a constant or a read-only property.

  • [x] 我尝试使用@types/react包但遇到了问题。
  • [x] 我尝试使用最新的稳定版 tsc。 https://www.npmjs.com/package/typescript
  • [x] 我有一个不适合StackOverflow的问题。 (请在那里提出任何适当的问题)。
  • [x] [提及](https://github.com/blog/821-mention-somebody-they-re-notified) 作者(参见Definitions by:中的index.d.ts ),以便他们可以回应。

    • 作者: @johnnyreilly @bbenezech @pzavolinsky @digiguru @ericanderson @morcerf @tkrotoff @DovydasNavickas @onigoetz @theruther4d @guilhermehubner @ferdaber @jrakotoharisoa @pascaloliv @Hotell @franklixuefei

最有用的评论

不是。 故意将其保留为只读以确保正确使用,即使它没有被冻结。 使用 null 初始化的 Refs 没有明确表示您希望能够为其分配 null 被解释为您希望由 React 管理的 refs —— 即 React “拥有”当前并且您只是在查看它。

如果您想要一个以 null 值开头的可变 ref 对象,请确保也将| null提供给通用参数。 这会让它变得可变,因为你“拥有”它而不是 React。

也许这对我来说更容易理解,因为我以前经常使用基于指针的语言,并且所有权在它们中_非常_重要。 这就是 refs 是一个指针。 .current正在取消引用指针。

所有48条评论

公关将不胜感激! 这应该是一个非常简单的换行😊

@ferdaber
你好,我喜欢选择这个问题。 由于我是 typescript 的新手(仅一周),这将是我对开源的第一次贡献。

我已经浏览了 node_modules/@types/react/index.d.ts 并且在第 61 行有以下代码,它将当前声明为只读。

interface RefObject<T> {
        readonly current: T | null;
    }

如果这是问题,那么我可以解决。 你能指导我的第一个 PR 吗?
感谢您的时间 :)

是的,它只是删除了该行上的readonly修饰符。

React.useRef返回一个MutableRefObject ,其current属性不是readonly 。 我想问题是是否应该统一 ref 对象类型。

它们应该是统一的,React 运行时对 $ React.createRef()创建的current属性没有限制,对象本身是密封但不冻结的。

不是。 故意将其保留为只读以确保正确使用,即使它没有被冻结。 使用 null 初始化的 Refs 没有明确表示您希望能够为其分配 null 被解释为您希望由 React 管理的 refs —— 即 React “拥有”当前并且您只是在查看它。

如果您想要一个以 null 值开头的可变 ref 对象,请确保也将| null提供给通用参数。 这会让它变得可变,因为你“拥有”它而不是 React。

也许这对我来说更容易理解,因为我以前经常使用基于指针的语言,并且所有权在它们中_非常_重要。 这就是 refs 是一个指针。 .current正在取消引用指针。

公平地说,我一直使用React.createRef()作为辅助函数来创建一个指针,因为 React 不会管理它,除非它作为ref道具传入,但你可以以及创建一个具有相同结构的简单指针对象。

我们可以修改createRef以具有相同的重载逻辑,但由于没有参数,它必须带有条件类型返回。 如果包含| null ,它将返回MutableRefObject ,如果不包含,它将是(不可变的) RefObject

不过,这对那些没有启用strictNullTypes的人有用吗?

🤔

我们真的应该让那些_do_ 启用strictNullTypes的人更容易编写不正确的代码吗?

当我创建这个问题时,我没有意识到已经有这个| null重载。 这不是一个很常见的模式,所以我没想到会存在这样的东西。 我不知道我是如何在文档中错过它的,它有很好的文档和解释。 除非您想统一useRefcreateRef ,否则我可以将此作为非问题关闭。

由于问题本身是基于useRef而不是createRef ,我也可以关闭它,因为很少有人直接修改createRef中的取消引用指针值。

我确实想知道这种重载模式是否会很好,查看钩子文档,我们看到一些使用默认值的useRef ,在这种情况下, .current的值可能永远不会null 但对于用户来说,不断使用非 null 断言运算符来克服它可能有点烦人。

如果给定初始值,则返回值是可变的,但如果它被省略,则返回值不可变是否更有意义? 如果给出了初始值,那么 ref 可能不会通过ref传递给组件,并且更像是实例变量。 另一方面,当创建一个仅用于传递给组件的 ref 时,可能不会提供初始值。

我就是这么想的。 @Kovensky你有什么想法?

@ferdaber useRef现在定义的将始终是可变的_并且_不能为空,除非您明确地给它一个不包括| null和初始值null的通用参数

传递给组件的 Refs 应该使用null进行初始化,因为这是 React 在释放它们时将 refs 设置为的内容(例如,在卸载有条件安装的组件时)。 useRef不传递值会导致它启动undefined ,所以现在你还必须将| undefined添加到只需要关心| null的东西上

React 文档中使用useRef没有初始值的问题是,React 团队似乎不太关心nullundefined之间的区别。 那可能是Flow的事情。


无论如何,由于我上面提到的原因,我定义useRef的方式完全适合@bschlenk的用例,只是null是“省略”值。

啊,仔细看就知道了,有趣的是它在 React 源中被初始化为undefined没有参数。 很酷,所以一切都是桃子,听起来像👍

我认为它的方式很好,只是有点奇怪,无论你是否包含| nullcurrent的类型仍然可以为空,只是可变性发生了变化。 此外,React 文档总是显式传递 null,那么省略 initialValue 是否有效?

通常不是这样,至少在过去,当我在一些文档中指出 React 团队说“即使它有效”时,也不打算省略它。

这就是为什么需要createContext的第一个参数的原因,例如,即使您确实希望上下文以undefined开头。 您实际上必须通过undefined

看起来在某些用法中它没有使用参数:

https://reactjs.org/docs/hooks-faq.html#is -there-something-like-instance-variables
https://reactjs.org/docs/hooks-faq.html#how -to-get-the-previous-props-or-state
https://reactjs.org/docs/hooks-faq.html#how -to-read-an-often-sharing-value-from-usecallback

@ferdaber那是因为他们不关心那里的类型。 useRef() (无参数)中current的类型应该是什么? undefined ? any ? 不管它是什么,即使你给出一个通用参数,它也必须是|undefined 。 _And_ 如果它是你用作反应元素 ref 的 ref(不仅仅是作为线程本地存储),那么你还必须| null因为 React 可能会在那里写一个null

现在您发现自己的currentT | null | undefined而不仅仅是T

// you should always give an argument even if the docs sometimes miss them
// $ExpectError
const ref1 = useRef()
// this is a mutable ref but you can only assign `null` to it
const ref2 = useRef(null)
// this is also a mutable ref but you can only assign `undefined`
const ref3 = useRef(undefined)
// this is a mutable ref of number
const ref4 = useRef(0)
// this is a mutable ref of number | null
const ref5 = useRef<number | null>(null)
// this is a mutable ref with an object
const ref6 = useRef<React.CSSProperties>({})
// this is a mutable ref that can hold an HTMLElement
const ref7 = useRef<HTMLElement | null>(null)
// this is the only case where the ref is immutable
// you did not say in the generic argument you want to be able to write
// null into it, but you gave a null anyway.
// I am taking this as the sign that this ref is intended
// to be used as an element ref (i.e. owned by React, you're only sharing)
const ref8 = useRef<HTMLElement>(null)
// not allowed, because you didn't say you want to write undefined in it
// this is essentially what would happen if we allowed useRef with no arguments
// to make it worse, you can't use it as an element ref, because
// React might write a null into it anyway.
// $ExpectError
const ref9 = useRef<HTMLElement>(undefined)

是的,这个 DX 对我有用,而且文档是 A++,所以我认为大多数人不会对此感到困惑。 感谢您的详细说明! 老实说,您应该在 typedoc 中永久链接此问题 :)

可能存在支持useRef<T>()并使其行为与useRef<T | undefined>(undefined)相同的情况; 但是无论你用它做什么 ref 仍然不能用作元素 ref,就像线程本地存储一样。

问题是......如果你_不_给出一个允许的通用参数会发生什么? TypeScript 只会推断{} 。 正确的默认类型是unknown但我们不能使用它。

我使用以下代码收到此错误:

~~~js
// ...
让 intervalRef = useRef(空值); // 也尝试使用 const 而不是 let
// ...
使用效果(()=> {
const interval = setInterval( () => { /* 做某事 */}, 1000);
intervalRef.current = 间隔; // 在这一行中,我得到了错误

return () => {
    clearInterval(intervalRef.current);
}

})
// ...
~当我在这里删除readonly时,它可以工作:〜js
接口 RefObject{
只读电流:T | 空值;
}
~~~

我是 reack 钩子和打字稿的新手(只是一起尝试)所以我的代码可能是错误的

默认情况下,如果您创建一个具有null默认值的 ref 并指定其通用参数,则表明您打算让 React “拥有”该引用。 如果您希望能够改变您拥有的 ref,您应该像这样声明它:

const intervalRef= useRef<NodeJS.Timeout | null>(null) // <-- the generic type is NodeJS.Timeout | null

@ferdaber谢谢!

更进一步,看看current的返回类型,它应该是T而不是T | null吗? 随着钩子的出现,我们_并不总是_遇到所有 refs 可能都是null的情况,尤其是在useRef被非空初始化器调用的常见情况下。

https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment -446660394 中的优秀示例列表继续,如果我写:

const numericRef = useRef<number>(42);

numericRef.current的类型应该是什么? 没有_need_ 是number | null

如果我们定义类型和函数如下:

interface RefObject<T> {
  current: T;
}

function useRef<T>(initialValue: T): RefObject<T>;
function useRef<T>(initialValue: T|null): RefObject<T | null>;

function createRef<T>(): RefObject<T | null>;

这将产生以下用法和类型:

const r1 = useRef(42);
// r1 is of type RefObject<number>
// r1.current is of type number (not number | null)

const r2 = useRef<number>(null);
// r2 is of type RefObject<number | null>
// r2.current is of type number | null

const r3 = useRef(null);
// r3 is of type RefObject<null>
// r3.current is of type null

const r4 = createRef<number>();
// r4 is of type RefObject<number | null>
// r4.current is of type number | null

有什么问题吗?

根据 React 团队的说法,对于“未参数化useRef的类型是什么?”的答案,答案是该调用(尽管有文档)是不正确的。

我添加了带有 |null _specifically_ 的重载作为 DOM/组件引用的便利重载,因为它们总是以 null 开头,卸载时它们总是重置为 null,而且你永远不会自己重新分配当前,只有 React。

readonly 比真正的 JavaScript 冻结对象/仅 getter 属性的表示更能防止逻辑错误。

当你们都给出一个通用参数说你不接受 null 时,你只能意外地把值初始化为 null 。 任何其他情况都是可变的。

啊,是的,与RefObject<T>相比,我现在看到MutableRefObject<T>已经删除了| null案例。 所以useRef<number>(42)案例已经正常工作了。 感谢您的澄清!

我们需要用createRef做什么? 现在它返回一个不可变的RefObject<T> ,这在我们的代码库中造成了问题,因为我们希望将它们传递并以与(可变) useRef ref 对象相同的方式使用它们。 有没有办法我们可以调整createRef类型以允许它形成可变的 ref 对象?

(并且ref属性被定义为Ref<T>类型,其中包括RefObject<T> ,从而使所有内容不可变。这对我们来说是一个大问题:即使我们得到一个可变的引用useRef ,我们不能利用它通过forwardRef调用不可变的事实。)

嗯......也许我们可以使用相同的技巧,只是因为它没有参数(并且总是以 null 开头)它需要一个条件类型。

: null extends T ? MutableRefObject<T> : RefObject<T>应该使用相同的逻辑。 如果您说要在其中放入空值,则它是可变的,如果您不这样做,则在当前含义中它仍然是不可变的。

这是一个非常好的主意。 因为createRef没有参数,所以它可能总是需要包含一个| null选项(不像useRef ),所以可能需要说MutableRefObject<T | null>

我一直无法在 TS 中工作。 这是使用它配置的 TS 游乐场:
https://tinyurl.com/y75c32y3

该类型始终被检测为MutableRefObject

你认为我们可以用forwardRef做什么? 它将ref声明为Ref<T> ,其中不包括MutableRefObject的可能性。

(导致困难的用例是mergeRefs函数,它接受一个 ref 数组,可以是函数式 ref 或 ref 对象,并创建一个可以传递的单个组合 ref [一个函数式 ref]到一个组件。然后,组合的 ref 将任何传入的引用元素传递给所有提供的 ref,如果它们是功能性 ref,则通过调用它们,或者如果它们是 ref 对象,则通过设置.current 。但是存在不可变的RefObject<T>MutableRefObject<T> in Ref<T>的缺乏使这很难。我应该为 forwardRef 和 Ref 提出一个单独的问题困难?)

由于上述原因,我们不会更改useRef的类型(尽管createRef仍在讨论中),您对基本原理有任何疑问吗?

我应该为https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment -457650501 中涵盖的项目提出单独的问题吗?

是的,让我们把它分开。

如果您想要一个以 null 值开头的可变 ref 对象,请确保也将| null提供给通用参数。 这会让它变得可变,因为你“拥有”它而不是 React。

谢谢你! 将 null 添加到useRef泛型为我解决了这个问题。

const ref = useRef<SomeType>(null) 

// Later error: Cannot assign to 'current' because it is a constant or a read-only property.

const ref = useRef<SomeType | null>(null)

是否创建了有关 forwardRef 组件的另一条评论? 本质上,您不能转发 ref 并直接更改它的当前值,我认为这是转发 ref 的一部分。

我创建了以下钩子:

export const useCombinedRefs = <T>(...refs: Ref<T>[]) =>
    useCallback(
        (element: T) =>
            refs.forEach(ref => {
                if (!ref) {
                    return;
                }

                if (typeof ref === 'function') {
                    ref(element);
                } else {
                    ref.current = element; // this line produces error
                }
            }),
        refs,
    );

我得到一个错误:“不能分配给'当前',因为它是一个只读属性。”
有没有办法在不将current更改为可写的情况下解决它?

为此,你将不得不作弊。

(ref.current as React.MutableRefObject<T> ).current = element;

是的,这有点不合理,但这是我能想到的唯一一种情况,这种分配是故意的而不是偶然的——你正在复制 React 的内部行为,因此必须打破规则。

我们可以修改createRef以具有相同的重载逻辑,但由于没有参数,它必须带有条件类型返回。 如果包含| null ,它将返回MutableRefObject ,如果不包含,它将是(不可变的) RefObject

多谢

(ref.current as React.MutableRefObject<T>).current = element;

应该:

(ref as React.MutableRefObject<T>).current = element;

你正在复制 React 的内部行为

这是否意味着这里的文档已经过时了? 因为他们清楚地将其描述为预期的工作流程,而不是内部行为。

在这种情况下,文档是指您拥有的 ref,但对于您作为ref属性传递给 HTML 元素的 ref,那么它们应该是只读的_to you_。

function Component() {
  // same API, different type semantics
  const countRef = useRef<number>(0); // not readonly
  const divRef = useRef<HTMLElement>(null); // readonly

  return <button ref={divRef} onClick={() => countRef.current++}>Click me</button>
}

我的错,应该进一步向上滚动。 对于我拥有的 ref 有一个关于readonly的错误,并假设这是相同的情况。 (我现在使用不同的工作流程,无法再重现错误,不幸的是......)

无论如何,非常感谢您的解释!

我不清楚话题的createRef部分是否移动了——但我也会在这里发帖。

我使用了一个流行的 React Native 导航库(React Navigation)。 在其文档中,它通常调用createRef然后改变 ref。 我确信这是因为 React 没有管理它们(没有 DOM)。

React Native 的类型应该不同吗?

请参阅: https ://reactnavigation.org/docs/navigating-without-navigation-prop

@sylvanaar
我在初始化导航时也遇到了这个问题,你找到解决方案了吗?

总结一下我刚刚读到的内容:为createRef<T>创建的引用设置值的唯一方法是在每次使用时强制转换它? 这背后的原因是什么? 在这种情况下, readonly实际上完全阻止了设置 ref 的值,因此违背了函数的全部目的。

TLDR:如果您的初始值为null (详细信息:或类型参数之外的其他内容),则将| null添加到您的类型参数中,这应该使.current能够被分配到像平常一样。

此页面是否有帮助?
0 / 5 - 0 等级