reactjs - Neden özellikle jenerikler ile yapılan bir kurulumu olan durumda isteğe bağlı alanda tür unknown olarak ayarlanıyor?
carbon-components-react üzerindeki DataTable ile ilgili bazı türleri düzeltmeye çalışıyordum, ama bir özelliği isteğe bağlı hale getirmekte takıldım. Bu sorunu göstermek için kendi demomu oluşturduğum noktaya geldim:
import React from "react";
interface Human {
name: string;
age: number;
address?: string;
}
interface ButtonProps<R extends Human> {
name: R["name"];
age: R["age"];
address?: R["address"];
onClick?: Function;
}
interface RenderProps<R extends Human> {
human: R;
getButtonProps(data?: ButtonProps<R>): ButtonProps<R>;
}
export interface DataTableProps<R extends Human> {
rows: R[];
render(props: RenderProps<R>): React.ReactNode;
}
class DataTable<R extends Human> extends React.Component<DataTableProps<R>> {
render() {
return this.props.rows.map((h) => {
const buttonProps: () => Human = () => ({ ...h }); // This is only to be able to see the type of buttonProps in IDE
return this.props.render({ human: h, getButtonProps: buttonProps });
});
}
}
interface TableButtonProps {
name: string;
address?: string;
}
const TableButton = (props: TableButtonProps) => (
<button>Button for {props.name}</button>
);
export const Demo = () => {
// This works
const man1: Human = {
name: "Anna",
age: 30,
};
// This seems to be ok since "address" is optional
// but this isn't working!
const man2 = {
name: "Tom",
age: 32,
};
// This works
const man3 = {
name: "John",
age: 40,
address: "404 Street Unknown",
};
return (
<DataTable
rows={[man2]}
render={({ getButtonProps }) => {
return <TableButton {...getButtonProps()} />;
}}
></DataTable>
);
};
TableButton'da aldığım hata:
Type '{ name: string; age: number; address?: unknown; onClick?: Function | undefined; }' is not assignable to type 'TableButtonProps'.
Types of property 'address' are incompatible.
Type 'unknown' is not assignable to type 'string | undefined'.
Type 'unknown' is not assignable to type 'string'. TS2322
address özelliğinin nedense unknown türü var. man1 veya man3 gibi bir sözdizimi kullandığımda her şey çalışıyor, bunu rows= kısmında değiştirebilirsiniz. Tür hâlâ Human'ı genişletecek şekilde ayarlı olmasına rağmen neden man2 çalışmıyor? TypeScript mi kafası karıştı?
Bunu, projeyi npx create-react-app mydemo --template=typescript kullanarak başlatarak test edebilirsiniz
// Düzenle:
interface ButtonProps<R extends Human> {
name: Human["name"];
age: Human["age"];
address?: Human["address"];
onClick?: Function;
}
Ayrıca bu sorunu düzeltiyor gibi görünüyor ve address türünü "koruyor".
Cevap #1
Muhtemelen sorunun address özelliğinde olduğunu fark etmişsinizdir.
Nedense unknown olarak çıkarılmış.
Genellikle unknown alıyorsunuz çünkü TS, genel parametreyi çıkaramıyor.
Örneğe bakın:
const foo = <T,>(arg: T): T => arg
// const foo: <unknown>(arg: unknown) => unknown
const result = foo() // unknown
Bu örneği düşünün:
type GetAddress<T extends Human> = T extends Human ? T['address'] : never
type Result = GetAddress<{ name: 'John', age: 42 }> // unknown
Teknik olarak, { name: 'John', age: 42 } Human tipine atanabilir çünkü address isteğe bağlıdır. Öte yandan, T['address'] undefined döndüremez çünkü bu Javascript değil - bu Typescript'in (Sparta) tipi sistemidir. Bu nedenle, TS address özelliğini elde edemez/çıkaramazsa, en güvenli seçenek bir unknown döndürmek olur.
Hadi senin örneğine geri dönelim.
Okunabilirlik adına, yerleşik React bileşenini özel Component sınıfı ile değiştirdim:
import React from "react";
interface Human {
name: string;
age: number;
address?: string;
}
interface ButtonProps<R extends Human> {
name: R["name"];
age: R["age"];
address?: R["address"];
onClick?: Function;
}
interface RenderProps<R extends Human> {
human: R;
getButtonProps(data?: ButtonProps<R>): ButtonProps<R>;
}
export interface DataTableProps<R extends Human, Address extends Human['address']> {
rows: R[];
render: (props: RenderProps<R>) => React.ReactNode;
}
class Component<Props>{
constructor(public props: Props) {
this.props = props
}
}
class Foo<T extends Human> extends Component<DataTableProps<T, T['address']>>{
render = () => {
return this.props.rows.map(
(row) =>
this
.props
.render(
{ human: row, getButtonProps: () => ({ ...row }) }
)
);
}
}
const human = { age: 42, name: 'John Doe' }
const jsx = new Foo({
rows: [human],
render: ({ getButtonProps }) => {
const x = getButtonProps().address
return <TableButton {...getButtonProps()} />;
}
})
Lütfen bu satıra bir göz atın Component<DataTableProps<T, T['address']>>
Size TS'nin bunu nasıl çıkaracağını göstermek için kasıtlı olarak T['address'] ekledim.
Gördüğünüz gibi - bilinmiyor. Yani, address nedeniyle Human'a atanabilir olmayan {age: number, name: string, address: unknown} nesnesi ile sonuçlandık.
Sorabilirsiniz:
Bekleyin, ama neden açık tip ile çalışıyor?
const human: Human = { age: 42, name: 'John Doe' }Bunun nedeni, açık bir tip belirtilmediğinde, R'nin {age: number, name: string} olarak çıkarılmasıdır; oysa açıkça Human tipi verildiğinde, {age:number, name: string, address?:string, onClick?:Function} olarak çıkarılmasıdır.
Bu nedenle, hatanızı düzeltmek istiyorsanız, sadece DataTable sınıfı için açık bir genel parametre sağlamanız gerekir.
Örneğe bakın:
<DataTable<Human> // <---------------- fix is here
rows={[man2]}
render={({ getButtonProps }) => {
return <TableButton {...getButtonProps()} />;
}}
></DataTable>
Bu arada, çok ilginç bir soru.
GÜNCELLEME Ayrıca bu yardımcıyı da kullanabilirsiniz:
/**
* Replace all unknown props with appropriate Human props
*/
type HandleProps<T extends Human> = T & Omit<Human, keyof T>
// ............
class DataTable<R extends Human> extends React.Component<DataTableProps<HandleProps<R>>> {
render() {
return this.props.rows.map((h) => {
const buttonProps: () => Human = () => ({ ...h });
return this.props.render({ human: h, getButtonProps: buttonProps });
});
}
}
Oyun alanı
Tüm unknown özelliklerini uygun Human özellikleri ile değiştirecektir
