非空断言操作符

343 阅读2分钟

原文地址

非空断言操作符 在变量后面添加一个 ! 就会忽略 undefined 和 null

 function simpleExample(a: number | undefined) { 
    const b: number = a; 
    // COMPILATION ERROR: undefined is not assignable to number.
    const c:number = a!; // OK 
}

当断言操作符在 runtime 失败时,代码就跟正常的 JavaScript 代码一样。这可能会带来意想不到的结果。以下演示了不安全的使用方式:

  const a: number | undefined = undefined; 
  const b: number = a!;
  console.log(b);// prints undefined, although b’s type does not include undefined ------- 
  type NumGenerator = () => number; 
  function myFunc(numGenerator: NumGenerator | undefined) { 
    const num1 = numGenerator!(); 
  } 
  myFunc(undefined); // runtime error: Uncaught TypeError: numGenerator is not a function

应用场景

React refs 的事件处理

React refs 可以用来访问HTML节点的DOM, ref.current 的值有时可能为null(元素没有加载完成)

const ScrolledInput = () => {
   const ref = React.createRef<HTMLInputElement>();

   const goToInput = () => ref.current.scrollIntoView(); //compilation error: ref.current is possibly null

   return (
       <div>
           <input ref={ref}/>
           <button onClick={goToInput}>Go to Input</button>
       </div>
   );
};

//我们知道当 `goToInput` 执行时,input 元素一定是 mounted。我们可以大胆假设 `ref.current` 是非空的:
 
 // 使用非空
  const goToInput = () => ref.current!.scrollIntoView(); //all good!
 // 不使用非空
 const goToInput = () => ref.current && ref.current.scrollIntoView();

当链式调用可选属性时,就会变得特别麻烦

// Logical AND
object && object.prop && object.prop.func && object.prop.func();

// Compared to the Non-null assertion operator:
object!.prop!.func!(); //assuming object.prop.func are all defined

React 中的 prop 注入测试

接下来的示例是一种常见的 prop 测试模式。我们会有两个组件。

第一个是可重用的组件,它也可以是第三方组件。它有一个 callback prop,当 input 的 value 改变时会调用这个 callback。Callback 会接受一个 event 参数。

 interface SpecialInputProps {
    onChange?: (e: React.FormEvent<HTMLInputElement>) => void;
 }

const SpecialInput = (s: SpecialInputProps) => <input onChange={s.onChange}/>;

第二个组件叫做 SpecificField,它包含着 SpecificInputSpecificField 知道如何提取当前值,并把它传递给 callback:

interface SpecificFieldProps {
   onFieldChanged: (newValue: string) => void;
}

const SpecificField = (props: SpecificFieldProps) => {
   const inputCallback = (e: React.FormEvent<HTMLInputElement>) => props.onFieldChanged(e.currentTarget.value);
   return <SpecialInput onChange={inputCallback}/>;
};

我们想测试 SpecificField 是否可以正确的调用 onChange

也就是说 SpecificField 的回调 onFieldChanged 是否可以得到一个 event.currentTarget.value:

//SpecificField.test.tsx
it('should inject callback to SpecialInput which calls the onFieldChanged callback with the event value', () => {
   const onFieldChanged = jest.fn();
   const wrapper = shallow(<SpecificField onFieldChanged={onFieldChanged}/>);
   const injectedCallback = wrapper.find(SpecialInput).props().onChange;

   expect(injectedCallback).toBeDefined();
   const event = {currentTarget: {value: "new value"}} as React.FormEvent<HTMLInputElement>;
   injectedCallback(event); //compilation error: cannot invoke an object which is possibly undefined

   expect(onFieldChanged).toHaveBeenCalledWith("new value");
});

因为 onFieldChanged 是可选的,因此可能是 undefined,这就会引起编译错误。

当然,我们知道 injectedCallback 已经被定义了。 — 我还测试过它。

这时,非空断言操作符就可以帮助我们:

 injectedCallback!(event); //no problem

另外一个解决方案,我们不使用断言操作符,而是,使用 if-else 条件语句:

if (injectedCallback) {
   injectedCallback(event); //injectedCallback has to be defined in the “if” block
   expect(onFieldChanged).toHaveBeenCalledWith("new value");
} else {
   fail("SpecialInput was not injected with a callback as expected");
}

非空断言操作符。它更加简短、清晰,没有多余的代码。