1. External Store

1. External Store

리액트의 외부에서 상태를 관리하는 방법 아키텍처 관점이 아니라, 스토어가 리액트 밖(External)에 있다는 의미 기존에는 useState 등의 hook을 이용해 처리

외부의 스토어에서 바뀐 상태를 화면에 업데이트하는 방뻡 forceUpdate와 같은 것이 있습니까?

  • forceUpdate는 Class component에서 사용하는 방법

예시

🔗 실습 링크

1. 기존의 useState

const [count, setCount] = useState(0);

2. useReducer 이용

const [, forceUpdate] = useReducer(reducer, {x: 0});

// Reducer 함수
function reducer(state) {
    return {...state, x: state.x + 1};
}

3. useForceUpdate 커스텀 훅 사용 ✅

export default function useForceUpdate() {
    const [, setState] = useState({});

    // useState와 forceUpdate 함수로 useReducer와 동일 효과
    const forceUpdate = () => {
        setState({}); // 새로운 객체 생성
    };

    return forceUpdate;
}

명확한 관심사의 분리

import useForceUpdate from '../hooks/useForceUpdate';

// Business Logic

const state = {
    count: 0,
};

function increase() {
    state.count += 1;
}

// UI

export default function Counter() {
    const forceUpdate = useForceUpdate();

    const handleClick = () => {
        increase();
        forceUpdate();
    };

    return (
        <div>
            <p>{state.count}</p>
            <button
                type='button'
                onClick={handleClick}
            >
                Increase
            </button>
        </div>
    );
}

React가 UI를 담당하고, 순수한 TypeScript(JavaScript)가 비즈니스 로직을 담당

장점

자주 바뀌는 UI 요소에 대한 테스트 대신, 오래 유지되는(바뀌면 치명적인) 비즈니스 로직에 대한 테스트 코드를 작성해 유지보수에 도움이 되는 테스트 코드를 치밀하게 작성할 수 있음

2. 관심사의 분리

관심사의 분리

관심사 분리(separation of concerns, SoC)는 컴퓨터 프로그램을 구별된 부분으로 분리시키는 디자인 원칙 관심사란 프로그램 코드에 영향을 미치는 정보의 집합이며, 각 부문은 개개의 관심사를 해결 관심사의 분리는 정보를 잘 정의된 인터페이스가 있는 코드 부분 안에 캡슐화(정보 숨기기의 한 수단) 시킴으로써 달성 계층화된 디자인은 관심사 분리의 다른 구현 (Ex. 표현 계층, 비즈니스 로직 계층, 데이터 접근 계층, 퍼시스턴스 계층) 관심사 분리는 추상화의 일종

React Component

컴포넌트는 한 가지 기능을 수행하는 UI 단위 리액트를 사용할 때 컴포넌트를 분리해서 사용 → 각 컴포넌트는 관심사(기능) 에 따라 분리된 것 작은 컴포넌트를 모아서 더 큰 컴포넌트를 만들어냄

3. Layered Architecture

Software Architecture Patterns - Layered Architecture

백엔드에서 많이 사용하는 방법 프론트에서도 비슷하게 접근할 수 있음

관심사를 분리하는 기준 - 설계

Layered Architecture에서는 사용자에게 가까운 것(UI), 사용자와 먼 것(비즈니스 로직) 으로 구분

🙋🏻‍♀️ 사람🔘 스위치(UI)⚙️ 모터(비즈니스 로직)🏍 바퀴(DB)

  • 사람이 스위치를 조종하면(UI)

  • 모터가 움직여서(비즈니스 로직을 이용)

  • 바퀴를 굴릴 수 있음(DB에 접근하고 처리할 수 있음)

관심사를 분리하는 기준 - 프로세스

간단하게 InputProcessOutput 3단계로 코드를 적절히 구분만 해도 코드를 이해하고 유지보수하는데 크게 도움이 됨 하나의 Output은 다시 사용자에게 Input을 요청하게 되고, 일반적인 프로그램은 다음과 같이 계속 순환하는 구조가 됨

  1. Input: 프로그램 시작

  2. Process: 프로그램 초기화

  3. Output: 사용자에게 초기 UI 보여주기

  4. Input: 사용자의 입력

  5. Process: 사용자의 입력에 따라 처리

  6. Output: 처리 결과 보여주기

  7. Input: 사용자의 또 다른 입력

  8. 반복

장점

각각은 하나의 역할(관심사)만 수행하기 때문에 복잡도가 낮아짐

MVC와 매핑

MVC로 거칠게 매핑할 경우, 다음과 같이 볼 수 있음 ⚠️ 딱 떨어지는 것은 아니고 어느 정도의 끼워 맞추기

  • Controller → Input

  • Model(비즈니스 로직을 포함) → Process

  • View → Output

🌿 MVC 패턴

모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어진 디자인 패턴 구성 요소를 3가지 역할로 구분해, 개발 프로세스에서 각각의 구성 요소에만 집중할 수 있음 재사용과 확장이 용이

4. Flux Architecture

Flux

MVC 패턴의 한계

🚨 View와 Model 간의 관계가 복잡해져 버그를 수정하거나 데이터 흐름을 알아보기 어려운 문제가 발생

