Edited at

TypeScriptで "Object is possibly null" と怒られたときにすること

メモ書きです。

https://github.com/Microsoft/TypeScript/issues/14889

参考にしたIssue

例えば、画像をアップするところをやっていて、次のようなコンポーネントを扱っているとします。

(適当にそれっぽい雰囲気にしています)

import * as React from 'react'

import { compose, pure } from 'recompose'
import Icon, { IconType } from '../../atoms/Icon/Icon'
import * as styles from './ImageUploader.css'

interface ImageUploaderProps {
onChangeImage: (data: any) => void
}

const ImageUploader: React.FunctionComponent<ImageUploaderProps> = ({ onChangeImage }) => {
return (
<div className={styles.container}>
<label className={styles.buttonLabel}>
<span>
<Icon iconType={IconType.IMAGE_UPLOAD_ICON} />
アップロード
</span>
<input
hidden
id={'file_image'}
type="file"
accept="image/png,image/jpg,image/bmp"
onChange={e =>
onChangeImage(targetImage: e.target.files[0])
}
/>
</label>
</div>
)
}

export default compose<ImageUploaderProps, ImageUploaderProps>(pure)(ImageUploader)

inputに画像をアップする際、そこからFileオブジェクトを抜き出すときは files に配列で格納されているのでこれをindex指定で取り出す必要があります。

しかし、上のコードはこのままだとエラーが出ます。

image.png

nullかもしれないオブジェクトに対する操作は認められない、という感じですね。では、nullじゃないことを保証すればよいわけです。ここでは2つ変更します。


interface変更

まずinterfaceが雑に any を使ってしまっているのでこれを直しましょう。


変更前

interface ImageUploaderProps {

onChangeImage: (data: any) => void
}


変更後

interface ImageUploaderProps {

onChangeImage: (object: { targetImage: File | null }) => void
}


onChange内で、nullの場合に関する切り分けをする

配列にアクセスする際に、nullでないことを保証します。


変更前


<input
hidden
id={'file_image'}
type="file"
accept="image/png,image/jpg,image/bmp"
onChange={e =>
onChangeImage(targetImage: e.target.files[0])
}
/>


変更後

<input

hidden
id={'file_image'}
type="file"
accept="image/png,image/jpg,image/bmp"
onChange={e =>
onChangeImage(targetImage: e.target.files !== null ? e.target.files[0] : null)
}
/>

参考にしたIssueでは、guard文で早めに抜けていました。

image.png

https://github.com/Microsoft/TypeScript/issues/14889#issuecomment-289613908

しかし、onChangeの関数内で同様に抜けようとするよりも、三項演算子でnullを回避するほうが見やすいかと思ってこのように書いています。

nullが裏のsagaなどに行ってしまうので、そこでの制御はどちらにせよ必要ですね...


まとめ

最終的には以下のようになります。


import * as React from 'react'
import { compose, pure } from 'recompose'
import Icon, { IconType } from '../../atoms/Icon/Icon'
import * as styles from './ImageUploader.css'

interface ImageUploaderProps {
onChangeImage: (object: { targetImage: File | null }) => void
}

const ImageUploader: React.FunctionComponent<ImageUploaderProps> = ({ onChangeImage }) => {
return (
<div className={styles.container}>
<label className={styles.buttonLabel}>
<span>
<Icon iconType={IconType.IMAGE_UPLOAD_ICON} />
アップロード
</span>
<input
hidden
id={'file_image'}
type="file"
accept="image/png,image/jpg,image/bmp"
onChange={e =>
onChangeImage(targetImage: e.target.files !== null ? e.target.files[0] : null )
}
/>
</label>
</div>
)
}

export default compose<ImageUploaderProps, ImageUploaderProps>(pure)(ImageUploader)