Abstruct

ReactのHooksについて、基本的な内容をまとめたので載せておきます。

1. useState

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useState } from "react";

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

  const incrementCount = () => {
    setCount((prevCount) => prevCount + 1);
  };
  const incrementCountTen = () => {
    for (let i = 0; i < 10; i++) {
      setCount((prevCount) => prevCount + 1);
    }
  };

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={incrementCount}>カウント +</button>
      <button onClick={incrementCountTen}>カウント 10+</button>
    </div>
  );
}

export default CounterHook;

2. useEffect

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, { useState, useEffect } from "react";

function EffectHook() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");

  useEffect(() => {
    document.title = `クリック数: ${count} 回`
    console.log(`render`)
  },[count]);

  return (
    <div>
      <p>クリック数: {count}  </p>
      <p>名前: {name}</p>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        カウント +
      </button>
    </div>
  );
}

export default EffectHook;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useState, useEffect } from "react";

function DataFetchById() {
  const [post, setPost] = useState(null);
  const [id, setId] = useState(1);
  const [loading, setLoading] = useState(true);

  const fetchData = async (id) => {
    setLoading(true);
    const url = "https://jsonplaceholder.typicode.com/posts/" + id;
    const response = await fetch(url);
    const item = await response.json();
    setPost(item);
    setLoading(false);
  };

  useEffect(() => {
    fetchData(id);
  }, [id]);

  return (
    <div>
      <input type="text" value={id} onChange={(e) => setId(e.target.value)} />
      {loading ? <h1>loading...</h1> : <h1>{post.title}</h1>}
    </div>
  );
}

export default DataFetchById;

3. useContext

3.1. App.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useState, createContext } from "react";
import "./App.css";

import ComponentC from "./component/ComponentC";

export const UserContext = createContext();
export const LanguageContext = createContext();

function App() {
  const [user, setUser] = useState({name: 'yamada', age: '32'})
  const [language, setLanguage] = useState('日本語')

  return (
    <div className="App">
      <UserContext.Provider value={user}>
        <LanguageContext.Provider value={language}>
          <ComponentC />
        </LanguageContext.Provider>
      </UserContext.Provider>
    </div>
  );
}

export default App;

3.2. ComponentC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import React from "react";
import ComponentE from "./ComponentE"

function ComponentC() {
  return (
    <div>
      <ComponentE />
    </div>
  );
}

export default ComponentC;

3.3. ComponentE

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import React from 'react'
import ComponentF from './ComponentF'

function ComponentE() {
    return (
        <div>
            <ComponentF />
        </div>
    )
}

export default ComponentE

3.4. ComponentF

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React, {useContext} from "react";
import { UserContext, LanguageContext } from "../App";

function ComponentF() {
  const user = useContext(UserContext);
  const language = useContext(LanguageContext)
    return (
    <div>
        <div>{user.name}: {language}</div>
    </div>
  );
}

export default ComponentF;

4. useReducer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import React, { useEffect, useReducer } from "react";
import "./App.css";
import axios from "axios";

const initialState = {
  loading: true,
  error: "",
  post: {},
};

const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SUCCESS":
      return {
        loading: false,
        post: action.payload,
        error: "",
      };
    case "FETCH_ERROR":
      return {
        loading: true,
        post: {},
        error: "データの取得に失敗しました。",
      };
    default:
      return state;
  }
};

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    if (state.loading) {
      axios
        .get("https://jsonplaceholder.typicode.com/posts/1")
        .then((res) => {
          dispatch({ type: "FETCH_SUCCESS", payload: res.data });
        })
        .catch((err) => {
          dispatch({ type: "FETCH_ERROR" });
        });
    }
  });

  return (
    <div className="App">
      <h1>{state.loading ? "Loading..." : state.post.title}</h1>
      <h2>{state.error ? state.error : null}</h2>
    </div>
  );
}

export default App;

5. useCallback

5.1. App.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import React from "react";
import "./App.css";
import WrapComponent from './component/WrapComponent'

function App() {
  return (
    <div className="App">
      <WrapComponent />
    </div>
  );
}

export default App;

5.2. WrapComponent.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, {useState, useCallback} from 'react'
import Title from './Title'
import Button from './Button'
import Count from './Count'

function WrapComponent() {

    const [age, setAge] = useState(30)
    const [score, setScore] = useState(100)

    const incrementAge = useCallback(() => {
        setAge(age + 1)
    }, [age])

    const incrementScore = useCallback(() => {
        setScore(score + 100)
    }, [score])

    return (
        <div>
            <Title />
            <Count text='年齢' count={age} />
            <Count text='信用スコア' count={score} />
            <Button handleClick={incrementAge}>年齢 +</Button>
            <Button handleClick={incrementScore}>信用スコア +</Button>
        </div>
    )
}

