ค้นหาเว็บไซต์

javascript - ประเภทของคอมโพเนนต์ Stateless แบบ Generic ใน React? หรือ การขยายอินเทอร์เฟซฟังก์ชันแบบ generic ใน TypeScript เพื่อให้มี generic เพิ่มเติม?


ปัญหา: ส่วนติดต่อของ Stateless Functional Component ถูกกำหนดเป็น

interface SFC<P = {}> {
    (props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null;
    propTypes?: ValidationMap<P>;
}

ประเภท prop ของคอมโพเนนต์ของฉันก็เป็นแบบทั่วไปดังนี้:

interface Prop<V>{
    num: V;
}

วิธีการกำหนดคอมโพเนนต์ของฉันอย่างถูกต้อง? ดังนี้:

const myCom: <T>SFC<Prop<T>> = <T>(props: Prop<T>)=> <div>test</div>

เกิดข้อผิดพลาดที่ ตัวอักษร 27 ว่า ไม่สามารถหาชื่อ 'T'

นี่คือ :Typescript Playground ของตัวอย่างที่ปรับปรุงแล้ว

ข้อค้นพบของฉัน:

1: Typescript 2.9.1 สนับสนุนคอมโพเนนต์เจเนอริกที่มีสถานะ: http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#generic-type-arguments-in-jsx-elements

class myCom<T> extends React.Component<Prop<T>, any> {
   render() {
      return <div>test</div>;
   }
}

2: การขยาย SFC เพื่อสร้างอินเทอร์เฟซใหม่ตามที่กล่าวไว้ในคำตอบต่อไปนี้จะทำให้ชนิดของ prop ของคอมโพเนนต์เป็น any: Typescript React ฟังก์ชัน stateless ที่มีพารามิเตอร์/ชนิดการคืนค่าทั่วไปซึ่งฉันไม่ต้องการ ฉันต้องการกำหนดชนิดที่เหมาะสมสำหรับ prop ของฉัน

คำตอบ #1

คุณไม่สามารถใช้เจนเนอริกแบบนี้ได้:

const myCom: <T>SFC<Prop<T>> = <T>(props: Prop<T>)=> <div>test</div>

ข้อกำหนดของ TypeScript กล่าวไว้ว่า:

โครงสร้างในรูปแบบ

< T > ( ... ) => { ... }

สามารถถูกวิเคราะห์เป็นนิพจน์ฟังก์ชันลูกศรพร้อมพารามิเตอร์ชนิด หรือการยืนยันชนิดที่นำไปใช้กับฟังก์ชันลูกศรที่ไม่มีพารามิเตอร์ชนิดได้

แหล่งที่มา; Microsoft/TypeScript spec.md

คำประกาศของคุณไม่ตรงกับรูปแบบที่กำหนดในข้อกำหนดของ TypeScript ดังนั้นมันจะใช้งานไม่ได้

อย่างไรก็ตามคุณสามารถไม่ใช้ส่วนต่อประสาน SFC และประกาศมันเองได้

interface Prop<V> {
    num: V;
}

// normal function
function Abc<T extends string | number>(props: Prop<T>): React.ReactElement<Prop<T>> {
    return <div />;
}

// const lambda function
const Abc: <T extends string | number>(p: Prop<T>) => React.ReactElement<Prop<T>> = (props) => {
   return <div />
};

export default function App() {
    return (
        <React.Fragment>
            <Abc<number> num={1} />
            <Abc<string> num="abc" />
            <Abc<string> num={1} /> // string expected but was number
        </React.Fragment>
    );
}

คำตอบ #2

มีรูปแบบในการลดปัญหานี้โดยการประกาศนามแฝงประเภทคอมโพเนนต์ทั่วไปภายนอกคอมโพเนนต์ แล้วจากนั้นเพียงแค่ยืนยันมันเมื่อคุณต้องการมัน

ไม่สวยเท่าไหร่ แต่ยังใช้ซ้ำได้และเข้มงวดอยู่

interface IMyComponentProps<T> {
  name: string
  type: T
}

// instead of inline with component assignment
type MyComponentI<T = any> = React.FC<IMyComponentProps<T>>

const MyComponent: MyComponentI = props => <p {...props}>Hello</p>

const TypedComponent = MyComponent as MyComponentI<number>

คำตอบ #3

รูปแบบโรงงาน:

import React, { SFC } from 'react';

export interface GridProps<T = unknown> {
  data: T[];
  renderItem: (props: { item: T }) => React.ReactChild;
}

export const GridFactory = <T extends any>(): SFC<GridProps<T>> => () => {
  return (
    <div>
      ...
    </div>
  );
};

const Grid = GridFactory<string>();

อัปเดต 08/03/2021

เพื่อหลีกเลี่ยงข้อผิดพลาดของกฎของ hook คุณต้องปรับแต่งไวยากรณ์แบบนี้:

import React, { FC } from 'react';

export interface GridProps<T = unknown> {
  data: T[]
  renderItem: (props: { item: T }) => React.ReactChild
}

export const GridFactory = <T extends any>() => {
  const Instance: FC<GridProps<T>> = (props) => {
    const [state, setState] = useState(props.data)

    return <div>...</div>
  }

  return Instance
}

const Grid = GridFactory<string>()

คำตอบ #4

รูปแบบ Factory ที่นำเสนอที่นี่โดย @chris นั้นยอดเยี่ยม แต่ฉันไม่สามารถใช้ React Hooks กับมันได้ ดังนั้นฉันจึงกำลังใช้แบบนี้

// Props
interface Props<T> {
  a: T;
}

// Component
export const MyComponent: <T>(p: PropsWithChildren<Props<T>>) => React.ReactElement = props => {
  return <div>Hello Typescript</div>;
};

ถ้าคุณไม่ต้องการเด็ก คุณสามารถลบส่วน PropsWithChildren ได้ การแยก Props และ hooks ก็ใช้งานได้เช่นกัน

export const MyComponent: <T>(p: Props<T>) => React.ReactElement = ({ a }) => {
  const [myState, setMyState] = useState(false);
  return <div>Hello Typescript</div>;
};

บทความที่เกี่ยวข้อง