Skip to content

[피어세션 준비] 21.11.04

J203_채호경 edited this page Nov 5, 2021 · 4 revisions

프로젝트를 개발하면서 겪은 어려운 점 및 해결방법

  • 테트리스 회전 알고리즘 구현 (월요일 밤샘)
    • SRS(Super Rotation System)
  • 블록이 떨어질 때 Freeze 되기까지 판정 (화요일 밤샘)
    • 블록이 바닥인지 검사하고 0.5초의 딜레이 동안 움직이지 않으면 Freeze 되게 구현
  • 게임 오버 판정 (수요일 밤샘)
    • Board 배열에 높이가 4인 부분부터 블록을 생성하고, 블록이 높이가 0 ~ 3인 위치에 쌓이면 게임 오버
    • 블록을 생성할 때, 이미 생성된 위치에 블록이 존재한다면 게임 오버
  • Oauth 제공 플랫폼간 인증 방식의 차이 존재
    • 현재는 하드코딩으로 대응하고 있습니다.
    • Higher-Order Components 도입을 통해 해결하려고 합니다.
  • 상태관리 라이브러리에 대한 전반적인 경험 부족
    • Redux 및 Redux Toolkit 라이브러리에 대한 이해
  • 리액트 렌더링 최적화에 대한 이해도 부족
  • Typescript....
    • any....

기술적인 내용

  • Redux Toolkit

당신의 createSlice()가 내포하는 것

Redux


4 개의 함수와 5개의 메서드로 이루어진 간단한 상태관리 라이브러리입니다.

  • createStore
    • getState()
    • dispatch(action)
    • subscribe(listener)
    • replaceReducer(nextReducer)
  • compbineReducers
  • appplyMiddleware
  • bindActionCreators
  • compose

주요 개념은 다음과 같습니다.

하나의 Store 객체에 상태를 저장하고 있고, 이를 수정하기 위해서는 항상 reducer를 통해서만 수정이 가능합니다. 이때 reducer에게 어떤 방식으로 업데이트 할 것인지 명시하는 방법으로 action 객체를 전달하는데, 이를 전달하는 과정을 dispatch라고 합니다.

action

{
    'type' : 'ADD_MENU',
    'payload': {}
}

reducer

reducerExample (state = {}, action) {
    switch(action.type) {
        case 'A':
            return {...state};
        case 'B':
            return  {...state};
        default
            return state;
    }
}
// switch-case, if/else , object 키 값 기준으로 매칭 등의 방법이 있을 수 있습니다.
// redux toolkit 에서는 마지막 방법을 사용하고 있습니다.
  1. createStore(reducer, initalState, enhancer) - store 객체를 반환합니다.

    initialState 를 사용할 수도 있지만, 각 reducer의 state parameter에 대해 ES6 의 default parameter 문법을 활용해야 여러개의 reducer를 활용할 경우 더 적합한 방법입니다.

    • getState() : 현재 상태 트리 반환
    • dispatch(action) : 상태 변경을 일으키는 유일한 방법으로 action을 보냅니다.
    • subscribe(listener) : 상태 변경에 대한 리스너를 추가합니다.
    • replaceReducer(nextReducer) : store에 등록된 reducer를 새로운 reducer로 교체합니다. code splitting의 상황에서 사용됩니다.
  2. combineReducers({reducer1, reducer2, ...}) - helper function입니다.

    객체 안에 여러개의 reducer를 담아서 인자로 전달하면 그 reducer들을 모두 실행해서 새로운 객체를 만드는 새로운 reducer를 반환합니다.

  3. compose(...fns) - helper function입니다.

    익숙한 그 compose 맞습니다. 대부분 함수형 프로그래밍 라이브러리에서 제공하는 함수 합성 유틸리티입니다. 오른쪽에서부터 왼쪽으로 각 함수의 반환값을 인자로 받는 하나의 함수로 조합합니다.

  4. bindActionCreators({actionCreator1, actionCreator2, ...}, dispatch) - helper function입니다.

    action 객체를 생성해서 store.dispatch()의 인자로 전달하는 기존의 과정을 한단계로 합치기 위한 함수입니다. actionCreator - 인자를 전달받아 원하는 형태의 action 객체를 생성하는 함수

  5. applyMiddleware

    enhancer - 리액트에서 고차 컴포넌트와 같은 개념 저장소 생성자를 전달받아 기능이 추가된 새로운 저장소 생산자를 반환합니다.

    applyMiddleware는 enhancer를 반환합니다. 주어진 middleware가 적용되어있습니다.

    각 미들웨어는 (store) => (next) => (action) 형태로 선언됩니다.

