简体   繁体   English

TypeScript:function 参数类型的更改未在 function 中的 forEach 中选取

[英]TypeScript: Change of a function argument type is not picked up in forEach within the function

I am working on something that has checkboxes and I don't understand why the following is not valid TypeScript:我正在研究带有复选框的东西,但我不明白为什么以下内容无效 TypeScript:

declare function checkChecked(isAllChecked: boolean): void

declare function determineIfChecked(): boolean

const checkBoxes: number[] = []

const toggleCheckAll = (isAllChecked?: boolean): void => {
  if (isAllChecked === undefined) isAllChecked = determineIfChecked()

  checkBoxes.forEach(() => checkChecked(isAllChecked))
  checkChecked(isAllChecked)
}

Here it is in a TS playground 这是在一个TS游乐场

The isAllChecked in the call of checkChecked in the forEach loop raises an: forEach循环中isAllChecked调用中的checkChecked引发:

Argument of type 'boolean | 'boolean | 类型的参数undefined' is not assignable to parameter of type 'boolean'. undefined' 不可分配给“boolean”类型的参数。 Type 'undefined' is not assignable to type 'boolean'.类型“未定义”不可分配给类型“布尔”。

I'm guessing my understanding of the scope of function arguments is not quite right as I would have thought the if statement should have updated the type of isAllChecked to be boolean and that this would be carried through to the forEach loop. I'm guessing my understanding of the scope of function arguments is not quite right as I would have thought the if statement should have updated the type of isAllChecked to be boolean and that this would be carried through to the forEach loop.

I can work round this but I would like to understand why this is not valid TypeScript.我可以解决这个问题,但我想了解为什么这不是有效的 TypeScript。

This is a well-known design limitation in TypeScript;这是 TypeScript 中众所周知的设计限制; see microsoft/TypeScript#9998 for a full description.有关完整说明,请参阅microsoft/TypeScript#9998

The problem is that the narrowing that happens when you check and reassign isAllChecked does not persist across function scope boundaries.问题是当您检查并重新分配isAllChecked时发生的 缩小不会持续跨越 function scope 边界。 So while the compiler knows that isAllChecked is definitely boolean in the outer scope, that information is completely gone in the scope of the callback () => checkChecked(isAllChecked) .因此,虽然编译器知道isAllChecked在外部 scope 中肯定是boolean ,但该信息在回调的 scope 中完全消失了() => checkChecked(isAllChecked) Any narrowing of closed-over values is reset inside a closure.封闭值的任何缩小都会在闭包内重置。

This happens because in general it would be completely unfeasible for the compiler to attempt to keep track of control flow into and out of functions.发生这种情况是因为通常编译器尝试跟踪进出函数的控制流是完全不可行的。 The compiler doesn't know when or if the callback () => checkChecked(isAllChecked) will be run, because even though we know that the array forEach() method runs essentially synchronously, that information is not part of the type system.编译器不知道回调() => checkChecked(isAllChecked)何时或是否会运行,因为即使我们知道数组forEach()方法本质上是同步运行的,但该信息并不是类型系统的一部分。 In general, a callback passed to some function could be called at any time.通常,可以随时调用传递给某些 function 的回调。

And in general, closed-over values could be reassigned or modified before that time.通常,可以在此之前重新分配或修改封闭值。 In your particular case, you never modify isAllChecked after the callback is created.在您的特定情况下,您永远不会在创建回调后修改isAllChecked But the compiler would need to spend extra resources to check that this is true, and therefore all closures would require the compiler to check for reassignments of all closed-over values everywhere, which would add a lot of compilation time for a benefit that only happens on occasion.但是编译器需要花费额外的资源来检查这是否属实,因此所有闭包都需要编译器检查所有封闭值的重新分配,这会增加大量编译时间,而这种好处只会发生不定期的。

So that's what's going on.这就是正在发生的事情。 The compiler can't keep track of what happens to closed-over values inside of callbacks, and it doesn't really try.编译器无法跟踪回调内部的封闭值发生了什么,并且它并没有真正尝试。


I know you're not asking for a workaround, but for completeness, the usual workaround in cases like this is to copy the value to a const once you know it will never change:我知道您不是在要求解决方法,而是为了完整性,在这种情况下,通常的解决方法是将值复制到const一旦您知道它永远不会改变:

const toggleCheckAll = (isAllChecked?: boolean): void => {
    if (isAllChecked === undefined) isAllChecked = false;
    const i = isAllChecked; // boolean
    checkBoxes.forEach(() => checkChecked(i)) // okay
    checkChecked(isAllChecked)
}

The i constant has type boolean and not boolean | undefined i常量的类型为boolean而不是boolean | undefined boolean | undefined because of the control flow narrowing of isAllChecked to boolean at the time of the assignment to i . boolean | undefined ,因为在分配给iboolean isAllChecked Which means that checkChecked(i) is always acceptable no matter where it appears;这意味着无论出现在哪里, checkChecked(i)总是可以接受的; there is no control flow narrowing of i to be reset.没有要重置的i的控制流变窄。

Playground link to code Playground 代码链接

There is no point for a parameter to be optional if its type is boolean and it is the only parameter in the function.如果参数的类型是boolean并且它是 function 中的唯一参数,则参数是可选的没有意义。 Just use toggleCheckAll(true) instead of toggleCheckAll() .只需使用toggleCheckAll(true)而不是toggleCheckAll() Plus, if you are declaring a function, use declare function instead of declare const另外,如果您要声明 function,请使用declare function而不是declare const

declare function checkChecked(isAllChecked: boolean): void

const toggleCheckAll = (isAllChecked: boolean): void => { 
  checkBoxes.forEach(() => checkChecked(isAllChecked))
  checkChecked(isAllChecked)
}

The problem is that isAllChecked?: boolean allows for undefined , while isAllChecked: boolean doesn't.问题是isAllChecked?: boolean允许undefined ,而isAllChecked: boolean不允许。

Either use a default value for the function, or add a check for undefined inside the function:为 function 使用默认值,或在 function 中添加未定义检查:

const toggleCheckAll = (isAllChecked: boolean = false): void => {
  checkBoxes.forEach(() => checkChecked(isAllChecked))
  checkChecked(isAllChecked)
}

Or或者

const toggleCheckAll = (isAllChecked?: boolean): void => {
  if (isAllChecked == null) {
    isAllChecked = false;
  }

  checkBoxes.forEach(() => checkChecked(isAllChecked))
  checkChecked(isAllChecked)
}

EDIT编辑

The second option doesn't work.第二个选项不起作用。 See this other answer .请参阅其他答案 Rather than introducing a new variable, I would suggest the Null Coalascing operator ( ?? ):我建议不要引入新变量,而是建议使用Null Coalascing运算符( ?? ):

const toggleCheckAll = (isAllChecked?: boolean): void => {
  if (isAllChecked == null) {
    isAllChecked = false;
  }

  checkBoxes.forEach(() => checkChecked(isAllChecked ?? false))
  checkChecked(isAllChecked)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM