コード例
例として、「x 座標が正である点の中で、x 座標が最小の点」を求めるプログラムを書いてみた。
interface Point {
x: number;
y: number;
}
const points: Point[] = [
{x: 7, y: 5},
{x: -1, y: 20},
{x: 3, y: 10},
];
let selectedPoint : Point | null = null;
points.forEach((point) => {
if (point.x > 0 && (!selectedPoint || selectedPoint.x > point.x)) {
selectedPoint = point;
}
});
if (selectedPoint) {
console.log(selectedPoint.x);
}
このコードを Playground (v5.3.3) に入力すると、以下のエラーが出てしまった。
Property 'x' does not exist on type 'never'.
このエラーは、以下の行で発生している。
console.log(selectedPoint.x);
forEach
内で selectedPoint
を更新する可能性があるにもかかわらず、上記の行の前の行
if (selectedPoint) {
において、selectedPoint
の型がなぜか null
になってしまっており、この if
文の中では never
になってしまっている。
このコードを実行してみると、
[LOG]: 3
と出力され、正しく求まっていることがわかる。
回避法
この章の地の文には、個人の感想が含まれる。
普通の for 文を使う
forEach
なんていうオシャレな記法はやめて、普通の for
文で普通に実装する。
先ほどのコードからの変更が最小限であり、素直で良い書き方である。
interface Point {
x: number;
y: number;
}
const points: Point[] = [
{x: 7, y: 5},
{x: -1, y: 20},
{x: 3, y: 10},
];
let selectedPoint : Point | null = null;
for (let i = 0; i < points.length; i++) {
const point = points[i];
if (point.x > 0 && (!selectedPoint || selectedPoint.x > point.x)) {
selectedPoint = point;
}
}
if (selectedPoint) {
console.log(selectedPoint.x);
}
reduce を使う
ループ内で値を更新していくということで、reduce
を用いる。
オシャレさは維持できるが、初期値が後ろに行くことでわかりやすさが下がる可能性がある。
interface Point {
x: number;
y: number;
}
const points: Point[] = [
{x: 7, y: 5},
{x: -1, y: 20},
{x: 3, y: 10},
];
const selectedPoint = points.reduce((selectedPoint: Point | null, point) => {
if (point.x > 0 && (!selectedPoint || selectedPoint.x > point.x)) {
selectedPoint = point;
}
return selectedPoint;
}, null);
if (selectedPoint) {
console.log(selectedPoint.x);
}
アルゴリズムを切り替える
「x 座標が正である点の中で、x 座標が最小の点を求める」という処理を、
- x 座標が正である点を求める
- その中で x 座標が最小の点を求める
という処理に分割する。
オシャレさはさらに上がる。
このような分割が可能な特殊な場合向け。
interface Point {
x: number;
y: number;
}
const points: Point[] = [
{x: 7, y: 5},
{x: -1, y: 20},
{x: 3, y: 10},
];
const positivePoints = points.filter((point) => point.x > 0);
const selectedPoint = positivePoints.length > 0
? positivePoints.reduce((acc, point) => point.x < acc.x ? point : acc)
: null;
if (selectedPoint) {
console.log(selectedPoint.x);
}
as でごまかす
as
で強制的に型を指定し、エラーを握りつぶす。
考えたくない人向け。
interface Point {
x: number;
y: number;
}
const points: Point[] = [
{x: 7, y: 5},
{x: -1, y: 20},
{x: 3, y: 10},
];
let selectedPoint : Point | null = null;
points.forEach((point) => {
if (point.x > 0 && (!selectedPoint || selectedPoint.x > point.x)) {
selectedPoint = point;
}
});
if (selectedPoint) {
console.log((selectedPoint as Point).x);
}
まとめ
以下の条件を満たす書き方をするのがいいだろう。
-
as
などの強行突破をなるべくしない - まわりから怒られない (コーディング規約に違反しないなど)
- 自分がわかりやすい・書きやすい