có thể được khởi tạo bằng một kiểu con khác của ràng buộc 'đối tượng'


80

A có lỗi đánh máy trong kiểu đệ quy.

Tôi đang cố gắng viết các kiểu cho đối tượng kiểu react-jss.

type StylesFn<P extends object> = (
  props: P
) => CSS.Properties<JssValue<P>> | number | string;

type JssValue<P extends object> =
  | string
  | number
  | Array<string | number>
  | StylesFn<P>;

// @ts-ignore
interface StylesObject<K extends string = any, P extends object = {}>
  extends Styles {
  [x: string]: CSS.Properties<JssValue<P>> | Styles<K, P>;
}
export type Styles<K extends string = any, P extends object = {}> = {
  [x in K]: CSS.Properties<JssValue<P>> | StylesObject<any, P> | StylesFn<P>
};

Nó hoạt động tốt, nhưng lỗi ghi kiểu chữ. Tôi sử dụng @ ts-ignore, nhưng điều này không lạ mắt

ERROR 24:11  typecheck  Interface 'StylesObject<K, P>' incorrectly extends interface 'Styles<any, {}>'.
  Index signatures are incompatible.
    Type 'Properties<JssValue<P>> | Styles<K, P>' is not assignable to type 'StylesFn<{}> | Properties<JssValue<{}>> | StylesObject<any, {}>'.
      Type 'Properties<JssValue<P>>' is not assignable to type 'StylesFn<{}> | Properties<JssValue<{}>> | StylesObject<any, {}>'.
        Type 'Properties<JssValue<P>>' is not assignable to type 'Properties<JssValue<{}>>'.
          Type 'JssValue<P>' is not assignable to type 'JssValue<{}>'.
            Type 'StylesFn<P>' is not assignable to type 'JssValue<{}>'.
              Type 'StylesFn<P>' is not assignable to type 'StylesFn<{}>'.
                Type '{}' is not assignable to type 'P'.
                  '{}' is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint 'object'.

Lỗi này có nghĩa là gì?


Đó là thông báo lỗi tương tự như trong câu hỏi này có thể được trả lời một phần bằng nhận xét.
— ChrisW

Câu trả lời:


146

Đang bổ sung câu trả lời tuyệt vời cho @fetzz.


CÂU TRẢ LỜI NGẮN

TLDR; Có hai nguyên nhân phổ biến cho loại thông báo lỗi này. Bạn đang làm việc đầu tiên (xem bên dưới). Cùng với văn bản, tôi giải thích rất chi tiết những gì thông báo lỗi này muốn truyền tải.

NGUYÊN NHÂN 1: Trong typscript, một cá thể cụ thể không được phép gán cho một tham số kiểu. Sau đây, bạn có thể xem ví dụ về 'vấn đề' và 'vấn đề đã được giải quyết', vì vậy bạn có thể so sánh sự khác biệt và xem những gì thay đổi:

VẤN ĐỀ

const func1 = <A extends string>(a: A = 'foo') => `hello!` // Error!

const func2 = <A extends string>(a: A) => {
    //stuff
    a = `foo`  // Error!
    //stuff
}

GIẢI PHÁP

const func1 = <A extends string>(a: A) => `hello!` // ok

const func2 = <A extends string>(a: A) => { //ok
    //stuff
    //stuff
}

Xem tại: Sân chơi TS

NGUYÊN NHÂN 2: Mặc dù bạn không gặp lỗi dưới đây trong mã của mình. Đây cũng là một trường hợp bình thường khi loại thông báo lỗi này xuất hiện. Bạn nên tránh làm điều này:

Lặp lại (do nhầm lẫn) Type Parametertrong một lớp, kiểu hoặc giao diện.

Đừng để sự phức tạp của đoạn mã dưới đây làm bạn bối rối, điều duy nhất tôi muốn bạn tập trung là cách loại bỏ ký tự 'A' giải quyết vấn đề:

VẤN ĐỀ:

type Foo<A> = {
    //look the above 'A' is conflicting with the below 'A'
    map: <A,B>(f: (_: A) => B) => Foo<B>
}

const makeFoo = <A>(a: A): Foo<A> => ({
   map: f => makeFoo(f(a)) //error!
})

GIẢI PHÁP:

type Foo<A> = {
    // conflict removed
    map: <B>(f: (_: A) => B) => Foo<B>
}

const makeFoo = <A>(a: A): Foo<A> => ({
   map: f => makeFoo(f(a)) //ok
})

Xem tại: Sân chơi TS


CÂU TRẢ LỜI DÀI


HIỂU THÔNG ĐIỆP LỖI

Sau đây, tôi sẽ phân tích từng phần tử của thông báo lỗi bên dưới:

Type '{}' is not assignable to type 'P'.
  '{}' is assignable to the constraint of type 'P', but 'P' could be
 instantiated with a different subtype of constraint'object'

