render props or higher-order 컴포넌트(HOC : 컴포넌트를 다른 컴포넌트로 wrapping)같은 패턴 사용해서 이 어려움을 해결하고 있었음 -> 문법이 어려워져 보기 힘듦
class 컴포넌트 내부에 있는 생명주기(Lifecycle) 함수들 기반으로 나뉘어져 있고, 그 내부에 관련 없는 로직이 자주 들어가게 되면서 로직이 한눈에 잘 들어오지 않음
state 관리하는 로직이 흩어져서 관리하기 힘듦
JS의 Class의 개념 자체가 헷갈리게 만듦(특히 this)
props
top-down으로 부모 컴포넌트로부터 전달되는 값, props에 의해 view가 결정됨
props는 수정해서는 안됨
React 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야 함
// 순수 함수 : 입력값을 바꾸지 않고 항상 동일한 입력값에 대해 동일한 결과 반환
function sum(a, b) {
return a + b;
}
// 입력값을 변경하기 때문에 순수 함수가 아님
function withdraw(account, amount) {
account.total -= amount;
}
state
사용자의 동작에 따른 업데이트를 위해 컴포넌트 내부에서 변경을 위해 추적하는 값을 담은 변수
📌 State Hook
그동안은 UI 렌더링에 관해서 알아봤다면 Hook으로 컴포넌트의 동작에 대해 더 알 수 있음
// useState : array로 반환
// state : state 값, setState : state를 update 할 수 있는 함수
const [state, setState] = useState(initialState);
// update 할 newState를 전달해서 state update
setState(newState);
setState(newState) 호출 되면 state update가 되고 component가 re-render 된다.
// 값이 아니라 함수 전달
setState((previousState) => nextState)
setState()에 update 함수를 전달할 수 있음
한 번의 render에 여러번 setState() 하는 경우 반드시 사용해야함
setState가 여러번 발생하면 react는 마지막 setState한 값으로 override 됨
함수로 전달하면 체이닝으로 연결하기 때문에 이전 state 값을 기준으로 다음 state 값을 계산
함수로 전달하지 않으면 setState() 호출로 기존 state는 대체 됨 (not merge)
@06-hooks/functional-state-update
const [count, setCount] = useState(0);
// 1. 만약 중간에 로직이 복잡해서 여러번 setState()를 호출하게 되는 경우 (억지로 만든 예시)
// 2. 컴포넌트가 render가 될 때, state 값을 매번 가져오는데
// setCount에서의 count는 컴포넌트 내의 count state를 바라봐서 마지막 state으로만 override 됨
const increase = () => {
setCount(count + 1);
setCount(count + 1);
};
// 3. 함수가 체이닝으로 이어지기 때문에 컴포넌트 내의 state 값을 가져오는게 아니라 setCount가 알고 있는 이전 값을 가져올 수 있음
const increaseFunctional = () => {
setCount(prevCount => { prevCount + 1 });
setCount(prevCount => { prevCount + 1 });
};
// 클래스 컴포넌트의 setState 메서드와는 다르게, useState는 갱신 객체(update objects)를 자동으로 합치지는 않음
// 함수 업데이터 폼을 객체 전개 연산자와 결합함으로써 이 동작을 복제 가능
const [state, setState] = useState({});
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Lazy initial state
계산 비용이 비싼 로직을 initial render에 한 번만 호출
// Component를 맨 처음 render 할 때 1번만 실행됨
// 그 다음 setState가 호출되면 Component가 매번 호출되지만, 넘긴 함수는 memoization이 되어서 다시 실행 X
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
component 내부 객체/배열 생성은 매번 다른 reference를 갖기 때문에 다른 state로 인식 함 (실행될 때마다 component가 다시 불리는데 그때마다 내부 변수의 reference가 매번 달라짐)
이전에 불린 setState와 다음에 불린 setState에 전달된 값을 비교하게 되는데, 같은 state면 re-render 되지 않고 다르면 re-render 때문에 dom이 불필요하게 update 될 수도 있음. 주의 !!!
동일 state로 업데이트 되면 render 되지 않음
⚡️ Effect hook
Effect?
Side effect (함수형 컴포넌트가 render외에 하는 다른 일)
Data fetching
Setting up a subscription(eg. eventHandler)
Manually changing the DOM
timers, logging...
React's purely functional world => imperative world (명령형)
함수형이 아닌 명령형으로..
useEffect
// didUpdate : 컴포넌트가 업데이트 된 다음에 실행되는 함수
// dependency : 이 값이 변경이 될 때만 effect 실행해달라 (최적화 연관), 없으면 비교대상이 없으니 항상 didUpdate (지양)
useEffect(didUpdate, [dependency]);
component render가 완료된 후에 실행
dependency 추가하여 특정 값이 변경된 경우에만 실행하도록 설정 가능
dependency 없는 경우 render 후에 항상 실행
빈 배열로 넘기면 변경 대상이 없기 때문에 component에서 딱 1번만 실행되어야 하는 경우도 useEffect에서 처리 가능
왜 component 안에서 호출될까?
props, state 접근성 - 동일 scope
props와 state를 접근하기에 같은 scope 안에 있기 때문에 함수 컴포넌트 안에서 호출하고 있음
useEffect 호출은 browser paint를 block 하지 않음(render를 먼저 하고 useEffect 실행) => 반응성 향상(UI를 먼저 렌더링하고 기능은 그 다음)
paint block 필요한 경우(컴포넌트가 render 되기 전에 반영이 되어야 하는 동기적인 경우) useLayoutEffect 사용 (useEffect 사용시 깜빡이는 현상이 있는 경우 사용)
🧹cleanup effect!
component가 unmount가 될 때, 이전에 등록해둔 effect를 cleanup
useEffect()의 return으로 cleanup function 전달 가능
다음 useEffect() 실행 전에 이전 useEffect()의 cleanup function 호출 됨
component unmount 될 때도 호출 (unmount 될 때 처리되어야 하는 코드일 경우 여기서 처리)
관심 주제에 따라 useEffect를 나누어서 사용 추천
관심 주제가 여러개면 여러개의 useEffect를 사용하는 것이 유지보수에 좋음
dependency를 가지기 때문에 굳이 실행되어야 하지 않는 로직이 다른 dependency 때문에 실행되면 안되니 분리해서 관리 추천
@06-hooks/effect
unction Example() {
console.log('1. component render start!');
const [count, setCount] = useState(0);
const [toggleCounter, setToggleCounter] = useState(true);
// render 된 다음에 useEffect로 넘긴 didUpdate 함수가 실행됨
// state가 변경되면서 전체적으로 useEffect와 render가 매번 실행됨
// dependency를 count로 전달해서 count state가 변경될 때만 실행되도록
// useEffect는 if, for 문 등에 들어가면 안되고 항상 상위에 위치해야하는 React Rule
// - 안지키면 React Hook은 항상 반드시 같은 순서로 실행이 보장되어야 한다 라는 오류가 뜸
useEffect(() => {
// if (count < 10) 일때 useEffect를 감싸는 구조 X, useEffect 안에서 조건문 처리 해야함
console.log('3. effect start!');
if (count > 10) return undefined;
// 직접 dom을 제어하는 부분이라 useEffect에서 처리
document.title = `You clicked ${count} times`;
console.log('title updated');
// 초기화 함수
// useEffect 실행하기 전에 이전 컴포넌트에서 등록된 useEffect의 clear 함수가 실행됨
return () => {
console.log('4. effect clear!');
document.title = 'clear!';
}
}, [count]);
// React Component는 Virtaul DOM만을 제어하고 실제 DOM을 제어하는 건 side effect로 판단되어 useEffect에 처리
console.log('2. Virtual DOM render!');
return (
<div>
<button onClick={() => setToggleCounter((prev) => !prev)}>
Toggle counter
</button>
{toggleCounter && (
<>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</>
)}
</div>
);
}
@06-hooks/clock
const Clock = () => {
const [time, setTime] = useState(() => new Date().toLocaleTimeString());
useEffect(() => {
const timer = setTimeout(() => setTime(new Date().toLocaleTimeString()), 1000);
// clearTimeout 해주지 않으면 Clock 컴포넌트가 빠지는 경우(뒤로가기) 1초 뒤에 실행이 될 때 이미 unmount된 컴포넌트에 state를 접근하는 경우 에러 발생
return(() => {clearTimeout(timer)});
}, [time]);
render(
<div>
<h1>current time: {time}</h1>
</div>
)
};
✌️ Rules of Hooks
Only Call Hooks at the Top Level
if, for 문 등에 포함되면 안됨, Hook이 실행이 안될수도 있음
Hook에 등록한 useState, useEffect가 함수형 컴포넌트 외에 Hook만을 관리하는 별도 메모리에 배열로 저장되는 구조
매번 render가 다시 되더라도 등록한 Hook이 유지됨
Hook은 예외처리가 되면 안되고 top level에서 호출되어야 배열 안에서 순서가 꼬이지 않음
// sideEffect가 발생하는 경우 사용
// react의 pure function에서 넘어가서 imperative 영역으로 넘어가는 경우 사용 (fetch, timer, logging, dom 제거 etc)
// class 컴포넌트의 didUpdate의 기능과 비슷
// dependency를 넣으면 각각 이전 값과 비교해서(값들은 값 비교, array나 object같은 reference를 가진 객체는 reference 비교, Object.is 비교) 바뀐 경우만 update
useEffect(didUpdate, [dependency]);
render가 일어나면 컴포넌트가 계속 호출되니 내부에 선언된 함수가 매번 render됨 -> 그 함수의 reference가 render될 때마다 변경되는데 이 함수를 children으로 props로 전달된다면 매번 변경되니 매번 re-render 되어버림 or useEffect의 dependency로 들어갈 경우 Object.is 비교로 reference가 변경되었기 때문에 여기도 re-render 되어버림
이런 경우를 피하기 위해서 전달한 함수는 전달한 dependency가 변경될 때만 실행한다
// 함수를 저장
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
memoization, 최적화 용도로 사용
// 값(함수의 반환값) 자체를 저장
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useRef
DOM 접근에 사용
render에 영향을 주지 않는 mutable data 관리에 편리
컴포넌트가 re-render되어도 유지되어야 하는 값이 있어야 함 (but 그 값이 화면을 render하는 곳에는 사용되지 않는, 컴포넌트의 instance 변수?)
const refContainer = useRef(initialValue);
const value = refContainer.current
useState, useEffect는 결국 컴포넌트 외에 따로 메모리 영역을 잡아서 유지하는 ? 그런 구조 -> 그렇기 때문에 너무 많은 컴포넌트에서 사용되는 경우 각각의 함수 영역을 하나씩 메모리에서 차지하기 때문에 정말 필요한 경우만 사용하는게 좋다. 너무 비용이 클 때만 최적화해라
섣부른 성능 최적화는 오히려 성능 저하를 발생시킬 수 있기 때문에 성능 이슈가 발생했을 때 진행 추천
Referential(성능) equality(최적화)
Computationally expensive calculations
📝 props vs. state
React Component가 render 되는 때/시점
dom을 실제로 update하는 건 react이기 때문에 react가 언제 dom을 update하는지 그 시점을 잘 알고 있으면 좋음
React Hooks #
소개: React Conf 2018 release: React v16.8
What are Hooks
[참고] Motivation #
props
state
📌 State Hook
setState(newState)
호출 되면 state update가 되고 component가 re-render 된다.state: any
: number, string, array, object...useState
여러번 사용해도 됨React가 화면을 update하는 3가지 방법
@06-hooks/state
Functional updates
setState()
에 update 함수를 전달할 수 있음setState()
하는 경우 반드시 사용해야함setState()
호출로 기존 state는 대체 됨 (not merge)@06-hooks/functional-state-update
@06-hooks/lazy-initial-state
⚡️ Effect hook
Effect?
Side effect (함수형 컴포넌트가 render외에 하는 다른 일)
React's purely functional world => imperative world (명령형)
useEffect
useEffect
호출은 browser paint를 block 하지 않음(render를 먼저 하고 useEffect 실행) => 반응성 향상(UI를 먼저 렌더링하고 기능은 그 다음)useLayoutEffect
사용 (useEffect 사용시 깜빡이는 현상이 있는 경우 사용)useEffect()
의 return으로 cleanup function 전달 가능useEffect()
실행 전에 이전useEffect()
의 cleanup function 호출 됨useEffect
를 나누어서 사용 추천@06-hooks/effect
@06-hooks/clock
✌️ Rules of Hooks
[필수] eslint-plugin-react-hooks 추가하여 lint 도움 받기
[참고] lint 도움을 받기 위해 custom hook 들은
use
prefix를 붙인다.💡 Building Your Own Hooks
custom hooks
이런 경우 사용
@06-hooks/fetch/data-fetch
Hooks API #
Basic Hooks
Additional Hooks
Redux
와 비슷한 형태로 사용forwardRef
와 같이 사용Hooks Summary
When to useMemo and useCallback #
📝 props vs. state
React Component가 render 되는 때/시점
dom을 실제로 update하는 건 react이기 때문에 react가 언제 dom을 update하는지 그 시점을 잘 알고 있으면 좋음
props와 state 구별
#cheatsheet