hyeonzzz's Tech Blog

[리액트를 다루는 기술] 8장 Hooks 본문

React

[리액트를 다루는 기술] 8장 Hooks

hyeonzzz 2023. 1. 27. 11:05

8장 Hooks

Hooks

: useState, useEffect 등의 기능을 제공하여 다양한 작업을 할 수 있게 해준다

 

8.1 useState

● useState

: 함수 컴포넌트에서 상태를 관리해야 할 때 사용

 

● useState 사용해서 숫자 카운터 구현해보기

 

1. src 디렉터리에 Counter.js 파일 생성

//Counter.js

import React from 'react';
import { useState } from 'react';

const Counter = () => {
    const [value, setValue] = useState(0);  
    /*카운터의 기본값을 0으로 설정한다
    이 함수가 호출되면 배열을 반환한다
    배열의 첫 번째 원소는 상태 값, 두 번째 원소는 상태를 설정하는 함수이다*/
    return (
        <div>
            <p>
                현재 카운터 값은 <b>{value}</b>입니다.
            </p>
            <button onClick={() => setValue(value + 1)}>+1</button>
            <button onClick={() => setValue(value - 1)}>-1</button>
        </div>
    );
};

export default Counter;

 

2. App 컴포넌트에서 Counter 컴포넌트 렌더링

//App.js

import React from "react";
import Counter from "./Counter";

const App = () => {
  return <Counter />;
};

export default App;

 

3. 터미널에 yarn start 명령어 입력해 개발 서버 구동

● useState를 여러 번 사용하기

 

1. Info.js 파일 생성

//Info.js

import React from 'react';
import { useState } from 'react';

const Info = () => {
    const [name, setName] = useState('');
    const [nickname, setNickname] = useState('');

    const onChangeName = e => {
        setName(e.target.value);
    };

    const onChangeNickname = e => {
        setNickname(e.target.value);
    };

    return (
        <div>
            <div>
                <input value={name} onChange={onChangeName} />
                <input value={nickname} onChange={onChangeNickname} />
            </div>
            <div>
                <div>
                    <b>이름:</b> {name}
                </div>
                <div>
                    <b>닉네임:</b> {nickname}
                </div>
            </div>
        </div>
    );
};

export default Info;

 

2. App 컴포넌트에서 렌더링

//App.js

import React from "react";
import Info from "./Info";

const App = () => {
  return <Info />;
};

export default App;

 

3. 화면 확인

 

8.2 useEffect

● useEffect

: 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook

 

● Info 컴포넌트에 useEffect 적용

 

//Info.js

import React from 'react';
import { useState, useEffect } from 'react';

const Info = () => {
    const [name, setName] = useState('');
    const [nickname, setNickname] = useState('');
    useEffect(() => {
        console.log('렌더링이 완료되었습니다! ');
        console.log({
            name,
            nickname
        });
    });

    const onChangeName = e => {
        setName(e.target.value);
    };

    const onChangeNickname = e => {
        setNickname(e.target.value);
    };
/*
    return (
        <div>
            <div>
                <input value={name} onChange={onChangeName} />
                <input value={nickname} onChange={onChangeNickname} />
            </div>
            <div>
                <div>
                    <b>이름:</b> {name}
                </div>
                <div>
                    <b>닉네임:</b> {nickname}
                </div>
            </div>
        </div>
    );
};
*/
export default Info;

※ 컴포넌트가 화면에 나타날 때 두 번 호출이 된다

 

 

● 마운트될 때만 실행하고 싶을 때

 

//Info.js

useEffect(() => {
	console.log('마운트될 때만 실행됩니다.');
}, []);

컴포넌트가 처음 나타날 때만 콘솔에 문구가 나타나고, 이후에는 나타나지 않는다

 

● 특정 값이 업데이트될 때만 실행하고 싶을 때 

 

//Info.js

useEffect(() => {
	console.log(name);
}, [name]);

useEffect의 두번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값을 넣어 준다

 

● 뒷정리하기 

 

1. Info 컴포넌트의 useEffect 부분 수정

//Info.js

useEffect(() => {
	console.log('effect');
	console.log(name);
    return () => {
    	console.log('cleanup');
		console.log(name);
    };
}, [name]);

컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업을 수행하고 싶다면

useEffect에서 cleanup 함수를 반환해준다

 

2. App 컴포넌트에서 Info 컴포넌트의 가시성을 바꿀 수 있게 수정

 

//App.js

import React from "react";
import { useState } from "react";
import Info from "./Info";

const App = () => {
  const [visible, setVisible] = useState(false);
  return (
    <div>
      <button
        onClick={() => {
          setVisible(!visible);
        }}
      >
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr />
      {visible && <Info />}
    </div>
    );
};

export default App;

렌더링될 때마다 뒷정리 함수가 계속 나타난다

뒷정리 함수가 호출될 때는 업데이트되기 직전의 값을 보여준다

 

 

8.3 useReducer

● useReducer

: useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트해주고 싶을 때 사용하는 Hook

 

● 카운터 구현하기

 

state : 현재 가리키고 있는 상태

dispatch : 액션을 발생시키는 함수

 

● 인풋 상태 관리하기

 

useReducer에서의 액션을 어떤 값도 사용 가능

 

 

8.4 useMemo

● useMemo

: 함수 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다

 

● 리스트에 숫자를 추가하면 평균을 보여주는 함수 컴포넌트

 

1. src 디렉터리에 Average.js 파일 생성

 

import React from "react";
import { useState } from 'react';

const getAverage = numbers => {
    console.log('평균값 계산 중..');
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
};

const Average = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');

    const onChange = e => {
        setNumber(e.target.value);
    };
    const onInsert = e => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    };

    return (
        <div>
            <input value={number} onChange={onChange} />
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>
                <b>평균값:</b> {getAverage(list)}
            </div>
        </div>
    );
};

