From 5847415c7217eacb505128834b16316d5814caec Mon Sep 17 00:00:00 2001 From: B Date: Wed, 10 Jan 2024 11:18:29 +0900 Subject: [PATCH 01/13] chore(#31): Add chapter3 --- chapter03/info.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 chapter03/info.md diff --git a/chapter03/info.md b/chapter03/info.md new file mode 100644 index 0000000..52cc335 --- /dev/null +++ b/chapter03/info.md @@ -0,0 +1,47 @@ +## 03장: 리액트 훅 깊게 살펴보기 + +
+ +- [03장: 리액트 훅 깊게 살펴보기](#03장-리액트-훅-깊게-살펴보기) +- [3.1 리액트의 모든 훅 파헤치기](#31-리액트의-모든-훅-파헤치기) + - [3.1.1 useState](#311-usestate) + - [3.1.2 useEffect](#312-useeffect) + - [3.1.3 useMemo](#313-usememo) + - [3.1.4 useCallback](#314-usecallback) + - [3.1.5 useRef](#315-useref) + - [3.1.6 useContext](#316-usecontext) + - [3.1.7 useReducer](#317-usereducer) + - [3.1.8 useImperativeHandle](#318-useimperativehandle) + - [3.1.9 useLayoutEffect](#319-uselayouteffect) + - [3.1.10 useDebugValue](#3110-usedebugvalue) + - [3.1.11 훅의 규칙](#3111-훅의-규칙) + - [3.1.12 정리](#3112-정리) +- [3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?](#32-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까) + - [3.2.1 사용자 정의 훅](#321-사용자-정의-훅) + - [3.2.2 고차 컴포넌트](#322-고차-컴포넌트) + - [3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?](#323-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까) + - [3.2.4 정리](#324-정리) + +
+ +## 3.1 리액트의 모든 훅 파헤치기 +### 3.1.1 useState +### 3.1.2 useEffect +### 3.1.3 useMemo +### 3.1.4 useCallback +### 3.1.5 useRef +### 3.1.6 useContext +### 3.1.7 useReducer +### 3.1.8 useImperativeHandle +### 3.1.9 useLayoutEffect +### 3.1.10 useDebugValue +### 3.1.11 훅의 규칙 +### 3.1.12 정리 + +
+ +## 3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? +### 3.2.1 사용자 정의 훅 +### 3.2.2 고차 컴포넌트 +### 3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? +### 3.2.4 정리 \ No newline at end of file From 4e4ff96b48b0d63a0d2a9630dedf198fe6d2edbc Mon Sep 17 00:00:00 2001 From: B Date: Thu, 11 Jan 2024 11:23:02 +0900 Subject: [PATCH 02/13] chore: Add PR template --- .github/pull_request_template.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 72c1ff7..ce79864 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,20 @@ -**Participant** +## 리뷰 요청 사항 +[어떤 부분에 대해 코드 리뷰를 요청하고 싶은지 명확하게 작성해주세요.] +## 리뷰 내용 +[코드 리뷰를 받고자 하는 코드에 대한 설명을 작성해주세요. 어떤 기능을 구현하려고 하는지, 어떤 문제를 해결하려는지 등을 포함시켜주세요.] + +## 주요 변경 사항 +[주요하게 변경한 부분이나 수정한 내용을 간략히 설명해주세요.] + +## 특별히 확인해야 할 부분 +[리뷰어에게 특별히 확인해야 할 부분이 있다면 작성해주세요. 예를 들어, 성능 개선, 보안 이슈, 코드 스타일 등을 확인해야 할 수 있습니다.] + +## 참고 사항 +[리뷰어에게 알려주고 싶은 추가적인 정보나 참고 사항이 있다면 작성해주세요.] + + +## 참여 - [ ] @functionBee - [ ] @HYEOK999 - [ ] @hyjoong From a27c0f12ff22934c39aa5d89f9f07ac143e15fed Mon Sep 17 00:00:00 2001 From: B Date: Fri, 12 Jan 2024 10:26:00 +0900 Subject: [PATCH 03/13] feat(#31): 3.1.1 useState --- chapter03/b.md | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 chapter03/b.md diff --git a/chapter03/b.md b/chapter03/b.md new file mode 100644 index 0000000..9a9f5c7 --- /dev/null +++ b/chapter03/b.md @@ -0,0 +1,131 @@ +## 03장: 리액트 훅 깊게 살펴보기 + +
+ +- [03장: 리액트 훅 깊게 살펴보기](#03장-리액트-훅-깊게-살펴보기) +- [3.1 리액트의 모든 훅 파헤치기](#31-리액트의-모든-훅-파헤치기) + - [3.1.1 `useState`](#311-usestate) + - [3.1.2 `useEffect`](#312-useeffect) + - [3.1.3 `useMemo`](#313-usememo) + - [3.1.4 `useCallback`](#314-usecallback) + - [3.1.5 `useRef`](#315-useref) + - [3.1.6 `useContext`](#316-usecontext) + - [3.1.7 `useReducer`](#317-usereducer) + - [3.1.8 `useImperativeHandle`](#318-useimperativehandle) + - [3.1.9 `useLayoutEffect`](#319-uselayouteffect) + - [3.1.10 `useDebugValue`](#3110-usedebugvalue) + - [3.1.11 훅의 규칙](#3111-훅의-규칙) + - [3.1.12 정리](#3112-정리) +- [3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?](#32-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까) + - [3.2.1 사용자 정의 훅](#321-사용자-정의-훅) + - [3.2.2 고차 컴포넌트](#322-고차-컴포넌트) + - [3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?](#323-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까) + - [3.2.4 정리](#324-정리) +- [References](#references) +- [Articles](#articles) + +
+ +## 3.1 리액트의 모든 훅 파헤치기 + +| 훅 이름 | 타입 | 정의 | 예제 | +|-----------------|-------------|---------------------------------------------------------|-----------------------------------------------------------| +| useState | 상태 훅 | 상태 값을 관리하고 업데이트하는 데 사용 | `const [count, setCount] = useState(0);` | +| useEffect | 효과 훅 | 컴포넌트의 생명주기에 따른 작업을 수행하는 데 사용 | `useEffect(() => { console.log('Component mounted'); }, []);` | +| useMemo | 메모이제이션 훅 | 계산 결과를 기억하고 재사용하는 데 사용 | `const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);` | +| useCallback | 콜백 훅 | 콜백 함수를 기억하고 재사용하는 데 사용 | `const memoizedCallback = useCallback(() => { /* callback logic */ }, [dependency]);` | +| useRef | 참조 훅 | 컴포넌트 내에서 변경 가능한 값을 저장하는 데 사용 | `const refContainer = useRef(initialValue);` | +| useContext | 컨텍스트 훅 | React의 컨텍스트를 사용하는 데 사용 | `const value = useContext(MyContext);` | +| useReducer | 리듀서 훅 | 상태와 액션을 받아 새로운 상태를 반환하는 리듀서 함수와 함께 사용 | `const [state, dispatch] = useReducer(reducer, initialState);` | +| useImperativeHandle | 인퍼런스 훅 | 부모 컴포넌트에서 자식 컴포넌트의 인스턴스를 조작하는 데 사용 | `useImperativeHandle(ref, () => ({ method() { /* method logic */ } }));` | +| useLayoutEffect | 레이아웃 훅 | DOM 업데이트 이후 동기적으로 작업을 수행하는 데 사용 | `useLayoutEffect(() => { /* layout effect logic */ }, []);` | +| useDebugValue | 디버그 훅 | 커스텀 훅에서 디버깅 정보를 제공하는 데 사용 | `useDebugValue(value);` | + + +### 3.1.1 `useState` + +```jsx +/** + * * @description useState 훅의 기본적인 사용법 (p. 190) + * * useState는 React에서 상태를 관리하기 위해 사용되는 훅입니다. + * * 이 훅은 두 개의 요소로 이루어진 배열을 반환합니다: + * * - 첫 번째 요소인 state는 현재 상태 값을 나타냅니다. + * * - 두 번째 요소인 setState는 상태 값을 업데이트하기 위해 사용되는 함수입니다. + * * * @param {*} initialState 초기 상태 값을 나타냅니다. 제공되지 않으면 undefined로 초기화됩니다. +* */ + +import { useState } from "react"; +const [state, setState] = useState(initialState); +``` + +- `useState`를 사용하여 상태를 업데이트할 때, 이전 상태와 새로운 상태를 병합하지 않고 새로운 상태로 대체합니다. +- `useState`를 사용하여 상태를 업데이트할 때 이전 상태와 새로운 상태를 병합하고 싶다면, 개별적인 상태 변수를 사용하거나 `useReducer` 훅을 사용할 수 있습니다. +- `useState` 훅은 상태를 변경할 때마다 전체 상태 객체를 대체하는 것이 아니라, 변경된 부분만 업데이트합니다. 이전 상태와 새로운 상태를 병합하지 않는 이유는 React가 불필요한 렌더링을 방지하고 성능을 최적화하기 위해서입니다. +- `useState`는 클래스 컴포넌트에서 찾을 수 있는 `setState` 메서드와 달리 업데이트 객체를 자동으로 병합하지 않습니다. 그러나 함수 업데이터 형식과 객체 전개 구문을 결합하여 이 동작을 복제할 수 있습니다. +- `useState`는 클래스 컴포넌트와 함수 컴포넌트에서 각각 사용됩니다. +- React에서 상태 업데이트는 비동기적으로 처리됩니다. 따라서 업데이트가 요청되었을 때 즉시 업데이트가 이루어질 것을 보장할 수는 없습니다. 이는 React의 내부 동작 원리에 따라 상태 업데이트는 일괄 처리되며, React는 업데이트가 발생한 컴포넌트를 감지하고 필요한 경우에만 화면을 다시 렌더링하기 때문입니다. +- `useState` 훅은 React에서 동기적으로 작동하지만, 재렌더링 과정 때문에 상태 변경이 비동기적으로 보일 수 있습니다. +- React에서 `useState` 훅은 함수형 컴포넌트에서 상태를 관리하기 위해 사용됩니다. 이 훅을 사용하면 상태 값을 저장하고 업데이트할 수 있습니다. +- `useState` 훅은 동기적으로 작동하지만, React는 상태 변경이 발생하면 컴포넌트를 재렌더링합니다. 재렌더링은 React가 가상 DOM을 사용하여 변경된 상태를 실제 DOM에 반영하는 과정입니다. 이 과정은 비동기적으로 처리됩니다. 따라서 상태 변경 후에 컴포넌트가 즉시 재렌더링되지 않을 수 있습니다. 대신, React는 상태 변경을 큐에 저장하고, 다음 렌더링 사이클에서 변경된 상태를 적용합니다.이러한 비동기적인 재렌더링 과정 때문에, `useState` 훅을 사용하여 상태를 변경한 후에도 즉시 변경된 상태를 접근할 수 없을 수 있습니다. +- `useState`는 클래스 컴포넌트에서 찾을 수 있는 `setState` 메서드와 달리 업데이트 객체를 자동으로 병합하지 않습니다. 그러나 함수 업데이터 형식과 객체 전개 구문을 결합하여 이 동작을 복제할 수 있습니다. + +```jsx +// spread 연산자 +const [state, setState] = useState({}); +setState(prevState => { + return {...prevState, ...updatedValues}; +}); +``` + +```jsx +// Object.assign +const [state, setState] = useState({}); +setState(prevState => { + return Object.assign({}, prevState, updatedValues); +}); +``` + +```jsx +// useReducer 사용 +// - 여러 하위 값이 포함된 상태 객체를 관리하기에 더 적합합니다. +const initialState = {}; +const reducer = (state, action) => { + switch (action.type) { + case 'UPDATE_STATE': + return {...state, ...action.payload}; + default: + return state; + } +}; + +const [state, dispatch] = useReducer(reducer, initialState); +dispatch({type: 'UPDATE_STATE', payload: updatedValues}); +``` + +### 3.1.2 `useEffect` +### 3.1.3 `useMemo` +### 3.1.4 `useCallback` +### 3.1.5 `useRef` +### 3.1.6 `useContext` +### 3.1.7 `useReducer` +### 3.1.8 `useImperativeHandle` +### 3.1.9 `useLayoutEffect` +### 3.1.10 `useDebugValue` +### 3.1.11 훅의 규칙 +### 3.1.12 정리 + +
+ +## 3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? +### 3.2.1 사용자 정의 훅 +### 3.2.2 고차 컴포넌트 +### 3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? +### 3.2.4 정리 + +## References +- [리액트 공식문서, Introducing Hooks](https://legacy.reactjs.org/docs/hooks-intro.html) +- [리액트 공식문서, Hooks API Reference](https://legacy.reactjs.org/docs/hooks-reference.html) +- [useState](https://react.dev/reference/react/useState) + +## Articles +- [DEV, Why useeffect is running twice in react](https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6#:~:text=In%20React%2C%20the%20useEffect%20hook,as%20%22dependencies%22)%20change) \ No newline at end of file From 2e61b2e6d9dd564c6ed477b99cf1cdac1dd03abd Mon Sep 17 00:00:00 2001 From: B Date: Mon, 15 Jan 2024 16:29:07 +0900 Subject: [PATCH 04/13] update: #35 3.1.2 useEffect --- chapter03/b.md | 42 +++++++++++++++++++++--------- examples/chapter03/usState.jsx | 0 examples/chapter03/useEffect-1.jsx | 8 ++++++ examples/chapter03/useEffect-2.jsx | 18 +++++++++++++ examples/chapter03/useEffect-3.jsx | 10 +++++++ examples/chapter03/useEffect-4.jsx | 14 ++++++++++ examples/chapter03/useEffect-5.jsx | 39 +++++++++++++++++++++++++++ examples/chapter03/useEffect-6.jsx | 30 +++++++++++++++++++++ examples/chapter03/useEffect-7.jsx | 11 ++++++++ examples/chapter03/useEffect-8.jsx | 33 +++++++++++++++++++++++ 10 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 examples/chapter03/usState.jsx create mode 100644 examples/chapter03/useEffect-1.jsx create mode 100644 examples/chapter03/useEffect-2.jsx create mode 100644 examples/chapter03/useEffect-3.jsx create mode 100644 examples/chapter03/useEffect-4.jsx create mode 100644 examples/chapter03/useEffect-5.jsx create mode 100644 examples/chapter03/useEffect-6.jsx create mode 100644 examples/chapter03/useEffect-7.jsx create mode 100644 examples/chapter03/useEffect-8.jsx diff --git a/chapter03/b.md b/chapter03/b.md index 9a9f5c7..80cfb96 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -28,22 +28,25 @@ ## 3.1 리액트의 모든 훅 파헤치기 -| 훅 이름 | 타입 | 정의 | 예제 | -|-----------------|-------------|---------------------------------------------------------|-----------------------------------------------------------| -| useState | 상태 훅 | 상태 값을 관리하고 업데이트하는 데 사용 | `const [count, setCount] = useState(0);` | -| useEffect | 효과 훅 | 컴포넌트의 생명주기에 따른 작업을 수행하는 데 사용 | `useEffect(() => { console.log('Component mounted'); }, []);` | -| useMemo | 메모이제이션 훅 | 계산 결과를 기억하고 재사용하는 데 사용 | `const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);` | -| useCallback | 콜백 훅 | 콜백 함수를 기억하고 재사용하는 데 사용 | `const memoizedCallback = useCallback(() => { /* callback logic */ }, [dependency]);` | -| useRef | 참조 훅 | 컴포넌트 내에서 변경 가능한 값을 저장하는 데 사용 | `const refContainer = useRef(initialValue);` | -| useContext | 컨텍스트 훅 | React의 컨텍스트를 사용하는 데 사용 | `const value = useContext(MyContext);` | -| useReducer | 리듀서 훅 | 상태와 액션을 받아 새로운 상태를 반환하는 리듀서 함수와 함께 사용 | `const [state, dispatch] = useReducer(reducer, initialState);` | -| useImperativeHandle | 인퍼런스 훅 | 부모 컴포넌트에서 자식 컴포넌트의 인스턴스를 조작하는 데 사용 | `useImperativeHandle(ref, () => ({ method() { /* method logic */ } }));` | -| useLayoutEffect | 레이아웃 훅 | DOM 업데이트 이후 동기적으로 작업을 수행하는 데 사용 | `useLayoutEffect(() => { /* layout effect logic */ }, []);` | -| useDebugValue | 디버그 훅 | 커스텀 훅에서 디버깅 정보를 제공하는 데 사용 | `useDebugValue(value);` | +| 훅 이름 | 정의 | 예제 | +|-----------------|---------------------------------------------------------|-----------------------------------------------------------| +| useState | 상태 값을 관리하고 업데이트하는 데 사용 | `const [count, setCount] = useState(0);` | +| useEffect | 컴포넌트의 생명주기에 따른 작업을 수행하는 데 사용 | `useEffect(() => { console.log('Component mounted'); }, []);` | +| useMemo | 계산 결과를 기억하고 재사용하는 데 사용 | `const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);` | +| useCallback | 콜백 함수를 기억하고 재사용하는 데 사용 | `const memoizedCallback = useCallback(() => { /* callback logic */ }, [dependency])` | +| useRef | 컴포넌트 내에서 변경 가능한 값을 저장하는 데 사용 | `const refContainer = useRef(initialValue);` | +| useContext | React의 컨텍스트를 사용하는 데 사용 | `const value = useContext(MyContext);` | +| useReducer | 상태와 액션을 받아 새로운 상태를 반환하는 리듀서 함수와 함께 사용 | `const [state, dispatch] = useReducer(reducer, initialState);` | +| useImperativeHandle | 부모 컴포넌트에서 자식 컴포넌트의 인스턴스를 조작하는 데 사용 | `useImperativeHandle(ref, () => ({ method() { /* method logic */ } }));` | +| useLayoutEffect | DOM 업데이트 이후 동기적으로 작업을 수행하는 데 사용 | `useLayoutEffect(() => { /* layout effect logic */ }, []);` | +| useDebugValue | 커스텀 훅에서 디버깅 정보를 제공하는 데 사용 | `useDebugValue(value);` | ### 3.1.1 `useState` +- 리액트에서 렌더링은 함수 컴포넌트의 return과 클래스 컴포넌트의 render함수를 실행한 다음, 이 실행 결과를 이전의 리액트 트리와 비교해 리렌더링이 필요한 부분만 업데이트해 이뤄진다. +- 리액트의 렌더링은 함수 컴포넌트에서 반환한 결과물인 return의 값을 빅해 실행되기 때문에 매번 렌더링이 발생할 때마 함수는 다시 새롭게 실행되고, 새롭게 실행된 함수에서 state는 매번 hello로 초기화 되므로 아무리 state를 변경해도 다시 hello로 초기화도니다. + ```jsx /** * * @description useState 훅의 기본적인 사용법 (p. 190) @@ -103,6 +106,17 @@ dispatch({type: 'UPDATE_STATE', payload: updatedValues}); ``` ### 3.1.2 `useEffect` +- `useEffect` 훅은 두 개의 매개변수를 받습니다. 첫 번째 매개변수는 작업을 담은 함수이고, 두 번째 매개변수는 `의존성 배열(dependency array)`입니다. 이 함수는 컴포넌트가 렌더링될 때마다 실행되며, 의존성 배열에 지정된 값이 변경될 때만 실행됩니다. 의존성 배열을 빈 배열로 설정하면 컴포넌트가 처음 렌더링될 때만 실행됩니다. +- `Clean-up` 함수 +- `의존성 배열(dependency array)` +- `useEffect`의 구현 +- `useEffect`를 사용할 때 주의할 점 + 1. `eslint-disable-line react-hooks/exhaustive-deps` 주석은 최대한 자제하라 + 2. `useEffect`의 첫번째 인수에 함수명을 부여하라 + 3. 거대한 `useEffect`를 만들지마라 + 4. 불필요한 외부 함수를 만들지마라 + - 왜 `useEffect`의 콜백 인수로 비동기 함수를 바로 넣을 수 있을까? + ### 3.1.3 `useMemo` ### 3.1.4 `useCallback` ### 3.1.5 `useRef` @@ -122,10 +136,14 @@ dispatch({type: 'UPDATE_STATE', payload: updatedValues}); ### 3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? ### 3.2.4 정리 +
+ ## References - [리액트 공식문서, Introducing Hooks](https://legacy.reactjs.org/docs/hooks-intro.html) - [리액트 공식문서, Hooks API Reference](https://legacy.reactjs.org/docs/hooks-reference.html) - [useState](https://react.dev/reference/react/useState) +
+ ## Articles - [DEV, Why useeffect is running twice in react](https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6#:~:text=In%20React%2C%20the%20useEffect%20hook,as%20%22dependencies%22)%20change) \ No newline at end of file diff --git a/examples/chapter03/usState.jsx b/examples/chapter03/usState.jsx new file mode 100644 index 0000000..e69de29 diff --git a/examples/chapter03/useEffect-1.jsx b/examples/chapter03/useEffect-1.jsx new file mode 100644 index 0000000..f24283c --- /dev/null +++ b/examples/chapter03/useEffect-1.jsx @@ -0,0 +1,8 @@ +// useEffect의 일반적인 형태 +function Component() { + // ... + useEffect(() => { + // do sth + }, [props, state]) + // ... +} \ No newline at end of file diff --git a/examples/chapter03/useEffect-2.jsx b/examples/chapter03/useEffect-2.jsx new file mode 100644 index 0000000..a087691 --- /dev/null +++ b/examples/chapter03/useEffect-2.jsx @@ -0,0 +1,18 @@ +/** + * + * @returns 버튼 클릭시 counter가 1씩 중가 + */ +function Component() { + const [counter, setCounter] = useState(0); + + function handleClick() { + setCounter((prev) => prev + 1); + } + + return ( + <> +

{counter}

+ + + ) +} \ No newline at end of file diff --git a/examples/chapter03/useEffect-3.jsx b/examples/chapter03/useEffect-3.jsx new file mode 100644 index 0000000..3fe9df9 --- /dev/null +++ b/examples/chapter03/useEffect-3.jsx @@ -0,0 +1,10 @@ +function Component() { + const counter = 1;; + // ... + return ( + <> +

{counter}

+ + + ) +} \ No newline at end of file diff --git a/examples/chapter03/useEffect-4.jsx b/examples/chapter03/useEffect-4.jsx new file mode 100644 index 0000000..82d0b32 --- /dev/null +++ b/examples/chapter03/useEffect-4.jsx @@ -0,0 +1,14 @@ +function Component() { + const counter = 1;; + + useEffect(() => { + console.log(counter) + }) + + return ( + <> +

{counter}

+ + + ) +} \ No newline at end of file diff --git a/examples/chapter03/useEffect-5.jsx b/examples/chapter03/useEffect-5.jsx new file mode 100644 index 0000000..4816dd1 --- /dev/null +++ b/examples/chapter03/useEffect-5.jsx @@ -0,0 +1,39 @@ +import { useState, useEffect } from 'react'; + +export default function App() { + const [counter, setCounter] = useState(0); + + function handleClick() { + setCounter((prev) => prev + 1) + } + + useEffect(() => { + function addMouseEvent() { + console.log(counter); + } + + window.addEventListener('click', addMouseEvent) + + // clean-up + return () => { + console.log('clean-up 함수 실행', counter); + window.removeEventListener('click', addMouseEvent) + } + }, [counter]) + + return ( + <> +

{counter}

+ + + ) + +} + +/** + * @return clean-up 함수를 사용하여 이벤트 리스너를 제거 + * clean-up 함수 실행: 0 + * 1 + * clean-up 함수 실행: 1 + * 2 + */ \ No newline at end of file diff --git a/examples/chapter03/useEffect-6.jsx b/examples/chapter03/useEffect-6.jsx new file mode 100644 index 0000000..46f8087 --- /dev/null +++ b/examples/chapter03/useEffect-6.jsx @@ -0,0 +1,30 @@ +// 최초 실행 +useEffect(() => { + function addMouseEvent() { + console.log(1); + } + + window.addEventListener('click', addMouseEvent) + + // clean-up 함수 + return () => { + console.log('clean-up 함수 실행', 1); + window.removeEventListener('click', addMouseEvent) + } +}, [counter]) + +// 이후 실행 +useEffect(() => { + function addMouseEvent() { + console.log(2); + } + + window.addEventListener('click', addMouseEvent) + + // clean-up 함수 + return () => { + console.log('clean-up 함수 실행', 2); + window.removeEventListener('click', addMouseEvent) + } +}, [counter]) + diff --git a/examples/chapter03/useEffect-7.jsx b/examples/chapter03/useEffect-7.jsx new file mode 100644 index 0000000..7dfd450 --- /dev/null +++ b/examples/chapter03/useEffect-7.jsx @@ -0,0 +1,11 @@ +// 1 +function Component() { + console.log('렌더링 됨'); +} + +// 2 +function Component() { + useEffect(() => { + console.log('렌더링 됨'); + }) +} diff --git a/examples/chapter03/useEffect-8.jsx b/examples/chapter03/useEffect-8.jsx new file mode 100644 index 0000000..8727867 --- /dev/null +++ b/examples/chapter03/useEffect-8.jsx @@ -0,0 +1,33 @@ +const MyReact = (function () { + const global = {} + let index = 0 + + function useEffect(callback, dependencies) { + const hooks = global.hooks + + // 이전 훅 정보가 있는지 확인한다. + let previousDependencies = hooks[index] + + // 변경되었는지 확인 + // 이전 값이 있다면 이전 값을 얕은 비교로 비교해 변경이 일어났는지 확인한다. + // 이전 값이 없다면 최초 실행이므로 변경이 일어난 것으로 간주해 실행을 유도한다. + let isDependenciesChanged = previousDependencies ? dependencies.some( + (value, index) => !Object.is(value, previousDependencies[idx]) + ) : true + + // 변경이 일어났다면 첫번째 인수인 콜백 함수를 실행 + if (isDependenciesChanged) { + callback() + } + + // 현재 의존성을 훅에 다시 저장 + hooks[index] = dependencies + + // 다음 훅이 일어날 때를 대비하기 위해 index를 추가 + index++ + } + + return { + useEffect + } +})() \ No newline at end of file From a80271cc23a5eec7d1abff94750dbc28919643c6 Mon Sep 17 00:00:00 2001 From: B Date: Mon, 15 Jan 2024 16:38:02 +0900 Subject: [PATCH 05/13] =?UTF-8?q?update(#31):=20=203.1.2=20useEffect=20?= =?UTF-8?q?=EC=98=88=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/chapter03/useEffect-10.tsx | 9 +++++++++ examples/chapter03/useEffect-11.jsx | 3 +++ examples/chapter03/useEffect-12.jsx | 5 +++++ examples/chapter03/useEffect-13.tsx | 0 examples/chapter03/useEffect-14.jsx | 0 examples/chapter03/useEffect-15.jsx | 0 examples/chapter03/useEffect-9.jsx | 3 +++ 7 files changed, 20 insertions(+) create mode 100644 examples/chapter03/useEffect-10.tsx create mode 100644 examples/chapter03/useEffect-11.jsx create mode 100644 examples/chapter03/useEffect-12.jsx create mode 100644 examples/chapter03/useEffect-13.tsx create mode 100644 examples/chapter03/useEffect-14.jsx create mode 100644 examples/chapter03/useEffect-15.jsx create mode 100644 examples/chapter03/useEffect-9.jsx diff --git a/examples/chapter03/useEffect-10.tsx b/examples/chapter03/useEffect-10.tsx new file mode 100644 index 0000000..1ef5890 --- /dev/null +++ b/examples/chapter03/useEffect-10.tsx @@ -0,0 +1,9 @@ +function Component({ + log +}: { + log: string +}){ + useEffect(() => { + logging(log) + }, []) // eslint-disable-line react-hooks/exhaustive-deps +} \ No newline at end of file diff --git a/examples/chapter03/useEffect-11.jsx b/examples/chapter03/useEffect-11.jsx new file mode 100644 index 0000000..60a40f6 --- /dev/null +++ b/examples/chapter03/useEffect-11.jsx @@ -0,0 +1,3 @@ +useEffect(() => { + logging(user.id) +}, [user.id]) \ No newline at end of file diff --git a/examples/chapter03/useEffect-12.jsx b/examples/chapter03/useEffect-12.jsx new file mode 100644 index 0000000..5ef7895 --- /dev/null +++ b/examples/chapter03/useEffect-12.jsx @@ -0,0 +1,5 @@ +useEffect(() => { + function logActiveUser() { + logging(user.id) + } +}, [user.id]) \ No newline at end of file diff --git a/examples/chapter03/useEffect-13.tsx b/examples/chapter03/useEffect-13.tsx new file mode 100644 index 0000000..e69de29 diff --git a/examples/chapter03/useEffect-14.jsx b/examples/chapter03/useEffect-14.jsx new file mode 100644 index 0000000..e69de29 diff --git a/examples/chapter03/useEffect-15.jsx b/examples/chapter03/useEffect-15.jsx new file mode 100644 index 0000000..e69de29 diff --git a/examples/chapter03/useEffect-9.jsx b/examples/chapter03/useEffect-9.jsx new file mode 100644 index 0000000..a646e8f --- /dev/null +++ b/examples/chapter03/useEffect-9.jsx @@ -0,0 +1,3 @@ +useEffect(() => { + console.log(props); +}, []) // eslint-disable-line react-hooks/exhaustive-deps From 79900533dde72c99679ed7dd09a31ed94b47b65a Mon Sep 17 00:00:00 2001 From: B Date: Mon, 15 Jan 2024 17:00:24 +0900 Subject: [PATCH 06/13] =?UTF-8?q?update(#35):=20=20=EC=99=9C=20`useEffect`?= =?UTF-8?q?=EC=9D=98=20=EC=BD=9C=EB=B0=B1=20=EC=9D=B8=EC=88=98=EB=A1=9C=20?= =?UTF-8?q?=EB=B9=84=EB=8F=99=EA=B8=B0=20=ED=95=A8=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=94=EB=A1=9C=20=EB=84=A3=EC=9D=84=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EC=9D=84=EA=B9=8C=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter03/b.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chapter03/b.md b/chapter03/b.md index 80cfb96..e60d56a 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -116,6 +116,9 @@ dispatch({type: 'UPDATE_STATE', payload: updatedValues}); 3. 거대한 `useEffect`를 만들지마라 4. 불필요한 외부 함수를 만들지마라 - 왜 `useEffect`의 콜백 인수로 비동기 함수를 바로 넣을 수 있을까? + - `useEffect`인수로 비동기 함수를 지정할 수 없는 거이지, 비동기 함수 자체가 문제가 되는 것은 아니다. + - `useEffect` 내부에서 비동기 함수를 선언해 실행하거나 즉시 실행 비동기 함수를 만들어서 사용하는 것은 가능하다. + - 다만 비동기 함가 내부에 존재하게 되면 `useEffect` 내부에서 비동기 함수가 실행되고 실행되는 것을 반복하므로 클린업 함수에서 이전 비동기 함수에 대한 처릴르 추가하는 것이 좋다. ### 3.1.3 `useMemo` ### 3.1.4 `useCallback` From 340308144843795d000d893b8583653c33223b8d Mon Sep 17 00:00:00 2001 From: B Date: Tue, 16 Jan 2024 11:23:36 +0900 Subject: [PATCH 07/13] =?UTF-8?q?update(#35):=20=203.1.11=20=ED=9B=85=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99(Rules=20of=20hooks)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter03/b.md | 228 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 160 insertions(+), 68 deletions(-) diff --git a/chapter03/b.md b/chapter03/b.md index e60d56a..cc6d3d9 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -14,12 +14,14 @@ - [3.1.8 `useImperativeHandle`](#318-useimperativehandle) - [3.1.9 `useLayoutEffect`](#319-uselayouteffect) - [3.1.10 `useDebugValue`](#3110-usedebugvalue) - - [3.1.11 훅의 규칙](#3111-훅의-규칙) + - [3.1.11 훅 규칙(Rules of hooks)](#3111-훅-규칙rules-of-hooks) - [3.1.12 정리](#3112-정리) - [3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?](#32-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까) - [3.2.1 사용자 정의 훅](#321-사용자-정의-훅) - [3.2.2 고차 컴포넌트](#322-고차-컴포넌트) - [3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?](#323-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까) + - [사용자 정의 훅이 필요한 경우](#사용자-정의-훅이-필요한-경우) + - [고차 컴포넌트를 사용해야 하는 경우](#고차-컴포넌트를-사용해야-하는-경우) - [3.2.4 정리](#324-정리) - [References](#references) - [Articles](#articles) @@ -44,91 +46,178 @@ ### 3.1.1 `useState` -- 리액트에서 렌더링은 함수 컴포넌트의 return과 클래스 컴포넌트의 render함수를 실행한 다음, 이 실행 결과를 이전의 리액트 트리와 비교해 리렌더링이 필요한 부분만 업데이트해 이뤄진다. -- 리액트의 렌더링은 함수 컴포넌트에서 반환한 결과물인 return의 값을 빅해 실행되기 때문에 매번 렌더링이 발생할 때마 함수는 다시 새롭게 실행되고, 새롭게 실행된 함수에서 state는 매번 hello로 초기화 되므로 아무리 state를 변경해도 다시 hello로 초기화도니다. - ```jsx -/** - * * @description useState 훅의 기본적인 사용법 (p. 190) - * * useState는 React에서 상태를 관리하기 위해 사용되는 훅입니다. - * * 이 훅은 두 개의 요소로 이루어진 배열을 반환합니다: - * * - 첫 번째 요소인 state는 현재 상태 값을 나타냅니다. - * * - 두 번째 요소인 setState는 상태 값을 업데이트하기 위해 사용되는 함수입니다. - * * * @param {*} initialState 초기 상태 값을 나타냅니다. 제공되지 않으면 undefined로 초기화됩니다. -* */ - -import { useState } from "react"; -const [state, setState] = useState(initialState); +/** + * + * 상태와 상태를 갱신하는 함수를 반환하는 훅입니다. + * @param initialState — 초기 상태 값입니다. + */ +useState(initialState); ``` -- `useState`를 사용하여 상태를 업데이트할 때, 이전 상태와 새로운 상태를 병합하지 않고 새로운 상태로 대체합니다. -- `useState`를 사용하여 상태를 업데이트할 때 이전 상태와 새로운 상태를 병합하고 싶다면, 개별적인 상태 변수를 사용하거나 `useReducer` 훅을 사용할 수 있습니다. -- `useState` 훅은 상태를 변경할 때마다 전체 상태 객체를 대체하는 것이 아니라, 변경된 부분만 업데이트합니다. 이전 상태와 새로운 상태를 병합하지 않는 이유는 React가 불필요한 렌더링을 방지하고 성능을 최적화하기 위해서입니다. -- `useState`는 클래스 컴포넌트에서 찾을 수 있는 `setState` 메서드와 달리 업데이트 객체를 자동으로 병합하지 않습니다. 그러나 함수 업데이터 형식과 객체 전개 구문을 결합하여 이 동작을 복제할 수 있습니다. -- `useState`는 클래스 컴포넌트와 함수 컴포넌트에서 각각 사용됩니다. -- React에서 상태 업데이트는 비동기적으로 처리됩니다. 따라서 업데이트가 요청되었을 때 즉시 업데이트가 이루어질 것을 보장할 수는 없습니다. 이는 React의 내부 동작 원리에 따라 상태 업데이트는 일괄 처리되며, React는 업데이트가 발생한 컴포넌트를 감지하고 필요한 경우에만 화면을 다시 렌더링하기 때문입니다. -- `useState` 훅은 React에서 동기적으로 작동하지만, 재렌더링 과정 때문에 상태 변경이 비동기적으로 보일 수 있습니다. -- React에서 `useState` 훅은 함수형 컴포넌트에서 상태를 관리하기 위해 사용됩니다. 이 훅을 사용하면 상태 값을 저장하고 업데이트할 수 있습니다. -- `useState` 훅은 동기적으로 작동하지만, React는 상태 변경이 발생하면 컴포넌트를 재렌더링합니다. 재렌더링은 React가 가상 DOM을 사용하여 변경된 상태를 실제 DOM에 반영하는 과정입니다. 이 과정은 비동기적으로 처리됩니다. 따라서 상태 변경 후에 컴포넌트가 즉시 재렌더링되지 않을 수 있습니다. 대신, React는 상태 변경을 큐에 저장하고, 다음 렌더링 사이클에서 변경된 상태를 적용합니다.이러한 비동기적인 재렌더링 과정 때문에, `useState` 훅을 사용하여 상태를 변경한 후에도 즉시 변경된 상태를 접근할 수 없을 수 있습니다. -- `useState`는 클래스 컴포넌트에서 찾을 수 있는 `setState` 메서드와 달리 업데이트 객체를 자동으로 병합하지 않습니다. 그러나 함수 업데이터 형식과 객체 전개 구문을 결합하여 이 동작을 복제할 수 있습니다. +### 3.1.2 `useEffect` ```jsx -// spread 연산자 -const [state, setState] = useState({}); -setState(prevState => { - return {...prevState, ...updatedValues}; -}); +/** + * + * 부수 효과를 포함하는 명령형 코드를 포함하는 함수를 받습니다. + * @param effect — 정리(clean-up) 함수를 반환할 수 있는 명령형 함수입니다. + * @param deps — 제공된 값이 변경될 때만 효과가 활성화됩니다. + */ +useEffect(() => { + /** + * 이 함수는 컴포넌트가 렌더링될 때마다, + * 그리고 의존성 중 어느 것이든 변경될 때마다 호출됩니다. + */ +}, +[dependencies] // 이 useEffect 훅에 대한 의존성 배열입니다. + // 의존성이 변경되면, useEffect에 전달된 함수가 다시 호출됩니다. +); ``` +### 3.1.3 `useMemo` + ```jsx -// Object.assign -const [state, setState] = useState({}); -setState(prevState => { - return Object.assign({}, prevState, updatedValues); -}); +/** + * + * 계산된 값을 기억하는 데 사용되는 훅입니다. + * @param factory — 새로운 값을 계산하는 함수입니다. + * @param deps — 제공된 값이 변경될 때만 새로운 값을 다시 계산합니다. + */ +useMemo(() => { + /** + * 이 함수는 의존성이 변경될 때마다 호출되며, + * 새로운 값을 계산하여 반환합니다. + */ +}, +[dependencies] // 이 useMemo 훅에 대한 의존성 배열입니다. + // 의존성이 변경되면, useMemo에 전달된 함수가 다시 호출되고 + // 새로운 값을 반환합니다. +); ``` +### 3.1.4 `useCallback` + ```jsx -// useReducer 사용 -// - 여러 하위 값이 포함된 상태 객체를 관리하기에 더 적합합니다. -const initialState = {}; -const reducer = (state, action) => { - switch (action.type) { - case 'UPDATE_STATE': - return {...state, ...action.payload}; - default: - return state; - } -}; - -const [state, dispatch] = useReducer(reducer, initialState); -dispatch({type: 'UPDATE_STATE', payload: updatedValues}); +/** + * + * 이전에 생성된 콜백 함수를 기억하는 데 사용되는 훅입니다. + * @param callback — 새로운 콜백 함수를 생성하는 함수입니다. + * @param deps — 제공된 값이 변경될 때만 새로운 콜백 함수를 다시 생성합니다. + */ +useCallback(() => { + /** + * 이 함수는 의존성이 변경될 때마다 호출되며, + * 새로운 콜백 함수를 생성하여 반환합니다. + */ +}, +[dependencies] // 이 useCallback 훅에 대한 의존성 배열입니다. + // 의존성이 변경되면, useCallback에 전달된 함수가 다시 호출되고 + // 새로운 콜백 함수를 반환합니다. +); ``` -### 3.1.2 `useEffect` -- `useEffect` 훅은 두 개의 매개변수를 받습니다. 첫 번째 매개변수는 작업을 담은 함수이고, 두 번째 매개변수는 `의존성 배열(dependency array)`입니다. 이 함수는 컴포넌트가 렌더링될 때마다 실행되며, 의존성 배열에 지정된 값이 변경될 때만 실행됩니다. 의존성 배열을 빈 배열로 설정하면 컴포넌트가 처음 렌더링될 때만 실행됩니다. -- `Clean-up` 함수 -- `의존성 배열(dependency array)` -- `useEffect`의 구현 -- `useEffect`를 사용할 때 주의할 점 - 1. `eslint-disable-line react-hooks/exhaustive-deps` 주석은 최대한 자제하라 - 2. `useEffect`의 첫번째 인수에 함수명을 부여하라 - 3. 거대한 `useEffect`를 만들지마라 - 4. 불필요한 외부 함수를 만들지마라 - - 왜 `useEffect`의 콜백 인수로 비동기 함수를 바로 넣을 수 있을까? - - `useEffect`인수로 비동기 함수를 지정할 수 없는 거이지, 비동기 함수 자체가 문제가 되는 것은 아니다. - - `useEffect` 내부에서 비동기 함수를 선언해 실행하거나 즉시 실행 비동기 함수를 만들어서 사용하는 것은 가능하다. - - 다만 비동기 함가 내부에 존재하게 되면 `useEffect` 내부에서 비동기 함수가 실행되고 실행되는 것을 반복하므로 클린업 함수에서 이전 비동기 함수에 대한 처릴르 추가하는 것이 좋다. - -### 3.1.3 `useMemo` -### 3.1.4 `useCallback` ### 3.1.5 `useRef` + +```jsx +/** + * + * 변경 가능한 값을 유지하는 데 사용되는 훅입니다. + * @param initialValue — 초기 값으로 사용할 값입니다. + */ +useRef(initialValue); +``` + ### 3.1.6 `useContext` + +```jsx +/** + * + * 컨텍스트(Context) 값을 반환하는 훅입니다. + * @param context — 컨텍스트 객체입니다. + */ +useContext(context); +``` + ### 3.1.7 `useReducer` + +```jsx +/** + * + * 상태와 상태를 갱신하는 함수를 반환하는 훅입니다. + * @param reducer — 상태 갱신을 처리하는 리듀서 함수입니다. + * @param initialState — 초기 상태 값입니다. + * @param initializer — 초기 상태를 설정하는 함수입니다. 선택적 매개변수입니다. + */ +useReducer(reducer, initialState, initializer); +``` + ### 3.1.8 `useImperativeHandle` + +```jsx +/** + * + * 외부에서 접근 가능한 인스턴스의 특정 함수를 정의하는 훅입니다. + * @param ref — 외부에서 접근 가능한 인스턴스의 ref 객체입니다. + * @param createHandle — 외부에서 접근 가능한 인스턴스에 정의할 함수를 생성하는 함수입니다. + * @param deps — 제공된 값이 변경될 때만 새로운 핸들을 생성합니다. + */ +useImperativeHandle(ref, createHandle, deps); +``` + ### 3.1.9 `useLayoutEffect` + +```jsx +/** + * + * 명령형 코드를 포함하는 함수를 받아 렌더링 이후에 동기적으로 실행되는 훅입니다. + * @param effect — 정리(clean-up) 함수를 반환할 수 있는 명령형 함수입니다. + * @param deps — 제공된 값이 변경될 때만 효과가 활성화됩니다. + */ +useLayoutEffect(() => { + /** + * 이 함수는 컴포넌트가 렌더링된 직후에 동기적으로 호출됩니다. + * 의존성 중 어느 것이든 변경될 때마다 호출됩니다. + */ +}, +[dependencies] // 이 useLayoutEffect 훅에 대한 의존성 배열입니다. + // 의존성이 변경되면, useLayoutEffect에 전달된 함수가 다시 호출됩니다. +); +``` + ### 3.1.10 `useDebugValue` -### 3.1.11 훅의 규칙 + +```jsx +/** + * + * 디버깅 목적으로 사용되는 훅입니다. + * @param value — 디버그할 값입니다. + * @param formatter — 값의 표시 형식을 지정하는 함수입니다. 선택적 매개변수입니다. + */ +useDebugValue(value, formatter); +``` + +### 3.1.11 훅 규칙(Rules of hooks) +- React 훅(Hooks) 규칙은 React 함수 컴포넌트에서 훅을 사용할 때 지켜야 하는 원칙들입니다. +- React 개발 팀은 이 규칙들을 준수하도록 돕기 위해 ESLint 플러그인인 [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)를 제공하고 있으며, 이를 사용하면 위반 사항을 쉽게 찾아낼 수 있습니다 + +> - [React 공식 문서, Rules of Hooks](https://legacy.reactjs.org/docs/hooks-rules.html) +> - (한국어 버전)[React 공식 문서, Rules of Hooks](https://ko.legacy.reactjs.org/docs/hooks-rules.html) +> - [React Dev, Rule of Hooks](https://react.dev/warnings/invalid-hook-call-warning) + +**훅 규칙(Rules of hooks)** +- Hooks는 함수형 컴포넌트 내에서만 사용되어야 합니다. 대신, 커스텀 훅을 만들어 사용할 수 있습니다. + - 클래스 컴포넌트에서는 Hooks를 사용할 수 없습니다. +- Hooks는 항상 최상위 레벨에서 호출되어야 합니다. 조건문, 반복문, 중첩 함수 내에서는 Hooks를 호출할 수 없습니다. 이는 훅의 호출 순서를 보장하기 위함입니다. +- 커스텀 Hooks는 `use`로 시작되어야 합니다. 예를 들어, `useCounter`, `useFetch` 등과 같이 이름을 지정해야 합니다. +- Hooks는 컴포넌트의 동일한 순서로 호출되어야 합니다. Hooks는 호출 순서에 의존하기 때문에, 조건문 안에서 Hooks를 사용하면 예상치 못한 동작을 할 수 있습니다. + +**훅(Hooks) 규칙이 만들어진 이유** +1. **훅 호출 순서의 보장**: React는 훅이 호출되는 순서에 의존하여 내부 상태를 관리합니다. 만약 조건문이나 반복문 내에서 훅을 호출한다면, 훅의 호출 순서가 보장되지 않아 React가 내부 상태를 올바르게 관리하지 못할 수 있습니다. +2. **컴포넌트 간의 코드 재사용 촉진**: 훅 규칙을 따르면 커스텀 훅을 만들어 다른 컴포넌트 간에 상태 로직을 쉽게 재사용할 수 있습니다. +3. **버그 예방**: 규칙을 준수하면 훅의 잘못된 사용으로 인한 버그 발생 가능성을 줄일 수 있습니다. +4. **코드의 이해와 유지보수 용이**: 모든 훅이 컴포넌트 최상단에서 호출되어야 한다는 규칙은 코드의 일관성을 유지하고, 다른 개발자들이 코드를 이해하고 유지보수하기 쉽게 만듭니다. + ### 3.1.12 정리
@@ -137,6 +226,9 @@ dispatch({type: 'UPDATE_STATE', payload: updatedValues}); ### 3.2.1 사용자 정의 훅 ### 3.2.2 고차 컴포넌트 ### 3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? +#### 사용자 정의 훅이 필요한 경우 +#### 고차 컴포넌트를 사용해야 하는 경우 + ### 3.2.4 정리
@@ -149,4 +241,4 @@ dispatch({type: 'UPDATE_STATE', payload: updatedValues});
## Articles -- [DEV, Why useeffect is running twice in react](https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6#:~:text=In%20React%2C%20the%20useEffect%20hook,as%20%22dependencies%22)%20change) \ No newline at end of file +- [DEV, Why useEffect is running twice in react](https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6#:~:text=In%20React%2C%20the%20useEffect%20hook,as%20%22dependencies%22)%20change) \ No newline at end of file From 6891efb06c055d40441f963eb125e8affc12895a Mon Sep 17 00:00:00 2001 From: B Date: Tue, 16 Jan 2024 15:31:57 +0900 Subject: [PATCH 08/13] =?UTF-8?q?update(#35):=20=203.2=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=A0=95=EC=9D=98=20=ED=9B=85=EA=B3=BC=20?= =?UTF-8?q?=EA=B3=A0=EC=B0=A8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=A4=91=20=EB=AC=B4=EC=97=87=EC=9D=84=20=EC=8D=A8=EC=95=BC=20?= =?UTF-8?q?=ED=95=A0=EA=B9=8C=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter03/b.md | 182 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 166 insertions(+), 16 deletions(-) diff --git a/chapter03/b.md b/chapter03/b.md index cc6d3d9..fed8812 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -17,8 +17,8 @@ - [3.1.11 훅 규칙(Rules of hooks)](#3111-훅-규칙rules-of-hooks) - [3.1.12 정리](#3112-정리) - [3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?](#32-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까) - - [3.2.1 사용자 정의 훅](#321-사용자-정의-훅) - - [3.2.2 고차 컴포넌트](#322-고차-컴포넌트) + - [3.2.1 사용자 정의 훅(Custom Hook)](#321-사용자-정의-훅custom-hook) + - [3.2.2 고차 컴포넌트(Higher Order Component;HOC)](#322-고차-컴포넌트higher-order-componenthoc) - [3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?](#323-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까) - [사용자 정의 훅이 필요한 경우](#사용자-정의-훅이-필요한-경우) - [고차 컴포넌트를 사용해야 하는 경우](#고차-컴포넌트를-사용해야-하는-경우) @@ -200,20 +200,91 @@ useDebugValue(value, formatter); ### 3.1.11 훅 규칙(Rules of hooks) - React 훅(Hooks) 규칙은 React 함수 컴포넌트에서 훅을 사용할 때 지켜야 하는 원칙들입니다. - React 개발 팀은 이 규칙들을 준수하도록 돕기 위해 ESLint 플러그인인 [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)를 제공하고 있으며, 이를 사용하면 위반 사항을 쉽게 찾아낼 수 있습니다 - -> - [React 공식 문서, Rules of Hooks](https://legacy.reactjs.org/docs/hooks-rules.html) -> - (한국어 버전)[React 공식 문서, Rules of Hooks](https://ko.legacy.reactjs.org/docs/hooks-rules.html) -> - [React Dev, Rule of Hooks](https://react.dev/warnings/invalid-hook-call-warning) + - [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) 설치: + ```bash + npm install eslint-plugin-react-hooks@next + ``` + ```json + // ESLint config 파일 + { + "plugins": [ + // ... + "react-hooks" + ], + "rules": { + // ... + "react-hooks/rules-of-hooks": "error" + } + } + ``` +- Hooks 규칙을 어길 경우에는 에러가 발생할 수 있습니다. + ```bash + # console + Hooks can only be called inside the body of a function component. + ``` **훅 규칙(Rules of hooks)** -- Hooks는 함수형 컴포넌트 내에서만 사용되어야 합니다. 대신, 커스텀 훅을 만들어 사용할 수 있습니다. +- Hooks는 함수형 컴포넌트 내에서만 사용되어야 합니다. 대신, 커스텀 훅을 만들어 사용할 수 있습니다. + - 커스텀 Hooks는 `use`로 시작되어야 합니다. 예를 들어, `useCounter`, `useFetch` 등과 같이 이름을 지정해야 합니다. - 클래스 컴포넌트에서는 Hooks를 사용할 수 없습니다. -- Hooks는 항상 최상위 레벨에서 호출되어야 합니다. 조건문, 반복문, 중첩 함수 내에서는 Hooks를 호출할 수 없습니다. 이는 훅의 호출 순서를 보장하기 위함입니다. -- 커스텀 Hooks는 `use`로 시작되어야 합니다. 예를 들어, `useCounter`, `useFetch` 등과 같이 이름을 지정해야 합니다. -- Hooks는 컴포넌트의 동일한 순서로 호출되어야 합니다. Hooks는 호출 순서에 의존하기 때문에, 조건문 안에서 Hooks를 사용하면 예상치 못한 동작을 할 수 있습니다. +- Hooks는 항상 최상위 레벨에서 호출되어야 합니다. 조건문, 반복문, 중첩 함수 내에서는 Hooks를 호출할 수 없습니다. 이는 훅의 호출 순서를 보장하기 위함입니다. + - Hooks는 컴포넌트의 동일한 순서로 호출되어야 합니다. Hooks는 호출 순서에 의존하기 때문에, 조건문 안에서 Hooks를 사용하면 예상치 못한 동작을 할 수 있습니다. + +```jsx +// ✅ Good: 함수 컴포넌트 내에서 훅을 최상위 레벨에서 호출하는 경우 +import React, { useState } from 'react'; + +function Counter() { + const [count, setCount] = useState(0); + + const increment = () => { + setCount(count + 1); + }; + + return ( +
+

Count: {count}

+ +
+ ); +} + +export default Counter; +``` + +```jsx +// ✅ Good: 최상위 레벨에서 커스텀 훅을 호출하는 경우 +import React, { useState, useEffect } from 'react'; + +function useWindowWidth() { + const [width, setWidth] = useState(window.innerWidth); -**훅(Hooks) 규칙이 만들어진 이유** -1. **훅 호출 순서의 보장**: React는 훅이 호출되는 순서에 의존하여 내부 상태를 관리합니다. 만약 조건문이나 반복문 내에서 훅을 호출한다면, 훅의 호출 순서가 보장되지 않아 React가 내부 상태를 올바르게 관리하지 못할 수 있습니다. + useEffect(() => { + const handleResize = () => { + setWidth(window.innerWidth); + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return width; +} + +function MyComponent() { + const windowWidth = useWindowWidth(); + + return

