프로젝트 캠프 : React 2기/학습 회고

[유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 사전직무교육 7일차 학습 회고 ( 동시성 모드 훅, 리액트 메모제이션, useContext )

지넛 2024. 8. 27. 09:35

 

2024.08.26(월) - 7일 차

 

 

 

 

React 18 에서 동시성 모드( concurrent mode )를 제공하게 되었습니다.

 

 

일반적으로 리액트는 작업을 순차적으로 하게 되는데

코드 상에서 무거운 렌더링 작업을 하게 되면 그 동안 메인 스레드가 거기에 할당 되어 다음 작업을 처리하지 못하게 됩니다. 따라서 유저와 상호작용이 불가하거나 느려지게 됩니다.

 

 

이러한 문제를 해결하기 위해 React 18 에 등장한 동시성 모드를 통해서

무조건 순차적으로 처리하는 것이 아닌 각 작업에

'우선순위'를 지정하여 동시에 처리할 수 있게 합니다.

( 우선순위 높은 이벤트 먼저 -> 이후에 우선순위가 낮은 이벤트 ) 

 

 

이를 위해 나온 리액트 훅 2가지 useTransition과 useDeferredValue를 알아보겠습니다.

 

 

useTransition

useTransition의 기본 타입은 다음과 같습니다

const [isPending, startTransition] = useTransition()
// isPending -> 상태변화를 나타내는 boolean 타입
// true -> 로딩중, false -> 완료
// startTransition은 상태변화를 일으키는 콜백함수를 전달받고
// 해당 콜백함수는 낮은 우선순위로 실행

 

 

 

아래와 같이  startTransition은 상태변화를 일으키는 콜백함수를 전달받고 해당 콜백함수는

낮은 우선순위로 실행되게 됩니다.

const [isPending, startTransition] = useTransition();

startTransition(() => {
      const i: number[] = [];
      for (let j = 0; j < 10000; j++) {
        i.push(j);
      }
      setNumArray((d) => [...d, ...i]);
    });

 

 


 

 

useDeferredValue

useDeferredValue의 기본 타입은 다음과 같습니다

const deferredDummy = useDeferredValue(value);

 

 

 

useTransition이 함수 대상이었다면 useDeferredValue는 값 대상입니다.

다시 말해 useDeferredValue는 상태 값 변화에 우선순위를 낮게 지정하기 위한 리액트 훅 입니다.

아래 코드에서 dummy값의 변경으로 렉이 걸리므로 

useDeferredValue로 dummy를 래핑하면 다른 상태 변화가 전부 처리된 후 가장 나중에 변경이 됩니다.

 const deferredDummy = useDeferredValue(dummy);
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setInput(e.target.value);
    const i: number[] = [];
    for (let j = 0; j < 20000; j++) {
      i.push(j);
    }
    setDummy((d) => [...d, ...i]);
  };

 

 

 


 

 

 

메모리제이션

 

 

 

 

 

 

 

컴포넌트 메모이제이션 - React.memo 

해체 조건 props로 넘어온게 변경되는거

함수 메모이제이션 -useCallback

의존성 배열에 들어간 데이터가 변경되었을때 빈배열 두면 영원히 해체 ㄴㄴ 종료될때까지

 

useState는 자동으로 메모이제이션처럼 되서 메모이제이션 대상이 되지도 않음

해제가 될땐? useState의 데이터가 변경이 생길때 

 

useMemo 값을 메모이제이션할 때 쓰는거

 

 

중간 부분에 메모이제이션 해도 메모제이션이 잘 되는거처럼 보이긴함

but 인터셉트해서 하는건 옳지않다 (안티패턴!)

 

하위 컴포넌트가 없어서 반복 호출이 염려 되어 최적화를 할필요가 업슬수도 잇음

배보다 배꼽이 ㅇㅇ

 

 

props 드릴링~

 

하지만 dfs가 크다면 구조의 한계

 

