ค่าคุณสมบัติเริ่มต้นในส่วนประกอบ React ใช้ TypeScript


153

ฉันไม่สามารถหาวิธีตั้งค่าคุณสมบัติเริ่มต้นสำหรับส่วนประกอบของฉันโดยใช้ Typescript

นี่คือซอร์สโค้ด:

class PageState
{
}

export class PageProps
{
    foo: string = "bar";
}

export class PageComponent extends React.Component<PageProps, PageState>
{
    public render(): JSX.Element
    {
        return (
            <span>Hello, world</span>
        );
    }
}

และเมื่อฉันพยายามใช้องค์ประกอบเช่นนี้

ReactDOM.render(<PageComponent />, document.getElementById("page"));

ฉันได้รับข้อผิดพลาดว่าทรัพย์สินfooหายไป ฉันต้องการใช้ค่าเริ่มต้น ฉันยังพยายามใช้static defaultProps = ...ภายในองค์ประกอบ แต่ก็ไม่มีผลตามที่ฉันสงสัย

src/typescript/main.tsx(8,17): error TS2324: Property 'foo' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes<PageComponent> & PageProps & { children?: ReactEle...'.

ฉันจะใช้ค่าคุณสมบัติเริ่มต้นได้อย่างไร ส่วนประกอบ JS หลายอย่างที่ บริษัท ของฉันใช้พึ่งพานั้นและไม่ได้ใช้มันไม่ใช่ตัวเลือก


static defaultPropsถูกต้อง. คุณช่วยโพสต์โค้ดนั้นได้ไหม
Aaron Beall

คำตอบ:


327

อุปกรณ์ประกอบฉากเริ่มต้นที่มีองค์ประกอบของคลาส

การใช้static defaultPropsถูกต้อง คุณควรใช้อินเตอร์เฟสไม่ใช่คลาสสำหรับอุปกรณ์ประกอบฉากและสถานะ

อัปเดต 2018/12/1 : TypeScript ได้ปรับปรุงการตรวจสอบประเภทที่เกี่ยวข้องกับdefaultPropsช่วงเวลาหนึ่ง อ่านเพื่อการใช้งานล่าสุดและยิ่งใหญ่ที่สุดจนถึงประเพณีและปัญหาที่เก่ากว่า

สำหรับ TypeScript 3.0 ขึ้นไป

TypeScript เพิ่มการสนับสนุนเป็นพิเศษสำหรับdefaultPropsการตรวจสอบประเภทงานตามที่คุณต้องการ ตัวอย่าง:

interface PageProps {
  foo: string;
  bar: string;
}

export class PageComponent extends React.Component<PageProps, {}> {
    public static defaultProps = {
        foo: "default"
    };

    public render(): JSX.Element {
        return (
            <span>Hello, { this.props.foo.toUpperCase() }</span>
        );
    }
}

ซึ่งสามารถเรนเดอร์และคอมไพล์ได้โดยไม่ต้องผ่านแอfooททริบิวต์:

<PageComponent bar={ "hello" } />

โปรดทราบว่า:

  • fooจะไม่ได้ทำเครื่องหมายตัวเลือก (เช่นfoo?: string) แม้ว่ามันจะไม่จำเป็นต้องเป็นแอตทริบิวต์ JSX การทำเครื่องหมายเป็นตัวเลือกอาจหมายถึงว่าเป็นไปได้undefinedแต่ในความเป็นจริงมันไม่เคยเป็นundefinedเพราะdefaultPropsให้ค่าเริ่มต้น คิดว่ามันคล้ายกับวิธีการที่คุณสามารถทำเครื่องหมายพารามิเตอร์ตัวฟังก์ชั่นหรือค่าเริ่มต้น แต่ไม่ได้ทั้งสองทั้งยังหมายถึงการเรียกร้องไม่จำเป็นต้องระบุค่า TypeScript 3.0+ ปฏิบัติdefaultPropsในลักษณะเดียวกันซึ่งยอดเยี่ยมมากสำหรับผู้ใช้ React!
  • defaultPropsไม่มีประเภทคำอธิบายประกอบอย่างชัดเจน ประเภทของมันถูกอนุมานและใช้งานโดยคอมไพเลอร์เพื่อกำหนดแอตทริบิวต์ JSX ที่จำเป็น คุณสามารถใช้defaultProps: Pick<PageProps, "foo">เพื่อให้แน่ใจว่าการแข่งขันย่อยของdefaultProps PagePropsเพิ่มเติมเกี่ยวกับคำเตือนนี้จะอธิบายที่นี่
  • ต้องใช้@types/reactเวอร์ชัน16.4.11เพื่อให้ทำงานได้อย่างถูกต้อง

สำหรับ TypeScript 2.1 จนถึง 3.0

ก่อน typescript 3.0 สนับสนุนคอมไพเลอร์นำมาใช้สำหรับdefaultPropsคุณยังสามารถใช้ประโยชน์จากมันและมันทำงานได้ 100% กับการตอบสนองที่รันไทม์ แต่เนื่องจาก typescript พิจารณาเฉพาะอุปกรณ์ประกอบฉากเมื่อตรวจหา JSX ?แอตทริบิวต์ที่คุณจะต้องประกอบฉากเครื่องหมายที่มีค่าเริ่มต้นเป็นตัวเลือกด้วย ตัวอย่าง:

interface PageProps {
    foo?: string;
    bar: number;
}

export class PageComponent extends React.Component<PageProps, {}> {
    public static defaultProps: Partial<PageProps> = {
        foo: "default"
    };

    public render(): JSX.Element {
        return (
            <span>Hello, world</span>
        );
    }
}

โปรดทราบว่า:

  • มันเป็นความคิดที่ดีที่อธิบายdefaultPropsด้วยPartial<>เพื่อที่จะพิมพ์-ตรวจสอบกับอุปกรณ์ประกอบฉากของคุณ แต่คุณไม่จำเป็นต้องจัดหาสถานที่ให้บริการที่จำเป็นทุกท่านด้วยค่าเริ่มต้นซึ่งทำให้รู้สึกไม่เนื่องจากคุณสมบัติที่จำเป็นไม่ควรจะต้องเริ่มต้น
  • เมื่อใช้strictNullChecksค่าของthis.props.fooจะเป็นpossibly undefinedและต้องมีการยืนยันไม่ใช่ null (คือthis.props.foo!) หรือประเภทยาม (คือif (this.props.foo) ...) undefinedเพื่อลบ สิ่งนี้น่ารำคาญเนื่องจากค่า prop เริ่มต้นหมายความว่ามันจะไม่ถูกกำหนด แต่ TS ไม่เข้าใจโฟลว์นี้ นั่นเป็นหนึ่งในเหตุผลหลักที่ TS สนับสนุนอย่างชัดเจน 3.0 defaultPropsเพิ่มสำหรับ

ก่อน TypeScript 2.1

สิ่งนี้ใช้งานได้เหมือนกัน แต่คุณไม่มีPartialประเภทดังนั้นเพียงแค่ละเว้นPartial<>และระบุค่าเริ่มต้นสำหรับอุปกรณ์ประกอบฉากที่จำเป็นทั้งหมด (แม้ว่าจะไม่ใช้ค่าเริ่มต้นเหล่านั้น) หรือละเว้นคำอธิบายประกอบประเภทที่ชัดเจน

อุปกรณ์ประกอบฉากเริ่มต้นที่มีส่วนประกอบที่ใช้งานได้

คุณสามารถใช้defaultPropsกับองค์ประกอบของฟังก์ชั่นเช่นกัน แต่คุณต้องพิมพ์ฟังก์ชั่นของคุณไปFunctionComponent( StatelessComponentใน@types/reactก่อนรุ่น16.7.2) อินเตอร์เฟซเพื่อให้ typescript รู้เกี่ยวกับdefaultPropsฟังก์ชั่นนี้

interface PageProps {
  foo?: string;
  bar: number;
}

const PageComponent: FunctionComponent<PageProps> = (props) => {
  return (
    <span>Hello, {props.foo}, {props.bar}</span>
  );
};

PageComponent.defaultProps = {
  foo: "default"
};

โปรดทราบว่าคุณไม่จำเป็นต้องใช้Partial<PageProps>ที่ใดก็ได้เนื่องจากFunctionComponent.defaultPropsมีการระบุไว้เป็นบางส่วนใน TS 2.1+ แล้ว

อีกทางเลือกที่ดี (นี่คือสิ่งที่ฉันใช้) คือการจัดโครงสร้างpropsพารามิเตอร์ของคุณและกำหนดค่าเริ่มต้นโดยตรง:

const PageComponent: FunctionComponent<PageProps> = ({foo = "default", bar}) => {
  return (
    <span>Hello, {foo}, {bar}</span>
  );
};

ถ้าอย่างนั้นคุณก็ไม่ต้องการdefaultPropsอะไรเลย! โปรดทราบว่าถ้าคุณไม่ให้defaultPropsในส่วนฟังก์ชั่นมันจะมีความสำคัญมากกว่าค่าพารามิเตอร์เริ่มต้นเพราะตอบสนองจะเสมออย่างชัดเจนผ่านdefaultPropsค่า (ดังนั้นพารามิเตอร์จะไม่ได้กำหนดจึงพารามิเตอร์เริ่มต้นจะไม่เคยใช้.) ดังนั้นคุณต้องการใช้ อย่างใดอย่างหนึ่งไม่ใช่ทั้งสองอย่าง


2
ข้อผิดพลาดดูเหมือนคุณกำลังใช้งาน<PageComponent>อยู่โดยไม่ผ่านfooเสา คุณสามารถทำให้มันเป็นตัวเลือกใช้foo?: stringในส่วนต่อประสานของคุณ
Aaron Beall

1
@Aaron แต่ tsc จะโยนข้อผิดพลาดในการคอมไพล์แล้วเนื่องจาก defaultProps ไม่ได้ใช้อินเทอร์เฟซ PageProps คุณต้องทำให้คุณสมบัติของอินเตอร์เฟสทั้งหมดเป็นตัวเลือก (ไม่ดี) หรือระบุค่าเริ่มต้นเช่นกันสำหรับฟิลด์ที่จำเป็นทั้งหมด (สำเร็จรูปที่ไม่จำเป็น) หรือหลีกเลี่ยงการระบุประเภทบน defaultProps
pamelus

1
@adrianmoisa คุณหมายถึงอุปกรณ์เริ่มต้นหรือไม่ ใช่มันใช้งานได้ แต่ซินแท็กซ์ต่างกัน ... ฉันจะเพิ่มตัวอย่างให้กับคำตอบเมื่อฉันกลับมาที่คอมพิวเตอร์ของฉัน ...
Aaron Beall

1
@AdrianMoisa อัปเดตด้วยตัวอย่างองค์ประกอบของฟังก์ชัน
Aaron Beall

1
@Jared ใช่มันจะต้องเป็นpublic static defaultPropsหรือstatic defaultProps( publicเป็นค่าเริ่มต้น) สำหรับทุกอย่าง (คอมไพเลอร์และรันไทม์ React) เพื่อให้ทำงานได้อย่างถูกต้อง มันอาจทำงานที่ runtime private static defaultPropsเพียงเพราะprivateและpublicไม่มีอยู่ที่ runtime แต่คอมไพเลอร์ทำงานไม่ถูกต้อง
Aaron Beall

18

ด้วย Typescript 2.1+ ให้ใช้Partial <T>แทนการทำให้คุณสมบัติส่วนต่อประสานของคุณเป็นตัวเลือก

export interface Props {
    obj: Model,
    a: boolean
    b: boolean
}

public static defaultProps: Partial<Props> = {
    a: true
};

6

ด้วย typescript 3.0 มีวิธีแก้ไขปัญหาใหม่นี้:

export interface Props {
    name: string;
}

export class Greet extends React.Component<Props> {
    render() {
        const { name } = this.props;
        return <div>Hello ${name.toUpperCase()}!</div>;
    }
    static defaultProps = { name: "world"};
}

// Type-checks! No type assertions needed!
let el = <Greet />

โปรดทราบว่าสำหรับการทำงานคุณต้องมีรุ่นใหม่กว่า@types/react จะทำงานร่วมกับ16.4.616.4.11


คำตอบที่ดี! วิธีการหนึ่งที่อาจจัดการ: export interface Props { name?: string;}ที่ชื่อเป็นเสาตัวเลือก ? ฉันได้รับTS2532 Object is possibly 'undefined'
Fydo

@Fydo ฉันไม่จำเป็นต้องมีค่าเริ่มต้นสำหรับอุปกรณ์เสริมเนื่องจากundefinedเป็นค่าเริ่มต้นอัตโนมัติสำหรับอุปกรณ์ประกอบฉากเหล่านั้น คุณต้องการที่จะผ่านundefinedเป็นค่าที่ชัดเจนในบางครั้ง แต่มีค่าเริ่มต้นอื่นได้หรือไม่ คุณเคยลองมาแล้วexport interface Props {name: string | undefined;}หรือยัง? ยังไม่ได้ลองแค่ความคิด
CorayThan

เพิ่มเป็นเหมือนการเพิ่ม? |undefinedฉันต้องการที่จะเลือกผ่านเสาและให้defaultPropsจัดการกับกรณีนั้น ดูเหมือนว่าจะเป็นไปไม่ได้ใน TS3 ฉันเพิ่งจะใช้หวั่นname!ไวยากรณ์ตั้งแต่ฉันรู้ว่ามันไม่undefinedเมื่อdefaultPropsมีการตั้งค่า ขอบคุณทุกคน!
Fydo

1
โหวตขึ้นเพราะนี่คือคำตอบที่ถูกตอนนี้! อัปเดตคำตอบที่ฉันยอมรับ (ซึ่งเริ่มเป็นหนังสือประวัติศาสตร์) ด้วยคุณสมบัติใหม่นี้และคำอธิบายเพิ่มเติมอีกเล็กน้อย :)
Aaron Beall

