React/1. React 기초

React Hooks

pancakemaker 2022. 6. 29. 15:58

1. Hooks

: 기존의 함수형 컴포넌트에서는 할 수 없었던 다양한 작업을 할 수 있게 해줌


2. useState(상태초기값)

: 가장 기본적인 Hook

: 함수형 컴포넌트에서도 상태를 관리할 수 있음

import { useState } from "react";

export const Counter = () => {
    //useState hook 사용 => useState(초기값)
    //첫번째 원소는 상태값, 두번째 원소는 상태를 설정하는 함수
    const [value, setValue] = useState(0);
    return (
        <div>
            <p>
                현재 카운터 값은 <b>{value}</b> 입니다.
            </p>
            <button onClick={() => setValue(value + 1)}>+1</button>
            <button onClick={() => setValue(value - 1)}>-1</button>
        </div>
    );
};

 

2-1. useState를 여러번 사용하기

import { useState } from "react";

const Info = () => {
    //useState(상태 초기값);
    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;


3. useEffect(function, dep)

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

: 클래스형 컴포넌트의 componentDidMount와 componentDidUpdate를 합친 형태로 보아도 무방 (LifeCycle)

 

3-1. Rendering 될 때마다 실행하고 싶을 때

: 두번째 파라미터에 아무것도 넣지 않음

import { useEffect, useState } from "react";

const Info = () => {
    //useState(상태 초기값);
    const [name, setName] = useState('');
    const [nickname, setNickname] = useState('');
    //useEffect 사용!
    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;

 

3-2. 마운트 될 때만 실행하고 싶을 때

: useEffect에서 설정한 함수가 컴포넌트가 화면에 가장 처음 rendering 될 때만 실행되고 업데이트 할 경우에는 실행할 필요가 없는 경우

: useEffect의 두번째 파라미터로 비어있는 배열을 넣어줌

import { useEffect, useState } from "react";

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

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

    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;

 

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

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

import { useEffect, useState } from "react";

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

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

    //특정값 업데이트시에만 실행
    useEffect(() => {
        console.log(name);
    }, [name])

    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;

 

3-4. 뒷정리 하기 (cleanup 함수)

: 컴포넌트가 언마운트되기 전이나, 업데이트 되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에 뒷정리(cleanup) 함수를 반환해줘야 함

import { useEffect, useState } from "react";

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

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

    // //특정값 업데이트시에만 실행
    // useEffect(() => {
    //     console.log(name);
    // }, [name])

    //뒷정리함수
    useEffect(() => {
        console.log('effect');
        console.log(name);
        return () => {
            console.log('cleanup');
            console.log(name);
        };
    });

    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;
import logo from './logo.svg';
import './App.css';
import { Counter } from './Counter';
import Info from './Info';
import { useState } from 'react';

const App = () => {
  const [visible, setVisible] = useState(false);
  return (
    <div>
      <button
        onClick={() => {
          setVisible(!visible);
        }}
      >
        {/* visible이 true면 숨기기가 보이고, false면 보이기가 보임 */}
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr />
      {/* visible이 true일 때만 Info 컴포넌트가 보임 */}
      {visible && <Info />}
    </div>
  );
}

export default App;


4. useContext

: 함수형 컴포넌트에서 Context를 쉽게 사용 가능

import { createContext, useContext } from "react";

const ThemeContext = createContext('black');
const ContextSample = () => {
    const theme = useContext(ThemeContext);
    const style = {
        width: '24px',
        height: '24px',
        background: theme
    };
    return <div style={style} />;
};

export default ContextSample;


5. useReducer(리듀서함수, 리듀서의 기본값)

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

: Reducer는 현재 상태와, 업데이트를 위해 필요한 정보를 담은 액션(action)값을 전달 받아 새로운 상태를 반환하는 함수

: 새로운 상태를 만들 때는 불변성을 반드시 지켜야 함

function reducer(state, action) {
	return {...};
}

↓ 액션값의 주된 형태

{
type: 'INCREMENT',
//다른 값들도 필요하다면, 추가적으로 들어감
}

 

5-1. 카운터 구현하기

import { useReducer, useState } from "react";

function reducer(state, action) {
    switch(action.type) {
        case 'INCREMENT':
            return { value: state.value + 1};
        case 'DECREMENT':
            return { value: state.value - 1};
        default:
            return state;
    }
}

const Counter = () => {
    //useState 대신 useReducer 사용
    //state = 상태, dispatch = 액션발생 함수 -> dispatch(action)
    const [state, dispatch] = useReducer(reducer, { value: 0 });

    return (
        <div>
            <p>
                현재 카운터 값은 <b>{state.value}</b> 입니다.
            </p>
            <button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
            <button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
        </div>
    );
};

export default Counter;


6. useMemo

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

: 렌더링 하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀐 것이 아니라면 이전에 연산했던 결과를 다시 사용

import { useMemo, 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('');
    };

    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> {avg}
            </div>
        </div>
    );
};

export default Average;

7. useCallback(함수, 배열)

: useMemo와 비슷

: 렌더링 성능 최적화 → 컴포넌트의 렌더링이 자주 발생하거나, 렌더링 해야 할 컴포너트의 개수가 많아질 때

: 이벤트 핸들러 함수를 필요시에만 생성할 수 있음

import { useCallback, useMemo, 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('');

    //useCallback 사용
    const onChange = useCallback(e => {
        setNumber(e.target.value);
    }, []);     //컴포넌트가 처음 렌더링 될 때만 함수 생성

    const onInsert = useCallback(
        e => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
        },
        [number, list]
    );          //number or 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> {avg}
            </div>
        </div>
    );
};

export default Average;

 

※ useCallback()과 useMemo()

useCallback(() => {
	console.log('hello world!');
}, []);

useMemo(() => {
	const fn = () => {
    	console.log('hello world!);
    };
    return fn;
}, []);

→ 결국 useCallback은 useMemo에서 함수를 반환하는 상황에서 더 편하게 사용할 수 있는 Hook!

객체처럼 일반 값을 재사용하기 위해서는 useMemo, 함수를 재사용하기 위해서는 useCallback을 사용


8. useRef

: 함수형 컴포넌트에서 엘리먼트에 접근할 수 있도록 해주는 Hook

: useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킴

import { useCallback, useMemo, useRef, 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('');
    //useRef 사용
    const inputEl = useRef(null);

    const onChange = useCallback(e => {
        setNumber(e.target.value);
    }, []);

    const onInsert = useCallback(
        e => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
        //useRef 호출
        inputEl.current.focus();
        },
        [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> {avg}
            </div>
        </div>
    );
};

export default Average;

 

8-1. 로컬 변수 사용하기

: 컴포넌트 로컬 변수를 사용할 때도 useRef를 활용

: 로컬변수 - 렌더링이라은 관계 없이 바뀔 수 있는 값

import { useRef } from "react"

const RefSample = () => {
    const id = useRef(1);
    const setId = (n) => {
        id.current = n;
    }
    const printId = () => {
        console.log(id.current);
    }
    return (
        <div>
            refsample
        </div>
    );
};

export default RefSample;