export default WrapComponent

5.3. Title.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import React from 'react'

function Title() {
    console.log('Title component')
    return (
        <div>
            <h1>useCallback hookによる最適化</h1>
        </div>
    )
}

export default React.memo(Title)

5.4. Count.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import React from 'react'

function Count({text, count}) {
    console.log('Count component - ', text)
    return (
        <div>
            {text}: {count}
        </div>
    )
}

export default React.memo(Count)

5.5. Button.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import React from 'react'

function Button({handleClick, children}) {
    console.log(`Button component - `, children)
    return (
        <button onClick={handleClick}>
            {children}
        </button>
    )
}

export default React.memo(Button)

6. useMemo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, {useState, useMemo} from 'react'

function Check() {
    const [countOne, setCountOne] = useState(0)
    const [countTwo, setCountTwo] = useState(0)

    const incrementOne = () => {
        setCountOne(countOne + 1)
    }

    const incrementTwo = () => {
        setCountTwo(countTwo + 1)
    }

    const isEven = useMemo(() => {
        let i = 0
        while( i < 200000000) i ++
        return countOne %2 === 0
    }, [countOne])

    return (
        <div>
            <h2>カウント1: {isEven ? '偶数' : '奇数'}</h2>
            <button onClick={incrementOne}>カウント1 - {countOne}</button> 
            <button onClick={incrementTwo}>カウント2 - {countTwo}</button>   
        </div>
    )
}

export default Check

7. useRef

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, {useState, useEffect, useRef} from 'react'

function CountComponent() {
const [count, setCount] = useState(0)
const intervalRef = useRef()
useEffect(() => {
    intervalRef.current = setInterval(() =>{
        setCount(prevCount => prevCount + 1)
    }, 1000)
    return () => {
        clearInterval(intervalRef.current)
    }
}, [])

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => clearInterval(intervalRef.current)}>ストップ</button>
        </div>
    )
}

export default CountComponent

8. Custom Hook

8.1. UseCase1

8.1.1. App.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import React from "react";
import "./App.css";

import DocTitleUpdateOne from "./component/DocTitleUpdateOne";
import DocTitleUpdateTwo from "./component/DocTitleUpdateTwo";

function App() {
  return (
    <div className="App">
      <DocTitleUpdateOne />
      <DocTitleUpdateTwo />
    </div>
  );
}

export default App;

8.1.2. DocTitleUpdateOne.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

import React, { useState } from "react";
import useDocumentTitle from "../hooks/useDocumentTitle";

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

  useDocumentTitle(count);

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

export default DocTitleUpdateOne;

8.1.3. DocTitleUpdateTwo.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import React, { useState } from "react";
import useDocumentTitle from "../hooks/useDocumentTitle";

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

  useDocumentTitle(count);

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

export default DocTitleUpdateTwo;

8.1.4. useDocumentTitle.js

1
2
3
4
5
6
7
8
9
import React, {useEffect} from "react";

function useDocumentTitle(count) {
  useEffect(() => {
    document.title = `カウント ${count}`;
  }, [count]);
}

export default useDocumentTitle;

8.2. UseCase2

8.2.1. App.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React from "react";
import "./App.css";

import Form from './component/Form'

function App() {
  return (
    <div className="App">
      <Form />
    </div>
  );
}

export default App;

8.2.2. Form.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react'
import useInput from '../hooks/useInput'

function Form() {

    const [firstName, bindFirstName, resetFirstName] = useInput('')
    const [lastName, bindLastName, resetLastName] = useInput('')

    const handleSubmit = (e) => {
        e.preventDefault();
        alert(`こんにちは! ${firstName} ${lastName}`)
        resetFirstName();
        resetLastName();
    }

    return (
        <div>
            <form onSubmit={handleSubmit}>
                <div>
                    <label>First Name</label>
                    <input type="text"
                    {...bindFirstName} />
                </div>
                <div>
                    <label>Last Name</label>
                    <input type="text"
                    {...bindLastName} />
                </div>
                <button type="submit">送信</button>
            </form>
        </div>
    )
}

export default Form

8.2.3. useInput.js

1
2
3
4
5
6
7
8
9
import React, {useEffect} from "react";

function useDocumentTitle(count) {
  useEffect(() => {
    document.title = `カウント ${count}`;
  }, [count]);
}

export default useDocumentTitle;

9. 参考リンク