Window Width: {windowWidth}

; +} + +export default MyComponent; +``` + +**훅 규칙(Rules of hooks)이 만들어진 이유** +1. **훅 호출 순서의 보장**: React는 훅이 호출되는 순서에 의존하여 내부 상태를 관리합니다. 만약 조건문이나 반복문 내에서 훅을 호출한다면, 훅의 호출 순서가 보장되지 않아 React가 내부 상태를 올바르게 관리하지 못할 수 있습니다. 2. **컴포넌트 간의 코드 재사용 촉진**: 훅 규칙을 따르면 커스텀 훅을 만들어 다른 컴포넌트 간에 상태 로직을 쉽게 재사용할 수 있습니다. 3. **버그 예방**: 규칙을 준수하면 훅의 잘못된 사용으로 인한 버그 발생 가능성을 줄일 수 있습니다. 4. **코드의 이해와 유지보수 용이**: 모든 훅이 컴포넌트 최상단에서 호출되어야 한다는 규칙은 코드의 일관성을 유지하고, 다른 개발자들이 코드를 이해하고 유지보수하기 쉽게 만듭니다. @@ -223,12 +294,81 @@ useDebugValue(value, formatter);
## 3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? -### 3.2.1 사용자 정의 훅 -### 3.2.2 고차 컴포넌트 +- Custom Hook과 Higher Order Component(HOC)는 React에서 재사용 가능한 로직을 공유하기 위한 두 가지 다른 패턴입니다. +### 3.2.1 사용자 정의 훅(Custom Hook) +- 사용자 정의 훅(Custom Hook)은 React에서 재사용 가능한 로직을 추상화하는 기법 중 하나입니다. + - 예를 들어, HTTP 요청을 하는 fetch를 기반으로 한 사용자 정의 훅인 `useFetch`를 만들 수 있습니다. 이 훅은 내부에 `useState와` `useEffect와` 같은 리액트 훅을 사용하여 로직을 구현합니다. + - 이렇게 사용자 정의 훅을 만들면, 사용하는 쪽에서는 `useFetch`만 사용하여 중복된 로직을 손쉽게 관리할 수 있습니다. + ```jsx + import { useState, useEffect } from 'react'; + + /** + * HTTP 요청을 하는 fetch를 기반으로 한 사용자 정의 훅인 useFetch를 만들 수 있습니다. + * 이 훅은 내부에 useState와 useEffect와 같은 리액트 훅을 사용하여 로직을 구현합니다. + * + * @param {string} url - 데이터를 가져올 URL + * @returns {{ data: any, loading: boolean }} - 데이터와 로딩 상태를 반환하는 객체 + */ + const useFetch = (url) => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + /** + * 데이터를 가져오는 비동기 함수 + */ + const fetchData = async () => { + try { + const response = await fetch(url); + const jsonData = await response.json(); + setData(jsonData); + setLoading(false); + } catch (error) { + console.error(error); + } + }; + + fetchData(); + }, [url]); + + return { data, loading }; + }; + + export default useFetch; + ``` +- 사용자 정의 훅(Custom Hook)은 `use`라는 접두사로 시작하는 함수로, 여러 컴포넌트에서 사용할 수 있는 로직을 캡슐화하여 재사용할 수 있게 해줍니다. + - 예를 들어, `useForm`이라는 사용자 정의 훅(Custom Hook)을 만들어 폼 처리 로직을 여러 컴포넌트에서 사용할 수 있습니다. + ```jsx + const [formData, handleInputChange] = useForm(initialValues); + ``` + +### 3.2.2 고차 컴포넌트(Higher Order Component;HOC) +- 고차 컴포넌트(Higher Order Component;HOC)는 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 함수입니다. +- HOC는 주로 React의 클래스 컴포넌트에서 사용되었으며, 컴포넌트 간에 공유할 수 있는 로직을 추상화하는 데 사용됩니다. + - 예를 들어, `withUser`라는 HOC를 사용해 여러 컴포넌트에 사용자 데이터를 주입할 수 있습니다. +- 고차 컴포넌트 사용 시 주의 해야할 점 + - 고차 컴포넌트(Higher Order Component;HOC)는 `with`라는 접두사를 시작해야한다. + ```jsx + const EnhancedComponent = withUser(BaseComponent); + ``` + - 부수 효과를 최소화해야 한다. + - 여러개의 고차 컴포넌트로 컴포넌트를 감쌀 경우 복잡성이 커질 수 있다. +- `React.memo` + - `React.memo`는 React의 기능 중 하나로, 컴포넌트를 메모이제이션하는 데 사용됩니다. + - `React.memo`는 컴포넌트가 동일한 props로 렌더링될 때 이전에 렌더링한 결과를 재사용합니다. 이를 통해 불필요한 렌더링을 방지하고 애플리케이션의 성능을 향상시킬 수 있습니다. + - `React.memo`를 사용하려면 함수형 컴포넌트를 생성하고 `React.memo` 함수로 해당 컴포넌트를 감싸주면 됩니다. + - `React.memo`는 두 번째 매개변수로 이전 props와 다음 props를 비교하는 함수를 받을 수도 있습니다. 이 함수를 사용하면 어떤 prop이 변경되었을 때에만 컴포넌트가 다시 렌더링되도록 할 수 있습니다. + - `React.memo`는 주로 무거운 계산이나 비용이 많이 드는 작업을 수행하는 컴포넌트의 성능을 최적화하는 데 사용됩니다. 그러나 모든 컴포넌트에 `React.memo`를 사용하는 것은 권장되지 않습니다. 성능 향상이 미미하거나 컴포넌트 자체가 빈번하게 업데이트되는 경우에는 오히려 부작용이 발생할 수 있습니다. + ### 3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? +고차 컴포넌트는 주로 렌더링 로직이나 생명주기 메서드와 같은 컴포넌트의 구조적인 부분에 관여하는 반면, 사용자 정의 훅은 보다 세밀한 데이터 관리나 사이드 이펙트 로직 등에 더 적합할 수 있습니다. #### 사용자 정의 훅이 필요한 경우 +- 리액트의 기본 훅(`useState`, `useEffect` 등)을 사용하여 간단하게 공통 로직을 분리할 수 있는 경우 +- 컴포넌트 내부에서의 영향을 최소화하고, 개발자가 원하는 방식으로 훅을 사용하고자 할 때 +- 컴포넌트 전반에 걸쳐 동일한 로직을 적용하거나, 특정한 상태 관리 패턴을 구현하고 싶을 때 #### 고차 컴포넌트를 사용해야 하는 경우 - +- 컴포넌트가 에러를 처리하고 에러 발생 시 대체 컴포넌트를 보여주어야 할 때 +- 컴포넌트의 렌더링 결과에 직접적인 영향을 주어야 하거나, 컴포넌트 트리에 추가적인 요소를 삽입해야 할 때 ### 3.2.4 정리
@@ -237,8 +377,18 @@ useDebugValue(value, formatter); - [리액트 공식문서, Introducing Hooks](https://legacy.reactjs.org/docs/hooks-intro.html) - [리액트 공식문서, Hooks API Reference](https://legacy.reactjs.org/docs/hooks-reference.html) - [useState](https://react.dev/reference/react/useState) +- [React 공식 문서, Rules of Hooks](https://legacy.reactjs.org/docs/hooks-rules.html) + - (한국어 버전) [React 공식 문서, Rules of Hooks](https://ko.legacy.reactjs.org/docs/hooks-rules.html) +- [React Dev, Rule of Hooks](https://react.dev/warnings/invalid-hook-call-warning)
## Articles -- [DEV, Why useEffect is running twice in react](https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6#:~:text=In%20React%2C%20the%20useEffect%20hook,as%20%22dependencies%22)%20change) \ No newline at end of file +- [DEV, Why useEffect is running twice in react](https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6#:~:text=In%20React%2C%20the%20useEffect%20hook,as%20%22dependencies%22)%20change) +- [Cleanup Function with useEffect in React: The Right Way to Clean Up After Yourself!](https://www.linkedin.com/pulse/cleanup-function-useeffect-react-right-way-clean-up-after-mushnik/) +- [HOC Vs Custom Hooks which is better for code sharing? Or simply write a function and import it and use it?](https://www.reddit.com/r/reactjs/comments/rzmowa/hoc_vs_custom_hooks/) +- [What is the difference between custom Hooks and HOC?](https://www.turing.com/blog/custom-react-js-hooks-how-to-use/#:~:text=HOCs%20can%20introduce%20complexity%20with,for%20inheritance%20or%20prop%20drilling) +- [How do you name custom Hooks?](https://www.linkedin.com/pulse/understanding-custom-hooks-reactjs-simplifying-logic-hossein-safari#:~:text=The%20naming%20convention%20for%20custom,the%20necessary%20rules%20and%20optimizations) +- [Why use custom Hooks?](https://react.dev/learn/reusing-logic-with-custom-hooks#:~:text=Custom%20Hooks%20let%20you%20share%20stateful%20logic%2C%20not%20state%20itself&text=They%20happened%20to%20have%20the,whether%20the%20network%20is%20on).&text=There's%20some%20repetitive%20logic%20for,state%20(%20firstName%20and%20lastName%20)) +- [React.memo 현명하게 사용하기](https://ui.toast.com/weekly-pick/ko_20190731#reactmemo-%ED%98%84%EB%AA%85%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0) +- [A guide to memoization using React.memo](https://github.com/RicardoMorato/React.memo) \ No newline at end of file From d1e47c6a3e746f85d1b8ba6a8826fbc6255690d1 Mon Sep 17 00:00:00 2001 From: B Date: Tue, 16 Jan 2024 16:25:36 +0900 Subject: [PATCH 09/13] update(#35): Related Articles with Link --- chapter03/b.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chapter03/b.md b/chapter03/b.md index fed8812..032a826 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -391,4 +391,6 @@ export default MyComponent; - [How do you name custom Hooks?](https://www.linkedin.com/pulse/understanding-custom-hooks-reactjs-simplifying-logic-hossein-safari#:~:text=The%20naming%20convention%20for%20custom,the%20necessary%20rules%20and%20optimizations) - [Why use custom Hooks?](https://react.dev/learn/reusing-logic-with-custom-hooks#:~:text=Custom%20Hooks%20let%20you%20share%20stateful%20logic%2C%20not%20state%20itself&text=They%20happened%20to%20have%20the,whether%20the%20network%20is%20on).&text=There's%20some%20repetitive%20logic%20for,state%20(%20firstName%20and%20lastName%20)) - [React.memo 현명하게 사용하기](https://ui.toast.com/weekly-pick/ko_20190731#reactmemo-%ED%98%84%EB%AA%85%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0) -- [A guide to memoization using React.memo](https://github.com/RicardoMorato/React.memo) \ No newline at end of file +- [A guide to memoization using React.memo](https://github.com/RicardoMorato/React.memo) +- [Higher-Order Components In React](https://www.smashingmagazine.com/2020/06/higher-order-components-react/) +- [HOC Pattern](https://www.patterns.dev/react/hoc-pattern/) \ No newline at end of file From 575892a101828543ba8fe77da4146cf741cb70da Mon Sep 17 00:00:00 2001 From: B Date: Tue, 16 Jan 2024 16:33:11 +0900 Subject: [PATCH 10/13] update(#35): 3.1.2 useEffect - clean-up function in syntax --- chapter03/b.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chapter03/b.md b/chapter03/b.md index 032a826..8c9d392 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -69,6 +69,9 @@ useEffect(() => { * 이 함수는 컴포넌트가 렌더링될 때마다, * 그리고 의존성 중 어느 것이든 변경될 때마다 호출됩니다. */ + return () => { + // clean-up + } }, [dependencies] // 이 useEffect 훅에 대한 의존성 배열입니다. // 의존성이 변경되면, useEffect에 전달된 함수가 다시 호출됩니다. From 9487166ecc522a1b6476f15eceefe4d65720440e Mon Sep 17 00:00:00 2001 From: B Date: Tue, 16 Jan 2024 16:49:58 +0900 Subject: [PATCH 11/13] update(#35): 3.1.2 useEffect - Describe clean-up function --- chapter03/b.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/chapter03/b.md b/chapter03/b.md index 8c9d392..4c8db63 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -70,13 +70,16 @@ useEffect(() => { * 그리고 의존성 중 어느 것이든 변경될 때마다 호출됩니다. */ return () => { - // clean-up + // clean-up(optional) } }, [dependencies] // 이 useEffect 훅에 대한 의존성 배열입니다. // 의존성이 변경되면, useEffect에 전달된 함수가 다시 호출됩니다. ); ``` +- **`clean-up function`**: + - 이 클린업 함수는 훅에서 생성된 리소스나 효과를 정리하는 데 사용됩니다. + - API에서 데이터를 가져오거나 이벤트 리스너를 설정하거나 간격이나 타임아웃을 생성하는 등의 부작용을 정리할 수 있습니다. ### 3.1.3 `useMemo` @@ -396,4 +399,5 @@ export default MyComponent; - [React.memo 현명하게 사용하기](https://ui.toast.com/weekly-pick/ko_20190731#reactmemo-%ED%98%84%EB%AA%85%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0) - [A guide to memoization using React.memo](https://github.com/RicardoMorato/React.memo) - [Higher-Order Components In React](https://www.smashingmagazine.com/2020/06/higher-order-components-react/) -- [HOC Pattern](https://www.patterns.dev/react/hoc-pattern/) \ No newline at end of file +- [HOC Pattern](https://www.patterns.dev/react/hoc-pattern/) +- [Cleaning up with useEffect function](https://www.zipy.ai/blog/understanding-react-useeffect-cleanup-function#:~:text=This%20cleanup%20function%20is%20used,side%20effects%20one%20by%20one.) \ No newline at end of file From 9fea0625ab51f6c5edd4923f116f140e948a3bd9 Mon Sep 17 00:00:00 2001 From: B Date: Tue, 16 Jan 2024 19:14:06 +0900 Subject: [PATCH 12/13] update(#35): 3.1.1 useState --- chapter03/b.md | 311 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 240 insertions(+), 71 deletions(-) diff --git a/chapter03/b.md b/chapter03/b.md index 4c8db63..f9af3d6 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -5,6 +5,8 @@ - [03장: 리액트 훅 깊게 살펴보기](#03장-리액트-훅-깊게-살펴보기) - [3.1 리액트의 모든 훅 파헤치기](#31-리액트의-모든-훅-파헤치기) - [3.1.1 `useState`](#311-usestate) + - [게으른 초기화(lazy initialization)](#게으른-초기화lazy-initialization) + - [`useState` 훅의 규칙](#usestate-훅의-규칙) - [3.1.2 `useEffect`](#312-useeffect) - [3.1.3 `useMemo`](#313-usememo) - [3.1.4 `useCallback`](#314-usecallback) @@ -30,53 +32,199 @@ ## 3.1 리액트의 모든 훅 파헤치기 -| 훅 이름 | 정의 | 예제 | -|-----------------|---------------------------------------------------------|-----------------------------------------------------------| -| useState | 상태 값을 관리하고 업데이트하는 데 사용 | `const [count, setCount] = useState(0);` | -| useEffect | 컴포넌트의 생명주기에 따른 작업을 수행하는 데 사용 | `useEffect(() => { console.log('Component mounted'); }, []);` | -| useMemo | 계산 결과를 기억하고 재사용하는 데 사용 | `const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);` | -| useCallback | 콜백 함수를 기억하고 재사용하는 데 사용 | `const memoizedCallback = useCallback(() => { /* callback logic */ }, [dependency])` | -| useRef | 컴포넌트 내에서 변경 가능한 값을 저장하는 데 사용 | `const refContainer = useRef(initialValue);` | -| useContext | React의 컨텍스트를 사용하는 데 사용 | `const value = useContext(MyContext);` | -| useReducer | 상태와 액션을 받아 새로운 상태를 반환하는 리듀서 함수와 함께 사용 | `const [state, dispatch] = useReducer(reducer, initialState);` | -| useImperativeHandle | 부모 컴포넌트에서 자식 컴포넌트의 인스턴스를 조작하는 데 사용 | `useImperativeHandle(ref, () => ({ method() { /* method logic */ } }));` | -| useLayoutEffect | DOM 업데이트 이후 동기적으로 작업을 수행하는 데 사용 | `useLayoutEffect(() => { /* layout effect logic */ }, []);` | -| useDebugValue | 커스텀 훅에서 디버깅 정보를 제공하는 데 사용 | `useDebugValue(value);` | - +| 훅 이름 | 정의 | 예제 | +| ------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| useState | 상태 값을 관리하고 업데이트하는 데 사용 | `const [count, setCount] = useState(0);` | +| useEffect | 컴포넌트의 생명주기에 따른 작업을 수행하는 데 사용 | `useEffect(() => { console.log('Component mounted'); }, []);` | +| useMemo | 계산 결과를 기억하고 재사용하는 데 사용 | `const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);` | +| useCallback | 콜백 함수를 기억하고 재사용하는 데 사용 | `const memoizedCallback = useCallback(() => { /* callback logic */ }, [dependency])` | +| useRef | 컴포넌트 내에서 변경 가능한 값을 저장하는 데 사용 | `const refContainer = useRef(initialValue);` | +| useContext | React의 컨텍스트를 사용하는 데 사용 | `const value = useContext(MyContext);` | +| useReducer | 상태와 액션을 받아 새로운 상태를 반환하는 리듀서 함수와 함께 사용 | `const [state, dispatch] = useReducer(reducer, initialState);` | +| useImperativeHandle | 부모 컴포넌트에서 자식 컴포넌트의 인스턴스를 조작하는 데 사용 | `useImperativeHandle(ref, () => ({ method() { /* method logic */ } }));` | +| useLayoutEffect | DOM 업데이트 이후 동기적으로 작업을 수행하는 데 사용 | `useLayoutEffect(() => { /* layout effect logic */ }, []);` | +| useDebugValue | 커스텀 훅에서 디버깅 정보를 제공하는 데 사용 | `useDebugValue(value);` | ### 3.1.1 `useState` ```jsx /** - * + * * 상태와 상태를 갱신하는 함수를 반환하는 훅입니다. * @param initialState — 초기 상태 값입니다. */ useState(initialState); ``` +```jsx +const [현재 상태, value를 업데이트하는 함수] = useState(초기값); +``` + +- `useState`는 React 함수 컴포넌트에서 상태(state)를 관리하기 위한 훅(hook)입니다. +- `useState`를 사용할 때는 상태의 초기값을 설정하기 위해 매개변수로 전달합니다. + + - 이 훅을 호출하면 배열이 반환되는데, 이 배열의 첫 번째 원소는 현재 상태 값을 나타내고, 두 번째 원소는 해당 상태 값을 업데이트하는 함수인 'Setter 함수'입니다. + - useState 훅을 사용할 때 상태 변수와 그 상태를 업데이트하는 함수에 대한 명명 규칙은 배열 구조 분해를 통해 `[something, setSomething]`과 같이 이름을 지정하는 것입니다. + +
+ 배열 비구조화 할당 + 배열 비구조화 할당은 JavaScript ES6에 도입된 기능으로, 배열의 요소들을 변수에 쉽게 할당할 수 있도록 해주는 문법입니다. 이 방법을 사용하면 배열 내부의 요소를 개별 변수에 할당하기 위해 인덱스를 사용할 필요가 없어 코드가 더 간결하고 읽기 쉬워집니다. + + 예를 들어, 다음과 같은 배열이 있다고 가정해 보겠습니다: + + ```javascript + let numbers = [1, 2, 3]; + ``` + + 전통적인 방식으로는 각각의 요소를 변수에 할당하기 위해 다음과 같이 작성해야 합니다: + + ```javascript + let one = numbers[0]; + let two = numbers[1]; + let three = numbers[2]; + ``` + + 하지만 배열 비구조화 할당을 사용하면 다음과 같이 훨씬 간단하게 작성할 수 있습니다: + + ```javascript + let [one, two, three] = numbers; + ``` + + 이렇게 하면 numbers 배열의 첫 번째 요소가 one 변수에, 두 번째 요소가 two 변수에, 세 번째 요소가 three 변수에 각각 할당됩니다. + + 또한 배열 비구조화 할당은 기본값 설정, 나머지 요소(rest elements) 수집 등의 추가적인 기능도 제공합니다. 예를 들어, 다음과 같은 경우를 살펴보겠습니다: + + ```javascript + let [one, two, three, four = 4] = numbers; + ``` + + 위 코드에서 numbers 배열에는 세 개의 요소만 존재하지만, four 변수에는 기본값으로 4가 할당됩니다. + + 나머지 요소를 수집하는 예시는 다음과 같습니다: + + ```javascript + let [one, ...rest] = numbers; + ``` + + rest 변수는 배열의 나머지 부분을 담게 되며, 이 경우 [2, 3]이 됩니다. +
+ +- 리액트의 함수형 컴포넌트는 매 렌더링마다 새로운 함수 호출로 간주되기 때문에, 컴포넌트의 로컬 변수들은 호출이 끝날 때 사라지고 다음 렌더링 때 새로 생성됩니다. 하지만 `useState`와 같은 훅을 사용하면, 리액트가 내부적으로 상태를 관리하여 컴포넌트가 다시 실행되어도 이전 상태 값을 유지할 수 있게 합니다. 이 상태 관리는 클로저와 밀접한 관련이 있습니다. 클로저는 함수가 선언될 때의 렉시컬 환경을 기억하는 함수를 말합니다. React에서 클로저는 주로 이벤트 핸들러나 비동기 작업에서 이전 상태 값을 참조할 때 중요한 역할을 합니다. + +```jsx +import React, { useState } from "react"; + +function Counter() { + // useState를 사용하여 'count'라는 상태 변수와 이를 업데이트하는 'setCount' 함수를 선언합니다. + const [count, setCount] = useState(0); + + // 클릭 이벤트 핸들러에서 클로저를 사용합니다. + // 이 함수는 'count' 상태를 참조하며, 클릭 시에 'setCount'를 호출하여 상태를 업데이트합니다. + const handleIncrement = () => { + setCount((prevCount) => prevCount + 1); // 클로저를 통해 이전 상태 값을 참조합니다. + }; + + // 컴포넌트가 렌더링될 때마다 'handleIncrement' 함수는 새로 생성되지만, + // 'setCount' 함수는 항상 최신 'count' 상태에 접근할 수 있습니다. + return ( +
+

현재 카운트: {count}

+ +
+ ); +} + +export default Counter; +``` + +> 위 코드에서 `handleIncrement` 함수는 `setCount` 함수를 호출할 때마다 `count` 상태를 업데이트합니다. 이 때 `handleIncrement` 내부에서 참조하는 `count` 값은 클로저를 통해 함수가 선언될 당시의 `count` 값으로 고정됩니다. +> `useEffect`의 의존성 배열이 빈 배열인 경우, `handleIncrement`는 컴포넌트가 처음 마운트될 때의 `count` 값인 0을 '기억'합니다. 따라서 `setInterval`에 의해 호출되는 `handleIncrement`는 항상 `count`를 `1`로만 설정하게 됩니다. 이 문제를 해결하기 위해서는, `count`에 대한 의존성을 `useEffect`의 배열에 추가하거나, `Setter` 함수에 함수를 전달하여 이전 상태 값을 기반으로 새 상태 값을 설정하도록 해야 합니다. +> +> ```jsx +> function handleIncrement() { +> setCount((prevCount) => prevCount + 1); +> } +> ``` +> +> 이렇게 하면 `setCount는` 현재의 `count` 값에 접근하는 대신, 업데이트 시점의 가장 최신 상태 값을 사용하여 업데이트를 수행하게 됩니다. + +#### 게으른 초기화(lazy initialization) + +컴포넌트의 상태를 초기화할 때 초기 상태 계산이 복잡하거나 계산 비용이 높을 경우에 사용되는 기법입니다. `useState` 훅을 사용할 때, 초기 상태를 함수로 전달함으로써 컴포넌트가 처음 렌더링될 때만 해당 상태의 초기값을 계산하도록 할 수 있습니다. + +```jsx +import React, { useState } from "react"; + +function ExpensiveInitialStateComponent() { + // 상태의 초기값을 설정하는 함수를 useState에 전달합니다. + // 이 함수는 컴포넌트가 처음 렌더링될 때 한 번만 호출됩니다. + const [expensiveState, setExpensiveState] = useState(() => { + console.log("이 로그는 컴포넌트가 처음 렌더링될 때만 나타납니다."); + // 복잡한 계산이나 데이터 추출 로직을 여기에 작성합니다. + // 예를 들어, 큰 배열을 생성하거나, 복잡한 연산을 수행하는 등의 작업을 할 수 있습니다. + const initialState = performExpensiveCalculation(); + return initialState; + }); + + // ... + + return
{/* 렌더링 로직 */}
; +} + +function performExpensiveCalculation() { + // 복잡하고 시간이 많이 걸리는 계산을 수행하는 함수입니다. + // 실제로는 여기에 실제 계산 로직이 들어갑니다. + console.log("비싼 계산을 수행합니다."); + return "계산된 초기 상태"; +} + +export default ExpensiveInitialStateComponent; +``` + +> 이 코드에서 `useState`는 초기 상태를 계산하기 위한 함수를 인자로 받습니다. 이 함수는 컴포넌트가 처음 렌더링될 때 한 번만 호출되고, 그 결과값은 상태의 초기값으로 사용됩니다. 이렇게 함으로써 불필요한 계산을 매 렌더링마다 반복하는 것을 방지할 수 있어 성능을 최적화할 수 있습니다. + +#### `useState` 훅의 규칙 + +```jsx +import React, { useState } from "react"; + +function MyComponent() { + // 최상위 레벨에서 useState를 호출하여 'count' 상태 변수를 선언하고 초기값을 0으로 설정합니다. + const [count, setCount] = useState(0); + + // 컴포넌트의 나머지 부분... +} +``` + +- 컴포넌트의 최상위 레벨에서 `useState`를 호출하여 상태 변수를 선언해야 합니다. + - `useState`는 반드시 함수의 최상위에서만 호출되어야 하며, 조건문, 반복문, 중첩된 함수 내부에서 호출되어서는 안 됩니다. 이 규칙은 React의 훅이 항상 동일한 순서로 호출되어야 하는 '훅 규칙'을 따르기 위함입니다 +- `useState`는 함수 컴포넌트 내에서만 호출되어야 합니다. + - 클래스 컴포넌트에서는 `useState`를 사용할 수 없습니다. + ### 3.1.2 `useEffect` ```jsx /** - * + * * 부수 효과를 포함하는 명령형 코드를 포함하는 함수를 받습니다. * @param effect — 정리(clean-up) 함수를 반환할 수 있는 명령형 함수입니다. * @param deps — 제공된 값이 변경될 때만 효과가 활성화됩니다. */ -useEffect(() => { - /** - * 이 함수는 컴포넌트가 렌더링될 때마다, - * 그리고 의존성 중 어느 것이든 변경될 때마다 호출됩니다. - */ +useEffect( + () => { + /** + * 이 함수는 컴포넌트가 렌더링될 때마다, + * 그리고 의존성 중 어느 것이든 변경될 때마다 호출됩니다. + */ return () => { - // clean-up(optional) - } -}, -[dependencies] // 이 useEffect 훅에 대한 의존성 배열입니다. - // 의존성이 변경되면, useEffect에 전달된 함수가 다시 호출됩니다. + // clean-up(optional) + }; + }, + [dependencies] // 이 useEffect 훅에 대한 의존성 배열입니다. + // 의존성이 변경되면, useEffect에 전달된 함수가 다시 호출됩니다. ); ``` + - **`clean-up function`**: - 이 클린업 함수는 훅에서 생성된 리소스나 효과를 정리하는 데 사용됩니다. - API에서 데이터를 가져오거나 이벤트 리스너를 설정하거나 간격이나 타임아웃을 생성하는 등의 부작용을 정리할 수 있습니다. @@ -85,20 +233,21 @@ useEffect(() => { ```jsx /** - * + * * 계산된 값을 기억하는 데 사용되는 훅입니다. * @param factory — 새로운 값을 계산하는 함수입니다. * @param deps — 제공된 값이 변경될 때만 새로운 값을 다시 계산합니다. */ -useMemo(() => { - /** - * 이 함수는 의존성이 변경될 때마다 호출되며, - * 새로운 값을 계산하여 반환합니다. - */ -}, -[dependencies] // 이 useMemo 훅에 대한 의존성 배열입니다. - // 의존성이 변경되면, useMemo에 전달된 함수가 다시 호출되고 - // 새로운 값을 반환합니다. +useMemo( + () => { + /** + * 이 함수는 의존성이 변경될 때마다 호출되며, + * 새로운 값을 계산하여 반환합니다. + */ + }, + [dependencies] // 이 useMemo 훅에 대한 의존성 배열입니다. + // 의존성이 변경되면, useMemo에 전달된 함수가 다시 호출되고 + // 새로운 값을 반환합니다. ); ``` @@ -106,20 +255,21 @@ useMemo(() => { ```jsx /** - * + * * 이전에 생성된 콜백 함수를 기억하는 데 사용되는 훅입니다. * @param callback — 새로운 콜백 함수를 생성하는 함수입니다. * @param deps — 제공된 값이 변경될 때만 새로운 콜백 함수를 다시 생성합니다. */ -useCallback(() => { - /** - * 이 함수는 의존성이 변경될 때마다 호출되며, - * 새로운 콜백 함수를 생성하여 반환합니다. - */ -}, -[dependencies] // 이 useCallback 훅에 대한 의존성 배열입니다. - // 의존성이 변경되면, useCallback에 전달된 함수가 다시 호출되고 - // 새로운 콜백 함수를 반환합니다. +useCallback( + () => { + /** + * 이 함수는 의존성이 변경될 때마다 호출되며, + * 새로운 콜백 함수를 생성하여 반환합니다. + */ + }, + [dependencies] // 이 useCallback 훅에 대한 의존성 배열입니다. + // 의존성이 변경되면, useCallback에 전달된 함수가 다시 호출되고 + // 새로운 콜백 함수를 반환합니다. ); ``` @@ -127,7 +277,7 @@ useCallback(() => { ```jsx /** - * + * * 변경 가능한 값을 유지하는 데 사용되는 훅입니다. * @param initialValue — 초기 값으로 사용할 값입니다. */ @@ -138,7 +288,7 @@ useRef(initialValue); ```jsx /** - * + * * 컨텍스트(Context) 값을 반환하는 훅입니다. * @param context — 컨텍스트 객체입니다. */ @@ -149,7 +299,7 @@ useContext(context); ```jsx /** - * + * * 상태와 상태를 갱신하는 함수를 반환하는 훅입니다. * @param reducer — 상태 갱신을 처리하는 리듀서 함수입니다. * @param initialState — 초기 상태 값입니다. @@ -162,7 +312,7 @@ useReducer(reducer, initialState, initializer); ```jsx /** - * + * * 외부에서 접근 가능한 인스턴스의 특정 함수를 정의하는 훅입니다. * @param ref — 외부에서 접근 가능한 인스턴스의 ref 객체입니다. * @param createHandle — 외부에서 접근 가능한 인스턴스에 정의할 함수를 생성하는 함수입니다. @@ -175,19 +325,20 @@ useImperativeHandle(ref, createHandle, deps); ```jsx /** - * + * * 명령형 코드를 포함하는 함수를 받아 렌더링 이후에 동기적으로 실행되는 훅입니다. * @param effect — 정리(clean-up) 함수를 반환할 수 있는 명령형 함수입니다. * @param deps — 제공된 값이 변경될 때만 효과가 활성화됩니다. */ -useLayoutEffect(() => { - /** - * 이 함수는 컴포넌트가 렌더링된 직후에 동기적으로 호출됩니다. - * 의존성 중 어느 것이든 변경될 때마다 호출됩니다. - */ -}, -[dependencies] // 이 useLayoutEffect 훅에 대한 의존성 배열입니다. - // 의존성이 변경되면, useLayoutEffect에 전달된 함수가 다시 호출됩니다. +useLayoutEffect( + () => { + /** + * 이 함수는 컴포넌트가 렌더링된 직후에 동기적으로 호출됩니다. + * 의존성 중 어느 것이든 변경될 때마다 호출됩니다. + */ + }, + [dependencies] // 이 useLayoutEffect 훅에 대한 의존성 배열입니다. + // 의존성이 변경되면, useLayoutEffect에 전달된 함수가 다시 호출됩니다. ); ``` @@ -195,7 +346,7 @@ useLayoutEffect(() => { ```jsx /** - * + * * 디버깅 목적으로 사용되는 훅입니다. * @param value — 디버그할 값입니다. * @param formatter — 값의 표시 형식을 지정하는 함수입니다. 선택적 매개변수입니다. @@ -204,6 +355,7 @@ useDebugValue(value, formatter); ``` ### 3.1.11 훅 규칙(Rules of hooks) + - React 훅(Hooks) 규칙은 React 함수 컴포넌트에서 훅을 사용할 때 지켜야 하는 원칙들입니다. - React 개발 팀은 이 규칙들을 준수하도록 돕기 위해 ESLint 플러그인인 [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)를 제공하고 있으며, 이를 사용하면 위반 사항을 쉽게 찾아낼 수 있습니다 - [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) 설치: @@ -230,6 +382,7 @@ useDebugValue(value, formatter); ``` **훅 규칙(Rules of hooks)** + - Hooks는 함수형 컴포넌트 내에서만 사용되어야 합니다. 대신, 커스텀 훅을 만들어 사용할 수 있습니다. - 커스텀 Hooks는 `use`로 시작되어야 합니다. 예를 들어, `useCounter`, `useFetch` 등과 같이 이름을 지정해야 합니다. - 클래스 컴포넌트에서는 Hooks를 사용할 수 없습니다. @@ -238,7 +391,7 @@ useDebugValue(value, formatter); ```jsx // ✅ Good: 함수 컴포넌트 내에서 훅을 최상위 레벨에서 호출하는 경우 -import React, { useState } from 'react'; +import React, { useState } from "react"; function Counter() { const [count, setCount] = useState(0); @@ -260,7 +413,7 @@ export default Counter; ```jsx // ✅ Good: 최상위 레벨에서 커스텀 훅을 호출하는 경우 -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect } from "react"; function useWindowWidth() { const [width, setWidth] = useState(window.innerWidth); @@ -270,10 +423,10 @@ function useWindowWidth() { setWidth(window.innerWidth); }; - window.addEventListener('resize', handleResize); + window.addEventListener("resize", handleResize); return () => { - window.removeEventListener('resize', handleResize); + window.removeEventListener("resize", handleResize); }; }, []); @@ -290,6 +443,7 @@ export default MyComponent; ``` **훅 규칙(Rules of hooks)이 만들어진 이유** + 1. **훅 호출 순서의 보장**: React는 훅이 호출되는 순서에 의존하여 내부 상태를 관리합니다. 만약 조건문이나 반복문 내에서 훅을 호출한다면, 훅의 호출 순서가 보장되지 않아 React가 내부 상태를 올바르게 관리하지 못할 수 있습니다. 2. **컴포넌트 간의 코드 재사용 촉진**: 훅 규칙을 따르면 커스텀 훅을 만들어 다른 컴포넌트 간에 상태 로직을 쉽게 재사용할 수 있습니다. 3. **버그 예방**: 규칙을 준수하면 훅의 잘못된 사용으로 인한 버그 발생 가능성을 줄일 수 있습니다. @@ -300,21 +454,26 @@ export default MyComponent;
## 3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? + - Custom Hook과 Higher Order Component(HOC)는 React에서 재사용 가능한 로직을 공유하기 위한 두 가지 다른 패턴입니다. + ### 3.2.1 사용자 정의 훅(Custom Hook) + - 사용자 정의 훅(Custom Hook)은 React에서 재사용 가능한 로직을 추상화하는 기법 중 하나입니다. + - 예를 들어, HTTP 요청을 하는 fetch를 기반으로 한 사용자 정의 훅인 `useFetch`를 만들 수 있습니다. 이 훅은 내부에 `useState와` `useEffect와` 같은 리액트 훅을 사용하여 로직을 구현합니다. - 이렇게 사용자 정의 훅을 만들면, 사용하는 쪽에서는 `useFetch`만 사용하여 중복된 로직을 손쉽게 관리할 수 있습니다. + ```jsx - import { useState, useEffect } from 'react'; + import { useState, useEffect } from "react"; /** * HTTP 요청을 하는 fetch를 기반으로 한 사용자 정의 훅인 useFetch를 만들 수 있습니다. - * 이 훅은 내부에 useState와 useEffect와 같은 리액트 훅을 사용하여 로직을 구현합니다. - * - * @param {string} url - 데이터를 가져올 URL - * @returns {{ data: any, loading: boolean }} - 데이터와 로딩 상태를 반환하는 객체 - */ + * 이 훅은 내부에 useState와 useEffect와 같은 리액트 훅을 사용하여 로직을 구현합니다. + * + * @param {string} url - 데이터를 가져올 URL + * @returns {{ data: any, loading: boolean }} - 데이터와 로딩 상태를 반환하는 객체 + */ const useFetch = (url) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); @@ -322,7 +481,7 @@ export default MyComponent; useEffect(() => { /** * 데이터를 가져오는 비동기 함수 - */ + */ const fetchData = async () => { try { const response = await fetch(url); @@ -342,6 +501,7 @@ export default MyComponent; export default useFetch; ``` + - 사용자 정의 훅(Custom Hook)은 `use`라는 접두사로 시작하는 함수로, 여러 컴포넌트에서 사용할 수 있는 로직을 캡슐화하여 재사용할 수 있게 해줍니다. - 예를 들어, `useForm`이라는 사용자 정의 훅(Custom Hook)을 만들어 폼 처리 로직을 여러 컴포넌트에서 사용할 수 있습니다. ```jsx @@ -349,6 +509,7 @@ export default MyComponent; ``` ### 3.2.2 고차 컴포넌트(Higher Order Component;HOC) + - 고차 컴포넌트(Higher Order Component;HOC)는 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 함수입니다. - HOC는 주로 React의 클래스 컴포넌트에서 사용되었으며, 컴포넌트 간에 공유할 수 있는 로직을 추상화하는 데 사용됩니다. - 예를 들어, `withUser`라는 HOC를 사용해 여러 컴포넌트에 사용자 데이터를 주입할 수 있습니다. @@ -367,19 +528,26 @@ export default MyComponent; - `React.memo`는 주로 무거운 계산이나 비용이 많이 드는 작업을 수행하는 컴포넌트의 성능을 최적화하는 데 사용됩니다. 그러나 모든 컴포넌트에 `React.memo`를 사용하는 것은 권장되지 않습니다. 성능 향상이 미미하거나 컴포넌트 자체가 빈번하게 업데이트되는 경우에는 오히려 부작용이 발생할 수 있습니다. ### 3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까? + 고차 컴포넌트는 주로 렌더링 로직이나 생명주기 메서드와 같은 컴포넌트의 구조적인 부분에 관여하는 반면, 사용자 정의 훅은 보다 세밀한 데이터 관리나 사이드 이펙트 로직 등에 더 적합할 수 있습니다. + #### 사용자 정의 훅이 필요한 경우 + - 리액트의 기본 훅(`useState`, `useEffect` 등)을 사용하여 간단하게 공통 로직을 분리할 수 있는 경우 - 컴포넌트 내부에서의 영향을 최소화하고, 개발자가 원하는 방식으로 훅을 사용하고자 할 때 - 컴포넌트 전반에 걸쳐 동일한 로직을 적용하거나, 특정한 상태 관리 패턴을 구현하고 싶을 때 + #### 고차 컴포넌트를 사용해야 하는 경우 + - 컴포넌트가 에러를 처리하고 에러 발생 시 대체 컴포넌트를 보여주어야 할 때 - 컴포넌트의 렌더링 결과에 직접적인 영향을 주어야 하거나, 컴포넌트 트리에 추가적인 요소를 삽입해야 할 때 + ### 3.2.4 정리
## References + - [리액트 공식문서, Introducing Hooks](https://legacy.reactjs.org/docs/hooks-intro.html) - [리액트 공식문서, Hooks API Reference](https://legacy.reactjs.org/docs/hooks-reference.html) - [useState](https://react.dev/reference/react/useState) @@ -390,6 +558,7 @@ export default MyComponent;
## Articles + - [DEV, Why useEffect is running twice in react](https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6#:~:text=In%20React%2C%20the%20useEffect%20hook,as%20%22dependencies%22)%20change) - [Cleanup Function with useEffect in React: The Right Way to Clean Up After Yourself!](https://www.linkedin.com/pulse/cleanup-function-useeffect-react-right-way-clean-up-after-mushnik/) - [HOC Vs Custom Hooks which is better for code sharing? Or simply write a function and import it and use it?](https://www.reddit.com/r/reactjs/comments/rzmowa/hoc_vs_custom_hooks/) @@ -400,4 +569,4 @@ export default MyComponent; - [A guide to memoization using React.memo](https://github.com/RicardoMorato/React.memo) - [Higher-Order Components In React](https://www.smashingmagazine.com/2020/06/higher-order-components-react/) - [HOC Pattern](https://www.patterns.dev/react/hoc-pattern/) -- [Cleaning up with useEffect function](https://www.zipy.ai/blog/understanding-react-useeffect-cleanup-function#:~:text=This%20cleanup%20function%20is%20used,side%20effects%20one%20by%20one.) \ No newline at end of file +- [Cleaning up with useEffect function](https://www.zipy.ai/blog/understanding-react-useeffect-cleanup-function#:~:text=This%20cleanup%20function%20is%20used,side%20effects%20one%20by%20one.) From 3142df8c58342ab5238f0558e5dbcffad97a88b5 Mon Sep 17 00:00:00 2001 From: B Date: Tue, 16 Jan 2024 19:41:41 +0900 Subject: [PATCH 13/13] =?UTF-8?q?update(#35):=203.1.1=20useState=20?= =?UTF-8?q?=EC=98=88=EC=A0=9C=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter03/b.md | 70 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/chapter03/b.md b/chapter03/b.md index f9af3d6..6692efc 100644 --- a/chapter03/b.md +++ b/chapter03/b.md @@ -109,27 +109,38 @@ const [현재 상태, value를 업데이트하는 함수] = useState(초기값); rest 변수는 배열의 나머지 부분을 담게 되며, 이 경우 [2, 3]이 됩니다. -- 리액트의 함수형 컴포넌트는 매 렌더링마다 새로운 함수 호출로 간주되기 때문에, 컴포넌트의 로컬 변수들은 호출이 끝날 때 사라지고 다음 렌더링 때 새로 생성됩니다. 하지만 `useState`와 같은 훅을 사용하면, 리액트가 내부적으로 상태를 관리하여 컴포넌트가 다시 실행되어도 이전 상태 값을 유지할 수 있게 합니다. 이 상태 관리는 클로저와 밀접한 관련이 있습니다. 클로저는 함수가 선언될 때의 렉시컬 환경을 기억하는 함수를 말합니다. React에서 클로저는 주로 이벤트 핸들러나 비동기 작업에서 이전 상태 값을 참조할 때 중요한 역할을 합니다. +- 리액트의 함수형 컴포넌트는 매 렌더링마다 새로운 함수 호출로 간주되기 때문에, 컴포넌트의 로컬 변수들은 호출이 끝날 때 사라지고 다음 렌더링 때 새로 생성됩니다. 하지만 `useState`와 같은 훅을 사용하면, 리액트가 내부적으로 상태를 관리하여 컴포넌트가 다시 실행되어도 이전 상태 값을 유지할 수 있게 합니다. + - 이 상태 관리는 클로저와 밀접한 관련이 있습니다. 클로저는 함수가 선언될 때의 렉시컬 환경을 기억하는 함수를 말합니다. React에서 클로저는 주로 이벤트 핸들러나 비동기 작업에서 이전 상태 값을 참조할 때 중요한 역할을 합니다. ```jsx import React, { useState } from "react"; function Counter() { - // useState를 사용하여 'count'라는 상태 변수와 이를 업데이트하는 'setCount' 함수를 선언합니다. + // 'count' 상태와 이 상태를 업데이트하는 'setCount' 함수를 초기화합니다. + // 초기값으로 0을 설정합니다. const [count, setCount] = useState(0); - // 클릭 이벤트 핸들러에서 클로저를 사용합니다. - // 이 함수는 'count' 상태를 참조하며, 클릭 시에 'setCount'를 호출하여 상태를 업데이트합니다. + // 카운트를 증가시키는 핸들러 함수입니다. + // 'setCount' 함수에 현재 상태를 기반으로 다음 상태를 결정하는 함수를 전달합니다. + // 이는 상태 업데이트가 비동기적으로 일어나기 때문에 중요합니다. const handleIncrement = () => { - setCount((prevCount) => prevCount + 1); // 클로저를 통해 이전 상태 값을 참조합니다. + setCount((prevCount) => prevCount + 1); // 이전 상태값을 사용하여 다음 상태값을 계산합니다. }; - // 컴포넌트가 렌더링될 때마다 'handleIncrement' 함수는 새로 생성되지만, - // 'setCount' 함수는 항상 최신 'count' 상태에 접근할 수 있습니다. + // 카운트를 감소시키는 핸들러 함수입니다. + // 여기서도 'setCount'에 함수를 전달하여 상태 업데이트를 안전하게 수행합니다. + const handleDecrement = () => { + setCount((prevCount) => prevCount - 1); // 이전 상태값을 사용하여 다음 상태값을 계산합니다. + }; + + // 'handleIncrement'와 'handleDecrement' 함수는 컴포넌트가 렌더링될 때마다 새로 생성됩니다. + // 그러나 이 함수들은 'setCount'를 통해 상태를 업데이트할 때 현재의 'count' 상태 값을 직접 참조하지 않습니다. + // 대신, 'setCount'에 전달된 이전 상태값(prevCount)을 기반으로 새로운 상태값을 계산합니다. return (

현재 카운트: {count}

+
); } @@ -137,16 +148,44 @@ function Counter() { export default Counter; ``` -> 위 코드에서 `handleIncrement` 함수는 `setCount` 함수를 호출할 때마다 `count` 상태를 업데이트합니다. 이 때 `handleIncrement` 내부에서 참조하는 `count` 값은 클로저를 통해 함수가 선언될 당시의 `count` 값으로 고정됩니다. -> `useEffect`의 의존성 배열이 빈 배열인 경우, `handleIncrement`는 컴포넌트가 처음 마운트될 때의 `count` 값인 0을 '기억'합니다. 따라서 `setInterval`에 의해 호출되는 `handleIncrement`는 항상 `count`를 `1`로만 설정하게 됩니다. 이 문제를 해결하기 위해서는, `count`에 대한 의존성을 `useEffect`의 배열에 추가하거나, `Setter` 함수에 함수를 전달하여 이전 상태 값을 기반으로 새 상태 값을 설정하도록 해야 합니다. +> 위 코드는 `useState`훅을 사용하여 간단한 카운터 컴포넌트를 구현한 것입니다. `useState`로부터 얻은 `setCount` 함수를 사용할 때, 현재 상태값을 직접 사용하는 대신에 이전 상태값에 기반하여 업데이트를 수행하는 콜백 함수를 전달함으로써, 상태 업데이트가 비동기적으로 일어나더라도 안정적으로 현재 상태값을 계산할 수 있습니다. 이 방식은 특히 여러 상태 업데이트가 동시에 발생할 때 유용하며, 잠재적인 동시성 문제를 방지할 수 있습니다. > -> ```jsx -> function handleIncrement() { -> setCount((prevCount) => prevCount + 1); -> } -> ``` +> **잠재적인 동시성 문제** > -> 이렇게 하면 `setCount는` 현재의 `count` 값에 접근하는 대신, 업데이트 시점의 가장 최신 상태 값을 사용하여 업데이트를 수행하게 됩니다. +> - **비동기적 상태 업데이트**: React가 상태 업데이트를 비동기적으로 처리할 수 있기 때문에, count 값이 예상과 다르게 변경될 수 있습니다. 이전 상태를 기반으로 하는 함수를 전달함으로써, 항상 올바른 참조값을 기반으로 상태를 업데이트할 수 있습니다. +> - **배치 처리된 상태 업데이트**: React는 성능 최적화를 위해 여러 setState 호출을 배치 처리할 수 있습니다. 이 경우, 여러 업데이트가 하나의 렌더링 사이클로 합쳐질 수 있으며, 각 업데이트는 독립적인 count 값을 가지지 않고 마지막 상태값을 기준으로 처리됩니다. 콜백 함수를 사용하면 이러한 문제를 방지할 수 있습니다. +> - **클로저에 의한 값의 '스냅샷'**: 함수형 업데이트를 사용하지 않으면, 이벤트 핸들러 내의 count 상태는 해당 함수가 생성될 당시의 값으로 고정됩니다. 이로 인해 실제 DOM 이벤트가 처리될 때 count의 최신 값이 아닌 오래된 값을 참조하는 문제가 발생할 수 있습니다. + +```jsx +import React, { useState, useEffect } from "react"; + +function AutoCounter() { + // 카운트 상태를 초기화합니다. + const [count, setCount] = useState(0); + + // 컴포넌트가 마운트되면 setInterval을 설정합니다. + useEffect(() => { + // 매 초마다 카운트를 증가시키는 interval을 설정합니다. + const intervalId = setInterval(() => { + // setCount 함수에 현재 상태를 기반으로 다음 상태를 결정하는 함수를 전달합니다. + setCount((prevCount) => prevCount + 1); + }, 1000); // 1000ms = 1초 + + // 컴포넌트가 언마운트되거나 재렌더링되기 전에 interval을 정리합니다. + return () => clearInterval(intervalId); + }, []); // 의존성 배열이 비어 있으므로 이 effect는 마운트될 때 딱 한 번만 실행됩니다. + + return ( +
+

자동 카운트: {count}

+
+ ); +} + +export default AutoCounter; +``` + +> 이 코드에서 `useEffect`의 의존성 배열이 빈 배열(`[]`)로 설정되어 있기 때문에, `useEffect` 내의 콜백 함수는 컴포넌트가 처음 마운트될 때 단 한 번만 실행됩니다. `setInterval`에 의해 호출되는 콜백 내에서 `setCount`는 이전 상태 값을 기반으로 새로운 상태 값을 계산하기 때문에, 실제 `count` 값이 어떻게 변화하든 항상 최신 상태를 기준으로 1을 더하게 됩니다. 따라서 카운터는 매 초마다 정확하게 `1`씩 증가합니다. #### 게으른 초기화(lazy initialization) @@ -554,6 +593,7 @@ export default MyComponent; - [React 공식 문서, Rules of Hooks](https://legacy.reactjs.org/docs/hooks-rules.html) - (한국어 버전) [React 공식 문서, Rules of Hooks](https://ko.legacy.reactjs.org/docs/hooks-rules.html) - [React Dev, Rule of Hooks](https://react.dev/warnings/invalid-hook-call-warning) +- [React Dev, useState](https://react.dev/reference/react/useState)