'읽은 표시(mark seen)에 관해 많은 View가 있다면 이를 어떻게 처리해야 할까?'에 대한 해답 (어떤 페이지에서 메시지를 읽었는데, 다른 페이지에서는 메시지가 안 읽었다고 뜨기도 하는 문제)

View에서 일어난 것이 Model에 영향을 끼치기도 하고, 그 반대도 영향을 미치는 로직이 있는 상황 → 데이터를 일관성 있게 View에 공유하기가 어려움(실제로 뷰가 모델을 직접 건드리거나 하는 상황은 없음) ⇒ 해결방법으로 데이터가 한 방향으로만 흐르도록 하는 flux 패턴이 등장

Flux 패턴의 탄생

Facebook(현 Meta)에서 MVC 패턴의 한계에 대안으로 내세운 아키텍처 기존의 2-way binding(양방향 바인딩)을 썼을 때 생길 수 있는 Model-View의 복잡한 관계(전통적인 MVC에선 이런 상황을 지양)를 겨냥 명확히 unidirectional data flow를 강조

🤝 양방향 바인딩

양방향 바인딩은 자식 컴포넌트에서 발생하는 이벤트를 감지하는 바인딩과, 자식 컴포넌트에 데이터를 전달하는 프로퍼티 바인딩이 결합된 형태

Action, Dispatcher, Store

  1. Action → 이벤트/메시지 같은 객체, (dispatchEvent 와 비슷한 행동)

  2. Dispatcher → (여러) Store로 Action을 전달. 메시지 브로커 와 유사

  3. Store (여러 개) → 받은 Action에 따라 상태를 변경, 상태 변경을 알림

  4. View → Store를 구독하고 있다가 변화가 있으면 Store의 상태를 반영, 화면의 무언가를 건드리면 다시 action 발생

장점

단방향 데이터 흐름을 활용해 복잡한 뷰 구성을 단순화한 디자인 패턴

  • 데이터 일관성이 늘어남

  • 버그 찾기 쉬움

  • 단위 테스팅이 쉬움

Redux의 Flux패턴 적용사례

Redux의 핵심

Redux는 단일 스토어를 사용함으로써 좀 더 단순한 그림을 제안

  1. Action → State를 변화시킴

    • switch를 통해 action의 type을 판단, 각각의 행위를 결정

  2. Store → dispatch를 통해 Action을 받고, 사용자가 정의한 reducer를 통해 State를 변경(기존 상태는 놔두고 새로운 객체 생성)

  3. View → State를 반영해서 보여줌, Action을 다시 만듦

  • 💡 Action을 어떻게 표현하느냐가 사용성에 큰 차이를 만듦

    • 객체/객체가 아닌 것

    • dispatch로 전달

    • Action creator 함수 생성

  • 하지만 상태를 UI에 반영하는 방법은 모두 동일 ⇒ React 사용

3단계 프로세스와 매핑

  • Input → Action + dispatch

  • Process → reducer

  • Output → View(React)

5. useReducer

useReducer

useReducer가 기본형 setState가 내부적으로 useReducer를 사용 거의 같은 것이라고 볼 수 있음(useState의 대체 함수)

사용 방법

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

(state, action) => newState의 형태로 reducer를 받고 dispatch 메소드와 짝의 형태로 현재 state를 반환

6. useCallback

useCallback

useMemo를 조금 더 편리하게 사용할 수 있도록 만든 버전 함수를 memoization 할 수 있도록 해주는 함수이며, 의존성 배열의 동작은 동일

사용 방법

const memoizedCallback = useCallback(() => {
    doSomething(a, b);
}, [a, b]);

useCallback은 콜백함수의 메모이제이션된 값을 반환 메모이제이션된 값은 콜백의 의존성이 변경되었을 때에만 변경(useEffect의 의존성 배열과 같음)

Memoization

특정한 값을 저장해뒀다가, 이후에 해당 값이 필요할 때 새롭게 계산해서 사용하는게 아니라 저장해둔 값을 활용하는 테크닉 리액트에서는 함수 컴포넌트에서 값을 memoization 할 수 있도록 useMemo, useCallback 등의 API(메소드)를 제공함

🔎 메모이제이션은 항상 좋을까?

저장해두고 필요할 때 꺼내서 쓴다 ⇒ 효율적일 것 같다고 생각되지만, ⚠️ 무조건 쓰는게 좋은 것은 아님 새로운 값을 만드는 것과 어딘가에 이전의 값을 저장해두고 메모이제이션 함수를 호출하고, 의존성을 비교해서 가져올지 말지 여부를 판단하는 것 중 어떤 것이 비용이 더 적게 들까? 💡 새로운 값을 만드는 과정이 복잡하지 않다면, 메모이제이션을 사용하는 것은 오히려 비용이 더 많이 들 수도 있음!

어떤 상황에서 사용할까?

  • 새로운 값을 만드는 연산이 복잡할 때

  • 함수 컴포넌트의 이전 호출과, 다음 호출 간 사용하는 값의 동일성을 보장하고 싶을 때

    • 함수 컴포넌트의 호출 간 값들의 동일성을 보장하기 위해서

    • 동일성을 보장해야 하는 이유는 React.memo 와 연동해서 사용하기 위해

useNavigate, useState등도 내부적으로 메모이제이션 되어있음

Last updated