React 초기 상태 관리

- 2014년 당시 웹 개발 생태계에서는 MVC 패턴이 대세, Model(JS) - View(HTML) 간 서로 변경할 수 있는 양방향 데이터 바인딩 방식

-> 웹 애플리케이션이 비대해지고, 데이터의 양도 많아짐에 따라 상태 관리가 어려워짐

-> 문제를 인식한 페이스북 팀에서 단방향 데이터 바인딩 제안 : FLUX 패턴

 

FLUX 패턴

- 단방향 데이터 바인딩을 사용하는 아키텍처 디자인 패턴

- 데이터 흐름 Action -> Dispatcher -> Model -> View (View에서 다시 Action -> .. )

1) Action : { type, payload } - type: 어떠한 작업 / payload: 작업할 데이터

2) Dispatcher : Action -> Store 로 디스패치 수행

3) Store : 실제 상태 값, Action type 에 따라 상태를 변경할 수 있는 메서드 포함

4) View : Component, Store 에서 만들어진 데이터를 화면에서 렌더링, 사용자의 행위에 Action 호출

type StoreState = {
	count: number
}

type Action = {
	type: "add";
    payload: number;
}

function reducer(prevState: StoreState, action: Action) {
	const { type: ActionType } = action;
    
    if (ActionType === "add") {
    	return { coun: prevState.count + action.payload };
    }
    
    throw new Error(`Unhandled Action [${ActionType}]`);
}

export default function App() {
	const [state, dispatcher] = useReducer(reducer, { count: 0 }); 
    
    function handleClick() {
    	dispatcher({ type: "add", payload: 1 });
    }
    
    return (
    	<div>
        	<h1>{state.count}</h1>
            <button onclick={handleClick}> + </button>
        </div>
    );
}

 

단방향 데이터 바인딩

- 사용자의 입력에 따라 데이터를 갱신하고, 화면을 업데이트 해야하는지 코드로 작성 -> 코드 양 증가

- 데이터의 흐름이 단방향으로 제한되기 때문에 흐름 추적 용이, 마찬가지로 코드 가독성 향상

- 리액트 : 대표적인 단방향 데이터 바인딩 라이브러리

 

Redux

- https://mykuromi.tistory.com/13

- FLUX 구조를 구현하기 위해 만들어진 라이브러리

- Elm 아키텍처 도입 : 웹페이지를 선언적으로 작성하기 위한 언어, 데이터의 흐름을 Model(상태), View(HTML), Update(Type, Logic)로 분류

- 등장과 동시에 리액트 생테게에 큰 영향 -> Global state 를 하위 컴포넌트에 전파해 props drilling 문제 해결

- dispatcher, selector, action type 선언 등 복잡한 구조, 보일러 플레이트가 많아지는 문제 -> Redux toolkit 으로 간소화

 

Context API

- https://react.dev/reference/react/useContext

- React 버전 16.3 에서 출시 (이전에는 getChildContext 로 구현)

- props drilling 대신 Context provider 사용

 

Hook, React Query, SWR
- React 버전 16.8 에서 함수형 컴포넌트 Hook API 추가(Custom hooks ..) -> state 재사용 용이 ⭐️

- 이전에는 없던 상태관리 방식 등장 : React Query, SWR -> API 호출에 따른 상태 관리, HTTP 요청에 특화

- React Query : https://tanstack.com/query/latest/docs/framework/react/overview

- SWR : https://swr.vercel.app/ko

 

다양한 상태관리 라이브러리 등장

- Redux 라이브러리와는 다르게, 훅을 활용해 상태를 작고 효율적으로 관리 (React ver 16.8 이상 조건)

- 전역 상태 관리 패러다임에서 벗어나 개발자가 지역적으로 상태 관리

- Recoil : https://recoiljs.org

- Jotai : https://jotai.org

- Zustand : https://zustand-demo.pmnd.rs

- Valtio : https://valtio.pmnd.rs

 


 

🖤 상태 관리 범위 : useState / useReducer < Context < 상태 관리 라이브러리

 

컴포넌트 내부 상태 관리

- 가장 기본적인 상태 관리 방법

- useState : https://react.dev/reference/react/useState

- useReducer : https://react.dev/reference/react/useReducer

- useState 나 useReducer 를 기반으로 한 사용자 정의 훅을 통해 컴포넌트 상태 관리

- useState 가 선언될 때마다 state 초기화 되므로, 이는 곧 해당 컴포넌트 내에서만 유효한 지역 상태(local state)

 

useState 를 사용해서 컴포넌트 외부에서 상태 관리하기

1) Parent 컴포넌트로 state 를 끌어올리기 :  여러 컴포넌트가 동일한 상태를 사용할 수 있지만 코드가 복잡해짐  ex) parent 에서 handleClick 같은 함수 drilling

2) 컴포넌트 외부 별개의 파일에서 state 를 관리하는 경우, 리렌더링이 되지 않는 문제 발생 (컴포넌트의 state를 업데이트 치는게 아니므로)

-> useState 로 컴포넌트 외부 상태까지 관리하기에는 코드 가독성이 저해되거나, 리렌더링 문제가 발생

 

전역 상태 관리 조건

* 상태 관리 라이브러리에서 공통적으로 구현된 부분

- 컴포넌트 외부에 위치, 여러 컴포넌트가 함께 사용

- 이 상태를 참조하는 컴포넌트는 상태 변화를 감지하고 리렌더링 되어야 함 -> callback / subscribe 

- 상태가 원시값이 아니라 객체인 경우에는 객체에서 감지하는 값이 변화되어야 리렌더링

-> state, initialState, get, set, callback, subscribe, unsubscribe(cleanup) 필요

- cf) useSubscribtion : 리액트 외부에서 관리하는 값 추적, 리렌더링 훅 - https://resthooks.io/docs/api/useSubscription

 

Context 로 상태 관리

- useState 만 사용하는 것 보다 좀 더 넓게 상태 관리

- Context provider 를 지역적으로, 또는 여러 번 사용해서 컴포넌트 레이어별로 상태 관리 가능

 

상태 관리 라이브러리를 사용한 상태 관리

특징 Recoil Jotai Zustand MobX Redux
API https://recoiljs.org/ko/ https://jotai.org https://zustand-demo.pmnd.rs https://mobx.js.org/README.html https://redux-toolkit.js.org
https://redux.js.org
examples https://app.sideguide.dev/recoil/tutorial/ https://tutorial.jotai.org/examples/textLenght https://github.com/pmndrs/zustand https://mobx.js.org/README.html#a-quick-example https://redux-toolkit.js.org/tutorials/quick-start
설계 철학 간단한 상태 관리와 효율적인 상태 업데이트 간단하고 작으며 원자 단위 상태 관리 불변성을 유지하지 않는 단순 상태 관리 반응형 상태 관리와 옵저버 패턴 전역 상태 관리와 상태의예측 가능성
상태 관리방식 Atom Selector 사용 Atom 사용 Immutable 아닌 mutable 상태 관리 Observable 상태 단일 스토어와 Reducer통한 상태 관리
데이터 구조 트리 구조 트리 구조 플랫 구조 (얕은 복사) 트리 구조 트리 구조
성능 높은 성능과 효율적인 리렌더링 높은 성능과 적은 메모리 사용 매우 높은 성능과 빠른 상태 업데이트 높은 성능과 효율적인 상태 업데이트 중간 정도의 성능, 대규모애플리케이션에 적합
리렌더링제어 Atom 단위의 리렌더링제어 Atom 단위의 리렌더링 제어 선택적 리렌더링 제어 Observable 통한 효율적인 리렌더링 필요할 때만 리렌더링
미들웨어지원 제한적 제한적 지원 - immer, persist 없음 다양한 미들웨어 지원 - Thunk / Saga
커뮤니티와 생태계 작음 작음 작음 작음 매우 
개발자 도구 기본 제공 기본 제공 없음 MobX DevTools 제공 Redux DevTools 제공
동시성 관리 기본 제공 기본 제공 없음 기본 제공 기본 제공
설정 복잡도 간단 매우 간단 매우 간단 중간 정도 복잡할  있음
TypeScript 지원 기본 제공 기본 제공 기본 제공 기본 제공 기본 제공
학습 곡선 낮음 낮음 낮음 중간 정도 중간에서 높음
애플리케이션 규모 중소규모 중소규모 중소규모 중소규모에서 대규모까지 대규모

- 사용자 추이 : https://json-5.com/awesome-react-state-management

 

Recoil

- 페이스북에서 만든 상태 관리 라이브러리

- 2020년에 출시했지만 아직 정식 출시 X - npm 0.7.7 (2024.08)

- RecoilRoot : 애플리케이션 최상단에 감싸서 스토어 생성 - createContext, 감싸지 않은 컴포넌트에서는 접근 불가 

- notifyComponents(store, storeState, treeState) : 값이 변경되면 콜백을 실행해 하위 컴포넌트에 상태 변화를 알림

- atom : Recoil의 최소 상태 단위, unique 해야함 

- selector : 하나 이상의 atom을 조합할 수 있는 API

- useRecoilValue(statementsAtom) : atom 의 값을 읽어오는 메서드

- useRecoilState(recoilState) : return [useRecoilValue(recoilState), useSetRecoilState(recoilState)]

 

Jotai