그래서 나온게 context api

 

context에 다 정의를 해놓고 

함수던 변수던 그 데이터가 필요한 컴포넌트가 context에 직접 요청을하는 방식

 

 

context api에 영향을 받을 컴포넌트 범위를 정해줘야됨

context의 제공영역을 설정

 

createContext를 통해 해줌 아래 코드 ㅇㅇ

이렇게 되면 contextother은 context 제공영역에서 빠지고 contextcount 가지 애들만

제공받아 사용할 수 있다

 

  const {count} = useContext(CounterContext)!;

 

이런식으로 사용한다 뒤에 !는 null 아니라는 걸 보장해주는 문자

 

createContext는 초기값 무조건 줘야됨

	type CounterContextType =  { count: number; increment: () => void; decrement: () => void; }

	export const CounterContext = createContext<CounterContextType | null>(null);
      
      
      
      <CounterContext.Provider value={{count, increment, decrement}}>
        <ContextCount 
          count={count} 
          increment={increment} 
          decrement={decrement}/>
      </CounterContext.Provider>
      
      <ContextOther/>
      
      
      import { useContext } from 'react';
import { CounterContext } from './ContextPage';

const ContextDisplay = () => {
  const {count} = useContext(CounterContext)!;
  return (
    <>
      <div>ContextDisplay Component</div>
      <h1>Count: {count}</h1>
    </>
  )
}
export default ContextDisplay;

 

불러올때 useContext 를 쓴다

 

 

1. 컨텍스트 공급자 생성 createContext() 함수로

2. 공급자 범위 지정  -> 생성한 공급자 컴포넌트의  콘텐츠로 공급할 다른  컴포넌트를 감싼다

3. 공급할 데이터를 value 속성으로 지정해줌

4. 공급한 데이터를 useContext(공급자 컴포넌트 객체): 받아서 사용한다.

 

근데 사실 이게 안티 패턴 이라신다! 대박

그 문제는 공급자 역할을 어떤 하나의 컴포넌트가 담당하고 있다는거?

컨텍스트는 정의하기위한 관련 코드가 한 컴포넌트에 작성되어 있따는거

 

컴포넌트와 컨텍스트 공급 코드가 너무 모호해짐 이제 실무에 어떻게 쓰는지 알아보자

 

컨텍스트를 별도의 파일로 분리해서 정의한다!

 

그 파일

CounterProvider.tsx

import React, { createContext, useState } from 'react';

type CounterContextType =  { 
  count: number; 
  increment: () => void; 
  decrement: () => void; }

export const CounterContext = 
createContext<CounterContextType | null>(null);


export const CounterProvider = ({children,}:{
  children: React.ReactNode;
})=>{
  const [count, setCount] = useState(0);

  const increment = ()=>{
    setCount(()=>count+1)
  }

  const decrement = ()=>{
    setCount(()=>count-1)
  }

  return (
    <CounterContext.Provider value={{count, increment, decrement}}>
      {children}
    </CounterContext.Provider>
  )
}

 

 

contextapi를 메모이제이션 하려면 공급자 분리를 해야해!

아래처럼

type CounterContextType =  { 
  count: number; 
}

type CounterContextFnType =  { 
  increment: () => void; 
  decrement: () => void; 
}

export const CounterContext = 
createContext<CounterContextType | null>(null); //count

export const CounterContxtFn =
createContext<CounterContextFnType | null>(null); //increment decrement


// 감싸주는 Provider도 수정
    <CounterContxtFn.Provider value={increment, decrement}>
      <CounterContext.Provider value={{count}}>
        {children}
      </CounterContext.Provider>
    </CounterContxtFn.Provider>

 

 

 

 

 

 

—————————————————————

본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 과정(B-log) 리뷰로 작성 되었습니다. #유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #리액트프로젝트 #프론트엔드개발자양성과정 #개발자교육과정