0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[React Hooks] 基礎

0
Posted at

Ver16.8(2019)から導入された新機能

Classコンポーネントを使わずに、Functionalコンポーネントを使います。
これによって、コーディングの簡素化、可読性の向上が実現されます。

useState

useStateのおかげで、状態の保持や変更が可能になりました。

Props Drilling問題の解決

a/b/cという階層になっていた場合、aからcに値を私たければ、
propsを経由してa→b→cの順序で辿る必要がありました。

Hooksでは、useContextを使ってa→cに直接値を渡すことが可能になりました。

Reduxの使用を最小限にできる

Hooksが持っている、useContext, useReducerを組み合わせることで、Reduxに近いことができるので「useContext, useReducerで解決できない複雑なことだけReduxを使う」という
ことができるようになりました。

Functional Components

stateもlifecycleも持たず、propsを受け取りElementを返すだけのコンポーネントです。

Props

渡す値に応じて結果が動的に変わるようにできる。

↓app.jsxからname="successful man"をbasic.jsxに渡し、それを利用します。

app.jsx
import Basic1 from "./components/Basic1";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Basic1 name="successful man" />
      </header>
    </div>
  );
}

export default App;
Basic.jsx
import React from 'react'

const basic1 = (props) => {
  return (
    <>
      <h1>You are {props.name} </h1>
    </>
  )
}

export default basic1

イベントハンドラー

buttonにonClickイベントを付与する。

Basic.jsx
import React from 'react'

const basic1 = (props) => {
  const clickHandler = () => {
    console.log("成功");
  };
  return (
    <>
      <button onClick={clickHandler}>Click</button>
      <h1>You are {props.name} </h1>
    </>
  )
}

export default basic1

useState

↓オブジェクトに対して使う
↓プロダクトの名前と価格をフォームに入力すると表示されるプログラム

Basic.jsx
import React, {useState} from 'react';

const Basic1 = () => {

  const [product, setProducts] = useState({name: "", price: ""})

  return (
    <>
      <form>
        <input type="text" value={product.name}
        onChange={evt => setProducts({...product, name: evt.target.value})}/>

        <input type="text" value={product.price}
        onChange={evt => setProducts({...product, price: evt.target.value})}/>


      </form>
      <h1>Product name is {product.name} </h1>
      <h1>Product price is {product.price} </h1>
    </>
  )
}

Image from Gyazo

↓配列に対して使う

Basic.jsx
import React, { useState } from 'react'

const Basic2 = () => {

  const [ products, setProducts ] = useState([])

  const newProducts = () => {
    setProducts([...products, {
      id: products.length,
      name: "Heloo React"
    }])
  }

  return (
    <div>
      <button onClick={newProducts}>Add New Product</button>
      <ul>
        {products.map(product => (
          <li key={product.id}>{product.name} id: {product.id} </li>
        ))}
      </ul>
    </div>
  )
}

Image from Gyazo

useEffect

useEffectはHooksの中でも複雑なフックのひとつです。
公式の説明では「副作用を実行するフック」となっています。

useEffectの使い方はuseEffect(func)です。このときfuncはすぐさま実行されず、
コンポーネントのレンダリング後に実行されます。つまり簡単にいうとuseEffectは
「関数の実行タイミングをReactのレンダリング後まで遅らせるフック」ということになります。

「コンポーネントが読み込まれた最初の1回だけ処理を実行したい」だったり、
「あるStateが変化したときだけある処理を実行したい」といったときに使われます。

useEffect.jsx
import React, {useState, useEffect} from 'react'

const UseEffect = () => {

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

  useEffect(() => {
    console.log("useEffect invoked")
  }, [])

  return (
    <div>
      <button onClick={() => setCount(prevCount=>prevCount+1)}>Click {count} </button>
    </div>
  )
}

export default UseEffect

useEffect(( ) => {
console.log("useEffect invoked")
}, [ ])

のように、useEffectの第2引数に[ ]を取ると、
最初のレンダリング時のみ第1引数の中が出力されるようになります。
Image from Gyazo

↓useEffectの第2引数に[count]というState名を取ると、countにのみ反応するようになります。
↓反応して欲しいStateを第2引数に取るのです。

useEffect.jsx
import React, {useState, useEffect} from 'react'

const UseEffect = () => {

  const [count, setCount] = useState(0);
  const [item, setItem] = useState("")

  useEffect(() => {
    console.log("useEffect invoked")
  }, [count])

  return (
    <div>
      <button onClick={() => setCount(prevCount=>prevCount+1)}>Click {count} </button>
      <input type="text" value={item} onChange={evt=>setItem(evt.target.value)}/>
    </div>
  )
}

export default UseEffect

Image from Gyazo

上と同コードで第2引数を空にすると、countだけでなくinputに応じても反応するようになってしまいます。
Image from Gyazo

useEffect(Cleannup)

