【问题标题】:Typescript: Type 'string | undefined' is not assignable to type 'string'打字稿:键入'字符串| undefined' 不能分配给 type 'string'
【发布时间】:2019-06-27 00:40:38
【问题描述】:

当我将接口的任何属性设为可选时,在将其成员分配给其他变量时会出现如下错误:

    TS2322: Type 'string | undefined' is not assignable to type 'string'.
    Type 'undefined' is not assignable to type 'string'.
    interface Person {
      name?: string,
      age?: string,
      gender?: string,
      occupation?: string,
    }
    
    function getPerson() {
      let person = <Person>{name:"John"};
      return person;
    }

    let person: Person = getPerson();
    let name1: string = person.name; // <<< Error here 

如何解决此错误?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您现在可以使用non-null assertion operator,这正是您的用例。

    它告诉 TypeScript,即使某些东西看起来可能是 null,它也可以相信你它不是:

    let name1:string = person.name!; 
    //                            ^ note the exclamation mark here  
    

    【讨论】:

    • 这正是我想要的!
    • 直到 ESLint 不限制使用 非空断言操作符 :)
    • 我很震惊这样的东西需要存在。 ?
    • 这是一个糟糕的建议!相反,请帮助编译器弄清楚它。在访问属性之前添加一个检查,如果未设置名称,您可以在其中抛出或返回。
    • 不同意最后两个 cmets...例如如果该属性存在,我将有条件地渲染一个可视元素,并且附加到该元素的是一个使用该属性的函数。不需要对函数内部的属性进行额外的非空检查
    【解决方案2】:

    为了避免我使用的编译错误

    let name1:string = person.name || '';
    

    然后验证空字符串。

    【讨论】:

    • 如果不是字符串而是对象怎么办?
    • 如果它是一个对象,那么它将是 {} 而不是 ' '
    • 当你链接值并且你得到这种错误Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong.时,这是你可以做的事情
    • 这不是给定问题的解决方案。编译器可以帮助发现潜在问题,而像 person.name || '' 这样的赋值只是将问题委托给其他地方。
    【解决方案3】:

    我知道这有点晚了,但是除了yannick's answer 之外,使用! 的另一种方法是将其转换为字符串,从而告诉 TypeScript:我确定这是一个字符串,从而转换它。

    let name1:string = person.name;//<<<Error here 
    

    let name1:string = person.name as string;
    

    这将使错误消失,但如果有任何机会这不是字符串,您将收到运行时错误...这是我们使用 TypeScript 的原因之一确保类型匹配并在编译时避免此类错误。

    【讨论】:

    • 这可能会使错误消失,但它永远不会改变变量的实际类型。
    • 我很抱歉,但我认为你错了,因为我已经提到这实际上会改变类型,因为如果类型不是字符串或失败,它将引发运行时异常有一个 toString 方法。这会将那里的任何内容转换为字符串,即您有一个数字,它将尝试调用那里的任何内容的 .toString 函数。
    • 但这不是正确的方法,比如说如果我的名字中有数字“0”,它只会将它转换为字符串......而是应该抛出一个错误并说无效姓名。就个人而言,我不会使用这种类型转换并专门检查对象中传递的类型.. 但我们都有不同的编码风格,欢迎就相同问题进行更多讨论
    • 是的,但这完全脱离了给出这个答案的问题的背景。如果没有事先过滤掉的步骤,我们不知道这是否是一种形式。此外,如果没有一些非常肮脏的解决方法,您所暗示的在那个阶段是不可能的。
    • 无需变通方法,只需在尝试访问变量之前进行检查。这两种情况都可以轻松处理,错误会消失,您的代码会更安全。试试看吧:)
    【解决方案4】:

    从 TypeScript 3.7 开始,您可以使用 nullish 合并运算符 ??。您可以将此功能视为在处理 null 或 undefined 时“回退”到默认值的一种方式

    let name1:string = person.name ?? '';
    

    ?? 运算符可以在尝试使用默认值时替换 || 的使用,并且可以在处理无法使用 || 的布尔值、数字等时使用。

    从 TypeScript 4 开始,您可以使用 ??= 赋值运算符作为 a ??= b,这是 a = a ?? b; 的替代品

    【讨论】:

      【解决方案5】:

      根据您的定义,Person.name 可以为空,但name1 不能。 有两种情况:

      Person.name 永远不会为空

      使用! 告诉编译器您确定名称不为空

      let name1: string = person.name!;
      

      Person.name 可以为空

      指定一个默认值,以防名称为空

      let name1: string = person.name ?? "default name";
      

      【讨论】:

        【解决方案6】:

        处理此问题的更适合生产的方法是实际确保存在name。假设这是一组人参与的大型项目的最小示例,您不知道getPerson 将来会发生什么变化。

        if (!person.name) {
            throw new Error("Unexpected error: Missing name");
        }
        
        let name1: string = person.name;
        

        或者,您可以将name1 键入为string | undefined,并进一步处理undefined 的情况。但是,最好尽早处理意外错误。

        您也可以通过省略显式类型让 TypeScript 推断类型:let name1 = person.name 例如,这仍将防止 name1 被重新分配为数字。

        【讨论】:

          【解决方案7】:

          尝试事先找出实际值是多少。如果person 有一个有效的name,则将其分配给name1,否则分配undefined

          let name1: string = (person.name) ? person.name : undefined;
          

          【讨论】:

          • 嗯。如果我想做多个作业,那将太冗长了。有没有办法关闭这个特定的警告?
          • 很遗憾,除非您通过删除问号使name 成为强制性的。
          • PS:你使用哪个版本的 TypeScript?我使用 3.2.4 并且没有遇到此消息。在我的测试类中,代码可以正确编译。
          • 同一个版本。我在 webstorm 中收到此错误,但在 vscode 中没有
          • 这很奇怪,因为我使用的 IntelliJ 在应用 TS-rules 时应该类似于 WebStorm?!
          【解决方案8】:

          这是了解正在发生的事情的快速方法:

          当您执行以下操作时:

          名字? : 字符串

          你对 TypeScript 说它是可选的。然而,当你这样做时:

          let name1 : string = person.name; //<<<Error here 
          

          你没有给它一个选择。你需要有一个 Union 来反映未定义的类型:

          let name1 : string | undefined = person.name; //<<<No error here 
          

          使用您的答案,我能够勾勒出以下内容,基本上是一个接口、一个类和一个对象。我发现这种方法更简单,如果你不这样做也没关系。

          // Interface
          interface iPerson {
              fname? : string,
              age? : number,
              gender? : string,
              occupation? : string,
              get_person?: any
          }
          
          // Class Object
          class Person implements iPerson {
              fname? : string;
              age? : number;
              gender? : string;
              occupation? : string;
              get_person?: any = function () {
                  return this.fname;
              }
          }
          
          // Object literal
          const person1 : Person = {
              fname : 'Steve',
              age : 8,
              gender : 'Male',
              occupation : 'IT'  
          }
          
          const p_name: string | undefined = person1.fname;
          
          // Object instance 
          const person2: Person = new Person();
          person2.fname = 'Steve';
          person2.age = 8;
          person2.gender = 'Male';
          person2.occupation = 'IT';
          
          // Accessing the object literal (person1) and instance (person2)
          console.log('person1 : ', p_name);
          console.log('person2 : ', person2.get_person());
          

          【讨论】:

            【解决方案9】:

            解决方案 1:删除显式类型定义

            由于getPerson 已经返回了带有名称的Person,我们可以使用推断类型。

            function getPerson(){
              let person = {name:"John"};
              return person;
            }
            
            let person = getPerson();
            

            如果我们要定义person: Person,我们会丢失一条信息。我们知道getPerson 返回一个具有非可选属性的对象name,但将其描述为Person 会恢复可选性。

            解决方案 2:使用更精确的定义

            type Require<T, K extends keyof T> = T & {
              [P in K]-?: T[P]
            };
            
            function getPerson() {
              let person = {name:"John"};
              return person;
            }
            
            let person: Require<Person, 'name'> = getPerson();
            let name1:string = person.name;
            

            解决方案 3:重新设计您的界面

            所有属性都是可选的形状称为弱类型,通常是设计不佳的指标。如果我们将 name 设为必需属性,您的问题就迎刃而解了。

            interface Person {
              name:string,
              age?:string,
              gender?:string,
              occupation?:string,
            }
            

            【讨论】:

              【解决方案10】:

              如果您想拥有可为空的属性,请将您的界面更改为:

              interface Person {
                name?:string | null,
                age?:string | null,
                gender?:string | null,
                occupation?:string | null,
               }
              

              如果未定义不是这种情况,您可以从属性名称前面删除问号 (?)。

              【讨论】:

              • 嗨,我知道已经有一段时间了。如果这个接口是函数的参数怎么办。我如何传递参数。只是通过类似这样的工作 { } ,提前感谢您的帮助。
              • @vigneshasokan:接口类型不能作为参数传递。
              【解决方案11】:

              您试图设置变量name1,巫婆类型设置为严格字符串(它必须是字符串),其值来自对象字段name,巫婆值类型设置为可选字符串(它可以是字符串或未定义,因为问号)。如果你真的需要这种行为,你必须像这样改变name1的类型:

              let name1: string | undefined = person.name;
              

              一切都会好的;

              【讨论】:

                【解决方案12】:

                你可以这样做!

                let name1:string = `${person.name}`;
                

                但请记住name1 可以是空字符串

                【讨论】:

                • 这是唯一对我有用的答案。我正在尝试做&lt;div style={{color:this.props.color}}&gt;text&lt;/div&gt;。这太奇怪了。
                【解决方案13】:

                您可以使用NonNullable 实用程序类型:

                示例

                type T0 = NonNullable<string | number | undefined>;  // string | number
                type T1 = NonNullable<string[] | null | undefined>;  // string[]
                

                Docs.

                【讨论】:

                  【解决方案14】:

                  如果您从 getPerson 函数中删除 &lt;Person&gt; 转换,那么 TypeScript 将足够聪明地检测到您返回的对象肯定具有 name 属性。

                  所以转一下:

                  interface Person {
                    name?: string,
                    age?: string,
                    gender?: string,
                    occupation?: string,
                  }
                  
                  function getPerson() {
                    let person = <Person>{name: 'John'};
                    return person;
                  }
                  
                  let person: Person = getPerson();
                  let name1: string = person.name;
                  

                  进入:

                  interface Person {
                    name?: string,
                    age?: string,
                    gender?: string,
                    occupation?: string,
                  }
                  
                  function getPerson() {
                    let person = {name: 'John'};
                    return person;
                  }
                  
                  let person = getPerson();
                  let name1: string = person.name;
                  

                  如果您不能这样做,那么您将不得不按照@yannick1976 的建议使用“确定赋值断言运算符”:

                  let name1: string = person.name!;
                  

                  【讨论】:

                  • 拥有一个界面而不用它来打字有什么意义?
                  【解决方案15】:
                    @Input()employee!:string ;`enter code here`
                  announced:boolean=false;
                  confirmed:boolean=false;
                  task:string="<topshiriq yo'q>";
                  
                    constructor(private taskService:TaskService ) {
                      taskService.taskAnnon$.subscribe(
                        task => {
                          this.task=task;
                          this.announced=true;
                          this.confirmed=false;
                        }
                      )
                    }
                  
                    ngOnInit(): void {
                    }
                    confirm(){
                     this.confirmed=true
                  this.taskService.confirimTask(this.employee);`enter code here`
                    }
                  }
                  

                  【讨论】:

                  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
                  • 我根本看不出这如何回答这个问题,对我来说只是随机代码。我对这个答案投了反对票,但如果答案以某种方式更新,我很乐意改变它。
                  【解决方案16】:

                  这是我找到的唯一一个检查属性是否未定义且不会生成警告的解决方案

                  type NotUndefined<T, K extends keyof T> = T & Record<K, Exclude<T[K], undefined>>;
                  function checkIfKeyIsDefined<T, K extends keyof T>(item: T, key: K): item is NotUndefined<T, K> {
                      return typeof item === 'object' && item !== null && typeof item[key] !== 'undefined';
                  }
                  

                  用法:

                  
                  interface Listing { 
                      order?: string
                      ...
                  }
                  obj = {..., order: 'pizza'} as Listing
                  if(checkIfKeyIsDefined(item: obj, 'order')) {
                      order.toUpperCase() //no undefined warning O.O
                  }
                  

                  原创answer

                  【讨论】:

                    【解决方案17】:

                    有同样的问题。

                    我发现 react-scrips 将 "strict": true 添加到 tsconfig.json

                    删除后一切正常。

                    编辑

                    需要警告,更改此属性意味着您:

                    不再被警告潜在的运行时错误。

                    正如PaulG 在 cmets 中指出的那样!谢谢你:)

                    只有在您完全了解它的影响时才使用"strict": false

                    【讨论】:

                    • 这可能“有效”。但它仅在您不再被警告潜在的运行时错误的意义上起作用。我鼓励研究 Typescript 中严格检查的作用:medium.com/webhint/going-strict-with-typescript-be3f3f7e3295
                    • 我应该提到 OP 问道,“我该如何解决这个错误?”不是,“我该如何解决这个问题?”
                    • 简单地添加 name:string = undefined (如 json2typescript 之类的包所推荐的)会抛出错误 2322,除非你关闭严格,这是大多数人需要做的。这个被接受的答案是明确、正确的,值得更多的投票。
                    猜你喜欢
                    • 2019-04-10
                    • 2021-08-19
                    • 2022-12-02
                    • 2021-09-14
                    • 2021-09-21
                    • 2021-12-29
                    • 1970-01-01
                    • 2022-01-17
                    • 2018-07-18
                    相关资源
                    最近更新 更多