LOẠI LÀ GÌ {}

Đó là một kiểu mà bạn có thể gán bất kỳ thứ gì ngoại trừ null hoặc undefined. Ví dụ:

type A = {}
const a0: A = undefined // error
const a1: A = null // error
const a2: A = 2 // ok
const a3: A = 'hello world' //ok
const a4: A = { foo: 'bar' } //ok
// and so on...

Xem tại: Sân chơi TS


NHỮNG GÌ LÀ is not assignable

Chỉ định là làm cho một biến của một kiểu cụ thể tương ứng với một cá thể cụ thể. Nếu bạn không khớp với loại phiên bản, bạn sẽ gặp lỗi. Ví dụ:

// type string is not assignable to type number 
const a: number = 'hello world' //error

// type number is assinable to type number
const b: number = 2 // ok


LÀ GÌ different subtype

Hai loại là bằng nhau : nếu chúng không thêm hoặc bớt các chi tiết liên quan đến nhau.

Hai loại khác nhau : nếu chúng không bằng nhau.

Loại Alà một loại phụ của loạiS : nếu Athêm chi tiết mà không xóa chi tiết đã tồn tại khỏi S.

kiểu Avà kiểu Blà các kiểu con khác nhau của kiểuS : Nếu Avà Blà kiểu con của S, nhưng Avà Blà các kiểu khác nhau. Nói cách khác: Avà Bthêm chi tiết vào loại S, nhưng chúng không thêm chi tiết giống nhau .

Ví dụ: Trong đoạn mã dưới đây, tất cả các câu sau đều đúng:

  1. A và D là loại bằng nhau
  2. B là kiểu con của A
  3. E không phải là kiểu con của A
  4. B và C là kiểu con khác nhau của A
type A = { readonly 0: '0'}
type B = { readonly 0: '0', readonly foo: 'foo'}
type C = { readonly 0: '0', readonly bar: 'bar'}
type D = { readonly 0: '0'}
type E = { readonly 1: '1', readonly bar: 'bar'}
type A = number
type B = 2
type C = 7
type D = number
type E = `hello world`
type A = boolean
type B = true
type C = false
type D = boolean
type E = number

LƯU Ý : Loại kết cấu

Khi bạn thấy trong TS việc sử dụng typetừ khóa, chẳng hạn như trong type A = { foo: 'Bar' }bạn nên đọc: Loại bí danh Alà trỏ đến cấu trúc loại{ foo: 'Bar' } .

Cú pháp chung là: type [type_alias_name] = [type_structure].