export default Average;

 

2. App에서 컴포넌트 렌더링

 

//App.js

import React from "react";
import Average from "./Average";

const App = () => {
  return <Average />;
};

export default App;

 

3. useMemo Hook 사용

 

import React from "react";
import { useState, useMemo } from 'react';

const getAverage = numbers => {
    console.log('평균값 계산 중..');
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
};

const Average = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');

    const onChange = e => {
        setNumber(e.target.value);
    };
    const onInsert = e => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    };

    const avg = useMemo(() => getAverage(list), [list]);  //useMemo 사용

    return (
        <div>
            <input value={number} onChange={onChange} />
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>
                <b>평균값:</b> {getAverage(list)}
            </div>
        </div>
    );
};

export default Average;

인풋 내용이 바뀔 때는 평균값이 다시 계산되지 않는다

 

 

8.5 useCallback

● useCallback

: 렌더링 성능을 최적화해야 하는 상황에서 사용한다

만들어 놨던 함수를 재사용할 수 있다

 

● useCallback 사용하여 최적화하기

 

import React from "react";
import { useState, useMemo, useCallback } from 'react';

const getAverage = numbers => {
    console.log('평균값 계산 중..');
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
};

const Average = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');

    const onChange = useCallback(e => {
        setNumber(e.target.value);
    }, []);   // 컴포넌트가 처음 렌더링될 때만 함수 생성
    const onInsert = useCallback(() => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    }, [number, list]);   // number 혹은 list가 바뀌었을 때만 함수 생성

    const avg = useMemo(() => getAverage(list), [list]);

    return (
        <div>
            <input value={number} onChange={onChange} />
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>
                <b>평균값:</b> {getAverage(list)}
            </div>
        </div>
    );
};

export default Average;

 

useCallback의

  • 첫 번째 파라미터 : 생성하고 싶은 함수
  • 두 번째 파라미터 : 배열

 

8.6 useRef

● useRef

: 함수 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해주는 Hook

 

import React from "react";
import { useState, useMemo, useCallback, useRef} from 'react';

const getAverage = numbers => {
    console.log('평균값 계산 중..');
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
};

const Average = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');
    const inputE1 = useRef(null);

    const onChange = useCallback(e => {
        setNumber(e.target.value);
    }, []);   // 컴포넌트가 처음 렌더링될 때만 함수 생성
    const onInsert = useCallback(() => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
        inputE1.current.focus();
    }, [number, list]);   // number 혹은 list가 바뀌었을 때만 함수 생성

    const avg = useMemo(() => getAverage(list), [list]);

    return (
        <div>
            <input value={number} onChange={onChange} ref={inputE1} />
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>
                <b>평균값:</b> {avg}
            </div>
        </div>
    );
};

export default Average;