使用TypeScript在React组件中的默认属性值


151

我不知道如何使用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是正确的。您可以发布该代码吗?
亚伦·比尔

Answers:


326

带类组件的默认道具

使用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它提供了默认值。可以将其视为类似于如何将函数参数标记为可选,或将其标记为默认值,但不能同时标记为两个,但这两者都意味着调用不需要指定value。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您仍然可以使用它,并且它在运行时可与React一起使用100%,但是由于TypeScript仅在检查JSX属性时才考虑使用props,因此您必须使用标记默认的props ?。例:

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>
        );
    }
}

注意:

  • 进行注释是一个好主意defaultPropsPartial<>以便它对您的道具进行类型检查,但是您不必为每个必需的属性提供默认值,这是没有意义的,因为必需的属性永远不需要默认值。
  • 使用will strictNullChecks的值时,this.props.foopossibly undefined需要一个非null的断言(即this.props.foo!)或类型保护(即if (this.props.foo) ...)来删除undefined。这很烦人,因为默认的prop值意味着它实际上永远不会被定义,但是TS不能理解这一流程。这是TS 3.0添加对的显式支持的主要原因之一defaultProps

在TypeScript 2.1之前

这样做的原理相同,但是您没有Partial类型,因此只需忽略Partial<>并为所有必需的prop提供默认值(即使永远不会使用这些默认值),也可以完全忽略显式类型注释。

具有功能组件的默认道具

您也可以defaultProps在函数组件上使用,但是必须在FunctionComponentStatelessComponent@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你的意思是默认道具?是的,它可以工作,但是语法不同……当我回到计算机时,我将在示例中添加一个示例……
Aaron Beall

1
@AdrianMoisa使用s函数组件示例进行了更新
Aaron Beall

1
@Jared是的,所有内容(编译器和React运行时)都必须为public static defaultPropsstatic defaultPropspublic默认)。它可能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/react16.4.6。它适用于16.4.11


好答案!一个人该怎么处理:export interface Props { name?: string;}name是一个可选的道具?我不断收到TS2532 Object is possibly 'undefined'
Fydo

@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>
    )
  }
}

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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.