ReactJS:フックチートシート





良い一日、友達!



主なReactフックのガイドは次のとおりです:useState、useEffect、useLayoutEffect、useContext、useReducer、useCallback、useMemo、およびUseRef。



インスピレーション:React Hooksチートシート:一般的な問題の解決策のロックを解除します



このガイドの目的は、各フックの目的と機能の概要を説明することです。フックの説明の後に、フックを使用するためのサンプルコードと、実験用のサンドボックスがあります。



フックの完全なセットは、このリポジトリで入手できます



  1. リポジトリのダウンロード
  2. 依存関係のインストール:npm i
  3. 実行:npm start


フックは「hooks」ディレクトリにあります。メインファイルはindex.jsです。特定のフックを実行するには、対応するインポート行とレンダリング行のコメントを解除する必要があります。



それ以上の序文なし。



useState



useStateを使用すると、機能コンポーネント内の変数の状態を操作できます



可変状態


変数の状態を判別するには、初期状態を引数としてuseStateを呼び出します:useState(initialValue)。



const DeclareState = () => {
  const [count] = useState(1);
  return <div>  - {count}.</div>;
};


変数の状態を更新する


変数の状態を更新するには、useStateによって返される更新関数を呼び出します。const[state、updater] = useState(initialValue)。



コード:



const UpdateState = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1);

  return (
    <>
      <p> {age} .</p>
      <button onClick={handleClick}> !</button>
    </>
  );
};


サンドボックス:





複数の可変状態


1つの機能コンポーネントで、複数の変数の状態を定義および更新できます。



コード:



const MultStates = () => {
  const [age, setAge] = useState(19);
  const [num, setNum] = useState(1);

  const handleAge = () => setAge(age + 1);
  const handleNum = () => setNum(num + 1);

  return (
    <>
      <p> {age} .</p>
      <p>  {num}   .</p>
      <button onClick={handleAge}> !</button>
      <button onClick={handleNum}>   !</button>
    </>
  );
};


サンドボックス:





オブジェクトを使用して変数の状態を判別する


文字列と数字に加えて、オブジェクトを初期値として使用できます。useStateUpdaterは、前のオブジェクトとマージされるのではなく置き換えられるため、オブジェクト全体を渡す必要があることに注意してください。



// setState ( ) - useState ( )
// ,    - {name: "Igor"}

setState({ age: 30 });
//   
// {name: "Igor", age: 30} -  

useStateUpdater({ age: 30 });
//   
// {age: 30} -   


コード:



const StateObject = () => {
  const [state, setState] = useState({ age: 19, num: 1 });
  const handleClick = (val) =>
    setState({
      ...state,
      [val]: state[val] + 1,
    });
  const { age, num } = state;

  return (
    <>
      <p> {age} .</p>
      <p>  {num}   .</p>
      <button onClick={() => handleClick('age')}> !</button>
      <button onClick={() => handleClick('num')}>   !</button>
    </>
  );


サンドボックス:





関数を使用して変数の状態を初期化する


変数の状態の初期値は、関数によって決定できます。



const StateFun = () => {
  const [token] = useState(() => {
    const token = localStorage.getItem("token");
    return token || "default-token";
  });

  return <div> - {token}</div>;
};


setStateの代わりに関数


useStateによって返される更新関数は、setStateだけではありません。



const [value, updateValue] = useState(0);
//    ,  ,  
updateValue(1);
updateValue((prevVal) => prevVal + 1);


2番目の方法は、更新が前の状態に依存する場合に適しています。



コード:



const CounterState = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <p>   {count}.</p>
      <button onClick={() => setCount(0)}></button>
      <button onClick={() => setCount((prevVal) => prevVal + 1)}>
         (+)
      </button>
      <button onClick={() => setCount((prevVal) => prevVal - 1)}>
         (-)
      </button>
    </>
  );
};


サンドボックス:





useEffect



useEffectは、追加の(副作用)効果を担当する関数を受け入れます。



基本的な使い方


コード:



const BasicEffect = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1);

  useEffect(() => {
    document.title = ` ${age} !`;
  });

  return (
    <>
      <p>      .</p>
      <button onClick={handleClick}> !</button>
    </>
  );
};


サンドボックス:





エフェクトの削除(キャンセル)


しばらくしてから効果を取り除くのが一般的な方法です。これは、useEffectに渡されたエフェクトによって返される関数を使用して実行できます。以下は、addEventListenerの例です。



コード:



const CleanupEffect = () => {
  useEffect(() => {
    const clicked = () => console.log("!");
    window.addEventListener("click", clicked);

    return () => {
      window.removeEventListener("click", clicked);
    };
  }, []);

  return (
    <>
      <p>        .</p>
    </>
  );
};


サンドボックス:





複数の効果


機能コンポーネントでは、複数のuseEffectsを使用できます。