react-redux


react에서 사용하기 위해서는 내부적으로 contextAPI를 사용해야합니다. redux store를 하나의 context로 제공하고, 이를 활용하는 render 트리 내부의 다양한 컴포넌트들이 store의 상태 변화를 구독해야합니다. 이러한 내부적인 연결API를 제공하는 라이브러리가 react-redux이고 크게 두가지 방법이 존재합니다. Hook 기반의 api와 HoC로 접근하는 connectAPI가 있고, 이 둘은 어느 하나가 절대적인 우위를 가지지 않고 서로 장단점이 분명합니다.

  1. hookAPI

useSelector(state => state)로 store의 상태를 가져올 수 있습니다. useDispatch() 로 dispatch 함수를 직접 사용할 수 있습니다.

/* use-actions */
import { bindActionCreators } from "redux";
import { useDispatch } from "react-redux";
import { useMemo } from "react";

export function useActions(actions) {
const dispatch = useDispatch();
return useMemo(() => {
        return bindActionCreators(actions, dispatch);
    }, [actions, dispatch]);
}
/* use-counter */
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { decrement, increment, set } from "./actions";
import { useActions } from "./use-actions";

export const useCounter = () => {
const count = useSelector((state) => state.count);
const actions = useActions({ increment, decrement, set });
return useMemo(() => {
        return { count, ...actions };
    }, [count, actions]);
};
import { SetCounter } from "./SetCounter";
import { useCounter } from "./use-counter";

export const Counter = () => {
const incident = "Incident";
const { count, increment, decrement, set } = useCounter(); /* 🌝 */

return (
        <main className="Counter">
        <h1>Days Since Last {incident}</h1>
        <p className="count">{count}</p>
        <section className="controls">
            <button onClick={() => increment()}>Increment</button>
            <button onClick={() => set(0)}>Reset</button>
            <button onClick={() => decrement()}>Decrement</button>
        </section>
        <SetCounter />
        </main>
    );
};

export default Counter;

구현이 간단하다는 장점이 있지만 UI 컴포넌트에 서비스 로직이 합쳐지기 때문에 비교적 테스트하기 어렵고 디자인 시스템으로 UI layer를 별도로 분리해서 관리하기 힘들다는 단점이 있습니다.

  1. connectAPI

HoC 패턴의 connect() 함수를 통해 스토어의 상태를 Presentational Component에 주입하여 사용할 수 있습니다.

import { connect } from "react-redux";
import { MenuItems } from "../components/MenuItems";

const mapStateToProps = (state) => {
  return {
    items: state.items,
  };
};

export const MenuItemsContainer = connect(mapStateToProps)(MenuItems);

마찬가지로 dispatch 함수를 props로 연결해서 제공합니다.

import { connect } from "react-redux";
import { NewItemForm } from "../components/NewItemForm";
import { addNewItem } from "../store/items/reducer";

const mapDispatchToProps = (dispatch) => {
  return {
    onSubmit: (name, price) => dispatch(addNewItem(name, price)),
  };
};

export const NewItemFormContainer = connect(
  null,
  mapDispatchToProps
)(NewItemForm);

redux의 bindActionCreators 함수 적용 예시

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators(
    {
      onSubmit: addNewItem,
    },
    dispatch
  );
};

비교적으로 구현할 내용의 분량이 있고, Container Component와 Presentational Component로 분리해야하는 단점이 있지만, 서비스 로직과 UI Layer를 분리하여 디자인 시스템에 적용하거나 테스트하기 좋은 컴포넌트라는 장점이 있습니다.

Redux Toolkit


Clone this wiki locally