useEffectでは「Cleannup関数」を登録できます。
そもそもuseEffectはレンダリングのたびに呼ばれます。
なので副作用の中でイベントリスナーの登録やタイマーのセットをしていると、
何重にも副作用が走ることになります。そこで「Cleannup関数」をreturnすることで、
2度目以降のレンダリング時に前回の副作用を消してしまうことができます。

Timer.jsx
import React, {useState, useEffect} from 'react'

const Timer = () => {

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

  const time = () => {
    setCount(prevCount => prevCount + 1);
  }

  useEffect(() => {
    const interval = setInterval(time, 1000);
    return () => {
      clearInterval(interval);
      console.log("cleared");
    }
  }, [])
  
  return (
    <div>
      {count}
    </div>
  )
}

export default Timer
TimerContainer.jsx
import React, {useState} from 'react'
import Timer from './Timer';

const TimerContainer = () => {

  const [display, setDisplay] = useState(true);

  return (
    <div>
      <button onClick={() => setDisplay(!display)}>Switch Timer</button>
      { display && <Timer /> }
    </div>
  )
}

export default TimerContainer

1秒ずつ増えていくタイマーで、Switch Timerを押すとカウントが非表示になり、
useEffectのreturnが返されます。
Image from Gyazo

useContext

Props Drilling問題の解決
a/b/cという階層になっていた場合、aからcに値を私たければ、
propsを経由してa→b→cの順序で辿る必要がありました。

Hooksでは、useContextを使ってa→cに直接値を渡すことが可能になりました。

useContext + useReducer = Reduxに近い機能

Image from Gyazo

C.jsxでApp.jsxの値を表示できています。

B.jsx
import React from 'react'
import C from './C'

const B = () => {
  return (
    <div>
      <h3>B</h3>
      <C />
    </div>
  )
}

export default B
C.jsx
import React, { useContext } from 'react'
import AppContext from '../contexts/AppContext'

const C = () => {
  const value = useContext(AppContext)

  return (
    <div>
      <h3>C</h3>
      { value }
    </div>
  )
}

export default C
App.jsx
import logo from './logo.svg';
import './App.css';
import B from './components/B';

function App() {
  return (
    <AppContext.Provider value={'value from App.js'}>
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <B />
        </header>
      </div>
    </AppContext.Provider>
  );
}

export default App;

useReducer

コンポーネント内での状態管理をしてくれます。

newState = reducer(currentState, action)

reducer関数の引数に、現在のStateの値と、
現在のStateに対してどのようなアクションを与えたいかという情報を指定します。
reducerの内部で定義されたロジックに基づいて、新しいStateを返してくれます。

[state, dispatch] = useReducer(reducer, initialState)

useReducerの引数にreducerと一番最初のState(initialState)を渡してあげます。
すると、useReducerがstateという変数とdispatchという関数を作って返してくれます。

state = Function内でカレントstateを参照できる変数
dispatch = 指定されたactionに基づいてreducer関数を実行してくれる関数

状態遷移の管理

ケースに応じてstateをアップデートしてくれます。

useReducer.jsx
import React, { useReducer } from 'react'

const initialState = 0;
const reducer = (currentState, action) => {
  switch(action) {
    case 'add_1':
      return currentState + 1;
    case 'multiple_3':
      return currentState * 3;
    case 'reset':
      return initialState;
    default:
      return currentState;
  }
}

const BasicReducer = () => {

  const [count, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <div>Count {count}</div>
      <button onClick={() => dispatch('add_1')}>Add + 1</button>
      <button onClick={() => dispatch('multiple_3')}>Multiple * 3</button>
      <button onClick={() => dispatch('reset')}> Reset</button>
    </div>
  )
}

export default BasicReducer

Image from Gyazo

useReducer + useContext

useReducer = コンポーネント内での状態管理
useContext = 孫コンポーネントからのdispatch実行

Image from Gyazo

useReducerとuseContextを組み合わせることで、
グローバルな状態遷移の管理を可能にしています。
孫コンポーネントのCで実行したdispatchに基づいて、
親コンポーネントが表示しているcountの値が変わります。
↓

App.jsx
import logo from './logo.svg';
import './App.css';
import AppContext from './contexts/AppContext';
import CompB from './components/CompB';

import { useReducer } from 'react'

const initialState = 0;
const reducer = (currentState, action) => {
  switch(action) {
    case 'add_1':
      return currentState + 1;
    case 'multiple_3':
      return currentState * 3;
    case 'reset':
      return initialState;
    default:
      return currentState;
  }
}

function App() {
  const [count, dispatch] = useReducer(reducer, initialState);
  return (
    <AppContext.Provider value={{countProvided: count, dispatchProvided: dispatch}}>
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          Count {count}
          <CompB />
        </header>
      </div>
    </AppContext.Provider>
  );
}

export default App;
CompB.jsx
import React from 'react'
import CompC from './CompC'

const CompB = () => {
  return (
    <div>
      <CompC />
    </div>
  )
}

export default CompB
CompC.jsx
import React, { useContext } from 'react'
import AppContext from '../contexts/AppContext'

