Hook은 React 버전 16.8부터 React 요소로 새로 추가된 기능이다. 이전에 리액트가 겪던 문제들을 해결해주며,
기존 함수 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 도와준다.
예) 클래스형 컴포넌트를 사용하지 않고도 함수 컴포넌트에서 상태 값 및 자식 요소에 접근 가능!
참고: React - Hook의 개요
const [state, setState] = useState(초기값);
state는 원하는 변수명으로 작성하면 된다.
// 사용 예)
const [number, setNumber] = useState(0);
// 상태 변경 방법 1
setState(newState);
// 상태 변경 방법 2
setState((prevState) => {
// .. some works ...
return newState; // return [input, ...prevState];
});
콜백함수: 다른 함수의 인자로 전달된 함수
useState(() => {
return heavyWorks()
});
리액트는 state가 변경될 때마다 렌더링이 된다.
만일 useState를 사용해서 초기값을 받아올 때 무거운 일을 해야 한다면, useState 인자로 콜백함수를 넣어줌으로써 맨 처음 렌더링 될 때만 실행되도록 할 수 있다.
어떤 컴포넌트가 마운트(화면에 첫 렌더링), 업데이트(다시 렌더링), 언마운트(화면에서 사라짐)되었을 때 실행시켜주고 싶은 작업이 있을 경우 사용한다.
useEffect(() => {
// 작업
});
useEffect(() => {
// 작업
}, []);
useEffect(() => {
// 작업
}, [value]);
useEffect(() => {
// 구독
return () => {
// 구독 해지
}
}, []);
변수 관리 또는 DOM 요소에 접근할 때 사용한다.
const ref = useRef(초기값); // { ref.current: 초기값}
ref.current = '변경할 값'; // { ref.current: 변경할 값}
state
와의 차이
- state의 변화 > 렌더링 > 컴포넌트 내부 변수들이 초기화 됨
👉🏼 원하지 않는 렌더링 때문에 곤란해짐- ref의 변화 > 렌더링 X > 컴포넌트 내부 변수들의 값이 유지됨
👉🏼 불필요한 렌더링을 막을 수 있음
단순 변수와의 차이
- 단순 변수 : 렌더링 시 초기값으로 값이 초기화 됨
- ref : 렌더링 되어도 값이 유지됨
const ref = useRef();
<input ref={ref} />
대표적인 예시로는 input 요소에 focus()를 주는 것이 있다.
순수 자바스크립트의 Document.querySelector()
와 유사하다.
useState로 렌더링 수를 확인하려고 한다면 무한루프에 빠지게 된다.
count 증가 시 업데이트 되어 useEffect가 불리고, useEffect 실행 시 renderCount가 증가되어 다시 useEffect가 불린다.
// useState
const [count, setCount] = useState(1);
const [renderCount, setRenderCount] = useState(1);
useEffect(() => {
console.log(‘렌더링’);
setRenderCount(renderCount + 1);
});
useRef를 사용하면 해결할 수 있다. 렌더링 수를 증가시켜도 리렌더링이 되지 않는다.
// useRef
const [count, setCount] = useState(1);
const renderCount = useRef(1);
useEffect(() => {
renderCount.current = reundercount.current +1 ;
});
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
<input ref={inputRef} type=“text” placeholder=“username”/>
전역적으로 사용하고 싶은 값이 있을 때 사용한다. 예시로 로그인된 사용자 정보, 테마, 언어 등이 있다.
🤬 모든 정보를 props로 일일이 전달할 경우 문제점
가장 하위에 있는 컴포넌트들만 해당 값이 필요함에도 불구하고 중간 컴포넌트들을 거쳐야 해(=Prop Drilling), 다음과 같은 문제들이 발생할 수 있다. 그리고 문제가 발생한 경우 부모 컴포넌트를 하나하나 확인하며 오류를 해결해야 한다.
- 전달해야 하는 props의 양이 많아진다.
- 코드가 지저분해 질 수 있다.
- 이상한 데이터를 전달할 수 있다.
- 수정한 데이터를 전달할 수 있다.
import { createContext } from ‘react’;
export const TempContext = React.createContext('초기값');
// App.js
// App 컴포넌트에서 context로 Page 컴포넌트를 감싼다.
// 여기서 context로 하위 컴포넌트를 감싸지 않을 경우
// 하위 컴포넌트에서 context를 호출하면 '초기값'이 불려진다.
const App = () => {
return (
<TempContext.Provider value={value}>
<Page />
</TempContext.Provider>
)
};
// Page.js
const Page = () => {
return (
<div className="page">
<Header />
<Content />
<Footer />
</div>
);
};
// Header.js
const Header = () => {
const { value } = useContext(TempContext);
return (...생략...);
}
context를 사용하면 컴포넌트를 재사용하기가 어려워지므로 꼭 필요할 때만 사용해야 한다.
🤯 useMemo는 값을 재활용하기 위해 메모리를 소비하며 값을 저장해 두는 것이다. 따라서 무분별하게 사용할 경우 성능이 악화될 수 있으므로 필요할 때만 사용해야 한다.
// const value = useMemo(콜백함수, 의존성 배열);
const value = useMemo(() => {
return 함수();
}, [item]);
첫번째 인자에는 콜백함수를 넣고, 두번째 인자에는 의존성 배열을 넣는다.
useEffect()를 사용하는데 비교해야 할 값이 객체 타입인 경우 useMemo()가 유용하다.
// 이 경우 렌더링 된다면 location이 바뀌지 않아도 console.log() 는 계속 찍힌다.
const [isKorea, setIsKorea] = useState(true);
const location = {
country: isKorea ? '한국' : '외국'
};
useEffect(() => {
console.log('useEffect() 호출');
}, [location]);
원시(Primitive) 타입 | 객체(Object) 타입 |
---|---|
String, Number, Boolean, Null, Undefined, BigInt, Symbol | 원시타입을 제외한 모든 것 Object, Array, … 등 |
변수에 객체 타입의 값을 할당하면, 객체는 너무 크기 때문에 메모리상의 어떤 공간에 보관되고 변수에는 객체가 담긴 메모리의 주소가 할당된다.
const one = “Hello”;
const two = “Hello”;
console.log(one === two); // true
const one = { obj: Hello };
const two = { obj: Hello };
console.log(one === two); // false
// 변수에 메모리 주소가 들어있고, 두 객체에는 각각 다른 주소가 들어있음
해당 컴포넌트가 다시 호출되면 변수도 객체를 또 할당받고 다른 메모리에 저장 된다. 이로 인해 변수 속 메모리 주소 값도 바뀌게 되어 useEffect()가 계속 실행된다.
const [isKorea, setIsKorea] = useState(true);
const location = useMemo(() => {
return {
country: isKorea ? '한국' : '외국';
};
}, [isKorea]);
useEffect(() => {
console.log('useEffect() 호출');
}, [location]);
값
을 캐싱해두는 것이고,콜백 함수
그 자체를 캐싱해둔다.// 일반 함수
const calculate = (num) => {
return num + 1;
};
// useCallback
// const value = useCallback(콜백함수, 의존성 배열);
const calculate = useCallback((num) => {
return num + 1;
}, [item]);
함수형 컴포넌트는 함수이다. 렌더링 시 컴포넌트 함수가 호출되며, 이후에 모든 내부 변수는 초기화가 된다. 그렇기 때문에 렌더링이 될 때마다 함수 객체를 할당받게 된다.
useCallback을 사용하면 Memoize 된 함수를 재사용하기 때문에 렌더링 될 때마다 함수 객체를 할당받는 행위를 막을 수 있다. 단, 의존성 배열 내부에 명시해준 값이 변경되면 새로 만들어진 함수 객체로 초기화 된다.
useState 처럼 state 생성, 관리를 위한 Hook이다. 복잡한 state를 다뤄야 할 때 사용한다.
const reducer = (state, action) => {
// 수행할 일
};
const [item, dispatch] = useReducer(reducer, '초기값');
useReducer는 useState 처럼 배열을 반환해 준다. 배열의 첫번째 요소(item)는 새로 생성한 state이고, 배열의 두번째 요소(dispatch)에는 useReducer가 만들어준 dispatch가 들어있다. useReducer는 인자로 리듀서와 초기 값을 받는다.
새로 생성한 item의 값을 수정하고 싶다면 reducer를 통해서만 가능하다. 수정을 원할 때마다 dispatch를 호출해야 하고, dispatch를 부르면 reducer가 호출된다. dispatch의 인자로 action을 전달할 수 있다.
// 예제) 버튼을 클릭할 때마다 숫자가 1씩 증가 또는 감소
// 1. 컴포넌트 내부에 useReducer를 선언한다.
const [number, dispatch] = useReducer(reducer, 0);
// 2. 컴포넌트 밖에 reducer를 만든다.
// 추후 편리한 수정을 위해 ACTION_TYPES를 따로 빼주는 것이 좋다.
const ACTION_TYPES = {
increase: 'increase',
decrease: 'decrease',
};
const reducer = (state, action) => {
// state는 number 상태이다.
switch(action.type) {
case ACTION_TYPES.increase:
return state + 1;
case ACTION_TYPES.decrease:
return state - 1;
default:
return state;
};
};
// type: 속성, payload: 해당 행동과 관련된 데이터
<button onClick={() => dispatch({ type: ACTION_TYPES.increase, payload: 1 })}>증가</button>
<button onClick={() => dispatch({ type: ACTION_TYPES.decrease, payload: 1 })}>감소</button>
dispatch()는 useReducer가 만들어준 함수이고, 인자로 action을 넣어주면 된다. dispatch를 호출하면 reducer가 호출되고, 인자로 action이 전달되어 전달된 내용을 토대로 state가 변경된다.
React.memo 또한 메모리에 저장해두며 재사용하는 것이므로, 무분별하게 사용할 시 메모리 소비로 인해 성능에 독이 될 수 있다.
const child = ({ name, age }) => {
// (...)
};
export default memo(child);
최적화 시키고 싶은 컴포넌트를 감싸주면 된다.
중복된 컴포넌트가 많은 경우 코드가 지저분해질 수 있다. 여러 컴포넌트 들의 반복되는 로직이 필요하다면 직접 훅을 만들어서 사용할 수 있다. 함수를 하나 만드는 것이다.
새로운 파일을 생성한다. 이때 파일명은 다른 훅처럼 앞에 use 키워드를 사용하는 것이 좋다. use 키워드 사용 시, 훅을 사용하면서 실수를 저지를 경우 리액트 자체에서 콘솔에 적절한 경고 메시지를 띄워준다.
export function useTest() {
(...)
return (...)
}
다른 훅과 사용방법은 동일하다.
리액트를 다루는 기술 이라는 책을 통해 1차적으로 보고, 부족한 부분은 유튜브(별코딩-React Hooks에 취한다)를 통해 정리하였다. 기록하기 위해 여러번 보니, 확실히 처음 봤을 때보다는 이해가 많이 되었다. 이제 프로젝트를 통해 직접 사용해 보면서 내 것으로 만들어야겠다 !!