- Recoil 의 atom 에서 영감을 받은 라이브러리

- atom 으로 하나 이상의 상태 생성, selector 필요 X

- Recoil 에서는 atom 의 key 를 지정해줘야 했지만, Jotai 에서는 별도의 key 가 필요하지 않음 -> 객체의 참조를 WeakMap 에 보관해 관리

- useAtomValue

- Recoil 과는 다르게 컴포넌트 루트 레벨에 Context가 존재할 필요 X -> Context 가 없으면 Provider 가 없는 형태로, 루트 레벨에 기본 스토어 생성

- Provider 를 사용하면 Context 처럼 각 Provider 별로 atom 관리 가능

- renderedIfChanged 를 통해 최신 atom 렌더링

- Recoil 에 비해 간결한 API

 

Zustand

- 리덕스에 영감을 받아 만든 라이브러리

- 하나의 중앙 집중 스토어에서 상태 관리

- ./src/vanila.ts : 프레임워크 독립적, 순수 자바스크립트 환경에서 사용 가능, 리액트에서 사용시 별도 세팅(useStore)

- 컴포넌트 내/외부 store 생성 가능, 외부 생성시 createStore

- 리덕스에 비해 짧은 코드, 가볍고 편리

 

 

'React' 카테고리의 다른 글

React strict mode  (0) 2024.07.10
Deep Dive : useReducer, useImperativeHandle  (0) 2024.07.08
240706 리액트 스터디 : useRef  (0) 2024.07.07
240629 리액트 스터디 : useRef, useContext  (0) 2024.06.29
Deep Dive : useRef, useContext  (0) 2024.06.29

 

 React Strict Mode

- 애플리케이션 내에서 잠재적인 문제를 알아내기 위해 도입된 도구

- 개발 모드에서만 활성화

- 특정 레거시 기능을 비활성화하거나 애플리케이션의 잠재적인 문제를 감지하는 역할

- Strict Mode는 실제로 UI에 영향을 미치지 않고, 개발 중에만 유용한 경고 메시지를 출력

 

 기능

- 안전한 라이프사이클 메서드 검출: componentWillMount, componentWillReceiveProps, componentWillUpdate 같은 메서드는 앞으로 React에서 사용되지 않기 때문에 경고 표시

- 부작용 감지: 의도하지 않은 부작용을 감지하기 위해 두 번 렌더링, useEffect 같은 훅이 예상대로 동작하는지 확인 가능 

- 레거시 문자열 ref 사용 경고

- 함수형 컴포넌트의 Deprecated 메서드 경고

- 추가적인 경고 및 검사

 

 주요 특징

- 개발 모드 전용: Strict Mode는 개발 중에만 활성화되며, 프로덕션 빌드에는 영향을 미치지 않음

- 경고 표시: 애플리케이션 내에서 잠재적인 문제나 추천되지 않는 패턴 감지, 경고 표시

- 디버깅 도구: Strict Mode는 애플리케이션 안정성 향상, 향후 React 업데이트에 대비할 수 있도록 도와줌


Strict mode(API doc)

- https://react.dev/reference/react/StrictMode

- https://ko.react.dev/reference/react/StrictMode

 

- lets you find common bugs in your components early during development.

개발 환경에서 컴포넌트에서 공통된 버그를 찾기 위한 모드

 

- Use StrictMode to enable additional development behaviors and warnings for the component tree inside:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

 

- StrictMode accepts no props.

 

- Caveats 

There is no way to opt out of Strict Mode inside a tree wrapped in <StrictMode>. This gives you confidence that all components inside <StrictMode> are checked. If two teams working on a product disagree whether they find the checks valuable, they need to either reach consensus or move <StrictMode> down in the tree.

StrictMode 태그 내부의 요소들은 모두 strict하게 검사되어짐

 

- Usage

1) Enabling Strict Mode for entire app

2) Enabling Strict Mode for a part of the app

 

- Strict Mode enables the following development-only behaviors:

All of these checks are development-only and do not impact the production build.

개발 환경에서만 작동되며 production 빌드 환경에는 영향을 끼치지 않음

 

1) Your components will re-render an extra time to find bugs caused by impure rendering.

순수하지 않은 렌더링으로 발생되는 버그를 찾기 위해 추가적으로 리렌더링

React assumes that every component you write is a pure function. This means that React components you write must always return the same JSX given the same inputs (props, state, and context).

리액트는 모든 컴포넌트가 순수 함수라고 가정.

즉, 리액트 컴포넌트는 항상 같은 input에 대해 같은 output(JSX)를 반환

Components breaking this rule behave unpredictably and cause bugs. To help you find accidentally impure code, Strict Mode calls some of your functions (only the ones that should be pure) twice in development.

impure한 코드를 찾기 위해 Strict mode 개발 환경에서 아래와 같은 함수는 두 번 실행됨

This includes:

a. Your component function body (only top-level logic, so this doesn’t include code inside event handlers)

컴포넌트 top level logic 함수(이벤트 핸들러 내부의 코드는 불포함)

b. Functions that you pass to useState, set functions, useMemo, or useReducer

useState, useMemo, useReducer, set functions에 전달되는 함수

c. Some class component methods like constructor, render, shouldComponentUpdate (see the whole list)

생성자, 렌더, 컴포넌트 업데이트와 같은 클래스 컴포넌트 메서드

If a function is pure, running it twice does not change its behavior because a pure function produces the same result every time. However, if a function is impure (for example, it mutates the data it receives), running it twice tends to be noticeable (that’s what makes it impure!) This helps you spot and fix the bug early.

순수 함수라면 항상 같은 결과를 반환하기 때문에, 두 번 실행되더라도 그 behavior가 변경되는 것은 아님

받은 데이터를 변경하는 것과 같이 순수한 함수가 아니라면 , 버그를 더 빨리 찾을 수 있도록 두 번 실행됨.

let stories = [
  {id: 0, label: "Ankit's Story" },
  {id: 1, label: "Taylor's Story" },
];

export default function StoryTray({ stories }) {
  // const items = stories; // impure because modify array directly
  const items = stories.slice(); // Clone the array
  items.push({ id: 'create', label: 'Create Story' });
  return (
    <ul>
      {items.map(story => (
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}

 

2) Your components will re-run Effects an extra time to find bugs caused by missing Effect cleanup.

effect 클린업이 빠져서 발생되는 버그를 찾기위해 추가적으로 effect

 

Strict Mode can also help find bugs in Effects.

Effects let you specify side effects that are caused by rendering itself, rather than by a particular event.

effect는 특정한 이벤트에 의해 발생되기 보다, 렌더링 하면서 발생되는 부작용 기술 

Sending a message in the chat is an event because it is directly caused by the user clicking a specific button. However, setting up a server connection is an Effect because it should happen no matter which interaction caused the component to appear. 

ex) 채팅에서 메시지 보내는 것은 이벤트, 그에 따른 서버 설정은 effect

Every Effect has some setup code and may have some cleanup code. Normally, React calls setup when the component mounts (is added to the screen) and calls cleanup when the component unmounts (is removed from the screen). React then calls cleanup and setup again if its dependencies changed since the last render.

리액트는 컴포넌트가 마운트될 때 setup을 호출하고, 언마운트될 때 cleanup을 호출

리액트가 dependencies에 변화가 있을 때 setup과 cleanup을 반복

When Strict Mode is on, React will also run one extra setup+cleanup cycle in development for every Effect. This may feel surprising, but it helps reveal subtle bugs that are hard to catch manually.

strict mode 개발 환경에서 리액트는 매 effect마다 추가적으로 setup + cleanup 사이클을 한번 더 돌게 됨

 

3) Your components will be checked for usage of deprecated APIs.

deprecated API를 사용하지 않았는지 검사

React warns if some component anywhere inside a <StrictMode> tree uses one of these deprecated APIs:

1) findDOMNode. See alternatives.

2) UNSAFE_ class lifecycle methods like UNSAFE_componentWillMount. See alternatives.

3) Legacy context (childContextTypes, contextTypes, and getChildContext). See alternatives.

4) Legacy string refs (this.refs). See alternatives.

These APIs are primarily used in older class components so they rarely appear in modern apps.

 

 

useReducer

- https://react.dev/reference/react/useReducer

- https://ko.react.dev/reference/react/useReducer

 

- React Hook that lets you add a reducer to your component.

컴포넌트에 리듀서를 추가하기 위한 React Hook

 

import { useReducer } from 'react';

const [state, dispatch] = useReducer(reducer, initialArg, init?);

 

- Call useReducer at the top level of your component to manage its state with a reducer.

리듀서로 state를 관리하기 위해 컴포넌트 top level에서 호출할 것

 

- Parameters

1) reducer: The reducer function that specifies how the state gets updated. It must be pure, should take the state and action as arguments, and should return the next state. State and action can be of any types.

state가 어떻게 업데이트될지 기술하는 리듀서 함수

반드시 state와 action을 파라미터로 가지면서, 다음 state를 반환하는 순수 함수여야 함

state와 action은 어떤 타입이던지 가능

2) initialArg: The value from which the initial state is calculated. It can be a value of any type. How the initial state is calculated from it depends on the next init argument.

초기 state를 계산할 때 사용하는 값, 어떤 타입이던지 가능

