274
Help us understand the problem. What are the problem?

posted at

updated at

React hooksを基礎から理解する (useEffect編)

React hooksとは

React 16.8 で追加された新機能です。
クラスを書かなくても、stateなどのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。

useEffectとは

useEffectを使うと、useEffectに渡された関数はレンダーの結果が画面に反映された後に動作します。
つまりuseEffectとは、「関数の実行タイミングをReactのレンダリング後まで遅らせるhook」です。

副作用の処理(DOMの書き換え、変数代入、API通信などUI構築以外の処理)を関数コンポーネントで扱えます。
クラスコンポーネントでのライフサイクルメソッドに当たります。

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

参考:React公式サイト 副作用フックの利用法

副作用を実行、制御するためにuseEffectを利用する

useEffect()の基本構文は以下の通りです。関数コンポーネントのトップレベルで宣言します。

useEffect(() => {
  /* 第1引数には実行させたい副作用関数を記述*/
  console.log('副作用関数が実行されました!')
},[依存する変数の配列]) // 第2引数には副作用関数の実行タイミングを制御する依存データを記述

第2引数を指定することにより、第1引数に渡された副作用関数の実行タイミングを制御することができます。Reactは第2引数の依存配列の中身の値を比較して、副作用関数をスキップするかどうかを判断します。

説明 データ型
第1引数 副作用関数(戻り値はクリーンアップ関数、または何も返さない) 関数
第2引数 副作用関数の実行タイミングを制御する依存データが入る(省略可能) 配列

create-react-appでReactのコードを書く

create-react-appをひさしぶりに npm installしようとしたらテンプレートが出来ない😦
困っていて見つけた記事。解決出来ました。ありがとうございます。

参考:ひさしぶりにcreate-react-appしたらテンプレートができなかった時の対処法

Material-UIをinstall

Material-UIをinstallしたら、使いたいコンポーネントをすぐ見つけられるし、勝手にスタイリングしてくれるのでテンションあがります😁

$ npm install @material-ui/core

参考:MATERIAL-UI

クリックしたらタイトルも同時に変更されるコンポーネントを作る

クラスコンポーネントで作成してみる

react.js
import React, { Component } from 'react'
import ButtonGroup from '@material-ui/core/ButtonGroup';
import Button from '@material-ui/core/Button';

class EffectClass extends Component {
  constructor(props){
    super(props);
    this.state = {
      count: 0,
    }
  }

  componentDidMount(){
    document.title =`${this.state.count}回クリックされました`
  }

  componentDidUpdate(){
    document.title =`${this.state.count}回クリックされました`
  }

  render() {
    return (
      <>
        <p>{`${this.state.count}回クリックされました`}</p>
        <ButtonGroup color="primary" aria-label="outlined primary button group">
          <Button onClick={()=> this.setState({count: this.state.count + 1})} >
            ボタン
          </Button>
          <Button onClick={()=> this.setState({count: 0})}>
            リセット
          </Button>
        </ButtonGroup>
      </>
    )
  }
}

export default EffectClass

クラスコンポーネントの場合、副作用は ReactがDOMを更新したあとに起こすようにしたいので、componentDidMountcomponentDidUpdateに記載します。するとReactDOMに変更を加えた後に、document.titleを更新しています。

react.js
  componentDidMount(){
    document.title =`${this.state.count}回クリックされました`
  }

  componentDidUpdate(){
    document.title =`${this.state.count}回クリックされました`
  }

関数コンポーネントで作成してみる

react.js
import React, {useState, useEffect} from 'react'
import ButtonGroup from '@material-ui/core/ButtonGroup'
import Button from '@material-ui/core/Button'

const EffectFunc = () => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    document.title =`${count}回クリックされました`
  })

  return (
    <>
      <p>{`${count}回クリックされました`}</p>
      <ButtonGroup color="primary" aria-label="outlined primary button group">
        <Button onClick={()=>setCount((prev) => prev + 1)}>
          ボタン
        </Button>
        <Button onClick={()=>setCount(0)}>
          リセット
        </Button>
      </ButtonGroup>
    </>
  )
}

export default EffectFunc

関数コンポーネントでuseEffectを使った場合、デフォルトでは、useEffectは毎回のレンダリング後に呼ばれます。第2引数を省略した場合、コンポーネントがレンダリングされるたびに、第1引数で渡した副作用関数が実行されます。

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

  useEffect(() => {
    document.title =`${count}回クリックされました`
  })

