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

228

我不知道如何使用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
12个回答

421

使用类组件的默认属性

使用static defaultProps是正确的。您还应该使用接口而不是类来定义props和state。

更新于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">来确保defaultPropsPageProps的子集匹配。更多关于此警告的信息在这里解释
  • 这需要@types/react版本16.4.11才能正常工作。

对于 TypeScript 2.1 到 3.0 版本

在 TypeScript 3.0 实现编译器支持 defaultProps 之前,您仍然可以使用它,并且在运行时与 React 完全兼容,但由于 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>
        );
    }
}

请注意:
- 使用Partial<>defaultProps进行注释是个好主意,这样它就可以针对您的props进行类型检查,但您不必为每个必需属性提供默认值,因为需要默认值的必需属性没有任何意义。 - 当使用strictNullChecks时,this.props.foo的值将为possibly undefined,需要使用非空断言(即this.props.foo!)或类型保护(即if (this.props.foo) ...)来消除undefined。这很让人恼火,因为默认prop值意味着它实际上永远不会是undefined,但TS并没有理解这个流程。这是TS 3.0明确支持defaultProps的主要原因之一。 在TypeScript 2.1之前:
- 这个工作方式相同,但您没有Partial类型,因此只需省略Partial<>并为所有必需的props提供默认值(即使永远不会使用那些默认值),或者完全省略显式类型注释。

使用函数式组件的默认属性

你也可以在函数组件中使用defaultProps,但是你需要将函数类型定义为FunctionComponent(在@types/react版本16.7.2之前为StatelessComponent),这样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>,因为在TS 2.1+中,FunctionComponent.defaultProps已经被指定为部分。
另一个不错的选择(这是我使用的)是解构您的props参数并直接分配默认值:
const PageComponent: FunctionComponent<PageProps> = ({foo = "default", bar}) => {
  return (
    <span>Hello, {foo}, {bar}</span>
  );
};

那么您根本不需要defaultProps!请注意,如果您在函数组件上提供了defaultProps,它将优先于默认参数值,因为React始终会明确传递defaultProps值(因此参数永远不会是未定义的,因此不会使用默认参数)。所以您只需要使用其中一个,而不是两个。


2
错误听起来像是你在某个地方使用了<PageComponent>,但没有传递foo属性。你可以在接口中使用foo?: string使其变为可选项。 - Aaron Beall
1
@Aaron 但是 tsc 将会抛出编译错误,因为 defaultProps 没有实现 PageProps 接口。你可以将所有接口属性都变成可选的(不好),或者为所有必需字段指定默认值(不必要的样板代码),或者避免在 defaultProps 上指定类型。 - ptkoz
1
@adrianmoisa 你指的是默认属性吗? 是的,它可以工作,但语法不同... 当我回到我的电脑时,我会在我的答案中添加一个例子... - Aaron Beall
2
@AdrianMoisa 更新了一个函数组件示例。 - Aaron Beall
1
@Jared 是的,它需要是public static defaultProps或者static defaultPropspublic是默认值),这样编译器和React运行时才能正常工作。在运行时,使用private static defaultProps可能会起作用,因为privatepublic在运行时不存在,但编译器将无法正确工作。 - Aaron Beall
显示剩余16条评论

23

使用Typescript 2.1+时,可以使用Partial < T >来代替将接口属性设置为可选。

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

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

19

函数式组件

实际上,对于函数式组件,最佳实践如下所示,我创建了一个样例Spinner组件:

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

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

const Spinner = ({
  color = colors.primary,
  size = 'small',
  animating = true,
  hidesWhenStopped = true,
}: SpinnerProps): JSX.Element => (
  <ActivityIndicator
    color={color}
    size={size}
    animating={animating}
    hidesWhenStopped={hidesWhenStopped}
  />
);

export default Spinner;
如果您的组件具有 children,最好使用 React.FC,如下所示:
export interface TypographyProps {
  color?: string;
}

const Typography: React.FC<TypographyProps> = ({
  children,
  color,
}) => (
  <span style={{ color }}>
    {children}
  </span>
);

export default Typography;

12
您可以使用展开运算符将标准函数组件中的props重新分配。我喜欢这种方法的原因是您可以将具有默认值的可选props与必需的props混合使用。

你可以使用展开语法来重新赋值函数组件中的props。我喜欢这种方法的原因是你可以将必需的props和带有默认值的可选props混合使用。

interface MyProps {
   text: string;
   optionalText?: string;
}

const defaultProps = {
   optionalText = "foo";
}

const MyComponent = (props: MyProps) => {
   props = { ...defaultProps, ...props }
}

在我看来,这是最干净、最易读的解决方案。 - MikeyT
1
最好使用临时变量来合并,而不是覆盖参数“props”。 - jfunk
@jfunk 为什么??? - Christopher
3
将值赋给函数参数声明的变量可能会产生误导性和令人困惑的行为,因为修改函数参数也会改变参数对象。通常情况下,对函数参数的赋值是无意的,并且表明存在错误或程序员的错误。 - jfunk

10

使用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 />

请注意,要使此操作起作用,您需要比16.4.6更高版本的@types/react。它适用于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! 语法,因为我知道当设置了 defaultProps 时它永远不会是 undefined。无论如何,谢谢! - Fydo
2
因为这是正确的答案,所以我点了赞!同时,我更新了我的被接受的答案(它开始变成一本历史书了),加入了这个新功能和更多的解释。 :) - Aaron Beall

5

以下是来自@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"
    };

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;

定义 FooComponent.defaultProps 没有意义,因为它没有被使用。你需要 手动 将属性直接合并到 defaultProps 变量中... - vsync

2

使用 Typescript 4.4+ 的函数组件,可以带有 optionaldefault 属性:

export const LoadingSpinner = ({
  size = "lg",
  children,
}: {
  size?: "sm" | "base" | "lg";
  children?: any;
}) => {
console.log(size);
return <div>{children}</div>
};

使用方法如下:

 <LoadingSpinner size="sm"><p>hello</p></LoadingSpinner>
 <LoadingSpinner><p>hello</p></LoadingSpinner>


0

请检查我的解决方案:

interface Props {
  foo?: string | undefined;
  bar: string;
  other?: string;
}

export const Component = ({foo, bar, other = 'default text'}:Props) => {
    console.log(foo, bar, other);
    return(
        ...//some cool stuff your component should do
    )
}

<Component bar='obligatory text'/>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接