React Nativeでは非同期関数としてcomponentDidMount()
を使用することをお勧めしますか、それとも避けるべきですか?
コンポーネントをマウントするときにAsyncStorage
から情報を取得する必要がありますが、それを可能にする唯一の方法はcomponentDidMount()
関数を非同期にすることです。
async componentDidMount() {
let auth = await this.getAuth();
if (auth)
this.checkAuth(auth);
}
それに関して何か問題はありますか、そしてこの問題に対する他の解決策がありますか?
違いを指摘し、それがどのように問題を引き起こす可能性があるかを判断することから始めましょう。
これがasyncと "sync" componentDidMount()
ライフサイクルメソッドのコードです。
// This is TypeScript code
componentDidMount(): void { /* do something */ }
async componentDidMount(): Promise<void> {
/* do something */
/* You can use "await" here */
}
コードを見て、私は以下の違いを指摘することができます。
async
キーワード:TypeScriptでは、これは単なるコードマーカーです。それは2つのことをします:void
ではなくPromise<void>
にする。戻り値の型を明示的に非約束に指定した場合(例:void)、TypeScriptはエラーを表示します。await
キーワードを使用することを許可します。void
からPromise<void>
に変更されました。async someMethod(): Promise<void> { await componentDidMount(); }
メソッド内でawait
キーワードを使用して、一時的にその実行を一時停止することができます。このような:
async componentDidMount(): Promise<void> {
const users = await axios.get<string>("http://localhost:9001/users");
const questions = await axios.get<string>("http://localhost:9001/questions");
// Sleep for 10 seconds
await new Promise(resolve => { setTimeout(resolve, 10000); });
// This line of code will be executed after 10+ seconds
this.setState({users, questions});
return Promise.resolve();
}
では、どうすれば問題が発生する可能性がありますか?
async
キーワードは絶対に無害です。戻り値の型Promise<void>
も無害であるため、componentDidMount()
メソッドを呼び出す必要がある状況は想像できません。
await
キーワードを指定せずに戻り型がPromise<void>
のメソッドを呼び出しても、戻り型がvoid
のメソッドを呼び出しても違いはありません。
componentDidMount()
の実行を遅らせた後にはライフサイクルのメソッドがないので、かなり安全なようです。しかし、落とし穴があります。
たとえば、上記のthis.setState({users, questions});
は10秒後に実行されます。遅れる時間の真っ只中に、別の...
this.setState({users: newerUsers, questions: newerQuestions});
...は正常に実行され、DOMは更新されました。結果はユーザーに見えました。時計は刻々と過ぎて10秒が経過した。遅延したthis.setState(...)
が実行され、DOMが再び更新されます。そのときは、古いユーザーと古い質問があります。結果はユーザーにも表示されます。
=> async
をcomponentDidMount()
メソッドと一緒に使用するのはかなり安全です(100%はよくわかりません)。私はそれが大好きで、今まであまりにも頭痛の種になるような問題に遭遇したことはありません。
あなたのコードは上手く読めます。これを参照してください Dale Jefferson氏の記事 では、非同期のcomponentDidMount
の例が示されていますが、これも非常に優れています。
しかし、コードを読んでいる人はReactが返された約束で何かをすると仮定するかもしれないと言う人もいるでしょう。
そのため、このコードの解釈、およびそれがグッドプラクティスであるかどうかは、非常に個人的なものです。
別の解決策が必要な場合は、 約束 を使用できます。例えば:
componentDidMount() {
fetch(this.getAuth())
.then(auth => {
if (auth) this.checkAuth(auth)
})
}
自分がしていることを知っていれば大丈夫だと思います。 componentWillUnmount
が実行され、コンポーネントがアンマウントされた後もasync componentDidMount()
が実行されている可能性があるため、混乱を招く可能性があります。
componentDidMount
内で同期と非同期の両方のタスクを開始することもできます。 componentDidMount
が非同期の場合は、すべての同期コードを最初のawait
の前に配置する必要があります。最初のawait
より前のコードが同期的に実行されることは、誰かには明らかではないかもしれません。この場合、私はおそらくcomponentDidMount
を同期させておくでしょうが、それにsyncとasyncメソッドを呼び出させるでしょう。
async componentDidMount()
vs sync componentDidMount()
を呼び出すasync
メソッドを選択するかどうかにかかわらず、コンポーネントがアンマウントされたときにまだ実行されている可能性があるリスナーまたは非同期メソッドを必ずクリーンアップする必要があります。
更新:
(私のビルド:React 16、Webpack 4、Babel 7):
Babel 7を使うと、次のことがわかります。
このパターンを使用して...
async componentDidMount() {
try {
const res = await fetch(config.discover.url);
const data = await res.json();
console.log(data);
} catch(e) {
console.error(e);
}
}
あなたは次のエラーに遭遇するでしょう...
参照されていない参照エラー:regeneratorRuntimeが定義されていません
この場合、babel-plugin-transform-runtimeをインストールする必要があります。
https://babeljs.io/docs/en/babel-plugin-transform-runtime.html
何らかの理由で上記のパッケージ(babel-plugin-transform-runtime)をインストールしたくない場合は、Promiseパターンに固執する必要があります。
componentDidMount() {
fetch(config.discover.url)
.then(res => res.json())
.then(data => {
console.log(data);
})
.catch(err => console.error(err));
}
私はいくつかの研究を行いましたが、1つの重要な違いを見つけました:Reactは非同期ライフサイクルメソッドからのエラーを処理しません。
したがって、次のように記述した場合:
componentDidMount()
{
throw new Error('I crashed!');
}
エラーは error boundry でキャッチされ、それを処理して適切なメッセージを表示できます。
このようにコードを変更した場合:
async componentDidMount()
{
throw new Error('I crashed!');
}
これはこれと同等です:
componentDidMount()
{
return Promise.reject(new Error('I crashed!'));
}
エラーは静かに飲み込まれます。恥を知れ、React ...
それでは、どのようにエラーを処理しますか?唯一の方法は、このような明示的なキャッチのようです:
async componentDidMount()
{
try
{
await myAsyncFunction();
}
catch(error)
{
//...
}
}
またはこのように:
componentDidMount()
{
myAsyncFunction()
.catch(()=>
{
//...
});
}
それでもエラー境界が豊富なエラーが必要な場合は、次のトリックを考えることができます。
render
メソッドからエラーをスローします。例:
class BuggyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
buggyAsyncfunction(){ return Promise.reject(new Error('I crashed async!'));}
async componentDidMount() {
try
{
await this.buggyAsyncfunction();
}
catch(error)
{
this.setState({error: error});
}
}
render() {
if(this.state.error)
throw this.state.error;
return <h1>I am OK</h1>;
}
}
実際のところ、Reactは従来のライフサイクルメソッド(componentWillMount、componentWillReceiveProps、componentWillUpdate)からAsync Renderingへと移行するので、ComponentDidMountでの非同期ロードは推奨設計パターンです。
このブログ記事は、これがなぜ安全なのかを説明し、ComponentDidMountでの非同期ロードの例を提供するのに非常に役立ちます。
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
componentDidMount
キーワードを指定せずにasync
を使用すると、ドキュメントには次のように表示されます。
あなたはすぐにcomponentDidMount()でsetState()を呼び出すことができます。これは余分なレンダリングを引き起こしますが、ブラウザが画面を更新する前に起こります。
async componentDidMount
を使用すると、この機能を失うことになります。ブラウザが画面を更新した後に、別のレンダリングが行われます。ただし、データの取得など、非同期の使用を検討している場合は、ブラウザが2回画面を更新することを避けられません。別の世界では、ブラウザが画面を更新する前にcomponentDidMountを一時停止することはできません。