blog

Reactフック

stateがオブジェクトの場合、部分的にsetStateすることはできません。 stateがオブジェクトの場合、部分的にsetStateすることはできません。 setStateは、objのアドレスが変...

Sep 4, 2020 · 7 min. read
シェア

リアクトフック

  • ステータスusestate
  • サイドエフェクト useEffect
  • コンテキスト useContext
  • Redux useReducer
  • メモuseMemo
  • 参考文献 useRef
  • カスタム・フック

useState

使用状況

const [n,setN] = React.useState(0)
const [user,setUser] = React.useState({name:''})

ローカル更新なし

stateがオブジェクトの場合、部分的にsetStateすることはできません。

ボタンをクリックすると

年齢が表示されなくなります。

なぜなら、setStateはプロパティのマージに役立たないからです。

このメソッドを使うことができます。

変更するアドレス

setState(obj), objのアドレスが変更されていない場合、Reactはデータが変更されていないとみなします。

useState 使用可能な関数

初期値が複雑な場合に使用できます。

const [state,setState] = useState(()=>{
 return initialState
})

この関数は初期状態を返し、一度だけ実行されます。

setState 受け付ける関数

setN(i=>i+1)

この方法はどのようなときに使うのですか?

import React, {useState} from "react";
import ReactDOM from "react-dom";
function App() {
 const [n, setN] = useState(0)
 const onClick = ()=>{
 setN(n+1) // setN(n+1) はnを変更しない
 setN(n+1) // nに2を加えることができないことに気づくだろう。
 // setN(i=>i+1)
 // setN(i=>i+1) // このようにして、2つの
 }
 return (
 <div className="App">
 <h1>n: {n}</h1>
 
 <button onClick={onClick}>+2</button>
 </div>
 );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

実は、この形式を使うべきなのです。

useReducer

useStateの代替です。これは => newState という形式のリデューサを受け取り、現在の状態とそれに付随するディスパッチメソッドを返します。

import React, { useState, useReducer } from "react";
import ReactDOM from "react-dom";
const initial = {
 n: 0
};
const reducer = (state, action) => {
 if (action.type === "add") {
 return { n: state.n + action.number };
 } else if (action.type === "multi") {
 return { n: state.n * 2 };
 } else {
 throw new Error("unknown type");
 }
};
function App() {
 const [state, dispatch] = useReducer(reducer, initial);
 const { n } = state;
 const onClick = () => {
 dispatch({ type: "add", number: 1 });
 };
 const onClick2 = () => {
 dispatch({ type: "add", number: 2 });
 };
 return (
 <div className="App">
 <h1>n: {n}</h1>
 <button onClick={onClick}>+1</button>
 <button onClick={onClick2}>+2</button>
 </div>
 );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

簡単に言うと、useReducerはuseStateのより複雑なバージョンです。

useContext

  • 使用C = createContext(initial) コンテキストの作成
  • <C.provider> スコープ
  • スコープ内でコンテキストを使用するには useContext(C) を使用します。
import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const C = createContext(null);
function App() {
 console.log("App 実行された");
 const [n, setN] = useState(0);
 return (
 <C.Provider value={{ n, setN }}>
 <div className="App">
 <Baba />
 </div>
 </C.Provider>
 );
}
function Baba() {
 const { n, setN } = useContext(C);
 return (
 <div>
 パパn: {n} <Child />
 </div>
 );
}
function Child() {
 const { n, setN } = useContext(C);
 const onClick = () => {
 setN(i => i + 1);
 };
 return (
 <div>
 息子: {n}
 <button onClick={onClick}>+1</button>
 </div>
 );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useEffect

副作用

  • 環境に対する変更は、document.title を変更するような副作用です。
  • useEffect に副作用を記述する必要はありません。
  • 実際には、各レンダーの後に実行されるafterRenderと呼ぶ方が良いかもしれません。
  • componentDidMountとして使用し、第2引数に[]を指定します。
  • componentDidUpdateとして使用し、依存関係を指定できます。
  • componentWillUnmountとして使用されます。
  • これら3つの使用法は、同時に存在することができます。

同時に複数の useEffect が存在する場合、それらは出現した順に実行されます。

useLayoutEffect

useEffect は、ブラウザのレンダリング終了後に実行されます。

useLayoutEffect はブラウザがレンダリングされる前に実行されます。

  • useLayoutEffect は常にuseEffectの前に実行されます。
  • useLayoutEffのタスクは、理想的にはレイアウトに影響を与えます。

ユーザーエクスペリエンスのためには、useEffectが望ましいです。

useMemo

リアクトメモ

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
 const [n, setN] = React.useState(0);
 const [m, setM] = React.useState(0);
 const onClick = () => {
 setN(n + 1);
 };
 return (
 <div className="App">
 <div>
 <button onClick={onClick}>update n {n}</button> 
 </div>
 <Child data={m}/>
 {/* <Child2 data={m}/> */}
 </div>
 );
}
function Child(props) {
 console.log("child 実行された");
 console.log('ここに多くのコードがあるとして')
 return <div>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Reactはデフォルトではレンダリングが冗長で、ボタンをクリックするとChild()が実行されますが、Childの依存データは変更されないので、このときChildの代わりにReact.memo(Child)を使うことができます。

propsが変更されなければ、別の関数コンポーネントを実行する必要はありません。

ただし、React.memoにはバグがあります。

const onClickChild = ()=>{}

このコンポーネントにリスナー関数を渡すと、リスナー関数が何もしなくても、外部コンポーネントのデータが変更されて再レンダリングされるたびにコンポーネントが実行されます。

これは、App()が再実行されるたびに、コンポーネント内のものとは異なるアドレスで新しいリスナー関数が生成されるため、このコンポーネントも実行されてしまうためです。



この問題を解決するには、useMemo を使用します。

const onClickChild = useMemo(()=>{ return console.log(m) },[m])

useMemoの特徴

  • 最初のパラメータは()=> value
  • 2番目の引数は依存関係[m,n]です。
  • 依存関係が変更された場合のみ新しい値が計算され、依存関係が変わらない場合は値が再利用されます。

値が関数の場合は、useMemo(=> console.log(x))と記述します。

これは関数を返す関数であり、使いにくいのでuseCallbackとします。

useCallback

useCallback(x=>log(x),[m]) useMemoに相当する=>x=>log(x),[m])

useRef

  • コンポーネントが何度もレンダリングされるときに同じ値が必要な場合
  • 初期化: const count=useRef(0)
  • 読み方:カウント.current
  • なぜ current が必要かというと、useRef が 2 回同じ値であることを確認するためです。

forwardRef

プロパティを渡すことはできません。

import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
 const buttonRef = useRef(null);
 return (
 <div className="App">
 <Button3 ref={buttonRef}> </Button3>
 </div>
 );
}
const Button3 = React.forwardRef((props, ref) => {
 return <button className="red" ref={ref} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useImperativeHandle

をカスタマイズするために使用されるプロパティです。

const Button2 = React.forwardRef((props, ref) => {
 const realButton = createRef(null);
 const setRef = useImperativeHandle;
 setRef(ref, () => {
 return {
 x: () => {
 realButton.current.remove();
 },
 realButton: realButton
 };
 });
 return <button ref={realButton} {...props} />;
});

カスタム・フック

データ操作のカプセル化

フック/useList.js

import { useState, useEffect } from "react";
const useList = () => {
 const [list, setList] = useState(null);
 useEffect(() => {
 ajax("/list").then(list => {
 setList(list);
 });
 }, []); // [] を実行するのは
 return {
 list: list,
 setList: setList
 };
};
export default useList;
function ajax() {
 return new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve([
 { id: 1, name: "Frank" },
 { id: 2, name: "Jack" },
 { id: 3, name: "Alice" },
 { id: 4, name: "Bob" }
 ]);
 }, 2000);
 });
}

インデックス.js

import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useList from "./hooks/useList";
function App() {
 const { list, setList } = useList();
 return (
 <div className="App">
 <h1>List</h1>
 {list ? (
 <ol>
 {list.map(item => (
 <li key={item.id}>{item.name}</li>
 ))}
 </ol>
 ) : (
 " ..."
 )}
 </div>
 );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Read next

[Web] JavaScriptにおけるコンパイルとポリフィル

「Babelの目的は、コードを受け取り、ブラウザがまだサポートしていないかもしれない新しい機能を使用し、あなたが使いたいと思うブラウザが理解できるコードに変えることです。Compile and Fill "のTyler McGinnis。 Babelはあなたのコードを古いバージョンと互換性を持たせるためにコンパイルするために使われます。 コンパイルとは...

Sep 4, 2020 · 5 min read