コード:



const MultEffects = () => {
  //   
  useEffect(() => {
    const clicked = () => console.log("!");
    window.addEventListener("click", clicked);

    return () => {
      window.removeEventListener("click", clicked);
    };
  }, []);

  //   
  useEffect(() => {
    console.log(" .");
  });

  return (
    <>
      <p>  .</p>
    </>
  );
};


サンドボックス:







再レンダリングでのuseEffectの呼び出しは、2番目の引数として空の配列を渡すことでスキップできることに注意してください。



効果の依存関係


コード:



const EffectDependency = () => {
  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1)

  useEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1)
  }, [randomInt]);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


サンドボックス:







この場合、randomInt依存関係をuseEffectに2番目の引数として渡しているため、関数は最初のレンダリング時、およびrandomIntが変更されるたびに呼び出されます。



スキップ効果(空の配列依存関係)


以下の例では、useEffectに依存関係として空の配列が渡されるため、エフェクトは初期レンダリングでのみ機能します。



コード:



const SkipEffect = () => {
  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1);

  useEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1);
  }, []);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


サンドボックス:







ボタンがクリックされても、useEffectは呼び出されません。



スキップ効果(依存関係なし)


依存関係の配列がない場合、ページがレンダリングされるたびにエフェクトがトリガーされます。



useEffect(() => {
  console.log(
    "        ."
  );
});


useContext



useContextを使用すると、コンテキストコンシューマに依存する必要がなくなります。MyContext.Consumerに比べてインターフェイスがシンプルで、小道具をレンダリングします。以下は、useContextとContext.Consumerを使用したコンテキストの使用の比較です。



//    Context
const ThemeContext = React.createContext("dark")

//   
function Button() {
    return (
        <ThemeContext.Consumer>
            {theme => <button className={thene}> !</button>}
        </ThemeContext.Consumer>
}

//  useContext
import { useContext } from "react"

function ButtonHook() {
    const theme = useContext(ThemeContext)
    return <button className={theme}> !</button>
}


コード:



const ChangeTheme = () => {
  const [mode, setMode] = useState("light");

  const handleClick = () => {
    setMode(mode === "light" ? "dark" : "light");
  };

  const ThemeContext = React.createContext(mode);

  const theme = useContext(ThemeContext);

  return (
    <div
      style={{
        background: theme === "light" ? "#eee" : "#222",
        color: theme === "light" ? "#222" : "#eee",
        display: "grid",
        placeItems: "center",
        minWidth: "320px",
        minHeight: "320px",
        borderRadius: "4px",
      }}
    >
      <p> : {theme}.</p>
      <button onClick={handleClick}>  </button>
    </div>
  );
};


サンドボックス:





useLayoutEffect



useLayoutEffect の動作は、useEffectの動作似ていますが、いくつかの例外があります。これについては後で説明します。



  useLayoutEffect(() => {
    // 
  }, []);


基本的な使い方


これはuseEffectを使用した例ですが、useLayoutEffectを使用しています。



コード:



  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1);

  useLayoutEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1);
  }, [randomInt]);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


サンドボックス:





useLayoutEffectおよびuseEffect


useEffectに渡された関数は、ページがレンダリングされた後に呼び出されます。要素のレイアウトとレンダリングの形成後。これは、流れを妨げてはならないほとんどの追加効果に適しています。ただし、たとえば、追加の効果としてDOM操作を実行する場合、useEffectは最良の選択ではありません。ユーザーに変更が表示されないようにするには、useLayoutEffectを使用する必要があります。useLayoutEffectに渡される関数は、ページがレンダリングされる前に呼び出されます。



useReducer



useReduceruseStateの代わりに使用できますが、その目的は、状態が前の値に依存している場合、または複数の状態がある場合に、状態を操作するための複雑なロジックをカプセル化することです。



基本的な使い方


以下の例では、useStateの代わりにuseReducerが使用されています。useReducer呼び出しは、状態値とディスパッチ関数を返します。



コード:



const initialState = { width: 30 };

const reducer = (state, action) => {
  switch (action) {
    case "plus":
      return { width: Math.min(state.width + 30, 600) };
    case "minus":
      return { width: Math.max(state.width - 30, 30) };
    default:
      throw new Error(" ?");
  }
};

const BasicReducer = () => {
  const [state, dispath] = useReducer(reducer, initialState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => dispath("plus")}>
          .
      </button>
      <button onClick={() => dispath("minus")}>
          .
      </button>
    </>
  );
};


サンドボックス:





遅延(「遅延」)状態の初期化


useReducerは、3番目のオプションの引数、つまり状態オブジェクトを返す関数を取ります。この関数は、2番目の引数としてinitialStateを使用して呼び出されます。



コード:



const initializeState = () => ({
  width: 90,
});

