🫙

TypeScript: null と undefined

2023/06/16に公開

JavaScript では nullundefined というキーワードがコード内で使用されますが、このふたつはよく混同されています。

他のほとんどのプログラミング言語では、Python では None、Ruby や Go では nil など、名前は違えど「何もない」を表す値はひとつだけです。

しかし、JavaScript では null に加えて undefined というものがあり、TypeScript でも型が分かれています。

この記事では、「nullundefined って何が違うの?」「結局どっちを使ったらいいの?」のお話をします。

違い

undefined は「未定義」 - null は「無」

まず、名前がそれらの表すものをだいたい示しています。

undefined は「未定義」で、宣言はされたものの値が割り当てられなかった変数や、実引数が渡されなかった関数の仮引数に自動的に割り当てられます。

let x;

console.log(x); // -> undefined
const f = (a) => {
  console.log(a);
};

f() // -> undefined

null は「無」で、値が存在しないことを表します。

つまり、undefined が意味を持たないのに対し、null明確に「無」という意味を持っています

undefined は自然発生する - null は意図性を含む

undefined は自動的に割り当てられることがあるので、自然発生する可能性があります。

null は基本的には意図的に使用しない限り発生しません。

本来、意図性を含むか含まないかが、nullundefined の大きな違いだと思っています。

使い分け

nullundefined はどのように使い分けるのがよいのかについてですが、
ここではこちらを使用しなければならない、というような must な決まりはなく、単なるお作法の問題です。

ここに書いている内容は個人の見解にすぎません。

チームの中でルールがあるのであれば、それに従うのがいいでしょう。

使い分けに関しては、JS や TS のエコシステムの中で特にこれといった慣例などはなさそうです。

使い方としては、次の 3 つに大きく分かれると思います。

  1. できるだけ null を使わずに undefined だけ使う
  2. できるだけ undefined を使わずに null だけ使う
  3. 場合によって使い分ける

できるだけ null を使わずに undefined だけ使う?

少し調べてみると、この「できるだけ null を使わずに undefined だけ使う」という意見がよく見られました。

自然発生してしまう undefined に統一したほうが簡単だからです。

確かに自然に回帰したほうが持続可能な開発はできそうです 🏞️

統一することで無駄な迷いはなくなるので、それは選択肢としてありだと思います。

しかし…

個人的には undefined に意図性や意味を持たせてしまうことに少し違和感があります。

意味をもつ値を「未定義」だと定義して使っているのか…?それは undefined だといえるのか…?

という気持ちになってしまいます 🥺

あとは、せっかく異なる意味合いのものが用意されているのだから、その意味に従って使い分けたいという気持ちもあります。

実際そうすることで自分がコードを読みやすくなるとも感じています。

また、副作用的な話で、TypeScript では undefined 型とのユニオン型を number | undefined のように書きますが、
引数やプロパティに a?: number のように書いても、定義する側では anumber | undefined として扱います。

? は省略可能という意味で、省略されたときに undefined が渡ってくるということなのですが、
型が同じになるため、undefined とのユニオン型のエイリアスのような感覚で使ってしまうことがあります(私がそうでした)。

a: number | undefined と書くより a?: number と書いた方が楽なのでしょうがありません。

しかし、省略可能かどうかにおいて大きな違いがあり、省略しても型エラーにならないことで、潜在的に不要な記述が生み出される可能性があります。

引数やプロパティが省略可能な値として渡されたときは、使用する側で省略してもエラーにならないため、実際には使用されていない引数やプロパティが残る可能性があります。

特に定義している場所と使用している場所が遠かったり、ファイルが分かれていたりすると気付きにくいのです。

const f(a: string, b?: number) => {...};
f('string');

...

f('another');

...

// あれ、b どこにも使われてなくない??

React でコンポーネントに props を渡すときにも、undefined なプロパティは省略できてしまいます。

type Props = {
  a: string;
  b?: number;
};

const Component = ({ a, b }: Props) => {
  ...
};
const Page = () => {
  return <Component a="string"/>
}

無駄な記述が含まれているとメンテナンス性の低下にもつながるので、個人的には省略可能な引数やプロパティを使用することはできるだけ避けています。

一方、? を使わずに number | undefined のようにユニオン型で書くか、最初から null を使えば、呼び出す側で明示的に指定する必要があるため、このようなリスクはありません。

type Props = {
  a: string;
  b: number | null;
};

const Component = ({ a, b }: Props) => {
  ...
};
const Page = () => {
  return <Component a="string" b={null}/>
}

undefined を使いましょう、というルールだけだと、a?: numbera: number | undefined が同じだと勘違いされ、(私はずっと勘違いしてた)
意図せず省略可能な引数やプロパティが混入してしまうかもしれません。

できるだけ undefined を使わずに null だけ使う?

とはいえ、自然発生した undefined は仕方ありません。自然の摂理を無理にねじまげるつもりもありません。

API の中には undefined 型になってしまうものもあります。

例えば、Web API の clearTimeout は、引数に timeoutID をとりますが、これは number | undefined 型です。

そういったものを毎回すべて null と変換するのも大変です。

場合によって使い分ける

自分としては、そのような自然発生するものなど、ある程度であれば undefined の使用は許容しています。

自然発生した undefined 型をもちうる値をそのまま引数やプロパティに渡すときにも、無理に null に変換することはせず、そのままにしています。

その場合、型の指定には注意して、ユニオン型を使います。

しかし、意図的に undefined を使用すること、undefined を自ら生み出すことは避けたほうがいいと考えています。

もし引数やプロパティに渡す段階で意図性や意味を加えるのであれば、その時点で null に変換します。


というわけで、私個人としては、

自然と生まれ出るものは、nullundefined も、どちらも受け入れよう。
ただし、意図的に undefined を生み出してはならない。
意図性を注ぎ込むのは、常に null である。

というルールに落ち着いています。

このルールに従うことで、コードを読むときに変数の型を見て、それがどういう意味をもった値なのか、より明快になると感じています。

他人のコードを見たときに、ここ null にしたいな!?って感じる機会が生じるというデメリットはあります 😇


まあ、そんな細かいこと、どっちでも動くからいいんですけどもね!!

nullundefined の使い分けで迷っている方は、参考になったらうれしいです!

ではでは 👋

Discussion