6 Matching Annotations
  1. Mar 2021
    1. 副作用内からなぜ関数を返したのか? これこそが副作用のクリーンアップのためのオプションの仕組みです。すべての副作用は、それをクリーンアップするための関数を返すことができます。これにより購読を開始するためのロジックと解除するためのロジックを並べて書くことができます。両方とも同じ副作用の一部なのです! React は具体的には副作用のクリーンアップをいつ発生させるのか? React はコンポーネントがアンマウントされるときにクリーンアップを実行します。しかし、すでに学んだ通り、副作用は 1 回だけでなく毎回のレンダー時に実行されます。このため React は、ひとつ前のレンダーによる副作用を、次回の副作用を実行する前にもクリーンアップします。この後で、これがなぜバグの回避につながるのか、そしてこれがパフォーマンスの問題を引き起こしている場合にどのようにしてこの挙動を止めるのかについて説明します。 補足 副作用から名前付きの関数を返す必要はありません。ここでは目的を明示するために cleanup という名前にしましたが、アロー関数を返すことも別の名前を付けることも可能です。 まとめ useEffect を用いることで、コンポーネントのレンダー後に実行される様々な種類の副作用を表現できることを学びました。いくつかの副作用はクリーンアップが必要である可能性があり、その場合は関数を返します: useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); クリーンアップフェーズが必要ない副作用もあり、その場合は何も返す必要はありません。 useEffect(() => { document.title = `You clicked ${count} times`; }); 副作用フックは両方のユースケースをひとつの API に統合します。 副作用フックの動作について十分わかったと感じる場合や、逆にもううんざりだという場合は、ここで次のページ(フックのルールについて)に進んでも構いません。 副作用を使う場合のヒント このページの残りの部分では、経験のある React 利用者が興味を持つかもしれない useEffect の深い概念について説明します。今すぐ読み進める必要があるとは思わないでください。副作用フックについて詳細が知りたくなったらいつでもこのページに戻ってくればいいのです。 ヒント:関心を分離するために複数の副作用を使う フックを導入する動機のページで述べた問題のひとつは、しばしばそれぞれのライフサイクルメソッドに関連のないロジックが含まれ、一方で関連するロジックが複数のメソッドに分割されてしまう、ということです。以下に示すのは、これまでの例で挙げたカウンタとフレンド状態インジケータとを組み合わせたコンポーネントです。 class FriendStatusWithCounter extends React.Component { constructor(props) { super(props); this.state = { count: 0, isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { document.title = `You clicked ${this.state.count} times`; ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } // ... ここで、document.title を設定するためのロジックが componentDidMount と componentDidUpdate に分離してしまっていることに注意してください。データ購読のためのロジックもやはり componentDidMount と componentWillUnmount とに分離しています。そして componentDidMount には両方の仕事のためのコードが含まれています。 ではフックはどのようにこの問題を解決するのでしょうか? ステートフックを複数回呼べるのと同様の方法で、副作用を複数回利用することができます。このため、互いに関係のないロジックは別の副作用に分離することが可能です。 function FriendStatusWithCounter(props) { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); // ... } フックを使うことで、ライフサイクルのメソッド名に基づくのではなく、実際に何をやっているのかに基づいてコードを分割できます。React はコンポーネントで利用されているすべての副作用を、指定されている順番で適用していきます。 解説:なぜ副作用は毎回の更新ごとに実行されるのか クラスに慣れていれば、なぜクリーンアップフェーズは、アンマウント時の 1 度だけではなく再レンダー時に毎回発生するのか、と不思議に思っているかもしれません。実践的な例で、この設計によりなぜバグの少ないコンポーネントが作れるようになるのか見てみましょう。 このページの前の部分で、フレンドがオンラインかどうかを表示する FriendStatus コンポーネントの例を示しました。このクラスでは this.props の中にある friend.id を参照して、コンポーネントがマウントした後にフレンドのステータスを購読し、アンマウント時には購読を解除します: componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } ですがコンポーネントが表示されている最中に friend プロパティが変わったらどうなるのでしょうか? このコンポーネントは間違ったフレンドのオンラインステータスを表示し続けてしまいます。これはバグです。しかも誤ったフレンド ID を使って購読解除を呼び出してしまうため、アンマウント時にメモリリークやクラッシュを引き起こしてしまうでしょう。 クラスコンポーネントの場合は、このようなケースに対処するために componentDidUpdate を加える必要がありました。 componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate(prevProps) { // Unsubscribe from the previous friend.id ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // Subscribe to the next friend.id ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } 適切な componentDidUpdate 処理をし忘れることが、React アプリケーションにおけるよくあるバグの原因となっていました。 ではこのコンポーネントのフックを利用したバージョンを見てみましょう。 function FriendStatus(props) { // ... useEffect(() => { // ... ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); 動作は変わっておらず、前述のバグも起こらなくなります。 useEffect はデフォルトで更新を処理するため、更新のための特別なコードは不要です。新しい副作用を適用する前に、ひとつ前の副作用をクリーンアップします。これを例示するため、このコンポーネントが経時的に発生させる可能性のある購読登録と購読解除のシーケンスを示します: // Mount with { friend: { id: 100 } } props ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect // Update with { friend: { id: 200 } } props ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect // Update with { friend: { id: 300 } } props ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect // Unmount ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect この挙動によりデフォルトで一貫性を保証することができ、クラスコンポーネントでよく見られた更新ロジック書き忘れによるバグを防止することができます。 ヒント:副作用のスキップによるパフォーマンス改善 いくつかの場合では、副作用のクリーンアップと適用とをレンダーごとに毎回行うことはパフォーマンスの問題を引き起こす可能性があります。クラスコンポーネントの場合、この問題は componentDidUpdate の内部で prevProps や prevState と比較するコードを加えることで解決できました。 componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { document.title = `You clicked ${this.state.count} times`; } } これはよくある要求なので、useEffect フックの API にはこの動作が組み込まれています。再レンダー間で特定の値が変わっていない場合には副作用の適用をスキップするよう、React に伝えることができるのです。そのためには、useEffect のオプションの第 2 引数として配列を渡してください。 useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // Only re-run the effect if count changes 上記の例では、第 2 引数として [count] を渡しています。どういう意味でしょうか? もし count が 5 で、次回のコンポーネントのレンダー時にも count がまだ 5 であった場合、React は前回のレンダー時に覚えておいた [5] と今回のレンダーの [5] とを比較します。配列内のすべての要素が同一 (5 === 5) ですので、React は副作用をスキップします。これが最適化です。 再レンダー時に count が 6 に変更されている場合、前回レンダー時に覚えておいた [5] と今回のレンダー時の [6] という配列とを比較します。今回は 5 !== 6 ですので React は副作用を再適用します。配列内に複数の要素がある場合、React は配列内の要素のうちひとつでも変わっている場合に副作用を再実行します。 クリーンアップフェーズがある副作用でも同様に動作します: useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }, [props.friend.id]); // Only re-subscribe if props.friend.id changes 将来的には、ビルド時の変換で第 2 引数を自動で加えられるようになるかもしれません。 補足 この最適化を利用する場合、時間の経過とともに変化し副作用によって利用される、コンポーネントスコープの値(props や state など)がすべて配列に含まれていることを確認してください。さもないとあなたのコードは以前のレンダー時の古い値を参照してしまうことになります。その他の最適化のオプションについてはフック API リファレンスで説明しています。 もしも副作用とそのクリーンアップを 1 度だけ(マウント時とアンマウント時にのみ)実行したいという場合、空の配列 ([]) を第 2 引数として渡すことができます。こうすることで、あなたの副作用は props や state の値のいずれにも依存していないため再実行する必要が一切ない、ということを React に伝えることができます。これは特別なケースとして処理されているわけではなく、依存配列を普通に処理すればそうなるというだけの話です。 空の配列 ([]) を渡した場合、副作用内では props と state の値は常にその初期値のままになります。[] を渡すことはおなじみの componentDidMount と componentWillUnmount による概念と似ているように感じるでしょうが、通常はこちらやこちらのように、副作用を過度に再実行しないためのよりよい解決方法があります。また useEffect はブラウザが描画し終えた後まで遅延されますので、追加の作業をしてもそれほど問題にならないということもお忘れなく。 eslint-plugin-react-hooks パッケージの exhaustive-deps ルールを有効にすることをお勧めします。これは依存の配列が正しく記述されていない場合に警告し、修正を提案します。 次のステップ おめでとうございます! 長いページでしたが、最終的に副作用に関するほとんどの疑問が解決していることを望みます。これでステートフックと副作用フックの両方を学んだので、それらを組み合わせてやれることがたくさんあります。クラスコンポーネントにおけるほとんどのユースケースがカバーされていますが、足りない部分については他のフックが役立つかもしれません。 また、動機のところで述べた問題をフックがどのように解決するのかについてもわかり始めてきたでしょう。副作用のクリーンアップがどのようにして componentDidUpdate と componentWillUnmount との間でのコードの重複を防ぎ、関係したコードを並べて書くことができるようにし、バグの少ないコードを記述できるようにするのかを見てきました。また目的別に副作用を分割する方法も学びましたが、これはクラスでは全く不可能なことでした。 この時点で、一体フックがどのように動作しているのか疑問に感じているかもしれません。useState のそれぞれの呼び出しがどの state 変数に対応しているのかを、React はどのようにして知るのでしょうか? 更新のたびに、前回と今回の副作用とを React はどのように対応付けるのでしょうか? 次のページではフックのルールについて学びます — このルールはフックが動作するために必須のものです。Is this page useful?このページを編集する

      useEffectで返されている関数は、 クラスコンポーネントにおけるcomponentWillUnmountの部分に実装する処理を書く部分だと理解しておけば良さそう。 つまり、コンポーネントがアンマウントされるときに実行される

    1. どの程度の精度を求めるには、どの程度の桁数が必要となるか、まとめてみましょう。 ざっくりと計算しますと地球の円周は約40,000,000mで 緯度はぐるっと360度ですから緯度1度は 40000000/360≒111111.1111... で約111,111mになります。 経度緯度のDBカラムの桁数 【OKWave】 http://okwave.jp/qa/q4506558.html

      地球の円周は約4万km

    1. 広告配信のためのキーワード収集 リスティング広告を出稿する際や新たにキーワードを追加する際に、キーワード収集方法のひとつとして利用することができます。 たとえば、リスティング広告に関する知識がほとんどないという場合、どういったキーワードで広告を出稿していけばよいか、すぐにはイメージがつきにくいかもしれません。

      競合のURLからキーワードを収集することで、コンテンツを作る際の新しいキーワードを見つける事もできそう。

      これと合わせて、競合サイトのサイトマップを作って強豪の把握が必須だな

    2. コンテンツ作成のためのキーワード選定 自然検索経由の流入増を目的としてコンテンツを作成する場合、具体的にどんなキーワードでの上位表示を狙うのかを決めることは必須です。 その際、狙うキーワードの選定材料のひとつとしてキーワードの検索ボリュームが重要になります。 自分の中でこれは上位表示を狙えそうだというキーワードの候補があったとしても、そのキーワードの月間検索数が10以下であれば、本当に狙ってコンテンツを作成していくべきか、優先順位含め考えなおす必要があります。 では単純に検索ボリュームが大きなキーワードを狙えばいいのでしょうか。もちろんそうではなく、そのキーワードの競合性やユーザーニーズなど様々な要素を考慮する必要があります。 ユーザーニーズの把握 狙うキーワードを決めた後、そのキーワードに関連するキーワードを確認することで、どのようなニーズがあるのかを把握することができます。

      キーワードプランナーでできること

    1. when you extracting a part of the existing component to assemble a new component for whatever reason (e.g., reuse / isolate / simplify / performance), it will bring us back to the problem of having margin on the top-level again :(

      トップレベルではなく、コンポーネント内でマージンを使おうことは一見問題なさそうに見えるが、 その要素を新たなコンポーネントとして切り出そうとすると、トップレベルのマージン問題と同じ問題が発生するため避けたほうが良い

    2. First of all, I would say it is not good to use margin on the component’s top-level, because it breaks the isolation of components.// A React component with margin set on top-levelconst MyComponent = () => { return <div style="margin: 1rem">...</div>;};margin here is not just affecting the component itself, but also affecting other components (e.g., it is pushing off another component / element sitting next to it)If we want to place this component in a different context or layout, margin would get in the way of resuse

      当然ながら、コンポーネントのトップレベルでマージンを使うことは次の2つの理由から良くない。

      • コンポーネント外の他のコンポーネントに影響を与えてしまう。(例えば、位置を変えてしまうなどの副作用)
      • 他のレイアウトやコンテキストでコンポーネントを再利用しづらくなってしまう