3) optional init: The initializer function that should return the initial state. If it’s not specified, the initial state is set to initialArg. Otherwise, the initial state is set to the result of calling init(initialArg).

초기 state를 반환하는 초기화 함수(옵션)

init이 없을 경우 두 번째 인자 initialArg가 초기 state가 되고, 있는 경우 init 함수를 호출한 결과가 초기 state로 세팅

 

- Return

1) The current state. During the first render, it’s set to init(initialArg) or initialArg (if there’s no init).

현재 state를 리턴. 첫 번째 렌더링인 경우 initialArg나 init 값

2) The dispatch function that lets you update the state to a different value and trigger a re-render.

state를 업데이트 하고 리렌더링을 트리거하는 디스패치 함수

 

- Caveats

1) useReducer is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.

2) In Strict Mode, React will call your reducer and initializer twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your reducer and initializer are pure (as they should be), this should not affect your logic. The result from one of the calls is ignored.

 

- dispatch function

The dispatch function returned by useReducer lets you update the state to a different value and trigger a re-render. You need to pass the action as the only argument to the dispatch function:

useReducer가 반환하는 디스패치 함수는 state를 다른 값으로 업데이트하고, 리렌더링을 트리거 함

반드시 action을 함수의 파라미터로 전달해야 함

 

const [state, dispatch] = useReducer(reducer, { age: 42 });

function handleClick() {
  dispatch({ type: 'incremented_age' });
  // ...

 

React will set the next state to the result of calling the reducer function you’ve provided with the current state and the action you’ve passed to dispatch.

리액트는 현재 state와 디스패치된 action을 인자로 하는 리듀서 함수를 실행한 결과로 다음 state를 세팅

 

- Parameters

action: The action performed by the user. It can be a value of any type. By convention, an action is usually an object with a type property identifying it and, optionally, other properties with additional information.

사용자가 행하는 action. 어떤 타입의 값이라도 가능.

action은 주로 어떤것을 서술하는 타입 속성을 가진 객체(컨벤션), 옵셔널하게 부가적인 정보를 나타내는 속성을 담기도 함.

 

- Returns

dispatch functions do not have a return value.

 

- Caveats

1) The dispatch function only updates the state variable for the next render. If you read the state variable after calling the dispatch function, you will still get the old value that was on the screen before your call.

디스패치 함는 다음 렌더링을 위한 state variable만 업데이트 함

디스패치 함수를 호출하고 state를 바로 읽으면, 디스패치 함수를 호출하기 전 old state를 가져옴

2) If the new value you provide is identical to the current state, as determined by an Object.is comparison, React will skip re-rendering the component and its children. This is an optimization. React may still need to call your component before ignoring the result, but it shouldn’t affect your code.

Object.is로 얕은 비교, 객체의 2 depth까지 비교해서 state를 업데이트 하는게 아님(렌더링 스킵)

3) React batches state updates. It updates the screen after all the event handlers have run and have called their set functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use flushSync.

리액트는 state를 업데이트 하는 것을 배치 형태로 실행. # State as Snapshot

하나의 이벤트 동안 여러번 리렌더링되는 것을 막기 위해, 모든 이벤트 핸들러가 실행되고 set 함수까지 완료한 후에 화면을 업데이트함

반면, DOM에 접근하기 위해 fulshSync(즉시 DOM 업데이트)를 사용하는 경우 React는 화면을 그전에 업데이트하게 됨

 

- Usage

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        Increment age
      </button>
      <p>Hello! You are {state.age}.</p>
    </>
  );
}

 

React will pass the current state and the action to your reducer function. Your reducer will calculate and return the next state. React will store that next state, render your component with it, and update the UI.

리액트는 리듀서 함수에 현재 state와 action을 전달

-> 리듀서는 함수 실행 후 다음 state 반환

-> 리액트는 다음 state를 저장하고

-> 다음 state로 컴포넌트를 렌더링

-> 그리고 UI 업데이트

 

useReducer is very similar to useState, but it lets you move the state update logic from event handlers into a single function outside of your component. Read more about choosing between useState and useReducer.

useState랑 매우 유사하지만, state 업데이트 로직을 이벤트 핸들러에서 꺼내 컴포넌트 밖의 하나의 리듀서 함수 안에 기술.

 

The action type names are local to your component. Each action describes a single interaction, even if that leads to multiple changes in data. The shape of the state is arbitrary, but usually it’ll be an object or an array.

각 action은 그게 아무리 데이터의 여러 변화를 이끌어도, 하나의 행위만을 서술해야 함

 

- Pitfall

// State is read-only. Don’t modify any objects or arrays in state:
function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // 🚩 Don't mutate an object in state like this:
      state.age = state.age + 1;
      return state;
    }

// Instead, always return new objects from your reducer:
function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // ✅ Instead, return a new object
      return {
        ...state,
        age: state.age + 1
      };
    }

 

- Writing concise update logic with Immer

If updating arrays and objects without mutation feels tedious, you can use a library like Immer to reduce repetitive code. Immer lets you write concise code as if you were mutating objects, but under the hood it performs immutable updates:

mutation 없이 객체나 배열을 수정하는게 걸리고, 반복되는 코드를 줄이고 싶다면 Immer 라이브러리 사용

Immer 라이브러리를 사용하면 객체를 변경(mutation)하는 것 처럼 느낄지라도, 변경하지 않는(immutable) 방향으로 업데이트 됨 

 

` Immer를 사용하지 않은 코드

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false }
];

 

` Immer를 사용한 코드 

import { useImmerReducer } from 'use-immer';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

function tasksReducer(draft, action) {
  switch (action.type) {
    case 'added': {
      draft.push({
        id: action.id,
        text: action.text,
        done: false
      });
      break;
    }
    case 'changed': {
      const index = draft.findIndex(t =>
        t.id === action.task.id
      );
      draft[index] = action.task;
      break;
    }
    case 'deleted': {
      return draft.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

export default function TaskApp() {
  const [tasks, dispatch] = useImmerReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false },
];

 

- Avoiding recreating the initial state

초기 state를 다시 생성하지 않는 법

React saves the initial state once and ignores it on the next renders.

리액트는 initial state를 한번 저장하고, 그 이후 렌더링부터는 무시함 

 

` 초기화 함수를 전달해서 재생성을 막는 법

function createInitialState(username) {
  // ...
}

function TodoList({ username }) {
  // Although the result of createInitialState(username) is only used for the initial render, 
  // you’re still calling this function on every render.  
  // const [state, dispatch] = useReducer(reducer, createInitialState(username));
  
  // To solve this, you may pass it as an initializer function to useReducer as the third argument instead:
  // If your initializer doesn’t need any information to compute the initial state, 
  // you may pass null as the second argument to useReducer.
  const [state, dispatch] = useReducer(reducer, username, createInitialState);
  // ...
}

 

- Trouble shooting

1) I’ve dispatched an action, but logging gives me the old state value.

디스패치 함수 뒤에 호출한 state가 old한 값인 경우

function handleClick() {
  console.log(state.age);  // 42

  dispatch({ type: 'incremented_age' }); // Request a re-render with 43
  console.log(state.age);  // Still 42!

  setTimeout(() => {
    console.log(state.age); // Also 42!
  }, 5000);
}

 

This is because states behaves like a snapshot. Updating state requests another render with the new state value, but does not affect the state JavaScript variable in your already-running event handler.

If you need to guess the next state value, you can calculate it manually by calling the reducer yourself:

State는 스냅샷같이 행동하기 때문에 발생

State 업데이트는 새로운 state 값으로 또 다른 렌더링을 요청함

현재 자바스크립트 변수로서의 state는 이에 영향을 받지 않음, 왜냐하면 이벤트 핸들러가 이미 진행중이기 때문.

 

2) I’ve dispatched an action, but the screen doesn’t update

디스패치 함수를 호출했는데 화면이 업데이트 되지 않는 경우

React will ignore your update if the next state is equal to the previous state, as determined by an Object.is comparison. This usually happens when you change an object or an array in state directly:

리액트는 Object.is 비교 방법으로 이전 state와 다음 state를 비교해 state 간 같은 경우 렌더링을 스킵

이 경우는 주로 state 내부의 객체나 배열을 직접적으로 수정했기 때문에 발생

 

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // 🚩 Wrong: mutating existing object
      state.age++;
      return state;
    }
    case 'changed_name': {
      // 🚩 Wrong: mutating existing object
      state.name = action.nextName;
      return state;
    }
    // ...
  }
}

 

3) I’m getting an error: “Too many re-renders”

무한 루프 렌더링 에러

You might get an error that says: Too many re-renders. React limits the number of renders to prevent an infinite loop. Typically, this means that you’re unconditionally dispatching an action during render, so your component enters a loop: render, dispatch (which causes a render), render, dispatch (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler:

렌더링 중에 무조건적으로 action을 디스패치 하는 경우 무한 렌더링

 

// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>

// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>

// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>

 

 

 

useImperativeHandle

- https://react.dev/reference/react/useImperativeHandle

- https://ko.react.dev/reference/react/useImperativeHandle

 

- React Hook that lets you customize the handle exposed as a ref.

ref를 사용하는 핸들러를 커스터마이징하기 위한 React Hook

 

- Call useImperativeHandle at the top level of your component to customize the ref handle it exposes:

 

import { useImperativeHandle } from 'react';

useImperativeHandle(ref, createHandle, dependencies?);

 

- Parameters

1) ref: The ref you received as the second argument from the forwardRef render function.

forwardRef 렌더링 함수로 부터 전달받은 두 번째 인자 ref

2) createHandle: A function that takes no arguments and returns the ref handle you want to expose. That ref handle can have any type. Usually, you will return an object with the methods you want to expose.

인자가 없고, ref 핸들러를  반환. 어떤 타입이든지 가능한데 주로 메서드를 포함한 객체를 반환

3) optional dependencies: The list of all reactive values referenced inside of the createHandle code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison. If a re-render resulted in a change to some dependency, or if you omitted this argument, your createHandle function will re-execute, and the newly created handle will be assigned to the ref.

옵셔널한 의존성 : createHandle에서 참조된 값 리스트.

Reactive한 값으로 컴포넌트 body에 선언된 props, state, 모든 변수나 함수를 포함

의존성은 반드시 상수와 배열 형태로 구성, 리액트는 각 의존성의 이전 값과 현재 값을 Object.is 방식으로 비교

의존성에 변화가 생기거나, 이 의존성을 작성하지 않으면 createHandle 함수는 매번 실행되고 ref에 새로운 createHandle 할당

 

- Returns

useImperativeHandle returns undefined.

undefined 리턴

 

- Usage

1) Exposing a custom ref handle to the parent component.

부모 컴포넌트에서 ref 핸들러 사용

 

` case 1

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  return <input {...props} ref={ref} />; // ref : <input> DOM node
});

 