const CompC = () => {
  const {dispatchProvided} = useContext(AppContext);
  return (
    <div>
      <button onClick={() => dispatchProvided('add_1')}>add + 1</button>
    </div>
  )
}

export default CompC

useMemo

同じ結果を返す処理に関しては初回のみ処理を実行しておき、
2回目以降は前回の処理結果を呼び出すことで毎回同じ処理を実行しなくてよくなります。

これはプログラミングではメモ化と呼ばれるテクニックで、
それをReact Hooks上で簡単に利用できるのがuseMemoとuseCallbackです。

functionalコンポーネントのトップレベルの位置で宣言を行いメモ化したい変数を定義します。
第2引数にはuseEfectと同様に依存変数を配列で指定しておくことで、
依存変数に変更があった場合には再処理が実行されるようになります。

Image from Gyazo

useMemoの第2引数にcount1指定しているので
count1は再計算が行われ、count2では行われません。
なので、count2の方がクリック時の反応が速くなっています。

Memo.jsx
import React, { useState, useMemo } from 'react'

const Memo = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const AddCount1 = () => {
    setCount1(prevCount1 => prevCount1 + 1 )
  }

  const AddCount2 = () => {
    setCount2(prevCount2 => prevCount2 + 1 )
  }

  const isOdd = useMemo(() => {
    let i = 0;
    while(i < 500000000)
      i++
    return count1 % 2 !== 0;
  }, [count1])

  return (
    <div>
      <button onClick={AddCount1}>Count1: {count1}</button>
      <span>{isOdd ? 'Odd number' : 'Even number'}</span>
      <button onClick={AddCount2}>Count2: {count2}</button>
    </div>
  )
}

export default Memo

useCallback

useCallbackはメモ化したコールバック関数を返すHooks APIです。

useCallbackを利用しない場合はコールバック関数はfunctionalコンポーネントの再描画のたびに
新しい関数インスタンスを生成してイベントとしてバインドされていきます。

useCallbackを利用することで関数をメモ化して新しい関数インスタンスを生成せずに再描画後のイベントとして再利用を行います。

↓count1を押したときはcount1、count2を押したときはcount2のみレンダリングされるようにし
↓たい。
Image from Gyazo

App.jsx
import logo from './logo.svg';
import './App.css';
import AppContext from './contexts/AppContext';

import { useReducer, useState, useCallback } from 'react';
import CountDisplay from './components/CountDisplay';
import CountClick from './components/CountClick';

const initialState = 0;
const reducer = (currentState, action) => {
  switch(action) {
    case 'add_1':
      return currentState + 1;
    case 'multiple_3':
      return currentState * 3;
    case 'reset':
      return initialState;
    default:
      return currentState;
  }
}

function App() {
  const [count, dispatch] = useReducer(reducer, initialState);
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const AddCount1 = () => {
    setCount1(prevCount1 => prevCount1 + 1 )
  }

  const AddCount2 = () => {
    setCount2(prevCount2 => prevCount2 + 1 )
  }

  return (
    <AppContext.Provider value={{countProvided: count, dispatchProvided: dispatch}}>
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <CountDisplay name="count1" count={count1} />
          <CountClick handleClick={AddCount1}>AddCount1</CountClick>
          <CountDisplay name="count2" count={count2} />
          <CountClick handleClick={AddCount2}>AddCount2</CountClick>
        </header>
      </div>
    </AppContext.Provider>
  );
}

export default App;

↓
↓AddCount1,AddCount2の関数をuseCallbackで囲み、
↓useCallbackの第2引数に反応して欲しいpropsを指定すると、

Image from Gyazo

count1を押したときはcount1、count2を押したときはcount2のみ
レンダリングされるようになりました。

App.jsx
import logo from './logo.svg';
import './App.css';
import AppContext from './contexts/AppContext';

import { useReducer, useState, useCallback } from 'react';
import CountDisplay from './components/CountDisplay';
import CountClick from './components/CountClick';

const initialState = 0;
const reducer = (currentState, action) => {
  switch(action) {
    case 'add_1':
      return currentState + 1;
    case 'multiple_3':
      return currentState * 3;
    case 'reset':
      return initialState;
    default:
      return currentState;
  }
}

function App() {
  const [count, dispatch] = useReducer(reducer, initialState);
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const AddCount1 = useCallback(() => {
    setCount1(prevCount1 => prevCount1 + 1 )
  }, [count1])

  const AddCount2 = useCallback(() => {
    setCount2(prevCount2 => prevCount2 + 1 )
  }, [count2])

  return (
    <AppContext.Provider value={{countProvided: count, dispatchProvided: dispatch}}>
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <CountDisplay name="count1" count={count1} />
          <CountClick handleClick={AddCount1}>AddCount1</CountClick>
          <CountDisplay name="count2" count={count2} />
          <CountClick handleClick={AddCount2}>AddCount2</CountClick>
        </header>
      </div>
    </AppContext.Provider>
  );
}

export default App;

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?