🖐️
takeWhile はメモリリーク (無駄なメモリ消費) の原因になり得る
takeWhile
に参照透過でない式を渡すのは良くない。
上記が分かっていれば下記を読む必要はありません。
経緯
React 開発ノウハウメモ(随時更新)
上記記事を読んで unsubscribe
を componentWillUnmount
に書かない方法を知ったので調べてみた。[1]
takeWhile
はメモリリークの原因になり得る。
takeWhile
は値が来たときのみ実行されるので値が来なければ unsubscribe
されない。
無駄なネットワーク通信や DB へのアクセスを続けてしまう。
takeWhile
は値に応じて処理を続けるべきか決まる場合で使うべき。
Observable
を流れる値と関係なく処理を止めたいなら unsubscribe
を呼ぶか takeUntil
を使う。
テストしてみる
実際に試してみるためのコードを書いた。
それらで下記の 3 つの関数を使っている。
utils.ts
// s 秒待つ
const sleep = (s: number) => new Promise(r => setTimeout(r, s * 1000))
// タイムスタンプをつけてログを出す
const log = (...v: any[]) => {
const ts = (performance.now() / 1000) | 0
console.log(...v, `(${ts})`)
}
// キャンセルできる Observable を生成
const make = (name: string) => {
return new Observable<number>(s => {
let canceled = false
const push = (v: number) => {
log(name, 'sub1', v, '- canceled', canceled)
s.next(v)
}
Promise.resolve().then(async () => {
push(12) // すぐに 12 を渡す
await sleep(10) // 10 秒待ってから
push(13) // 13 を渡す
push(14) // 14 を渡す
})
return () => {
log(name, 'unsub1')
canceled = true
}
})
}
テスト内容
上記 make
で Observable
を生成したのち 1 秒後に処理を止める。
unsubscribe
する
一般的なやりかたであると思われる。
問題なく動いている。
test1.ts
const test1 = async () => {
const read = make('test1').pipe(
tap(v => log('test1', 'sub2', v)),
).subscribe()
await sleep(1)
read.unsubscribe()
}
test1() /*
test1 sub1 12 - canceled false (0)
test1 sub2 12 (0)
test1 unsub1 (1)
test1 sub1 13 - canceled true (10)
test1 sub1 14 - canceled true (10)
*/
takeWhile
を使う
1 秒後に止めようとしたが、その後 9 秒経って次の値が来るまで止まっていない。
test2.ts
const test2 = async () => {
let alive = true
make('test2').pipe(
takeWhile(() => alive),
tap(v => log('test2', 'sub2', v)),
).subscribe()
await sleep(1)
alive = false
}
test2() /*
test2 sub1 12 - canceled false (0)
test2 sub2 12 (0)
test2 sub1 13 - canceled false (10)
test2 unsub1 (10)
test2 sub1 14 - canceled true (10)
*/
takeUntil
を使う
問題なく動く。
test3.ts
const test3 = async () => {
const unsub = new Subject<void>()
make('test3').pipe(
takeUntil(unsub),
tap(v => log('test3', 'sub2', v)),
).subscribe()
await sleep(1)
unsub.next()
unsub.complete()
}
test3() /*
test3 sub1 12 - canceled false (0)
test3 sub2 12 (0)
test3 unsub1 (1)
test3 sub1 13 - canceled true (10)
test3 sub1 14 - canceled true (10)
*/
unsubscribe
と takeUntil
、どちらを使うべきか?
こちらの記事 (英語)では takeUntil
がオススメされている。
手続き的な unsubscribe
よりも宣言的な takeUntil
が良いという判断だろうか?
補足
React
と RxJS
を組み合わせる場合
大規模なアプリなら redux-observable
を、小規模なら rxjs-hooks
を使うことを勧める。
古い React
を使っているなら recompose
も良い。
-
useEffect
のある今のReact
でcomponentWillUnmount
のような古い方法を使う必要はないと思う。 ↩︎
Discussion