Hệ thống loại chỉ định chỉ kiểm tra chống lại [type_structure]và không chống lại [type_alias_name]. Điều đó có nghĩa là trong TS không có sự khác biệt về kiểu kiểm tra giữa sau: type A = { foo: 'bar }và type B = { foo: 'bar' }. Để biết thêm, hãy xem: Tài liệu chính thức .


constraint of type'X' LÀ GÌ

Giới hạn Loại đơn giản là những gì bạn đặt ở phía bên phải của từ khóa 'mở rộng'. Trong ví dụ dưới đây Type Constraintlà 'B'.

const func = <A extends B>(a: A) => `hello!`

Đọc: Loại Ràng buộc 'B' làconstraint of type 'A'


TẠI SAO LỖI XẢY RA

Tôi sẽ chỉ cho bạn ba trường hợp. Điều duy nhất sẽ thay đổi trong mỗi trường hợp là Type Constraint, không có gì khác sẽ thay đổi.

Điều tôi muốn bạn lưu ý là hạn chế Type Constraintáp đặt Type Parameter không bao gồm các loại phụ khác nhau . Hãy xem nó:

Được:

type Foo         =  { readonly 0: '0'}
type SubType     =  { readonly 0: '0', readonly a: 'a'}
type DiffSubType =  { readonly 0: '0', readonly b: 'b'}

const foo:             Foo         = { 0: '0'}
const foo_SubType:     SubType     = { 0: '0', a: 'a' }
const foo_DiffSubType: DiffSubType = { 0: '0', b: 'b' }

TRƯỜNG HỢP 1: KHÔNG HẠN CHẾ

const func = <A>(a: A) => `hello!`

// call examples
const c0 = func(undefined) // ok
const c1 = func(null) // ok
const c2 = func(() => undefined) // ok
const c3 = func(10) // ok
const c4 = func(`hi`) // ok
const c5 = func({}) //ok
const c6 = func(foo) // ok
const c7 = func(foo_SubType) //ok
const c8 = func(foo_DiffSubType) //ok

TRƯỜNG HỢP 2: MỘT SỐ HẠN CHẾ

Lưu ý dưới đây rằng hạn chế không ảnh hưởng đến các kiểu phụ.

RẤT QUAN TRỌNG : Trong Typecript Type Constraint không hạn chế các kiểu phụ khác nhau

const func = <A extends Foo>(a: A) => `hello!`

// call examples
const c0 = func(undefined) // error
const c1 = func(null) // error
const c2 = func(() => undefined) // error
const c3 = func(10) // error
const c4 = func(`hi`) // error
const c5 = func({}) // error
const c6 = func(foo) // ok
const c7 = func(foo_SubType) // ok  <-- Allowed
const c8 = func(foo_DiffSubType) // ok <-- Allowed

TRƯỜNG HỢP 3: CÒN LẠI NHIỀU

const func = <A extends SubType>(a: A) => `hello!`

// call examples
const c0 = func(undefined) // error
const c1 = func(null) // error
const c2 = func(() => undefined) // error
const c3 = func(10) // error
const c4 = func(`hi`) // error
const c5 = func({}) // error
const c6 = func(foo) // error <-- Restricted now
const c7 = func(foo_SubType) // ok  <-- Still allowed
const c8 = func(foo_DiffSubType) // error <-- NO MORE ALLOWED !

Xem trong sân chơi TS


PHẦN KẾT LUẬN

Chức năng bên dưới:

const func = <A extends Foo>(a: A = foo_SubType) => `hello!` //error!

Đưa ra thông báo lỗi này:

Type 'SubType' is not assignable to type 'A'.
  'SubType' is assignable to the constraint of type 'A', but 'A'
could be instantiated with a different subtype of constraint 
'Foo'.ts(2322)

Bởi vì Typecript suy ra Atừ lời gọi hàm, nhưng không có hạn chế trong ngôn ngữ hạn chế bạn gọi hàm với các kiểu con khác nhau của 'Foo'. Ví dụ: tất cả lệnh gọi của hàm dưới đây được coi là hợp lệ:

const c0 = func(foo)  // ok! type 'Foo' will be infered and assigned to 'A'
const c1 = func(foo_SubType) // ok! type 'SubType' will be infered
const c2 = func(foo_DiffSubType) // ok! type 'DiffSubType' will be infered

Do đó, việc gán một loại cụ thể cho một loại chung chung Type Parameterlà không chính xác vì trong TS, luônType Parameter có thể được khởi tạo thành một số kiểu phụ khác nhau tùy ý:

Giải pháp:

Đừng bao giờ gán một kiểu cụ thể cho một tham số kiểu chung chung, hãy coi nó là read-only! Thay vào đó, hãy làm điều này:

const func = <A extends Foo>(a: A) => `hello!` //ok!

Xem trong Sân chơi TS


1
Có phải "<A expand Foo> (a: A) => hello!" không tương đương với "(a: Foo) => hello!" không?
— Trevor Karjanis

@TrevorKarjanis chúng hơi khác nhau, ví dụ: <A extends Foo>(a: A = foo) => 'hello!'tạo ra lỗi biên dịch trong khi (a: Foo = foo) => 'hello' mã hợp lệ. Xem tại đây.
— Flavio Vilante

1
@Max Cảm ơn bạn. bạn hỏi: Why? SubFoo and DiffSubType both implement same prop1 and both add one additional prop, shouldn't they be both be just subtypes of Foo?Họ chỉ là kiểu phụ của Foo, bạn không sai. Câu hỏi đặt ra là: Vì chúng LÀ kiểu con, chúng bằng nhau hay khác kiểu con? Trả lời: Khác nhau vì chúng thêm các thuộc tính / phương thức khác nhau trong mối quan hệ với nhau. Xem ví dụ này tại đây (có thể bạn đã quen với từ khóa giao diện hơn).
— Flavio Vilante

1
@ ford04 cảm ơn bạn. Tôi đã cập nhật câu trả lời để phản ánh nhận xét của bạn.
— Flavio Vilante

1
@MrWolfZ Trong biểu thức const f = <T extends X>(a: T) => //..., thuật ngữ extendsthể hiện yêu cầu TPHẢI tuân theo bất kỳ trong hai truy vấn lại sau: hoặc (1) Tbằng X, hoặc; (2) Tbằng với bất kỳ kiểu con tùy ý nào củaX . Người erroneusly cho rằng thuật ngữ extendschỉ thể hiện (1) và không thể hiện (2). Nhưng bởi vì (2) cũng có thể xảy ra, TS hạn chế bạn chỉ định một phiên bản cụ thể của loại Xđể nhập Tvà mong bạn chọn một ứng dụng khác nếu bạn muốn duy trì kiểu an toàn. (Vì vậy, trong trường hợp của bạn, đó không phải là một giới hạn đối với Pick, mà đó là một giới hạn đối với Tchính nó).
— Flavio Vilante

41

Lỗi đó đang cảnh báo bạn rằng Pkhông thể chỉ định Loại Chung của bạn {}, vì Loại Chung Pcó thể là một loại được xác định rõ hơn hoặc bị hạn chế cho một loại cụ thể có thể xung đột với giá trị mặc định.

Điều đó có nghĩa là giá trị {}không thể đáp ứng tất cả các Loại có thể được sử dụng bởi Loại Chung P.

Hãy tạo một ví dụ khác chỉ với các boolean sẽ dễ hiểu hơn:

interface OnlyBoolIdentityInterface<T> {
  (arg: T): T;
}

function onlyBoolGeneric<T extends boolean>(arg: T = false): T {
  return arg;
}

nếu bạn xác định một Loại cụ thể hơn boolean, ví dụ:

type TrueType = true;

và nếu bạn chuyên biệt hóa hàm OnlyBoolIdentityInterfaceđể chỉ hỗ trợ các giá trị true như thế này:

const onlyTrueIdentity: OnlyBoolIdentityInterface<TrueType> = onlyBoolGeneric;

ngay cả khi TrueType tôn trọng ràng buộc được đặt bởi T extends booleangiá trị mặc định arg: T = falsekhông phải là a TrueType.

Đây là tình huống mà lỗi đang cố gắng truyền đạt cho bạn.

Vì vậy, làm thế nào bạn có thể sửa chữa loại lỗi này?

  1. Hoặc bạn loại bỏ giá trị mặc định
  2. Hoặc T cần mở rộng loại chuyên biệt của tham số mặc định mà trong ví dụ của tôi là sai
  3. Hoặc T có thể can thiệp trực tiếp vào các tham số nhận các tham số mặc định

Để biết thêm ngữ cảnh về thông báo lỗi này, hãy xem sự cố đã đề xuất thông báo lỗi này https://github.com/Microsoft/TypeScript/issues/29049 .


Tôi đã thay thế P extends object = {}bằng P extends object = anyin type Stylesvà điều này giải quyết được vấn đề của tôi. Thx
— teux

1
Tôi không hiểu một trong hai câu trả lời. Tôi nghĩ rằng tôi có cơ chế làm cho nó không thể, nhưng tôi không hiểu tại sao đây lại là một cảnh báo có chủ đích. Sử dụng fntừ câu trả lời của bạn, cả hai fn(true)và fn(false)đều đúng, phải không? Không phải = falsephần trong định nghĩa hàm cho nó một giá trị mặc định, vì vậy fn()sẽ tương đương với fn(false)? Tại sao có objthể trueảnh hưởng đến ý định của tôi rằng tham số mặc định sẽ là false?
— ShioT

2
@ShioT khi bạn tạo một hàm Chung, bạn cho phép người nào đó sử dụng hàm tạo phiên bản mới của nó chuyên biệt hơn. Vì vậy, nếu tạo một phiên bản mới hơn chỉ chấp nhận các giá trị true, giá trị mặc định của bạn sẽ phá vỡ phiên bản chuyên biệt có thể có đó, có thể mã ví dụ khác mà tôi đã tạo này sẽ giúp ích
— Fetz

@Fetz Điều này có ý nghĩa ngay bây giờ.
— ShioT

Đọc chuỗi lần thứ ba, những gì tôi đã tìm ra cho đến nay: Để khắc phục điều này, bạn có thể cần đơn giản hóa mã hoặc loại bỏ các tham số mặc định. Đồng ý rằng đây có lẽ là một thông báo lỗi tốt hơn.
— Thom

0

Giải thích ngắn hơn một chút.

Ví dụ gây ra lỗi:

type ObjectWithPropType<T> = {prop: T};

// Mind return type - T
const createCustomObject = <T extends ObjectWithPropType<any>>(prop: any): T => ({ prop });

type CustomObj = ObjectWithProp<string> & { id: string };

const customObj = createCustomObj<CustomObj>('value'); // Invalid
// function will only ever return {prop: T} type.

Vấn đề ở đây là đối tượng trả về sẽ chỉ khớp với thuộc tính propchứ không phải bất kỳ thuộc tính nào khác. Việc mở rộng ObjectWithPropTypesẽ tạo ra cảm giác sai về kiểu ràng buộc. Ví dụ này hoàn toàn là một cách tiếp cận sai lầm, nó được sử dụng chỉ để minh họa để hiển thị xung đột thực tế trong các thuộc tính đối tượng.

Cách hạn chế kiểu con trong hàm tạo:

type StringPropObject = ObjectWithPropType<string>

const createCustomObject = <T>(prop: T extends ObjectWithPropType<infer U> ? U : T): ObjectWithPropType<T> => ({ prop });

const stringObj = createCustomObject<StringPropObject>('test');

Trong trường hợp này, hàm yêu cầu đối số là một chuỗi. Đối tượng chỉ có propthuộc tính và chức năng trả về hình dạng yêu cầu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.