AsyncStorage
のような永続データを読み込んで初期値にしたい。
データ読み込みのような非同期処理は基本的に初期値には出来ない。
使っていたコンポーネントの初期値指定タイミングでハマったのでメモ。
まずは非同期処理の結果を扱う基本形を見てみます。
1 2 3 4 5 6 7 8 9 |
constructor(props) { super(props); this.state = { A: [] }; storage.load("key").then(a => { this.setState({ A: a }); }); } |
Aというパラメータが空配列として読み込まれレンダリングされたのちに、setState
処理が始まり再レンダリングされます。大体のケースはこれで問題ない。
問題が起きるのはコンポーネント作成時(componentDidMount
)に必要となる値を非同期で読み込みたい場合です(これのselectedIndex
とか)。
このとき配列であればlength
などで判定することもできますが、空配列がきても最初のstate
なのかストレージに空として保存されているのか判断が出来ません。
最初にレンダリングされるときに非同期処理の結果を持っていたい。
コンストラクタでawait
処理したいところですが出来ないようになっています。
要はロードが終わるまでレンダリングさせなければいいわけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
constructor(props) { super(props); this.state = { ready: false, A: [] }; storage.load("key").then(a => { this.setState({ ready: true, A: a }); }); } render() { return !this.state.ready ? <></> : <AppContainer>...</AppContainer>; } |
state
に初期値の準備ができたかどうかを追加して、最初のレンダープロセスで空のJSXノードを返すようにします。
これはスプラッシュから画面表示まで多少の空白時間ができるので空ノードではなく専用ノードを入れるなどしてもいいかもしれない。
カラーテーマの初期設定なんかをするときに画面がちらつかなくなるも効果ある。
このとき複数の非同期読み込みを行う時は入れ子にしたり順番にawait
で読み込むと無駄な時間がかかるのでPromise.All
を活用する。
1 2 |
let r = await Promise.all([storage.load("key"), storage.load("key2")]); this.setState({ ready: true, A: r[0], B: r[1] }); |
複数プロミスを対象に全てが終わったら結果を得られるので効率的です。