【问题标题】:Default property value in React component using TypeScript使用 TypeScript 的 React 组件中的默认属性值
【发布时间】:2016-09-13 21:50:19
【问题描述】:

我不知道如何使用 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 是正确的。你能发布那个代码吗?

标签: reactjs typescript tsx


【解决方案1】:

类组件的默认道具

使用static defaultProps 是正确的。您还应该为道具和状态使用接口,而不是类。

2018 年 12 月 1 日更新:随着时间的推移,TypeScript 改进了与 defaultProps 相关的类型检查。继续阅读以了解最新和最有用的用法,直至旧用法和问题。

对于 TypeScript 3.0 及更高版本

TypeScript 专门added support for 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 提供了一个默认值。想想它类似于you can mark a function parameter optional, or with a default value, but not both, yet both mean the call doesn't need to specify a value。 TypeScript 3.0+ 以类似的方式处理 defaultProps,这对 React 用户来说真的很酷!
  • defaultProps 没有显式类型注释。编译器推断并使用它的类型来确定需要哪些 JSX 属性。您可以使用defaultProps: Pick&lt;PageProps, "foo"&gt; 来确保defaultProps 匹配PageProps 的子集。有关此警告的更多信息是explained here
  • 这需要@types/react 版本16.4.11 才能正常工作。

对于 TypeScript 2.1 到 3.0

在 TypeScript 3.0 实现对 defaultProps 的编译器支持之前,你仍然可以使用它,并且它在运行时与 React 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>
        );
    }
}

注意:

  • Partial&lt;&gt; 注释 defaultProps 是个好主意,这样它就可以对你的 props 进行类型检查,但是你不必为每个必需的属性提供默认值,这没有任何意义,因为必需的属性应该永远不需要默认值。
  • 使用strictNullChecks 时,this.props.foo 的值将是possibly undefined,并且需要非空断言(即this.props.foo!)或类型保护(即if (this.props.foo) ...)来删除undefined。这很烦人,因为默认的 prop 值意味着它实际上永远不会未定义,但 TS 不理解这个流程。这是 TS 3.0 明确支持 defaultProps 的主要原因之一。

TypeScript 2.1 之前

这同样适用,但你没有 Partial 类型,所以只需省略 Partial&lt;&gt; 并为所有必需的道具提供默认值(即使这些默认值永远不会被使用)或完全省略显式类型注释.

Functional Components 的默认道具

你也可以在函数组件上使用defaultProps,但是你必须将你的函数输入到FunctionComponent@types/react 之前版本@types/react 中的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&lt;PageProps&gt;,因为 FunctionComponent.defaultProps 已在 TS 2.1+ 中指定为部分。

另一个不错的选择(这是我使用的)是解构您的 props 参数并直接分配默认值:

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

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

【讨论】:

  • 该错误听起来像是您在某处使用 &lt;PageComponent&gt; 而不通过 foo 属性。您可以在界面中使用foo?: string 将其设为可选。
  • @Aaron 但是 tsc 会抛出编译错误,因为 defaultProps 没有实现 PageProps 接口。您要么必须将所有接口属性设为可选(错误),要么为所有必填字段(不必要的样板)指定默认值,或者避免在 defaultProps 上指定类型。
  • @adrianmoisa 你的意思是默认道具?是的,它有效,但语法不同......当我回到我的电脑时,我会在我的答案中添加一个例子......
  • @AdrianMoisa 更新了s函数组件示例
  • @Jared 是的,它必须是 public static defaultPropsstatic defaultPropspublic 是默认值)才能让一切(编译器和 React 运行时)正常工作。它可能在运行时与private static defaultProps 一起工作,只是因为privatepublic 在运行时不存在,但编译器无法正常工作。
【解决方案2】:

对于 Typescript 2.1+,使用 Partial 而不是让你的界面属性可选。

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

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

【讨论】:

    【解决方案3】:

    对于 Typescript 3.0,有一个 new solution 来解决这个问题:

    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 是 optional 属性?我不断收到TS2532 Object is possibly 'undefined'
    • @Fydo 我不需要为可选道具设置默认值,因为undefined 是这些道具的一种自动默认值。您希望有时能够将undefined 作为显式值传递,但有另一个默认值?您是否尝试过export interface Props {name: string | undefined;}?没试过,只是一个想法。
    • 添加?与添加|undefined相同。我想选择传入道具,让defaultProps 处理这种情况。看起来这在 TS3 中是不可能的。我将只使用可怕的name! 语法,因为我知道在设置defaultProps 时它永远不会是undefined。无论如何,谢谢!
    • 赞成,因为这是现在的正确答案!还使用这个新功能更新了我接受的答案(它开始成为历史书),并提供更多解释。 :)
    【解决方案4】:

    对于那些需要默认值的可选道具。 Credit here :)

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

    【讨论】:

      【解决方案5】:

      对于功能组件,我宁愿保留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 没有意义,因为它没有被使用。您正在手动将 prop 直接与 defaultProps 变量合并...
      【解决方案6】:

      您可以使用扩展运算符重新分配具有标准功能组件的道具。我喜欢这种方法的一点是,您可以将必需的道具与具有默认值的可选道具混合使用。

      interface MyProps {
         text: string;
         optionalText?: string;
      }
      
      const defaultProps = {
         optionalText = "foo";
      }
      
      const MyComponent = (props: MyProps) => {
         props = { ...defaultProps, ...props }
      }
      

      【讨论】:

      • 在我看来,这是最干净、最容易阅读的解决方案。
      • 使用临时变量合并可能会更好,而不是覆盖参数props
      • @jfunk 为什么??????
      • "对声明为函数参数的变量赋值可能会产生误导并导致混淆行为,因为修改函数参数也会改变参数对象。通常,对函数参数的赋值是无意的,表示错误或程序员错误。” eslint.org/docs/rules/no-param-reassign
      【解决方案7】:

      来自@pamelus 对已接受答案的评论:

      您要么必须将所有接口属性设为可选(错误),要么 还为所有必填字段指定默认值(不必要的 样板)或避免在 defaultProps 上指定类型。

      其实你可以使用 Typescript 的interface inheritance。生成的代码只是有点冗长。

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

      【讨论】:

        【解决方案8】:

        功能组件

        实际上,对于功能组件,最佳实践如下所示,我创建了一个示例 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;
        

        【讨论】:

          【解决方案9】:

          钩子(带有打字稿)

          export interface ApprovalRejectModalProps{
           singleFileApprove:boolean;
          }
          
          ApproveRejectModal.defaultProps={
           singleFileApprove:false --> default value
          }
          
          export const ApproveRejectModal:React.FC<ApprovalRejectModalProps>=(props)=>{
          return (
                  <div>
                      ....
                  </div>
                 )
          }
          

          【讨论】:

            【解决方案10】:

            带有optionaldefault 功能组件的道具(Typescript 4.4+):

            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>
            
            

            【讨论】:

              猜你喜欢
              • 2019-06-30
              • 2018-04-12
              • 2013-05-30
              • 2020-01-28
              • 2020-05-27
              • 2022-11-15
              • 2020-10-30
              • 2018-12-13
              • 2018-07-26
              相关资源
              最近更新 更多