安全导航运算符 (?.) 或 (!.) 和空属性路径

Posted

技术标签:

【中文标题】安全导航运算符 (?.) 或 (!.) 和空属性路径【英文标题】:Safe navigation operator (?.) or (!.) and null property paths 【发布时间】:2017-03-07 09:29:02 【问题描述】:

在 Angular 2 模板中,安全运算符 ?. 有效,但在使用 TypeScript 2.0 的 component.ts 中无效。此外,安全导航运算符 (!.) 也不起作用。

例如:

这个打字稿

if (a!.b!.c)  

编译成这个 javascript

if (a.b.c)  

但是当我运行它时,我得到了以下错误:

无法读取未定义的属性“b”

下面有什么替代方法吗?

if (a && a.b && a.b.c)  

【问题讨论】:

typescript 操作符只存在于编译,它们不存在于编译的 javascript 中。您发布的错误是运行时错误。 o(o(o(o(test).level1).level2).level3 或 o(o(o(o(a).b).c).d ***.com/a/35169378/3914072 这个解决方案在编译时非常适合我们,并且它具有安全类型 这能回答你的问题吗? Does Typescript support the ?. operator? (And, what's it called?) 这对谷歌来说太难了。谢谢! 【参考方案1】:

typescript 3.7 以上的版本支持 typescript

export function isAccessible(data, keys, start=0) 
  if (start == 0 && (data == null || data == undefined)) 
    console.warn("data",data);
    return false;
   else 
    if (data[keys[start]] == null || data[keys[start]] == undefined) 
      console.warn("Object valid till", keys.slice(0,start),keys[start],"undefined");
      return false;
     else 
      if (start + 1 >= keys.length) 
        return data[keys[start]];
      
      return this.isAccessible(data[keys[start]], keys, start + 1);
    
  

代码中的函数调用

假设我们有一个对象obj,它的键可以变化并且我们想要检查 如果obj.key1.key2 可访问或不可访问,则函数调用如下:

isAccessible(Object,["key1","key2"])

【讨论】:

【参考方案2】:

自从 TypeScript 3.7 发布以来,您现在可以使用可选链。

属性示例:

let x = foo?.bar.baz();

这相当于:

let x = (foo === null || foo === undefined)
  ? undefined
  : foo.bar.baz();

此外,您可以致电:

可选调用

function(otherFn: (par: string) => void) 
   otherFn?.("some value");

仅当otherFn 不等于nullundefined 时才会调用otherFn

在 IF 语句中使用可选链

这个:

if (someObj && someObj.someProperty) 
  // ...

现在可以用这个替换

if (someObj?.someProperty) 
  // ...

参考:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html

【讨论】:

这在编译时不断抛出 Expression expected 错误,如果我在 "typescript": "~3.5.3" 中使用 ! 而不是 ? 则它可以工作跨度> @Scaramouche 这是因为可选链接运算符 (?.) 是在 3.7 中引入的,而您正在运行 3.5.3。在 3.7 之前的版本中,TypeScript 假定 ? 以 if 形式 (foo ? bar : baz) 的简写形式使用,因此期望表达式的其余部分【参考方案3】:

! 是 non-null assertion operator(后修复表达式) - 它只是告诉类型检查器 确定 a 不是 nullundefined

操作a! 产生a 类型的值,但不包括nullundefined


Optional chaining 最后是made 到打字稿(3.7)?

可选的链接运算符?. 允许读取位于连接对象链深处的属性值,而无需明确验证链中的每个引用是否有效。 ?. 运算符的功能类似于 . 链接运算符,除了如果引用为空(nullundefined)不会导致错误,表达式短路并返回值为 undefined .与函数调用一起使用时,如果给定函数不存在,则返回 undefined

语法

obj?.prop // Accessing object's property
obj?.[expr] // Optional chaining with expressions
arr?.[index] // Array item access with optional chaining
func?.(args) // Optional chaining with function calls

注意

可选链在赋值左侧无效

const object = ;
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment

【讨论】:

对于那些有兴趣查看此状态的人。 TypeScript 计划在 ECMAScript 添加支持后添加它。 ECMAScript 正在跟踪这个问题here。 o(o(o(o(test).level1).level2).level3 或 o(o(o(o(a).b).c).d ***.com/a/35169378/3914072 这个解决方案在编译时非常适合我们,并且它具有安全类型 新功能的游乐场(将于 11 月 5 日发布):typescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/… @aleksey-l:在“最后一次更新”之前删除内容可能不是一个坏主意。它将帮助那些不从上到下阅读的人(即太快)! :) 这太酷了——我不知道你可以用它来调用函数。正在寻找解决方案并遇到了您的答案,非常感谢!【参考方案4】:

更新:

计划在 3.7 版本范围内https://github.com/microsoft/TypeScript/issues/33352


你可以试着写一个这样的自定义函数。

该方法的主要优点是类型检查和部分智能感知。

export function nullSafe<T, 
    K0 extends keyof T, 
    K1 extends keyof T[K0],
    K2 extends keyof T[K0][K1],
    K3 extends keyof T[K0][K1][K2],
    K4 extends keyof T[K0][K1][K2][K3],
    K5 extends keyof T[K0][K1][K2][K3][K4]>
    (obj: T, k0: K0, k1?: K1, k2?: K2, k3?: K3, k4?: K4, k5?: K5) 
    let result: any = obj;

    const keysCount = arguments.length - 1;
    for (var i = 1; i <= keysCount; i++) 
        if (result === null || result === undefined) return result;
        result = result[arguments[i]];
    

    return result;

及用法(最多支持5个参数,可扩展):

nullSafe(a, 'b', 'c');

playground 上的示例。

【讨论】:

这很好用,但它有一个问题——在其中一个键上使用“查找所有引用”时,VSCode 不会显示这个。 什么样的类型检查?当您将字符串作为参数传递时,您就失去了类型。如果是这样,你最好只使用 lodash.get【参考方案5】:

一个名为 ts-optchain 的新库提供了此功能,并且与 lodash 的解决方案不同,它还可以确保您的类型安全,这里是它的使用示例(摘自自述文件):

import  oc  from 'ts-optchain';

interface I 
  a?: string;
  b?: 
    d?: string;
  ;
  c?: Array<
    u?: 
      v?: number;
    ;
  >;
  e?: 
    f?: string;
    g?: () => string;
  ;


const x: I = 
  a: 'hello',
  b: 
    d: 'world',
  ,
  c: [ u:  v: -100  ,  u:  v: 200  , ,  u:  v: -300  ],
;

// Here are a few examples of deep object traversal using (a) optional chaining vs
// (b) logic expressions. Each of the following pairs are equivalent in
// result. Note how the benefits of optional chaining accrue with
// the depth and complexity of the traversal.

oc(x).a(); // 'hello'
x.a;

oc(x).b.d(); // 'world'
x.b && x.b.d;

oc(x).c[0].u.v(); // -100
x.c && x.c[0] && x.c[0].u && x.c[0].u.v;

oc(x).c[100].u.v(); // undefined
x.c && x.c[100] && x.c[100].u && x.c[100].u.v;

oc(x).c[100].u.v(1234); // 1234
(x.c && x.c[100] && x.c[100].u && x.c[100].u.v) || 1234;

oc(x).e.f(); // undefined
x.e && x.e.f;

oc(x).e.f('optional default value'); // 'optional default value'
(x.e && x.e.f) || 'optional default value';

// NOTE: working with function value types can be risky. Additional run-time
// checks to verify that object types are functions before invocation are advised!
oc(x).e.g(() => 'Yo Yo')(); // 'Yo Yo'
((x.e && x.e.g) || (() => 'Yo Yo'))();

【讨论】:

【参考方案6】:

使用外部库的另一种替代方法是 _.has() 来自 Lodash。

例如

_.has(a, 'b.c')

等于

(a && a.b && a.b.c)

编辑: 如 cmets 中所述,使用此方法时您会丢失 Typescript 的类型推断。 例如。假设一个对象的类型正确,如果 z 未定义为对象 b 的字段,则使用 (a && a.b && a.b.z) 会出现编译错误。但是使用 _.has(a, 'b.z') 不会出现该错误。

【讨论】:

不错的提示,虽然没有其他解决方案可用,但这似乎是一个不错的选择。 但是它如何与 TypeScript 的类型推断一起工作? @VitalyB 可能你的意思是导入lodash的相应接口。 (从'lodash'导入*作为_;) @AnkurArora,我认为 VitalyB 的意思是没有使用 Lodash 方法进行类型检查。例如。假设一个对象的类型正确,如果 z 未定义为对象 b 的字段,则使用 (a && a.b && a.b.z) 会出现编译错误。但是使用 _.has(a, 'b.z') 不会出现该错误。至于import语句,是的,肯定是需要的。 @TiaanM 这正是我的意思。但是,我刚刚找到了一个涵盖类型问题的新库:***.com/a/55462483/126574【参考方案7】:

基于@Pvl 的回答,如果您使用覆盖,您也可以在返回值中包含类型安全:

function dig<
  T,
  K1 extends keyof T
  >(obj: T, key1: K1): T[K1];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1]
  >(obj: T, key1: K1, key2: K2): T[K1][K2];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2]
  >(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4): T[K1][K2][K3][K4];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5): T[K1][K2][K3][K4][K5];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2?: K2, key3?: K3, key4?: K4, key5?: K5):
  T[K1] |
  T[K1][K2] |
  T[K1][K2][K3] |
  T[K1][K2][K3][K4] |
  T[K1][K2][K3][K4][K5] 
    let value: any = obj && obj[key1];

    if (key2) 
      value = value && value[key2];
    

    if (key3) 
      value = value && value[key3];
    

    if (key4) 
      value = value && value[key4];
    

    if (key5) 
      value = value && value[key5];
    

    return value;

playground 上的示例。

【讨论】:

以上是关于安全导航运算符 (?.) 或 (!.) 和空属性路径的主要内容,如果未能解决你的问题,请参考以下文章

ES11(2020)可选链操作符和空值合并运算符

ES11(2020)可选链操作符和空值合并运算符

Groovy 中的安全导航运算符

C#语法糖空合并运算符??和空合并赋值运算符 ??=

有没有可以在数组上使用的安全导航运算符之类的东西?

ThinkPHP空操作和空控制器的处理