🐈

[TS] Non-null assertion operator を理解する

Non-null assertion operator とは、オブジェクトのうしろにつける ! のこと。 たとえばこんなもの。TS 2.7 から使える。

object!.open();

TypeScript Deep Dive によれば、

新しい ! ポストフィックス式演算子を使用して、型チェッカーが結論付けられないコンテキストにおいて、そのオペランドが非 null でかつ非 undefined であることをアサートすることができます。

そしてこうも書いてある。

これは単なるアサーションであり、型アサーションと同じように、あなたは値が null でないことを確認する責任があることに注意してください。 非 null アサーションは、本質的にはコンパイラに”それは null でないことが分かっているから、null ではないものとして使います”と伝えるものです。

なるほど、強制的に null または undefined でないことにできてしまうから、自分で気をつけないといけないよと。

一応「アサーション」についても調べてみる。

表明とは、プログラミングにおける概念のひとつであり、そのプログラムの前提条件を示すのに使われる。 アサーションとも呼ばれる。表明は、プログラムのその箇所で必ず真であるべき式の形式をとる。 Wikipedia

ここでは「オブジェクトが null でないこと」を前提条件として表明していることになる。 となると、Non-null assertion とは null または undefind でないことを表明することであり、Non-null assertion operator はそのための演算子であるといえるかな。

Deep Dive にはこんなコードも書かれていた。 ここまで調べれば理解できるはず。

// Compiled with --strictNullChecks
function validateEntity(e?: Entity) {
  // Throw exception if e is null or invalid entity
}

function processEntity(e?: Entity) {
  validateEntity(e);
  let a = e.name; // TS ERROR: e may be null.
  let b = e!.name; // OKAY. We are asserting that e is non-null.
}

🐈

具体的な使用例をみてみる。この記事を書くきっかけになったのが以下のコード。

単純なリストで、各項目に異なる ref をわりあてている。 リストの項目をクリックすると ref を参照してそこまでスクロールするよ、というもの。 handleClick のなかで Non-null assertion operator が使われている。

const refs = data.map(_ => React.createRef<HTMLDivElement>());

const handleClick = (key: number) => {
  refs[key]!.current!.scrollIntoView({
    behavior: "smooth",
    block: "start"
  });
};

...中略...

return (
  <List>
    data.map((v, i) => {
      return (
        <ListItem
          ref={refs[key]}
          onClick={handleClick(i)}
        />
      );
    }
  </List>
);

ref には current というプロパティがあって、それをもとにスクロールする位置を決める。 しかし current の型は HTMLDivElement | null なので、たとえすべてのリスト項目に ref を指定して null になる可能性をなくしたとしても、型として null が含まれてしまう。

そうなると困るのが ref.current を使うとき。以下のように書くと怒られる。

refs[key].current.scrollIntoView();
// Object is possiblly 'null'

それもそのはず、型として null が含まれているから。 ちなみに、これは undefined でも同じことになる。 じゃあどうするかというと、ここは null の可能性も undefined の可能性もありませんよ!と明示する。 そのために使うのが Non-null assertion operator 。

refs[key].current!.scrollIntoView();