5

สำหรับผู้ที่มีอุปกรณ์ประกอบฉากทางเลือกที่ต้องการค่าเริ่มต้น เครดิตที่นี่ :)

interface Props {
  firstName: string;
  lastName?: string;
}

interface DefaultProps {
  lastName: string;
}

type PropsWithDefaults = Props & DefaultProps;

export class User extends React.Component<Props> {
  public static defaultProps: DefaultProps = {
    lastName: 'None',
  }

  public render () {
    const { firstName, lastName } = this.props as PropsWithDefaults;

    return (
      <div>{firstName} {lastName}</div>
    )
  }
}

3

จากความคิดเห็นโดย @pamelus ตามคำตอบที่ยอมรับ:

คุณต้องทำให้คุณสมบัติของอินเตอร์เฟสทั้งหมดเป็นตัวเลือก (ไม่ดี) หรือระบุค่าเริ่มต้นเช่นกันสำหรับฟิลด์ที่จำเป็นทั้งหมด (สำเร็จรูปที่ไม่จำเป็น) หรือหลีกเลี่ยงการระบุประเภทบน defaultProps

ที่จริงคุณสามารถใช้ typescript ของมรดกอินเตอร์เฟซ โค้ดผลลัพธ์เป็นเพียง verbose มากกว่าเล็กน้อย