第2引数を省略すると、コンポーネントがレンダリングされるたびに副作用関数が実行されることから、無限ループの温床になりやすいので注意する必要があります。実際には、第2引数を省略するケースはほとんどありません。

React公式サイトのstate とライフサイクルをもう一度読むと理解が進みました。
参考:React公式サイト state とライフサイクル

初回レンダリング時のみ副作用関数を実行させる

副作用関数を初回レンダリング時の一度だけ実行させたい場合、第2引数に空の依存配列[]を指定します。
この場合、初回レンダリング時のみ副作用関数が実行され、document.titleは更新されません。

react.js
  useEffect(() => {
    document.title =`${count}回クリックされました`
    console.log(`再レンダーされました`)
  },[])

依存配列の値が変化した場合のみ副作用関数を実行させる

useEffect()の第2引数に[count]を渡すと、countに変化があったときだけ副作用関数を実行します。

react.js
import React, {useState, useEffect} from 'react'
import { makeStyles } from '@material-ui/core/styles';
import ButtonGroup from '@material-ui/core/ButtonGroup'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input';

const useStyles = makeStyles((theme) => ({
  root: {
    '& > *': {
      margin: theme.spacing(1),
    },
  },
}));

const EffectFunc = () => {
  const classes = useStyles();
  const [count, setCount] = useState(0)
  const [name, setName] = useState({
    lastName: '',
    firstName: ''
  })
  useEffect(() => {
    document.title =`${count}回クリックされました`
  },[count])

  return (
    <>
      <p>{`${count}回クリックされました`}</p>
      <ButtonGroup color="primary" aria-label="outlined primary button group">
        <Button onClick={()=>setCount((prev) => prev + 1)}>
          ボタン
        </Button>
        <Button onClick={()=>setCount(0)}>
          リセット
        </Button>
      </ButtonGroup>
      <p>{`私の名前は${name.lastName} ${name.firstName}です`}</p>
      <form className={classes.root} noValidate autoComplete="off">
        <Input
          placeholder=""
          value={name.lastName}
          onChange={(e)=>{setName({...name,lastName: e.target.value})}}/>
        <Input
          placeholder=""
          value={name.firstName}
          onChange={(e)=>{setName({...name,firstName: e.target.value})}}/>
      </form>
    </>
  )
}

export default EffectFunc

useEffectの第2引数に[count]を取るとき

react.js
  useEffect(() => {
    document.title =`${count}回クリックされました`
    console.log(`再レンダーされました`)
  },[count])

nameが更新されても副作用関数は実行されず、countが更新された場合だけ、document.titleが実行され、再描画されていることがわかります。

クリーンアップについて

クリーンアップとはイベントリスナの削除、タイマーのキャンセルなどのことです。
クリーンアップ関数をreturnすると、2度目以降のレンダリング時に前回の副作用を消してしまうことができます。

クラスコンポーネントの場合

componentWillUnmountは、クリーンアップ(addEventLitenerの削除、タイマーのキャンセルなど)に使用されます。componentDidMountに副作用を追加し、componentWillUnmountで副作用を削除します。

react.js
componentDidMount() {
  elm.addEventListener('click', () => {})
}

componentWillUnmount() {
  elm.removeEventListener('click', () => {})
}
関数コンポーネントの場合

上記に相当するhookは以下。「クリーンアップ関数」をreturnすることで、2度目以降のレンダリング時に前回の副作用を消してしまうことができます。

react.js
useEffect(() => {
   elm.addEventListener('click', () => {})

  // returned function will be called on component unmount 
  return () => {
     elm.removeEventListener('click', () => {})
  }
}, [])
ライフサイクル

useEffect()では、副作用関数がクリーンアップ関数を返すことで、マウント時に実行した処理をアンマウント時に解除します。またその副作用関数は、毎回のレンダリング時に実行され、新しい副作用関数を実行する前に、ひとつ前の副作用処理をクリーンアップします。

このようにマウント処理とアンマウント処理の繰り返し処理のことを「ライフサイクル」と言います。

最後に

次回は useContext について書きたいと思います。

参考にさせていただいたサイト

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
274
Help us understand the problem. What are the problem?