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

İlgili makaleler