본문 바로가기
TIL

상태관리

by 은지:) 2023. 3. 6.
728x90
반응형

 

 

useReducer

 

 

 

간단한 상태일 경우엔 useState만을 쓰는게 간편하지만

관리할 상태가 많고 서로 의존하고 있고 조작할 동작이 여러 개일시 useReducer를 쓴다

 

상태가 변하는 로직을 reducer에 몰아넣고

상태 간의 관계를 case로 보여주기 때문에 한번에 파악하기 용이하다

 

 

 

 

 

예시

 

 

기존 코드

 

import { useState } from 'react';

const Reducer = () => {
	const [num, setNum] = useState(0);

	const plus = () => {
		setNum(prev => prev + 1);
	};
	const minus = () => {
		setNum(prev => prev - 1);
	};

	return (
		<>
			<h1>{num}</h1>
			<button onClick={plus}>더하기</button>
			<button onClick={minus}>빼기</button>
		</>
	);
};

export default Reducer;

 

 

 

적용 후

 

import { useReducer } from 'react';

const Reducer = () => {
	function reducer(state: number, action: { type: string }) {
		switch (action.type) {
			case 'INCREMENT':
				return state + 1;
			case 'DECREMENT':
				return state - 1;
			default:
				return state;
			// return throw new Error("unhandled action")
		}
	}

	const [num, dispatch] = useReducer(reducer, 0);
	// 초기값 0, 액션이 발생함에 따라서 상태가 바뀜

	const plus = () => {
		dispatch({
			type: 'INCREMENT',
		});
	};
	const minus = () => {
		dispatch({
			type: 'DECREMENT',
		});
	};

	return (
		<>
			<h1>{num}</h1>
			<button onClick={plus}>더하기</button>
			<button onClick={minus}>빼기</button>
		</>
	);
};

export default Reducer;

 

 

 

단점으로는 코드량이 매우 는다는 것... 

위 코드의 경우에도 확연히 코드가 늘어났다. 무거워진 느낌...

 

 

 

 

contextAPI 

 

Reducer를 학습한 이유는 contextAPI 를 사용하며 리렌더링 이슈에 대해 고민했기 때문이다

 

app.tsx 나 index.tsx에서 상태를 모아둔 컴포넌트를 상위로 올려

컴포넌트에 state를 props drilling에 구애 받지 않도록 구현했는데

 

 

   <FileContext.Provider
      value={{
        fileUrl,
        setFileUrl,
        buttonState,
        setButtonState,
        fileList,
        setFileList,
      }}
    >
      <ModalContext.Provider
        value={{
          onModal,
          setOnModal,
          isModalUploadButton,
          setIsModalUploadButton,
          keyEventTarget,
          setKeyEventTarget,
        }}
      >
        <LoadingContext.Provider
          value={{
            loadingToogle,
            setLoadingToogle,
            mode,
            setMode,
          }}
        >
          {children}
        </LoadingContext.Provider>
      </ModalContext.Provider>
    </FileContext.Provider>

 

결국 이것도 context API 끼리 감싼 형태라 그런지

상태가 하나 변할 때마다 하위 나머지 상태들이 리렌더링된다

 

이를 위해 useReducer + dispatch + useMemo를 함께 사용하는 것이 통용적인 방법인 거 같은데

catbow-convert 는 단 하나의 페이지만 있고 후에 확장할 계획이 없어서

적용한다면 기능에 비해 매우매우 무거울 거라는 생각이 들었다

 

 

 

 

+ 이래서 recoile 이 등장했구나... ^-^

 

전역상태로 관리하기 위해 + 기업 요구사항으로 급하게 써봤지만

atom의 값이 같으면 내부적으로 반환값을 메모이즈하고 있어 캐싱된 값을 반환한다는 점은 몰랐다

도대체 selector는 언제 쓰는거지? 했는데 이제야 쬐끔 이해가 간다 ㅠㅠ

 

 

 

 

 

리액트 최적화라고 했을 때 가장 먼저 

memo 와 useCallback을 떠올리는 거 같다

 

 

 

 

 

React.memo

 

 

리액트는 굳이 새롭게 컴포넌트 함수를 호출할 필요 없이

이전에 있던 결과를 그대로 사용하는 것이 효율적이라고 판단하고

 

컴포넌트가 리렌더링이 되어야 할지 아닐지에 대한 여부를 표현할 수 있는 react.memo 함수를 제공한다

 

 

useMemo는 컴포넌트를 인자로 받아서 컴포넌트를 리턴하는 컴포넌트이다(HOC)

 

function HOC(Component) {
  /* do something */
	return <Component />
}

 

 

기본적으로 props의 변화를 이전 상태와 새로운 상태를 얕은 복사를 이용해 판단하는데

이 기본적인 비교 로직을 사용하지 않고 비교를 판단하는 로직을 직접 작성하고 싶을 때

변화를 판단하는 함수를 인자로 받을 수 있도록 설정해두었다

 

 

 

 

+

 

자바스크립트는 비교연산자를 수행할 때 해당 데이터의 메모리 주소를 통해서 비교 한다

원시형 타입은 변경시 새로운 데이터가 만들어지고 교체되는 방식이기 때문에

let a = "prev"
a = "next"

1."next"를 만들기
2. a에 할당된 "prev" => "next"로 바꿈

👆 이렇게 교체됨, 참조형(객체)타입은 가변

 

 

객체는 안의 내용물이 바뀌어도 객체를 가리키는 메모리 주소가 동일하기 때문에 비교가 어려움

또, 내용물이 같더라도 각 객체가 가르키는 메모리 주소가 다르기 때문에 두 객체는 같지 않다는 결과가 나올 수 있음

 

때문에 내용물이 같은지 판단하기 위해서는 비교하는 객체 안의 모든 property를 순회하면서 일일이 비교해야함

-> 연산 복잡도가 높아 요즘에는 객체를 수정하는 것이 아니라 새로운 객체를 만들고 교체하는 원시형 타입과 비슷한 방식을 적용함

 

 

 

Memoization

 

 

 

+ 추가중

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90
반응형

'TIL' 카테고리의 다른 글

react 왜 쓰나요  (0) 2023.03.16
react) public 폴더 src 폴더  (0) 2023.03.10
Node.js ) import/export 사용  (0) 2023.03.04
인증 인가 Session vs Token Based Authentication  (0) 2023.03.02
promise - catch / async await (try-catch)  (0) 2023.02.28

댓글