TypeScript class 中第一个定义的方法使用类型为 this 的函数未正确类型化(但之后的所有内容)
First defined method in TypeScript class is not properly typed (but everything after it is) using functions with typed this
我真的不知道这里可能出了什么问题。本质上,我正在尝试制作一个 MyModel
class 来接收一些数据并初始化它自己的值,并定义一些对所述数据起作用的方法。但是,该模型可能只接收数据的特定部分——因此我将 MyModel<Included extends keyof MyData>
定义为 data: Pick<MyData, Included>
。我现在只希望在 MyData
的这个子集上运行的方法可以从某些模型实例中调用,因此成员方法是通过 DependentMethod([ ...dependencies], method)
辅助函数定义的,returns 一个需要适当的函数this
—— 即 MyModel
在其 Included
中具有某些属性(这也添加了一些运行时保护,因此它 returns undefined
如果它被不正确的调用this
无论如何)。
然后我定义 usernameCapitalised = DependentMethod([ 'username' ], ...)
等,工作正常...除了在 class 中,无论第一个通过 DependentMethod
定义的成员函数的类型是 any
并且根本不进行类型检查。如果我在它之前添加一个新的这样的函数,那个函数不会进行类型检查,但前一个函数会开始这样做。如果我将第一个函数移动到不再是第一个,它会再次开始类型检查,但现在是第一个的函数不会。最初我认为问题出在我使用更高种类的类型上,但正如您在我对最小复制的最佳尝试中看到的那样,情况似乎并非如此:
type UnionOfList<List extends any[]> = List[number]
export const DependentMethod = function<Dependencies extends (keyof MyData)[], R>
(dependencies: Dependencies, fn: (this: MyModel<UnionOfList<Dependencies>>) => R):
(this: MyModel<UnionOfList<Dependencies>>) => R
{
if(dependencies.every(dependency => this.data.includes(dependency))) {
return fn.call(this)
} else {
return undefined
}
}
let noop = DependentMethod(['username'], () => 0)
class MyModel<Included extends keyof MyData> {
data: Pick<MyData, Included>
constructor(data: Pick<MyData, Included>) {
this.data = data
}
// noop = DependentMethod([ 'username' ], function() {}) // Makes usernameCapitalised work
// static static_noop = DependentMethod([ 'username' ], function() {}) // Makes usernameCapitalised work
// noop_arrow = DependentMethod([ 'username' ], () => undefined) // DOESN'T make usernameCapitalised work
// static snoop_arrow = DependentMethod([ 'username' ], () => undefined) // DOESN'T make usernameCapitalised work
usernameCapitalised = DependentMethod(
[ 'username' ],
function() {
return this.data.username.toUpperCase()
}
)
useridTimesTen = DependentMethod(
[ 'userid' ],
function() {
return this.data.userid
}
)
}
interface MyData {
username: string
userid: number
}
const model = new MyModel({})
model.usernameCapitalised() // No error - unexpected
model.useridTimesTen() // Error - as expected
同样奇怪的是,静态成员方法也算作一个新的 "first" 方法,因此插入静态 DependentMethod
调用也会在此处进行 usernameCapitalised
类型检查——只要它是不是箭头函数(即使我覆盖了 this
类型)。您可以通过取消注释 class 中的注释行来自己尝试。 class 之外的 DependentMethod
也没有效果,即使它们都在完全相同的类型上运行,这让我更倾向于认为这是一个错误。无论如何,静态成员似乎也被正确键入。
您的示例代码的问题在于 DependentMethod
的类型取决于 MyModel
的类型,后者具有其类型由 DependentMethod
的输出确定的属性,这取决于 MyModel
的类型,这...哎呀。该类型以编译器无法推断的方式循环。 MyModel
中的错误告诉你:
usernameCapitalised = DependentMethod( // error!
//~~~~~~~~~~~~~~~~~~~ <-- 'usernameCapitalised' implicitly has type 'any'
//because it does not have a type annotation and is referenced directly or
//indirectly in its own initializer.
[ 'username' ],
function() {
return this.data.username.toUpperCase()
}
)
这最终会级联到您的大部分属性,它们也有 any
类型。 MyModel
的实例行为异常这一事实并不奇怪;您几乎需要修复 MyModel
中的错误,然后才能期望编译器对其进行合理的处理。所以我认为错误发生在 "first" 方法上是一个转移注意力的事实;这很有趣,但我不会尝试通过更改第一个方法来修复它。相反,如果可能的话,我会修复潜在的循环性。
现在我不能确定示例代码是否足以代表您的用例,但假设它是:我看到每个 [=] 的 fn
参数传递到 DependentMethod
MyModel
的 60=] 仅取决于 MyModel
的 data
属性 而不是其他属性。所以也许 DependentMethod
不应该引用 MyModel<UnionOfList<Dependencies>>
,而应该只引用 {data: Pick<MyData, UnionOfList<Dependencies>>
,像这样:
declare const DependentMethod: <D extends keyof MyData, R>(
dependencies: D[],
fn: (this: { data: Pick<MyData, D> }) => R
) => (this: { data: Pick<MyData, D> }) => R;
注意:我不担心这里DependentMethod
的实现;我已将泛型类型参数更改为更传统(如果表达能力较差)的单个大写字符;并且我更改了泛型类型参数以表示键而不是键数组。现在 MyModel
没有错误:
class MyModel<K extends keyof MyData> {
data: Pick<MyData, K>
constructor(data: Pick<MyData, K>) {
this.data = data
}
usernameCapitalised = DependentMethod(
['username'],
function () {
return this.data.username.toUpperCase()
}
)
useridTimesTen = DependentMethod(
['userid'],
function () {
return this.data.userid
}
)
}
并且您的 MyModel
实例的行为与我假设的一样:
const emptyModel = new MyModel({})
emptyModel.usernameCapitalised(); // error
emptyModel.useridTimesTen(); // error
const usernameModel = new MyModel({ username: "Alice" });
usernameModel.usernameCapitalised(); // okay
usernameModel.useridTimesTen(); // error
const useridModel = new MyModel({ userid: 1 });
useridModel.usernameCapitalised(); // error
useridModel.useridTimesTen(); // okay
const fullModel = new MyModel({ userid: 1, username: "Alice" });
fullModel.usernameCapitalised(); // okay
fullModel.useridTimesTen(); // okay
如果发现 DependentMethod()
需要访问 MyModel
的更多属性,那么您可能需要将其重构为包含这些属性的基础 class,以及在没有它们的情况下扩展 class,并让 DependentMethod()
仅引用基数 class。这个想法是为了确保您的类型是 "grounded" 而不是循环类型。
好的,希望对您有所帮助。祝你好运!
我真的不知道这里可能出了什么问题。本质上,我正在尝试制作一个 MyModel
class 来接收一些数据并初始化它自己的值,并定义一些对所述数据起作用的方法。但是,该模型可能只接收数据的特定部分——因此我将 MyModel<Included extends keyof MyData>
定义为 data: Pick<MyData, Included>
。我现在只希望在 MyData
的这个子集上运行的方法可以从某些模型实例中调用,因此成员方法是通过 DependentMethod([ ...dependencies], method)
辅助函数定义的,returns 一个需要适当的函数this
—— 即 MyModel
在其 Included
中具有某些属性(这也添加了一些运行时保护,因此它 returns undefined
如果它被不正确的调用this
无论如何)。
然后我定义 usernameCapitalised = DependentMethod([ 'username' ], ...)
等,工作正常...除了在 class 中,无论第一个通过 DependentMethod
定义的成员函数的类型是 any
并且根本不进行类型检查。如果我在它之前添加一个新的这样的函数,那个函数不会进行类型检查,但前一个函数会开始这样做。如果我将第一个函数移动到不再是第一个,它会再次开始类型检查,但现在是第一个的函数不会。最初我认为问题出在我使用更高种类的类型上,但正如您在我对最小复制的最佳尝试中看到的那样,情况似乎并非如此:
type UnionOfList<List extends any[]> = List[number]
export const DependentMethod = function<Dependencies extends (keyof MyData)[], R>
(dependencies: Dependencies, fn: (this: MyModel<UnionOfList<Dependencies>>) => R):
(this: MyModel<UnionOfList<Dependencies>>) => R
{
if(dependencies.every(dependency => this.data.includes(dependency))) {
return fn.call(this)
} else {
return undefined
}
}
let noop = DependentMethod(['username'], () => 0)
class MyModel<Included extends keyof MyData> {
data: Pick<MyData, Included>
constructor(data: Pick<MyData, Included>) {
this.data = data
}
// noop = DependentMethod([ 'username' ], function() {}) // Makes usernameCapitalised work
// static static_noop = DependentMethod([ 'username' ], function() {}) // Makes usernameCapitalised work
// noop_arrow = DependentMethod([ 'username' ], () => undefined) // DOESN'T make usernameCapitalised work
// static snoop_arrow = DependentMethod([ 'username' ], () => undefined) // DOESN'T make usernameCapitalised work
usernameCapitalised = DependentMethod(
[ 'username' ],
function() {
return this.data.username.toUpperCase()
}
)
useridTimesTen = DependentMethod(
[ 'userid' ],
function() {
return this.data.userid
}
)
}
interface MyData {
username: string
userid: number
}
const model = new MyModel({})
model.usernameCapitalised() // No error - unexpected
model.useridTimesTen() // Error - as expected
同样奇怪的是,静态成员方法也算作一个新的 "first" 方法,因此插入静态 DependentMethod
调用也会在此处进行 usernameCapitalised
类型检查——只要它是不是箭头函数(即使我覆盖了 this
类型)。您可以通过取消注释 class 中的注释行来自己尝试。 class 之外的 DependentMethod
也没有效果,即使它们都在完全相同的类型上运行,这让我更倾向于认为这是一个错误。无论如何,静态成员似乎也被正确键入。
您的示例代码的问题在于 DependentMethod
的类型取决于 MyModel
的类型,后者具有其类型由 DependentMethod
的输出确定的属性,这取决于 MyModel
的类型,这...哎呀。该类型以编译器无法推断的方式循环。 MyModel
中的错误告诉你:
usernameCapitalised = DependentMethod( // error!
//~~~~~~~~~~~~~~~~~~~ <-- 'usernameCapitalised' implicitly has type 'any'
//because it does not have a type annotation and is referenced directly or
//indirectly in its own initializer.
[ 'username' ],
function() {
return this.data.username.toUpperCase()
}
)
这最终会级联到您的大部分属性,它们也有 any
类型。 MyModel
的实例行为异常这一事实并不奇怪;您几乎需要修复 MyModel
中的错误,然后才能期望编译器对其进行合理的处理。所以我认为错误发生在 "first" 方法上是一个转移注意力的事实;这很有趣,但我不会尝试通过更改第一个方法来修复它。相反,如果可能的话,我会修复潜在的循环性。
现在我不能确定示例代码是否足以代表您的用例,但假设它是:我看到每个 [=] 的 fn
参数传递到 DependentMethod
MyModel
的 60=] 仅取决于 MyModel
的 data
属性 而不是其他属性。所以也许 DependentMethod
不应该引用 MyModel<UnionOfList<Dependencies>>
,而应该只引用 {data: Pick<MyData, UnionOfList<Dependencies>>
,像这样:
declare const DependentMethod: <D extends keyof MyData, R>(
dependencies: D[],
fn: (this: { data: Pick<MyData, D> }) => R
) => (this: { data: Pick<MyData, D> }) => R;
注意:我不担心这里DependentMethod
的实现;我已将泛型类型参数更改为更传统(如果表达能力较差)的单个大写字符;并且我更改了泛型类型参数以表示键而不是键数组。现在 MyModel
没有错误:
class MyModel<K extends keyof MyData> {
data: Pick<MyData, K>
constructor(data: Pick<MyData, K>) {
this.data = data
}
usernameCapitalised = DependentMethod(
['username'],
function () {
return this.data.username.toUpperCase()
}
)
useridTimesTen = DependentMethod(
['userid'],
function () {
return this.data.userid
}
)
}
并且您的 MyModel
实例的行为与我假设的一样:
const emptyModel = new MyModel({})
emptyModel.usernameCapitalised(); // error
emptyModel.useridTimesTen(); // error
const usernameModel = new MyModel({ username: "Alice" });
usernameModel.usernameCapitalised(); // okay
usernameModel.useridTimesTen(); // error
const useridModel = new MyModel({ userid: 1 });
useridModel.usernameCapitalised(); // error
useridModel.useridTimesTen(); // okay
const fullModel = new MyModel({ userid: 1, username: "Alice" });
fullModel.usernameCapitalised(); // okay
fullModel.useridTimesTen(); // okay
如果发现 DependentMethod()
需要访问 MyModel
的更多属性,那么您可能需要将其重构为包含这些属性的基础 class,以及在没有它们的情况下扩展 class,并让 DependentMethod()
仅引用基数 class。这个想法是为了确保您的类型是 "grounded" 而不是循环类型。
好的,希望对您有所帮助。祝你好运!