//  ,  initializeState   
const initialState = { width: 0 };

const reducer = (state, action) => {
  switch (action) {
    case "plus":
      return { width: Math.min(state.width + 30, 600) };
    case "minus":
      return { width: Math.max(state.width - 30, 30) };
    default:
      throw new Error(" ?");
  }
};

const LazyState = () => {
  const [state, dispath] = useReducer(reducer, initialState, initializeState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => dispath("plus")}>
          .
      </button>
      <button onClick={() => dispath("minus")}>
          .
      </button>
    </>
  );
};


サンドボックス:





this.setStateの動作のシミュレーション


useReducerは、Reduxよりも厳密性の低いレデューサーを使用します。たとえば、レデューサーに渡される2番目の引数はtypeプロパティを必要としません。これは私たちに興味深い機会を提供します。



コード:



const initialState = { width: 30 };

const reducer = (state, newState) => ({
  ...state,
  width: newState.width,
});

const NewState = () => {
  const [state, setState] = useReducer(reducer, initialState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => setState({ width: 300 })}>
          .
      </button>
      <button onClick={() => setState({ width: 30 })}>
          .
      </button>
    </>
  );
};


サンドボックス:





useCallback



useCallbackは、保存された(キャッシュされた)コールバックを返します。



スターターテンプレート


コード:



const CallbackTemplate = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";
  const doSomething = () => someValue;

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


サンドボックス:







上記の例では、ボタンがクリックされると、Ageコンポーネントが更新され、再レンダリングされます。新しいコールバックがdoSomethingプロパティに渡されると、Guideコンポーネントも再レンダリングされます。GuideはReact.memoを使用してパフォーマンスを最適化しますが、それでも再描画されます。どうすればこれを修正できますか?



基本的な使い方


コード:



const BasicCallback = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";
  const doSomething = useCallback(() => someValue, [someValue]);

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


サンドボックス:







組み込みのuseCallback


useCallbackは、組み込み関数として使用できます。



コード:



const InlineCallback = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={useCallback(() => someValue, [someValue])} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


サンドボックス:





useMemo



useMemoは、保存された(キャッシュされた)値を返します。



スターターテンプレート


コード:



const MemoTemplate = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = { value: "some value" };
  const doSomething = () => someValue;

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


サンドボックス:





このテンプレートは、someValueが文字列ではなくオブジェクトであることを除いて、最初のuseCallbackテンプレートと同じです。React.memoを使用しているにもかかわらず、ガイドコンポーネントも再レンダリングされます。



しかし、なぜこれが起こっているのですか?結局のところ、オブジェクトは参照によって比較され、someValueへの参照はレンダリングごとに変化します。何か案は?



基本的な使い方


doSomethingによって返される値は、useMemoを使用して保存できます。これにより、不要なレンダリングを防ぐことができます。



コード:



const BasicMemo = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = () => ({ value: "some value" });
  const doSomething = useMemo(() => someValue, []);

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


サンドボックス:





useRef


useRefはrefオブジェクトを返します。このオブジェクトの値は、「current」プロパティから入手できます。このプロパティには、初期値useRef(initialValue)を割り当てることができます。refオブジェクトは、コンポーネントの存続期間中存在します。



DOMへのアクセス


コード:



const DomAccess = () => {
  const textareaEl = useRef(null);
  const handleClick = () => {
    textareaEl.current.value =
      " - ,     . ,    !";
    textareaEl.current.focus();
  };
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <button onClick={handleClick}>
         .
      </button>
      <label htmlFor="msg">
                 .
      </label>
      <textarea ref={textareaEl} id="msg" />
    </>
  );
};


サンドボックス:





インスタンスのような変数(ジェネリック)


refオブジェクトには、DOM要素へのポインタだけでなく、任意の値を含めることができます。



コード:



const StringVal = () => {
  const textareaEl = useRef(null);
  const stringVal = useRef(
    " - ,     . ,    !"
  );
  const handleClick = () => {
    textareaEl.current.value = stringVal.current;
    textareaEl.current.focus();
  };
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <button onClick={handleClick}>
         .
      </button>
      <label htmlFor="msg">
               .
      </label>
      <textarea ref={textareaEl} id="msg" />
    </>
  );
};


サンドボックス:





useRefを使用して、後で停止するためのタイマー識別子を保存できます。



コード:



const IntervalRef = () => {
  const [time, setTime] = useState(0);
  const setIntervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      setTime((time) => (time = new Date().toLocaleTimeString()));
    }, 1000);

    setIntervalRef.current = id;

    return () => clearInterval(setIntervalRef.current);
  }, [time]);

  return (
    <>
      <p> :</p>
      <time>{time}</time>
    </>
  );
};


サンドボックス:





この記事を楽しんでいただけたでしょうか。清聴ありがとうございました。



All Articles