interface OptionalGoogleAdsProps {
    format?: string;
    className?: string;
    style?: any;
    scriptSrc?: string
}

interface GoogleAdsProps extends OptionalGoogleAdsProps {
    client: string;
    slot: string;
}


/**
 * Inspired by https://github.com/wonism/react-google-ads/blob/master/src/google-ads.js
 */
export default class GoogleAds extends React.Component<GoogleAdsProps, void> {
    public static defaultProps: OptionalGoogleAdsProps = {
        format: "auto",
        style: { display: 'block' },
        scriptSrc: "//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"
    };

0

สำหรับองค์ประกอบที่ใช้งานได้ฉันอยากจะเก็บpropsอาร์กิวเมนต์เอาไว้ดังนั้นนี่คือทางออกของฉัน:

interface Props {
  foo: string;
  bar?: number; 
}

// IMPORTANT!, defaultProps is of type {bar: number} rather than Partial<Props>!
const defaultProps = {
  bar: 1
}


// externalProps is of type Props
const FooComponent = exposedProps => {
  // props works like type Required<Props> now!
  const props = Object.assign(defaultProps, exposedProps);

  return ...
}

FooComponent.defaultProps = defaultProps;

0

องค์ประกอบการทำงาน

ที่จริงแล้วสำหรับองค์ประกอบที่ใช้งานได้แนวปฏิบัติที่ดีที่สุดก็เหมือนด้านล่างฉันสร้างตัวอย่างส่วนประกอบสปินเนอร์:

import React from 'react';
import { ActivityIndicator } from 'react-native';
import { colors } from 'helpers/theme';
import type { FC } from 'types';

interface SpinnerProps {
  color?: string;
  size?: 'small' | 'large' | 1 | 0;
  animating?: boolean;
  hidesWhenStopped?: boolean;
}

const Spinner: FC<SpinnerProps> = ({
  color,
  size,
  animating,
  hidesWhenStopped,
}) => (
  <ActivityIndicator
    color={color}
    size={size}
    animating={animating}
    hidesWhenStopped={hidesWhenStopped}
  />
);

Spinner.defaultProps = {
  animating: true,
  color: colors.primary,
  hidesWhenStopped: true,
  size: 'small',
};

export default Spinner;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.