TypeScript: null と undefined
JavaScript では null
と undefined
というキーワードがコード内で使用されますが、このふたつはよく混同されています。
他のほとんどのプログラミング言語では、Python では None
、Ruby や Go では nil
など、名前は違えど「何もない」を表す値はひとつだけです。
しかし、JavaScript では null
に加えて undefined
というものがあり、TypeScript でも型が分かれています。
この記事では、「null
と undefined
って何が違うの?」「結局どっちを使ったらいいの?」のお話をします。
違い
undefined
は「未定義」 - null
は「無」
まず、名前がそれらの表すものをだいたい示しています。
undefined
は「未定義」で、宣言はされたものの値が割り当てられなかった変数や、実引数が渡されなかった関数の仮引数に自動的に割り当てられます。
let x;
console.log(x); // -> undefined
const f = (a) => {
console.log(a);
};
f() // -> undefined
null
は「無」で、値が存在しないことを表します。
つまり、undefined
が意味を持たないのに対し、null
は明確に「無」という意味を持っています。
undefined
は自然発生する - null
は意図性を含む
undefined
は自動的に割り当てられることがあるので、自然発生する可能性があります。
null
は基本的には意図的に使用しない限り発生しません。
本来、意図性を含むか含まないかが、null
と undefined
の大きな違いだと思っています。
使い分け
null
と undefined
はどのように使い分けるのがよいのかについてですが、
ここではこちらを使用しなければならない、というような must な決まりはなく、単なるお作法の問題です。
ここに書いている内容は個人の見解にすぎません。
チームの中でルールがあるのであれば、それに従うのがいいでしょう。
使い分けに関しては、JS や TS のエコシステムの中で特にこれといった慣例などはなさそうです。
使い方としては、次の 3 つに大きく分かれると思います。
- できるだけ
null
を使わずにundefined
だけ使う - できるだけ
undefined
を使わずにnull
だけ使う - 場合によって使い分ける
null
を使わずに undefined
だけ使う?
できるだけ 少し調べてみると、この「できるだけ null
を使わずに undefined
だけ使う」という意見がよく見られました。
自然発生してしまう undefined
に統一したほうが簡単だからです。
確かに自然に回帰したほうが持続可能な開発はできそうです 🏞️
統一することで無駄な迷いはなくなるので、それは選択肢としてありだと思います。
しかし…
個人的には undefined
に意図性や意味を持たせてしまうことに少し違和感があります。
意味をもつ値を「未定義」だと定義して使っているのか…?それは undefined
だといえるのか…?
という気持ちになってしまいます 🥺
あとは、せっかく異なる意味合いのものが用意されているのだから、その意味に従って使い分けたいという気持ちもあります。
実際そうすることで自分がコードを読みやすくなるとも感じています。
また、副作用的な話で、TypeScript では undefined
型とのユニオン型を number | undefined
のように書きますが、
引数やプロパティに a?: number
のように書いても、定義する側では a
を number | 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?: number
と a: number | undefined
が同じだと勘違いされ、(私はずっと勘違いしてた)
意図せず省略可能な引数やプロパティが混入してしまうかもしれません。
undefined
を使わずに null
だけ使う?
できるだけ とはいえ、自然発生した undefined
は仕方ありません。自然の摂理を無理にねじまげるつもりもありません。
API の中には undefined
型になってしまうものもあります。
例えば、Web API の clearTimeout
は、引数に timeoutID
をとりますが、これは number | undefined
型です。
そういったものを毎回すべて null
と変換するのも大変です。
場合によって使い分ける
自分としては、そのような自然発生するものなど、ある程度であれば undefined
の使用は許容しています。
自然発生した undefined
型をもちうる値をそのまま引数やプロパティに渡すときにも、無理に null
に変換することはせず、そのままにしています。
その場合、型の指定には注意して、ユニオン型を使います。
しかし、意図的に undefined
を使用すること、undefined
を自ら生み出すことは避けたほうがいいと考えています。
もし引数やプロパティに渡す段階で意図性や意味を加えるのであれば、その時点で null
に変換します。
というわけで、私個人としては、
自然と生まれ出るものは、null
も undefined
も、どちらも受け入れよう。
ただし、意図的に undefined
を生み出してはならない。
意図性を注ぎ込むのは、常に null
である。
というルールに落ち着いています。
このルールに従うことで、コードを読むときに変数の型を見て、それがどういう意味をもった値なのか、より明快になると感じています。
他人のコードを見たときに、ここ null
にしたいな!?って感じる機会が生じるというデメリットはあります 😇
まあ、そんな細かいこと、どっちでも動くからいいんですけどもね!!
null
と undefined
の使い分けで迷っている方は、参考になったらうれしいです!
ではでは 👋
Discussion