Значення властивості за замовчуванням у компоненті 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правильно. Чи можете ви опублікувати цей код?
Аарон Білл

Відповіді:


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" } />

Зауважте, що:

Для TypeScript 2.1 до 3.0

Перед тим, як TypeScript 3.0 підтримував компілятор, defaultPropsви все ще могли використовувати його, і він працював на 100% з React під час виконання, але оскільки 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.foowill буде possibly undefinedі вимагає ненульового твердження (тобто this.props.foo!) або type-guard (тобто if (this.props.foo) ...) для видалення undefined. Це дратує, оскільки значення опорних значень за замовчуванням означає, що воно насправді ніколи не буде визначеним, але TS не зрозумів цього потоку. Це одна з головних причин TS 3.0 додала явну підтримку defaultProps.

Перед TypeScript 2.1

Це працює так само, але у вас немає Partialтипів, тому просто опустіть Partial<>або введіть значення за замовчуванням для всіх необхідних реквізитів (навіть якщо ці типові параметри ніколи не будуть використані) або повністю опустіть явну анотацію типу.

Реквізити за замовчуванням з функціональними компонентами

Ви також можете використовувати defaultPropsдля функціональних компонентів, але вам потрібно ввести свою функцію в інтерфейс FunctionComponent( до версії ), щоб TypeScript знав про функцію:StatelessComponent@types/react16.7.2defaultProps

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на функції компонента буде мати пріоритет над значеннями параметрів по замовчуванню, тому що React буде завжди явно передати defaultPropsзначення (тому параметри ніколи не визначено, що параметр за замовчуванням ніколи не використовується.) Таким чином , ви будете використовувати той чи інший, не обидва.


2
Помилка здається, що ви використовуєте <PageComponent>десь, не передаючи fooопору. Ви можете зробити його необов’язковим, використовуючи foo?: stringу своєму інтерфейсі.
Аарон Білл

1
@Aaron Але tsc викине помилку компіляції, оскільки defaultProps не реалізує інтерфейс PageProps. Вам або потрібно зробити всі властивості інтерфейсу необов’язковими (поганими) або вказати значення за замовчуванням також для всіх необхідних полів (непотрібна котловарна панель) або уникати вказування типу у defaultProps.
pamelus

1
@adrianmoisa Ви маєте на увазі реквізити за замовчуванням? Так, це працює, але синтаксис інший ... Я додаю приклад до своєї відповіді, коли я знову за своїм комп’ютером ...
Аарон Білл

1
@AdrianMoisa Оновлено на прикладі функціонального компонента s
Aaron Beall

1
@Jred Так, для правильної роботи все (компілятор та React час виконання) має бути public static defaultPropsабо static defaultProps( publicза замовчуванням). Він може працювати в режимі виконання private static defaultPropsтільки тому , що privateі publicне існує під час виконання, але компілятор не працюватиме правильно.
Аарон Біл

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.6. Це працює з 16.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встановлені. Спасибі все одно!
Фідо

1
Оновлено, тому що це правильна відповідь зараз! Також оновив свою прийняту відповідь (яка починає перетворюватися на книгу історії) з цією новою функцією та ще трохи пояснення. :)
Аарон Білл

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 . Отриманий код є лише дещо докладнішим.

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

Функціональний компонент

Насправді, для функціонального компонента найкраща практика, як показано нижче, я створюю зразок компонента Spinner:

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.