` case 2

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

 

Note that in the code above, the ref is no longer forwarded to the <input>.

For example, suppose you don’t want to expose the entire <input> DOM node, but you want to expose two of its methods: focus and scrollIntoView. To do this, keep the real browser DOM in a separate ref. Then use useImperativeHandle to expose a handle with only the methods that you want the parent component to call:

Now, if the parent component gets a ref to MyInput, it will be able to call the focus and scrollIntoView methods on it. However, it will not have full access to the underlying <input> DOM node.

위 코드에서 ref는 더이상 input을 가리키지 않음

예를 들자면, 전체 input DOM 노드를 가리키는 것 대신 focus와 scrollIntoView 메서드만 보여주고 싶을 때 useImeperativeHandle을 사용

그러면 부모 컴포넌트에서 자식 컴포넌트의 ref를 받을 때, 전체의 DOM 노드에 대해 접근하는 것이 아니라 메서드만 접근

대신, useImperatieHandle 내부 별도의 ref에 진짜 브라우저 DOM을 저장

 

2) Exposing your own imperative methods

커스텀 imperative 메서드

The methods you expose via an imperative handle don’t have to match the DOM methods exactly. For example, this Post component exposes a scrollAndFocusAddComment method via an imperative handle. This lets the parent Page scroll the list of comments and focus the input field when you click the button:

DOM 메서드가 아닌 메서드를 사용하고 싶을 때. 예를 들면, 스크롤과 포커스를 동시에 하는 메서드

 

'React' 카테고리의 다른 글

Deep Dive : React 상태 관리 라이브러리  (0) 2024.08.03
React strict mode  (0) 2024.07.10
240706 리액트 스터디 : useRef  (0) 2024.07.07
240629 리액트 스터디 : useRef, useContext  (0) 2024.06.29
Deep Dive : useRef, useContext  (0) 2024.06.29

 

useReducer 

- 기존 Redux 존재, 이후 React 팀에서 useReducer Hook 개발

 

선언형 프로그래밍

- Declarative programming

- "WHAT"

- 어떻게 해야할 지 내부 동작을 몰라도 되는 경우, 무엇을 해야할 지에 대해 집중

- 작업의 절차가 아닌 목표나 결과 중심으로 기술

- 세부적인 실행 과정을 추상화

- 간결한 코드, 유지보수 용이

- ex) html, css, react, jsx, js filter함수

SELECT name FROM users WHERE age > 30;


명령형 프로그래밍

- Imperative programming
- "HOW"

- 어떻게 해야할 지 서술이나 명령

- 작업을 수행하는 구체적인 절차나 순서를 기술

- 상태를 기술하는 연속적인 명령어 사용

- 상태 변화를 통해 문제를 해결 -> 상태 변화나 명령어의 순서가 중요

- 직관적이고 이해하기 쉬운 논리 흐름, 세부적 제어 가능

- ex) for문, useImperativeHandle

let total = 0;
for (let i = 0; i < 10; i++) {
  total += i;
}


함수형 프로그래밍

- Functional programming

- 순수 함수 : 같은 입력에 대해 항상 같은 출력 반환, 부작용이 없는 함수

- 불변성 : 상태나 데이터가 변경되지 않으며, 새로운 데이터를 생성하여 처리

- 고차 함수 : 함수를 인자로 받거나 함수를 반환하는 함수

- 예측 가능한 코드, 테스트 및 병렬 처리에 유리

const add = (a, b) => a + b;
const square = x => x * x;
const sumOfSquares = (a, b) => square(add(a, b));

// 함수형 업데이트 패턴 - 불변성
const state = { todos: ['Learn JavaScript', 'Learn React'] };
const newState = {
  ...state,
  todos: [...state.todos, 'Learn Redux']
};

 

 useImperativeHandle 이 구현된 이유

- 선언형 프로그래밍 방식인 React에서 명령형 프로그래밍 방식으로 구현된 부분 -> useRef, useImperativeHandle

- 사용자가 무수히 많고 복잡한 API를 가진 DOM을 직접 조작하지 않는 편의를 제공해주는 React에서 이러한 부분이 왜 생겼을까?

-> 사용자가 DOM을 조작할 필요가 없다는 React의 원칙에 어긋난 부분, useRef / useImperativeHandle은 DOM 조작 필요

 

useImpearativeHandle 을 사용하는 이유

- 사실 TypeScript에서 ref 타입을 다 잡아주는 이유로 굳이 useImperativeHandle을 사용할 필요는 없음

- DOM api가 아니라 커스텀 훅을 만들어 사용하고 싶을 때 useImperativeHandle 사용

 

'React' 카테고리의 다른 글

React strict mode  (0) 2024.07.10
Deep Dive : useReducer, useImperativeHandle  (0) 2024.07.08
240629 리액트 스터디 : useRef, useContext  (0) 2024.06.29
Deep Dive : useRef, useContext  (0) 2024.06.29
240622 리액트 스터디 : useMemo  (0) 2024.06.22

 

null vs undefined

- 명시성 차이

- undefined : 글로벌 객체의 속성에 대한 식별자

- null : 객체가 존재하지 않을 때, 리터럴
- 변수를 선언하고, 값을 할당하지 않았을 때 undefined
- 변수를 선언했으나 명시적으로 값이 비어있음을 나타낼 때 null

null === undefined; // false
null == undefined; // true

 

undefined

- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/undefined

- 값을 할당하지 않은 상태 

- not writable / not enumerable / not configurable

 

null

- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/null

- 어떤 값이 의도적으로 비어있음

- 변수가 아무런 객체를 가리키지 않음

- boolean -> false

- 값이 없음을 명시


useRef

- useRef() 처럼 파라미터 없이 사용할 때에는, DOM에 바인딩시키지 않아도 되는 경우

- cf) DOM에 바인딩 시키려면 반드시 null 등 초기값을 지정해 줘야함

- 리액트는 commit (렌더링) 단계 이후 ref값 바인딩

- 비디오, 이미지 조정 등의 경우에 사용

 

- useRef type

function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T | null): RefObject<T>;
function useRef<T>(initialValue: T | null): RefObject<T>;

 

- mutableRefObject ⭐️

// error
import React, { useRef } from "react";

export const CuseRef = () => {
  const ref = useRef(0);

  return (
    <div>
      <input ref={ref} />
    </div>
  );
  /*
  'MutableRefObject<number>' 형식은 'LegacyRef<HTMLInputElement> | undefined' 형식에 할당할 수 없습니다.
  'MutableRefObject<number>' 형식은 'RefObject<HTMLInputElement>' 형식에 할당할 수 없습니다.
    'current' 속성의 형식이 호환되지 않습니다.
      'number' 형식은 'HTMLInputElement' 형식에 할당할 수 없습니다.ts(2322)
  */
};
// error
import React, { useRef } from "react";

export const CuseRef = () => {
  const ref = useRef<HTMLInputElement>();

  return (
    <div>
      <input ref={ref} />
    </div>
  );
  /*
  'MutableRefObject<HTMLInputElement | undefined>' 형식은 'LegacyRef<HTMLInputElement> | undefined' 형식에 할당할 수 없습니다.
  'MutableRefObject<HTMLInputElement | undefined>' 형식은 'RefObject<HTMLInputElement>' 형식에 할당할 수 없습니다.
    'current' 속성의 형식이 호환되지 않습니다.
      'HTMLInputElement | undefined' 형식은 'HTMLInputElement | null' 형식에 할당할 수 없습니다.
        'undefined' 형식은 'HTMLInputElement | null' 형식에 할당할 수 없습니다.ts(2322)
  */
};

 

useContext

- 컴포넌트 재활용이 어려움 -> context의 특정 값을 참조하는 경우, 이 context를 벗어나면 값을 참조하기 어렵기 때문. ex) user 정보

 

Array.prototype.some()

Array.some(callbackFn, thisArg);

- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/some

- 배열 안의 어떤 요소라도 주어진 판별 함수를 적어도 하나라도 통과하는지 테스트

- callbackFn이 true 반환하면 true 반환, 그렇지 않으면 false 반환

- 배열 변경 X

 

DOM

- DOM이 없는 값을 찾으면 null 반환(undefined X)

 

'React' 카테고리의 다른 글

Deep Dive : useReducer, useImperativeHandle  (0) 2024.07.08
240706 리액트 스터디 : useRef  (0) 2024.07.07
Deep Dive : useRef, useContext  (0) 2024.06.29
240622 리액트 스터디 : useMemo  (0) 2024.06.22
Deep Dive : UseMemo, UseCallback  (0) 2024.06.22

 

useRef

- https://react.dev/reference/react/useRef

- https://ko.react.dev/reference/react/useRef

 

- React Hook that lets you reference a value that’s not needed for rendering.

렌더링이 필요없는 값을 참조할 수 있도록 레퍼런스를 제공하는 훅

 

import { useRef } from 'react';

const ref = useRef(initialValue);


- Call useRef at the top level of your component to declare a ref.

 

- initialValue: The value you want the ref object’s current property to be initially. It can be a value of any type. This argument is ignored after the initial render.

ref 객체의 초기값, 어떤 타입이던지 가능하며 첫번째 렌더이후 이 argument는 무시됨

 

- useRef returns an object with a single property:

current: Initially, it’s set to the initialValue you have passed. You can later set it to something else. If you pass the ref object to React as a ref attribute to a JSX node, React will set its current property.

On the next renders, useRef will return the same object.

하나의 프로퍼티 current만 return

처음에는 사용자가 전달한 initialValue로 세팅되나 변경 가능, jsx 노드의 어트리뷰트로 전달 가능

다음 렌더링에서 동일한 객체 반환 (리렌더링 X)

 

- Caveats

1) You can mutate the ref.current property. Unlike state, it is mutable. However, if it holds an object that is used for rendering (for example, a piece of your state), then you shouldn’t mutate that object.

ref.current는 state와 달리 변경 가능, 하지만 렌더링에 사용하는 객체의 경우 변경하면 안됨 -> 이벤트 핸들러 이용

2) When you change the ref.current property, React does not re-render your component. React is not aware of when you change it because a ref is a plain JavaScript object

ref.current 속성은 변경할 수 있으나 이는 자바스크립트 객체라 리액트가 변화를 인식할 수 없으므로, 리렌더링 X

3) Do not write or read ref.current during rendering, except for initialization. This makes your component’s behavior unpredictable.

초기화 단계 제외 렌더링중 ref.current 읽기 및 쓰기 금지 -> 예측 불가능한 오류 발생 가능

4) In Strict Mode, React will call your component function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. Each ref object will be created twice, but one of the versions will be discarded. If your component function is pure (as it should be), this should not affect the behavior.

strict mode에서는 2번 call되나 이는 개발모드 한정. ref 객체가 두 번 생성되고, 하나는 무시됨.

 

- Usage

1) Referencing a value with a ref : 값 참조

import { useRef } from 'react';

function Stopwatch() {
  const intervalRef = useRef(0);
  // ...

  function handleStartClick() {
    const intervalId = setInterval(() => {
    // ...
    }, 1000);
  intervalRef.current = intervalId;
  }  

  function handleStopClick() {
    const intervalId = intervalRef.current;
    clearInterval(intervalId);
  }
}

 

useRef returns a ref object with a single current property initially set to the initial value you provided.

On the next renders, useRef will return the same object. You can change its current property to store information and read it later. This might remind you of state, but there is an important difference.

current 프로퍼티만 가진 ref 객체를 return하고, 렌더링 이후에도 같은 객체 리턴. 

current 프로퍼티에 정보를 저장하고 나중에 읽을 수 있음(state랑은 다름)

 

Changing a ref does not trigger a re-render. This means refs are perfect for storing information that doesn’t affect the visual output of your component. For example, if you need to store an interval ID and retrieve it later, you can put it in a ref. To update the value inside the ref, you need to manually change its current property:

리렌더링을 트리거 하는게 아님. 즉, 컴포넌트의 비쥬얼적인 요소를 변경시키는 게 아님. 정보를 저장하기 위한 것.

 

By using a ref, you ensure that:

- You can store information between re-renders (unlike regular variables, which reset on every render).

- Changing it does not trigger a re-render (unlike state variables, which trigger a re-render).

- The information is local to each copy of your component (unlike the variables outside, which are shared).

Changing a ref does not trigger a re-render, so refs are not appropriate for storing information you want to display on the screen. Use state for that instead. Read more about choosing between useRef and useState.

리렌더 간 정보 저장용, 리렌더 트리거 X, 컴포넌트의 local information copy

 

examples) Click Counter 카운터 횟수 저장 / Stop Watch

 

* Do not write or read ref.current during rendering.

-> If you have to read or write something during rendering, use state instead.

// bad example
function MyComponent() {
  // ...
  // 🚩 Don't write a ref during rendering
  myRef.current = 123;
  // ...
  // 🚩 Don't read a ref during rendering
  return <h1>{myOtherRef.current}</h1>;
}

// good example
function MyComponent() {
  // ...
  useEffect(() => {
    // ✅ You can read or write refs in effects
    myRef.current = 123;
  });
  // ...
  function handleClick() {
    // ✅ You can read or write refs in event handlers
    doSomething(myOtherRef.current);
  }
  // ...
}

 

2) Manipulating the DOM with a ref : DOM 조작

import { useRef } from 'react';

function MyComponent() 
  // First, declare a ref object with an initial value of null:
  const inputRef = useRef(null);
  
  function handleClick() {
    inputRef.current.focus();
  }
  
  // Then pass your ref object as the ref attribute to the JSX of the DOM node you want to manipulate:
  return <input ref={inputRef} />;
}

After React creates the DOM node and puts it on the screen, React will set the current property of your ref object to that DOM node. Now you can access the <input>’s DOM node and call methods like focus():

useRef(null) 로 초기화 -> input ref 프로퍼티 -> DOM 렌더링 이후 ref.current : DOM 노드 반환

 

React will set the current property back to null when the node is removed from the screen.
노드가 화면에서 사라지면 ref.current = null로 세팅

 

examples)

- Focusing a text input 

- Scrolling an image into view : view로 이미지 스크롤

- Playing and pausing a video : play or pause 정보로 활용

- Exposing a ref to your own component : parent + child 컴포넌트 조합 시 useRef + forwardRef 활용

 

3) Avoiding recreating the ref contents : ref 컨텐츠를 재생성하는 것 방지

function Video() {
  // expensive code -> on every render
  // const playerRef = useRef(new VideoPlayer()); 
  
  function getPlayer() {
    if (playerRef.current !== null) {
      return playerRef.current;
    }
    
    // How to avoid null checks when initializing useRef later
    const player = new VideoPlayer();
    playerRef.current = player;
    return player;
  }
}

 

 

 

 useContext

- https://react.dev/reference/react/useContext

- https://ko.react.dev/reference/react/useContext

 

-  React Hook that lets you read and subscribe to context from your component.

컴포넌트가 context를 구독하고 읽을 수 있도록 도와주는 리액트 훅

 

import { useContext } from 'react';

const value = useContext(SomeContext);

 

- Call useContext at the top level of your component to read and subscribe to context.

 

- SomeContext: The context that you’ve previously created with createContext. The context itself does not hold the information, it only represents the kind of information you can provide or read from components.

createContext로 이전에 만든 context, context 자체는 정보를 가지고 있지 않고 단지 사용자가 컴포넌트를 읽거나 제공할 수 있는 정보의 종류를 나타냄

 

- Returns

the context value for the calling component. It is determined as the value passed to the closest SomeContext.Provider above the calling component in the tree. If there is no such provider, then the returned value will be the defaultValue you have passed to createContext for that context. The returned value is always up-to-date. React automatically re-renders components that read some context if it changes.

호출하는 컴포넌트에 대한 context value 리턴, 해당 컴포넌트 트리에서 가장 가까운 SomeContext.Provider에서 전달받은 값

provider가 없으면 createContext에서 생성된 context의 defaultValue, 리턴되는 value는 항상 최신 값.

리액트는 해당 컨텍스트가 변경되면 그 컨텍스트를 참조하는 컴포넌트를 리렌더함

 

- Caveats

1) useContext() call in a component is not affected by providers returned from the same component. The corresponding <Context.Provider> needs to be above the component doing the useContext() call.

useContext는 같은 컴포넌트에서 반환되는 provider의 영향을 받지 않으며, context.provider는 반드시 useContext를 호출하는 컴포넌트 상위에 배치되어야 함

2) React automatically re-renders all the children that use a particular context starting from the provider that receives a different value. The previous and the next values are compared with the Object.is comparison. Skipping re-renders with memo does not prevent the children receiving fresh context values.

리액트는 다른 value를 받아 provider로 시작해서, 특정 context를 사용하는 모든 childeren을 자동으로 리렌더

이전 값과 다음 값은 Object.is에 의해 비교됨, memo로 리렌더를 스킵해도 children은 context의 새로운 값을 받음

3) If your build system produces duplicates modules in the output (which can happen with symlinks), this can break context. Passing something via context only works if SomeContext that you use to provide context and SomeContext that you use to read it are exactly the same object, as determined by a === comparison.

빌드 시스템이 결과적으로 중복 모듈을 생성하는 경우(심볼릭 링크), 컨텍스트가 파괴될 수 있음.

context 통해 무언가를 전달하는 것은 === 비교에 의해 결정되는 것처럼, 컨텍스트를 제공하는 사용하는 SomeContext context 읽는 사용하는 SomeContext 정확하게 동일한 객체인 경우에만 작동

 

- Usage

1) Passing data deeply into the tree : 트리 깊은 곳까지 값 전달

import { useContext } from 'react';

function MyPage() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  );
}

function Form() {
  // ... renders buttons inside ...
  function Button() {
  const theme = useContext(ThemeContext);
  // ...
  }
}

 

useContext returns the context value for the context you passed. To determine the context value, React searches the component tree and finds the closest context provider above for that particular context. It doesn’t matter how many layers of components there are between the provider and the Button. When a Button anywhere inside of Form calls useContext(ThemeContext), it will receive "dark" as the value.

전달한 컨텍스트에서 값을 리턴, 컴포넌트에서 가장 가까운 context provider를 찾는다.

컴포넌트 레이어가 몇 겹이던지 상관없이 useContext를 호출한 곳을 찾아 value 리턴

 

useContext() always looks for the closest provider above the component that calls it. It searches upwards and does not consider providers in the component from which you’re calling useContext().

useContext를 호출한 컴포넌트와 동일한 계층이 아니라, 무조건 상위 계층에서 가까운 provider를 찾음

 

2) Updating data passed via context : 컨텍스트를 통해 데이터(state) 업데이트

function MyPage() {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeContext.Provider value={theme}>
      <Form />
      <Button onClick={() => {
        setTheme('light');
      }}>
        Switch to light theme
      </Button>
    </ThemeContext.Provider>
  );
}

 

a. Updating a value via context

b. Updating an object via context

c. Multiple contexts

d. Extracting providers to a component

e. Scaling up with context and a reducer : ex) 동적으로 input 추가하는 로직

 

3) Specifying a fallback default value : fallback 기본값 지정 -> context.provider를 찾을 수 없을 때

// const ThemeContext = createContext(null);
const ThemeContext = createContext('light');

 

If React can’t find any providers of that particular context in the parent tree, the context value returned by useContext() will be equal to the default value that you specified when you created that context:

The default value never changes. If you want to update context, use it with state as described above.

Often, instead of null, there is some more meaningful value you can use as a default.

리액트가 parent 트리에서 context를 찾지 못하면, useContext의 default value 반환.

default value로 null 대신 의미있는 단어 사용 권장

 

4) Overriding context for a part of the tree : 트리의 일정 부분 오버라이드 -> 컨텍스트 중첩 사용

 

5) Optimizing re-renders when passing objects and functions : 객체와 함수를 전달해 리렌더링 최적화

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  function login(response) {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }

  return (
    <AuthContext.Provider value={{ currentUser, login }}>
      <Page />
    </AuthContext.Provider>
  );
}

 

Here, the context value is a JavaScript object with two properties, one of which is a function. Whenever MyApp re-renders (for example, on a route update), this will be a different object pointing at a different function, so React will also have to re-render all components deep in the tree that call useContext(AuthContext).

login이 function object이기 때문에 매번 다른 주소를  가리키게 됨 -> 리액트는 트리 내부의 useContext를 호출하는 모든 컴포넌트 리렌더

 

import { useCallback, useMemo } from 'react';

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  const login = useCallback((response) => {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }, []);

  const contextValue = useMemo(() => ({
    currentUser,
    login
  }), [currentUser, login]);

  return (
    <AuthContext.Provider value={contextValue}>
      <Page />
    </AuthContext.Provider>
  );
}

 

To help React take advantage of that fact, you may wrap the login function with useCallback and wrap the object creation into useMemo

useMemo랑 useCallback으로 감싸서 컴포넌트 최적화

 

'React' 카테고리의 다른 글

240706 리액트 스터디 : useRef  (0) 2024.07.07
240629 리액트 스터디 : useRef, useContext  (0) 2024.06.29
240622 리액트 스터디 : useMemo  (0) 2024.06.22
Deep Dive : UseMemo, UseCallback  (0) 2024.06.22
240615 리액트 스터디 : hooks  (0) 2024.06.15
const user = {
	id: 123,
	info: {
 		name: ‘체리’,
 		age:10
	}
}

const MemoizedComponent1 = useMemo(() => {
	console.log("object depth 1");
}, [user]); 

const MemoizedComponent2 = useMemo(() => {
	console.log("object depth 2");
}, [user.info.name]);

 

- user.info.name 이 바뀌는 경우

-> Object.is의 비교 알고리즘에 의해 얕은 복사에 의한 비교

MemoizedComponent1 에서는 user.info 가 가리키는 객체의 주소가 같기 때문에 변경되었다 인식 X, 리렌더링 X

MemoizedComponent2 에서는 의존성 배열로 user.info.name 문자열 자체가 들어가기 때문에 값이 변경되었다 인식, 렌더링 O

 

'React' 카테고리의 다른 글

240629 리액트 스터디 : useRef, useContext  (0) 2024.06.29
Deep Dive : useRef, useContext  (0) 2024.06.29
Deep Dive : UseMemo, UseCallback  (0) 2024.06.22
240615 리액트 스터디 : hooks  (0) 2024.06.15
Deep Dive : React hooks  (0) 2024.06.15

 

 useMemo

- https://react.dev/reference/react/useMemo

- https://ko.react.dev/reference/react/useMemo

 

import { useMemo } from 'react';

const cachedValue = useMemo(calculateValue, dependencies);

 

- React Hook that lets you cache the result of a calculation between re-renders.

리렌더링 시 계산 결과를 캐싱(memoization)하도록 해주는 훅

 

- You should only rely on useMemo as a performance optimization. If your code doesn’t work without it, find the underlying problem and fix it first. Then you may add useMemo to improve performance.

성능 최적화에만 사용 -> 코드 버그시 수정 후 사용

 

- calculatedValue : The function calculating the value that you want to cache. It should be pure, should take no arguments, and should return a value of any type. React will call your function during the initial render. On next renders, React will return the same value again if the dependencies have not changed since the last render. Otherwise, it will call calculateValue, return its result, and store it so it can be reused later.

캐시하고 싶은 값을 계산하는 함수, 해당 함수는 arguments가 없어야 하고 무조건 값을 리턴(void X)

리액트는 첫번째 렌더링 이후 이 함수를 호출하고, 이후 리렌더링시 이전 렌더링에서 값이 변하지 않았다면 같은 값을 리턴

값이 변했다면 이 함수를 호출해서 그 값을 리턴하고, 나중에 재사용할 수 있도록 값을 저장

 

- dependencies: The list of all reactive values referenced inside of the calculateValue code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison.

앞선 calculateValue 함수에서 참조되는 값, 이 값들은 props나 state, 컴포넌트에서 선언된 모든 변수나 함수가 될 수 있음

즉, 의존성으로 배열 형태로 서술, 리액트는 Object.is 비교 방법으로 이전 값들과 각 의존성을 비교

 

- 사용법

1) Skipping expensive recalculations : 비용이 많이 드는 로직 다시 계산하는 것 스킵

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  // ...
}

-> 컴포넌트 최상위 레벨에 작성, todos나 tab이 변경되었을 때만 filterTodos 호출(그 외, 이전에 캐싱한 값 return)

 

2) Skipping re-rendering of components : 컴포넌트 리렌더링 스킵

const List = memo(function List({ items }) {
  // ...
});

export default function TodoList({ todos, tab, theme }) {
  // Every time the theme changes, this will be a different array...
  const visibleTodos = filterTodos(todos, tab);
  return (
    <div className={theme}>
		{/* ... so List's props will never be the same, and it will re-render every time */}
      <List items={visibleTodos} />
    </div>
  );

}

-> 리액트는 기본적으로 컴포넌트 리렌더링 시 그 children을 재귀적으로 리렌더링

하지만 memo로 감싸고 props가 이전 렌더링 시점과 동일하면 리렌더링 스킵

export default function TodoList({ todos, tab, theme }) {
  // Tell React to cache your calculation between re-renders...
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab] // ...so as long as these dependencies don't change...
  );
  return (
    <div className={theme}>
      {/* ...List will receive the same props and can skip re-rendering */}
      <List items={visibleTodos} />
    </div>
  );
}

-> filterTodo() 가 새로운 배열을 생성하는 경우 List의 props가 동일하지 않아 memo가 작동하지 않음, useMemo 사용

 

3) Memoizing a dependency of another Hook : 훅 종속성 메모

function Dropdown({ allItems, text }) {
  const searchOptions = useMemo(() => {
    return { matchMode: 'whole-word', text };
  }, [text]); // ✅ Only changes when text changes

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes

-> useMemo가 없으면 text가 변경될 때 마다, 객체가 재생성되어 컴포넌트 전체 렌더링

useMemo를 사용해서 text가 변경될 때만 렌더링

 

4) Memoizing a function : 함수 메모

export default function Page({ productId, referrer }) {
  const handleSubmit = useMemo(() => {
    return (orderDetails) => {
      post('/product/' + productId + '/buy', {
        referrer,
        orderDetails
      });
    };
  }, [productId, referrer]);

  return <Form onSubmit={handleSubmit} />;
}

-> 함수를 메모하려면 또다른 함수를 리턴해야함

export default function Page({ productId, referrer }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails
    });
  }, [productId, referrer]);
  return <Form onSubmit={handleSubmit} />;
}

-> 이런 경우 useMemo대신 useCallback Hook 사용

 

- 주의 사항

1) useMemo is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.

useMemo는 Hook이므로, 컴포넌트 최상위 레벨에서 호출되어야 함

다른 루프나 조건문 내에서 호출할 수 없고, 이러한 형태가 필요한 경우 새로운 컴포넌트를 만들어서 작성

2) In Strict Mode, React will call your calculation function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your calculation function is pure (as it should be), this should not affect your logic. The result from one of the calls will be ignored.

Strict 모드(보안 모드) 개발 환경에서 리액트는 실수로 발생한 오류를 찾기 위해 useMemo를 두 번 호출

-> calculatedValue가 pure한 함수면 로직에 영향을 미치지 않음

3) React will not throw away the cached value unless there is a specific reason to do that. For example, in development, React throws away the cache when you edit the file of your component. Both in development and in production, React will throw away the cache if your component suspends during the initial mount. In the future, React may add more features that take advantage of throwing away the cache—for example, if React adds built-in support for virtualized lists in the future, it would make sense to throw away the cache for items that scroll out of the virtualized table viewport. This should be fine if you rely on useMemo solely as a performance optimization. Otherwise, a state variable or a ref may be more appropriate.

리액트는 특별한 이유없이 캐싱된 값을 삭제하지 않음

(단, 개발환경에서 소스 코드를 수정했을 때나 개발/production 환경에서 마운트 이후 컴포넌트가 중단되면 삭제)

-> 추후 리액트 built-in 기능 추가시 캐싱된 값 삭제 가능

 

- Q.  How to tell if a calculation is expensive?

A. Chrome offers a CPU Throttling option

 

- Q. Should you add useMemo everywhere?

A. The calculation you’re putting in useMemo is noticeably slow, and its dependencies rarely change.

계산이 느리고 종속성 거의 변하지 않는 경우

You pass it as a prop to a component wrapped in memo. You want to skip re-rendering if the value hasn’t changed. Memoization lets your component re-render only when dependencies aren’t the same.

The value you’re passing is later used as a dependency of some Hook. For example, maybe another useMemo calculation value depends on it. Or maybe you are depending on this value from useEffect.

전달한 값을 useEffect 같은 Hook의 종속성으로 이용하는 경우 

 

- In practice, you can make a lot of memoization unnecessary by following a few principles:

메모이제이션 사용 원리
1) When a component visually wraps other components, let it accept JSX as children. This way, when the wrapper component updates its own state, React knows that its children don’t need to re-render.

JSX를 children처럼 이용

2) Prefer local state and don’t lift state up any further than necessary. For example, don’t keep transient state like forms and whether an item is hovered at the top of your tree or in a global state library.

전역적 상태 대신 지역 상태 사용

3) Keep your rendering logic pure. If re-rendering a component causes a problem or produces some noticeable visual artifact, it’s a bug in your component! Fix the bug instead of adding memoization

순수 렌더링 로직 권장

4) Avoid unnecessary Effects that update state. Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over.

불필요하게 상태 업데이트 effect 사용 X

5) Try to remove unnecessary dependencies from your Effects. For example, instead of memoization, it’s often simpler to move some object or a function inside an Effect or outside the component.

불필요한 종속성 제거

 

 

useCallback

- https://react.dev/reference/react/useCallback

- https://ko.react.dev/reference/react/useCallback

 

import { useCallback } from 'react';

const cachedFn = useCallback(fn, dependencies)

- React Hook that lets you cache a function definition between re-renders.

리렌더링 시 함수를 캐싱하도록 해주는 훅

 

- fn: The function value that you want to cache. It can take any arguments and return any values. React will return (not call!) your function back to you during the initial render. On next renders, React will give you the same function again if the dependencies have not changed since the last render. Otherwise, it will give you the function that you have passed during the current render, and store it in case it can be reused later. React will not call your function. The function is returned to you so you can decide when and whether to call it.

캐시하고 싶은 함수, useMemo와 다르게 arguments를 가질 수 있고 어떤 값이든 리턴

리액트는 첫번째 렌더링시 함수를 호출하는 것이 아니라 함수를 리턴 -> 개발자가 함수를 호출할 수 있도록 !

리렌더시 이전 렌더링으로 부터 의존성이 동일하다면 같은 함수 리턴 -> 렌더링 패스하고 저장한 함수 재사용

 

- dependencies: The list of all reactive values referenced inside of the fn code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison algorithm.

fn 함수에서 참조되어지는 리액트 값, 컴포넌트 내부에 선언된 함수, 변수 또는 props, state

의존성 배열 형태로 작성, Object.is 비교 알고리즘을 이용해 이전 값과 의존성을 비교

 

- Return 

On the initial render, useCallback returns the fn function you have passed.

During subsequent renders, it will either return an already stored fn  function from the last render (if the dependencies haven’t changed), or return the fn function you have passed during this render.

 

개발자가 전달한 함수 리턴

지속적 리렌더시에는 의존성과 비교 후 지난 렌더링과 값이 동일하다면 저장된 함수, 다르다면 fn 리턴

 

 

- 주의사항

1) useCallback is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.

컴포넌트 최상위 레벨에 선언, 루프나 조건문 내부에 사용해야 하면 새로운 컴포넌트 작성

2) React will not throw away the cached function unless there is a specific reason to do that. For example, in development, React throws away the cache when you edit the file of your component. Both in development and in production, React will throw away the cache if your component suspends during the initial mount. In the future, React may add more features that take advantage of throwing away the cache—for example, if React adds built-in support for virtualized lists in the future, it would make sense to throw away the cache for items that scroll out of the virtualized table viewport. This should match your expectations if you rely on useCallback as a performance optimization. Otherwise, a state variable or a ref may be more appropriate.

-> useMemo 주의사항과 동일

 

- 사용법

1) Skipping re-rendering of components : 컴포넌트 리렌더링 스킵

function ProductPage({ productId, referrer, theme }) {
  // Tell React to cache your function between re-renders...
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); // ...so as long as these dependencies don't change...

  return (
    <div className={theme}>
      {/* ...ShippingForm will receive the same props and can skip re-rendering */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

-> 의존성 productId, referrer 가 변경되면 새로운 함수 반환, 아니면 이전 렌더링시 캐싱된 함수 반환

ShippingForm props가 이전 렌더링과 같다면 렌더링 스킵

⭐️ 자바스크립트에서 function () { } 나 () => { } 는 항상 다른 함수 생성 -> useCallback 사용

 

2) Updating state from a memoized callback : 메모된 콜백 함수에서 state 업데이트

function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
  }, []); // ✅ No need for the todos dependency
  // ...

-> 의존성 배열에서 todos 생략

You’ll usually want memoized functions to have as few dependencies as possible. When you read some state only to calculate the next state, you can remove that dependency by passing an updater function instead

 

3) Preventing an Effect from firing too often : effect가 너무 자주 발생되는 것을 막음

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() { // ✅ No need for useCallback or function dependencies!
      return {
        serverUrl: 'https://localhost:1234',
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ✅ Only changes when roomId changes
  // ...

-> createOptions를 useEffect 밖 useCallback으로 빼는 것 대신 useEffect 내부에서 사용

 

4) Optimizing a custom Hook : 커스텀 훅 최적화

function useRouter() {
  const { dispatch } = useContext(RouterStateContext);

  const navigate = useCallback((url) => {
    dispatch({ type: 'navigate', url });
  }, [dispatch]);

  const goBack = useCallback(() => {
    dispatch({ type: 'back' });
  }, [dispatch]);

  return {
    navigate,
    goBack,
  };
}

-> 커스텀 훅 작성시에는 반환하는 모든 함수를 useCallback으로 감싸면 최적화 가능

 

- Q. Should you add useCallback everywhere?

A. 1) You pass it as a prop to a component wrapped in memo. You want to skip re-rendering if the value hasn’t changed. Memoization lets your component re-render only if dependencies changed.

memo 컴포넌트에 props로 넘길 때

2) The function you’re passing is later used as a dependency of some Hook. For example, another function wrapped in useCallback depends on it, or you depend on this function from useEffect.

함수가 useEffect나 Hook의 의존성으로 사용될 때

 

 

'React' 카테고리의 다른 글

Deep Dive : useRef, useContext  (0) 2024.06.29
240622 리액트 스터디 : useMemo  (0) 2024.06.22
240615 리액트 스터디 : hooks  (0) 2024.06.15
Deep Dive : React hooks  (0) 2024.06.15
240601 리액트 스터디 : 리액트 파이버  (0) 2024.06.12

 

 useState 리턴값이 배열인 이유

- useState 리턴값인 배열은 0번째 인덱스에는 state, 1번째 인덱스에는 setter 함수로 구성

-> 배열이라 자유롭게 네이밍  ex. const [name, setName] = useState("hi");

- 객체의 경우 배열과 달리 인덱스가 아니라, 객체의 key를 가져와야 구조분해할당이 되기 때문


useEffect

- 의존성 배열로 빈 배열 [] 을 주면 클린업 함수가 unmount 될 때 실행되는 효과

- 원래 클린업 함수(return)는 의존성 배열의 값이 변화(리렌더링)하거나, 언마운트 되었을 때 실행 

 

 useRef 를 null 로 초기화 하는 이유

- const ref = useRef(initialValue)

- 리액트 권장사항
- ref는 useRef가 실행되고, DOM이 렌더링될 때 까지 셋팅되지 않음 

- useRef는 변경할 수 있는(mutable) ref 객체를 반환하는데, ref.current는 파라미터 initialValue로 초기화 됨.

-> initialValue를 설정하지 않으면 ref.current = undefined 

error. TypeError: Cannot read properties of undefined (reading 'style') // 렌더링 전 접근 시도

-> initialValue null로 초기화 해서 값이 할당되기 전에 접근했을 때 발생하는 오류 방지 

- 컴포넌트가 렌더링 되기 전에 ref가 선언되므로, 차후 발생하는 렌더링 에러 방지

- null로 셋팅하여 나중에 값이 할당될 예정임을 나타냄

 

 

 
 

 

 

 

'React' 카테고리의 다른 글

240622 리액트 스터디 : useMemo  (0) 2024.06.22
Deep Dive : UseMemo, UseCallback  (0) 2024.06.22
Deep Dive : React hooks  (0) 2024.06.15
240601 리액트 스터디 : 리액트 파이버  (0) 2024.06.12
Formik -> React-Hook-Form 리팩토링  (0) 2024.06.10

 

- React 함수 컴포넌트에서 등장한 개념

- 리액트의 렌더링은 함수 컴포넌트에서 반환한 결과물인 return 값을 비교해 실행

- 매 렌더링마다 함수 재실행 

 

 useState

https://react.dev/reference/react/useState
- const [state, setState] = useState(initialState)

- initialState를 넘겨주지 않으면 undefined

- useState retrurn : 배열

- 클로저에 의존해 구현 -> 클로저 내부에 useState 관련 정보 저장, 필요할 때 마다 꺼내쓰는 형식

const [value, setValue] = useState(0);
setValue(1);
console.log(value); // 0
// -> 값이 1이 되게 하려면, state를 함수로 바꿔야 값을 호출할 때 마다 현재 state 반환

- 게으른 초기화 : 초기값 설정 시 함수를 실행해서 값 반환, 무거운 연산시 사용 

-> 오직 state가 처음 만들어 질 때만 함수 실행, 리렌더링시 함수 재실행 X

const [count, setCont] = useState(() => {
	Nummber.parseInt(window.localStorage.getItem(cache(key))),
});

 

useEffect

- https://react.dev/reference/react/useEffect
- useEffect(setup, dependencies?)

- setup : 콜백 함수

- dependencies : 의존성 배열, 이 값이 변경되면 콜백 함수 실행

-> 아무것도 넘겨주지 않으면 매 렌더링마다 실행, 빈 배열을 넘기면 컴포넌트가 마운트됐을 때에만 실행

- 컴포넌트 렌더링 완료 후 실행

- return으로 클린업 함수 반환 가능

-> 이 때 클린업 함수는 컴포넌트가 의존성 배열의 값이 변화해서 리렌더링되거나 언마운트될 때 실행

- 컴포넌트가 렌더링 될 때 return 전까지 실행되고, return문은 리렌더링 전에 실행

useEffect((
	function addMouseEvent() {
    	console.log(counter);
    }
    
    window.addEventListener('click', addMouseEvent);
    
    return () => {
    	// 클린업 함수 -> 이벤트 핸들러가 무한하게 추가되는 것을 방지
    	window.removeEventListener('click', addMouseEvent);
    }
) => [counter]);

- 최대한 간결하고, 부수효과가 없게 작성

- cf. 경쟁 상태(race condition) - useEffect 내부에 비동기 함수 작성, useEffect 이후 비동기 응답이 오면 이전 state 기반 결과값이 나올 수 있음

 

 useMemo

- https://react.dev/reference/react/useMemo

- const cachedValue = useMemo(calculateValue, dependencies)

- calculateValue : 어떠한 값을 반환하는 생성 함수 (값)

- dependencies : 해당 함수가 의존하는 값의 배열

- 비용이 큰 연산을 저장, 저장된 <값> 반환

- cf. 컴포넌트를 감쌀 때에는 React.memo 사용

 

useCallback

- https://react.dev/reference/react/useCallback

- const cachedFn = useCallback(fn, dependencies)

- fn : 기억할 함수

- dependencies : 의존성 배열 -> 변경시 함수 재생성

- 특정 <함수>를 새로 만들지 않고 재사용

 

useRef

- https://react.dev/reference/react/useRef

- const ref = useRef(initialValue)

- useRef return : object

-> object.current 로 값에 접근, 변경 가능

- 값이 변화더라도 렌더링 X, 렌더링 없이 원하는 상태값 저장

- DOM에 접근할 때 사용

function RefComponent() {
	const inputRef = useRef();
    
 	console.log(inputRef.current); // undefined -> 렌더링 전이기 때문
    
    useEffect(() => {
    	console.log(inputRef.current); // <input teype="text></input>
    }, [inputRef]);
    
    return <input ref={inputRef} type="text" />
}

 

 useContext

- https://react.dev/reference/react/useContext

- const value = useContext(SomeContext)

- props drilling(부모->자식 간 데이터 이동) 문제 해결

-> props를 명시적으로 전달하지 않고 하위 컴포넌트에서 값 사용

- Provider에 의존하게 되므로 컴포넌트 재활용 어려움

const Context = createContext<{ hello: string } | undefined>(undefined);

function ParentComponent() {
	return (
    	<>
        	<Context.Provider value={{ hello: 'react' }}>
            	<ChildComponent />
            </Context.Provider>
        </>
    )
}

function ChildComponent() {
	const value = useContext(Context);
    
    return (<>{value ? value.hello : ''}</>);
}

 

 useReducer

- https://react.dev/reference/react/useReducer

- const [state, dispatch] = useReducer(reducer, initialArgs, init?)

- dispatcher : state를 업데이트하는 함수

- action : state를 변경할 수 있는 액션

- reducer : action을 정의하는 함수

- initialArgs : useReducer chrlrkqt

- init : 게으른 초기화시 사용

- state를 업데이트하는 것을 dispatcher로 제한

- 비즈니스 로직 분리

 

 useImperativeHandle

- https://react.dev/reference/react/useImperativeHandle

- useImperativeHandle(ref, createHandle, dependencies?)

- 부모에게 넘겨받은 ref를 수정할 수 있는 훅

-> 함수 등 추가적인 동작 사용 가능

 

 forwardRef

- https://react.dev/reference/react/forwardRef

- const SomeComponent = forwardRef(render)

- ref(useRef에서 반환하는 객체)를 전달하는 데 일관성 제공

 

useLayoutEffect

- https://react.dev/reference/react/useLayoutEffect

- useLayoutEffect(setup, dependencies?)

- 리액트 DOM update -> useLayoutEffect -> 브라우저 반영 -> useEffect

- useLayoutEffect가 완료되어야 브라우저가 화면을 그림

- 리액트에서 사용 권장 X

 

 useDebugValue

- https://react.dev/reference/react/useDebugValue

- useDebugValue(value, format?)

- value가 변경될 때에만 로그

- F12 react component 탭에서 로그 확인 가능

 

 리액트 훅 규칙

- 최상위에서만 훅 호출 -> 반복문 / 조건문 / 중첩함수에서 훅 실행 불가

-> 항상 동일한 순서로 훅이 호출되는 것을 보장하기 위함

- 훅은 리액트 함수 컴포넌트, 사용자 정의 훅에서만 호출 가능

-> 일반 자바스크립트에서 호출 불가

 

✓  훅 특징

-  파이버 객체의  링크드 리스트 호출 순서에 따라 훅에 대한 정보 저장 

- 조건문에서 호출되면 링크드 리스트가 깨지면서 순서가 깨짐 -> 리액트 코드 에러

 

사용자 정의 훅 

- https://react.dev/learn/reusing-logic-with-custom-hooks

- 서로 다른 컴포넌트 내부에서 같은 로직을 공유할 때 사용

- use로 시작하는 네이밍, 아닐 시 error

 

고차 컴포넌트

- HOC(Higher Order Component)

- 함수를 인수로 받거나 결과를 반환하는 함수

- 컴포넌트 자체의 로직을 재사용하기 위한 방법

-> 값이나 로직보다 컴포넌트를 반환해서 렌더링을 제어해할 때 주로 사용

- 고차 함수의 일종, 자바스크립트의 일급 객체 함수의 특징을 이용

- 접두사 with를 사용해서 네이밍

- ex. React.memo, Array.prototype.map

 

React.memo

- https://react.dev/reference/react/memo

- const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

- useMemo와 달리, 값이 아니라 컴포넌트를 메모이제이션 할 때 사용

const MemoComponent = memo(
	return (<>안녕!</>);
);

 

 

 

 

 

+ Recent posts