정의
hooking - 소프트웨어 구성요소간 발생하는 함수호출, 메시지, 이벤트 등을 중간에 바꾸거나 가로채는 명령, 행위
hook - 이때의 함수호출, 이벤트 또는 메시지를 처리하는 코드를 hook 이라고 함
React에서의 hook
함수 컴포넌트에서 React state와 생명주기 기능을 연동할 수 있게 해주는 함수
- props, state, context, refs, lifecycle 과 같은 react 개념에 좀더 직관적인 API 를 제공
- 컴포넌트 사이에서 상태 로직을 재사용 하기 위함
- 독립적인 테스트가 가능
- class 컴포넌트 안에서는 동작하지 않음
class 컴포넌트의 단점
- this 키워드의 작동이해가 되지 않으면 혼란을 줄수있음
- 코드의 최소화를 힘들게 만듦
Hook 사용 규칙(linter plugin 에서 강제함)
- 취상위 에서만 Hook을 호출해야함 → 반복문, 조건문, 중첩된 함수 내에서 hook 실행 금지
- 함수 컴포넌트 또는 custom hook 내에서만 호출
State Hook
useState
- 컴포넌트가 다시 렌더링 되어도 그대로 유지
- 초기값은 첫 번째 렌더링에만 딱 한번 사용
- 매번 렌더링시 useState가 사용된 순서대로 실행
예시
state 변수 선언
import React, { useState } from 'react';
function Example() {
// 새로운 state 변수를 선언하고, 이것을 count라 부르겠습니다.
const [count, setCount] = useState(0);
- 인자로 state의 초기값 전달
- return
- state 변수
- 해당 변수를 갱신할 수 있는 함수
state 갱신 & 가져오기
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
- 클래스 방식과 달리 this 호출하지 않아도됨
- this.setState와 달리 state를 갱신 하는것은 병합이 아니라 대체함
Effect Hook
effect(side effects) - 컴포넌트 안에서 데이터를 가져오거나 구독, DOM 조작 등을 말함
- clean-up 이 필요한 effect
- 그렇지 않은 effect
- 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있도록 해줌
- class 컴포넌트 life cycle 의 세가지를 하나의 API 로 통합한것
- componentDidMount
- componentDidUpdate
- componentWillUnmount
Clean-up 이 필요없는 Effects
- React 가 DOM을 업데이트한 뒤(rendering 이후) 추가로 코드를 실행해야하는 경우
- 첫번째 rendering 과 모든 업데이트에서 수행
- 실행 이후 신경쓸게 없는 effects
- class life cycle method
- componentDidMount
- componentDidUpdate
- ex) - network request, DOM 수동조작, logging
예시
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Clean-up을 이용하는 Effects
- 외부 테이터에 구독을 설정해야 하는 경우 메모리 누수가 발생하지 않도록 clean-up 필요
- class life cycle method
- componentDidMount → 외부 데이터 구독
- componentWillUnmount → clean-up
예시
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다.
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
- clean-up은 마운트 해제되는 시점에 실행
Multiple Effect
예시
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
- 각 관심사에 따라 로직을 분리시킬 수 있음
Effect가 업데이트시 마다 실행되는 이유
예시
// { friend: { id: 100 } } state을 사용하여 마운트합니다.
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 첫번째 effect가 작동합니다.
// { friend: { id: 200 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 다음 effect가 작동합니다.
// { friend: { id: 300 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 다음 effect가 작동합니다.
// 마운트를 해제합니다.
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 마지막 effect를 정리(clean-up)합니다.
- clean-up이 Unmount시 한번이 아니라 모든 re-rendering시 실행되는 이유
- → 컴포넌트가 이미 rendering이 된 상태에서 update가 일어났을때
- → 기존 값(외부 데이터)에 해당하는 effect는 clean-up을 시키고 update된 effect를 작동시킴
- 클래스 컴포넌트에서는 보통 update 로직을 빼먹었을시 발생할 수 있는 버그를 예방함
useContext
- context 객체를 받아 현재 값을 반환
- useContext를 호출한 컴포넌트는 context 값이 변경되면 항상 re-rendering
- re-redering 으로 인한 최적화가 필요할 경우 메모이제이션(useMemo 등..)을 이용하여 최적화
- context API 사용시 아래와 동일한 의미
- static contextType = MyContext
- <MyContext.Consumer>
- provider는 사용해야함
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// context 생성 및 초기값 세팅
const ThemeContext = React.createContext(themes.light);
function App() {
return (
// provider component 사용 및 value 로 새로운 값 전달
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
// useContext를 통해 context 사용
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}useCallback
useCallback
- 메모이제이션된 콜백을 반환
- 의존성이 변경 되었을 때에만 새로운 콜백으로 변경됨
- 불필요한 렌더링을 방지하기 위함
- useCallback(fn, deps) == useMemo(() => fn, deps)
예시
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
- 메모이제이션된 값을 반환
- 의존성이 변경 되었을 때에만 다시 계산함
- rendering 중에 실행됨
- useEffect에서 하는 일과 구분 필요
- 이것을 사용하지 않고도 동작할 수 있도록 작성하고 최적화 할때 사용하는것을 권장
예시
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useRef
- 전달된 인자로 초기화된 변경 가능한 ref객체 반환
예시
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` attribute는 mount된 input element를 가리키고있음
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useLayoutEffect
- useEffect와 컨셉은 동일
- 모든 DOM 변경이후 동기적으로 발생
- DOM 에서 레이아웃을 읽고 동기적으로 리렌더링 하는 경우 사용
- 화면 갱신 차단의 방지가 가능할때 표준 useEffect를 먼저 사용
useDebugValue
- 개발자도구에서 custom hook 레이블을 표시할때 사용
- custom hook 에서도 공유된 라이브러리에서만 사용 권장
예시
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 개발자도구에서 표시될 내용 -> "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
참고자료
'Front-end > React' 카테고리의 다른 글
Styled-component (0) | 2022.12.05 |
---|---|
React Query (0) | 2022.11.28 |
Redux (0) | 2022.11.23 |
Next.js (0) | 2022.11.23 |
React core (0) | 2022.11.23 |