「Reactハンズオンラーニング」の感想・備忘録3

スポンサーリンク
「Reactハンズオンラーニング」の感想・備忘録2の続き

フック

7.1.1 依存配列

  • useEffectを使った処理をカスタムフックとして定義すると、利用する側にはuseEffectを隠蔽することができる。
  • 以下の例では、配列postsを描画するだけでよくなる。
import {useEffect, useState} from 'react'

function usePosts() {
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    // 記事のサブスクライブなど
    setPosts(["記事1", "記事2"])
  }, [])
  return posts;
}

export default usePosts

利用時

import usePosts from './effect-hooks'
function Effect() {
  const posts = usePosts()
  return (
    <div style={{border: "1px solid #ccc", padding: "50px"}}>
      <h2>Effect.js</h2>
      {posts.map(post=><p>{post}</p>)}
    </div>
  );
}

export default Effect;

7.1.2 依存配列の同一性チェック

  • useEffectなどの第2引数の依存配列は値がプリミティブ値の場合は問題ないが、配列やオブジェクトなどの参照型の場合は生成するタイミングを考慮しなければならない。
  • 例えば、以下の場合は再描画の度に配列が生成されるため、useEffect内の処理が毎回実行されてしまう。
 import {useEffect, useState} from 'react'
 
 function Effect2() {
   const [, setText] = useState("");
   const words = ["aaa", "bbb"]
   useEffect(() => {
     window.addEventListener("keydown", setText)
     console.log("Effect2 useEffect called");
   }, [words])
   return (
     <div style={{border: "1px solid #ccc", padding: "50px"}}>
     <h2>Effect2.js</h2>
     </div>
   );
 }
 
 export default Effect2;

以下のように、配列やオブジェクトを関数の外で初期化すると再描画時は不変となる。

 import {useEffect, useState} from 'react'
 
 const words = ["aaa", "bbb"]
 function Effect2() {
   const [, setText] = useState("");
   useEffect(() => {
     window.addEventListener("keydown", setText)
     console.log("Effect2 useEffect called");
   }, [words])
   return (
     <div style={{border: "1px solid #ccc", padding: "50px"}}>
     <h2>Effect2.js</h2>
     </div>
   );
 }
 
 export default Effect2;

useMemo

  • 上記の例では変数の宣言位置を変えるだけで対応できたが、この方法が使えない場合もある。
  • 例えば、以下のようにプロパティの値から配列を生成し、それを依存配列の要素にする場合、描画の度に配列が生成されてしまう。
import {useEffect, useState} from 'react'

function Effect3({str}) {
  const words = str.split(" ")
  const [, setText] = useState("");
  useEffect(() => {
    window.addEventListener("keydown", setText)
    console.log("Effect3 useEffect called");
  }, [words])
  return (
    <div style={{border: "1px solid #ccc", padding: "50px"}}>
      <h2>Effect3.js</h2>
    </div>
  );
}

export default Effect3;

このような場合はuseMemoフックを使う。 useMemoはメモ化された値を返す。

import {useEffect, useState, useMemo} from 'react'

function Effect3({str}) {
  const words = useMemo(() => str.split(" "), [str])
  const [, setText] = useState("");
  useEffect(() => {
    window.addEventListener("keydown", setText)
    console.log("Effect3 useEffect called");
  }, [words])
  return (
    <div style={{border: "1px solid #ccc", padding: "50px"}}>
      <h2>Effect3.js</h2>
    </div>
  );
}

export default Effect3;

useCallback

  • useMemoがメモ化された値を返すのに対し、useCallbackはメモ化された関数を返す。
  • 例えば以下の関数fnは、描画の度に生成されてしまう。
    (KeyDownの度にstateが更新される⇒コンポーネントが再描画される⇒関数fnが生成される⇒useEffectが毎回実行される)
import {useEffect, useState} from 'react'

function Effect4() {
  const [, setText] = useState("");
  const fn = ()=> {
    console.log("fn")
  }
  useEffect(() => {
    window.addEventListener("keydown", setText)
    console.log("Effect4 useEffect called");
    fn()
  }, [fn])
  return (
    <div style={{border: "1px solid #ccc", padding: "50px"}}>
      <h2>Effect4.js</h2>
    </div>
  );
}

export default Effect4;

useCallbackを使うと関数定義がキャッシュされるため、再描画しても関数fnは再生成されない。

import {useEffect, useState, useCallback} from 'react'

function Effect4() {
  const [, setText] = useState("");
  const fn = useCallback(()=> {
    console.log("fn")
  },[])
  useEffect(() => {
    window.addEventListener("keydown", setText)
    console.log("Effect4 useEffect called");
    fn()
  }, [fn])
  return (
    <div style={{border: "1px solid #ccc", padding: "50px"}}>
      <h2>Effect4.js</h2>
    </div>
  );
}

export default Effect4;

コメント