Значение свойства по умолчанию в компоненте React с использованием TypeScript

161

Я не могу понять, как установить значения свойств по умолчанию для моих компонентов с помощью 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правильно. Вы можете опубликовать этот код?
Аарон Билл

Ответы:

346

Свойства по умолчанию с компонентом класса

Использование 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!) или защиты типа (т.е. if (this.props.foo) ...) undefined. Это раздражает, поскольку значение свойства по умолчанию означает, что оно никогда не будет неопределенным, но 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на функции компонента будет иметь приоритет над значениями параметров по умолчанию, потому что React будет всегда явно передать defaultPropsзначения (поэтому параметры никогда не определено, что параметр по умолчанию никогда не используется.) Таким образом , вы будете использовать один или другой, но не оба.

Аарон Бил
источник
2
Ошибка звучит так, будто вы <PageComponent>где-то используете, не передав fooопору. Вы можете сделать это необязательным, используя foo?: stringв своем интерфейсе.
Аарон Билл
1
@Aaron Но тогда tsc выдаст ошибку компиляции, поскольку defaultProps не реализует интерфейс PageProps. Вы должны либо сделать все свойства интерфейса необязательными (плохие), либо указать значение по умолчанию также для всех обязательных полей (ненужный шаблон), либо не указывать тип в defaultProps.
pamelus
1
@adrianmoisa Вы имеете в виду реквизит по умолчанию? Да, это работает, но синтаксис другой ... Я добавлю пример к своему ответу, когда вернусь к своему компьютеру ...
Аарон Билл,
1
@AdrianMoisa Обновлено с помощью примера компонента функции s
Aaron Beall
1
@Jared Да, это должно быть public static defaultPropsили static defaultProps( publicпо умолчанию), чтобы все (компилятор и среда выполнения React) работало правильно. Он может работать во время выполнения private static defaultPropsтолько потому, что privateи publicне существует во время выполнения, но компилятор не будет работать правильно.
Aaron Beall
18

В Typescript 2.1+ используйте Partial <T> вместо того, чтобы делать свойства интерфейса необязательными.

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

public static defaultProps: Partial<Props> = {
    a: true
};
Ученик
источник
7

В 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.

CorayThan
источник
Отличный ответ! Как можно справиться: export interface Props { name?: string;}где имя - необязательная опора? Я продолжаю получатьTS2532 Object is possibly 'undefined'
Фидо
@Fydo Мне не нужно было иметь значение по умолчанию для необязательного реквизита, поскольку undefinedэто своего рода автоматическое значение по умолчанию для этих реквизитов. Вы хотите иметь возможность undefinedиногда передавать в качестве явного значения, но иметь другое значение по умолчанию? Вы пробовали export interface Props {name: string | undefined;}вместо этого? Не пробовал, просто идея.
CorayThan
Добавление ?аналогично добавлению |undefined. Я хочу, при желании, передать опору и позволить defaultPropsобработать этот случай. Похоже, что в TS3 это пока невозможно. Я просто использовать страшились name!синтаксис , так как я знаю , что никогда , undefinedкогда defaultPropsустановлены. В любом случае спасибо!
Fydo
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>
    )
  }
}
Морло Мбакоп
источник
4

Что касается функционального компонента, я бы предпочел сохранить 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;
xiechao06
источник
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"
    };
Харальд Мюльхофф
источник
2

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

Собственно, для функционального компонента лучшая практика такая, как показано ниже, я создаю образец компонента 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;
AmerllicA
источник