[React] 리덕스

cool_kim·2021년 7월 28일
0

React

목록 보기
8/8
post-thumbnail

📌리덕스

: 컴포넌트 코드로부터 상태관리 코드를 분리 가능
: 미들웨어 활용한 다양한 기능 추가 가능
- 강력한 미들웨어 라이브러리 (redux-saga)
- 로컬 스토리지에 데이터 저장하기 및 불러오기
: SSR시 데이터 전달 간편
: 리액트 콘텍스트보다 효율적인 렌더링 가능

context API

import React, { userContext, createContext, useReducer } from 'react';

// 컨텍스트
const AppContext = createContext({});
const DispatchContext = createContext(() => {});

// 루트 컴포넌트
export default function App() {
  const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
  return(
    <>
      <AppContext.Provider value={state}>
        <DispatchContext.Provider value={dispatch}>
          <User />
          <Product />
        </DispatchContext.Provider>
      </AppContext.Provider>
    </>
  );
}

// 리듀서
const INITIAL_STATE = {
  user: {name: 'mike'},
  produce: {name: 'iphone'},
}
function reducer(state, action) {
  switch (action.type) {
    case 'setUserName':
      return {
        ...state, 
        user: {...state.user, name: action.name}};
			default:
				return state;
  }
}
  • 하나의 context로 관리하게 되면 상태값이 하나만 바뀌어도 모두 다 렌더링 됨


리덕스

  1. 뷰에서 상태값을 변경하고 싶을 때 액션 발생
  2. 미들웨어가 액션 처리
  3. 리듀서에서 해당 액션에 의해 상태값이 변경되어 출력
  4. 스토어가 새로운 상태값을 저장
  5. 옵저버에게 데이터의 변경 사실을 알려 뷰 바꾸기

📍 뷰 → 액션 → 미들웨어 → 리듀서 → 스토어 → 뷰

✅ 액션

  • dispatch : 리액트에서 액션이 발생했다는 것을 리덕스에게 알려주는 함수
//일반적이지 않은 예
store.dispatch({ type: 'todo/ADD', title: '영화 보기', priority: 'high' });
store.dispatch({ type: 'todo/REMOVE', id: 123 });
store.dispatch({ type: 'todo/REMOVE_ALL' });

//일반적인 예
function addTodo({ title, priority }) {
	return { type: 'todo/ADD', title, priority };
}
store.dispatch(addTodo({ title: '영화 보기', priority: 'high' }));

✔️ type 속성값 : 액션 구분해줌 → 유니크 해야함
- 상수 변수로 만드는 것이 좋음
✔️ action creator : 각 액션 구조를 일관성 있게 만들기 위함



✅ 미들웨어

const myMiddleware = store => next => action => next(action);

⇒ 미들웨어의 모양

📍 상태값을 가져오는 코드

const printLog = store => next => action => {
  console.log(`prev state = ${JSON.stringify(store.getState())}`);
  const result = next(action);
  console.log(`next state = ${JSON.stringify(store.getState())}`);
  return result;
}

📍딜레이 후에 리듀서가 실행되게 하는 코드

const delayAction = store => next => action => {
	const delay = action.meta?.delay;
	if(!delay) {
		return next(action);
	}
	const timoutId = setTimeout(() => next(action), delay);
	return function cancel() {
		clearTimout(timeoutId);
	};
};

const myReducer - [state = {name: 'mike' }, action} => {
	console.log('myReducer');
	if(action.type === 'someAction') {
		return { name: 'mike2' };
	}
	return state;
};

const store = createStore(myReducer, applyMiddleware(delayAction)};
const cancle = store.dispatch({type: 'someAction', meta: {delay: 3000 }});
cancel();

export default function App() {
	return <div>실전 리액트</div>;
}

📍 로컬 스토리지에 저장해주는 코드

const saveToLocalStorage = store => next => action => {
  if (action.meta?.localStorageKey) {
    localStorage.setItem(action.meta?.loaclStorageKey, JSON.stringify(action));
  }
  return next(action);
}

const myReducer = (state = { name: 'mike' }, action) => {
  console.log('myReducer');
  switch(action.type) {
    case 'someAction':
      return {...state, name:'mike2'}
    default:
      return state;
  }
  return state;
}

const store = createStore(myReducer, applyMiddleware(saveToLocalStorage));

store.dispatch({ 
  type: 'someAction',
  title: 'asdf',
  meta: { localStorageKey: 'myKey' } 
});


✅ 리듀서

: 액션이 발생했을 때 새로운 상태값을 만드는 함수
상태값 변경 → 액션 객체와 함께 dispatch 메소드 호출

function reducer(state = INITIAL_STATE, action) {
//...
  switch (action.type) {
    case REMOVE_ALL:
      return {
        ...state,
        todos: [],
      };
    case REMOVE:
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.id),
      };
    default:
      return state;
  }
}

const INITIAL_STATE = { todos: [] }

⚠️ 반환되는 state 값은 불변객체로 관리 해야함
⇒ 이전 상태랑 다음 상태를 === 로 단순 비교가 가능함

📌 immer패키지
: 전개 연산자 대신 불변객체로 관리해주는 라이브러리

import produce from 'immer';

const person = { name: 'mike', age: 22 };
const newPerson = produce(person, draft => {
  draft.age = 32
})

//다른 파일내의 코드
function reducer(state = INITIAL_STATE, action) {
  return produce(state, draft => {
    switch (action.type) {
      case SET_SELECTED_PEOPLE:
        draft.selectedPeople = draft.peopleList.find(
          item => item.id === action.id,
        );
        break;
      case EDIT_PEOPLE_NAME:
        const people = draft.peopleList.find(
          item => item.id === action.id,
        );
        people.name = action.name;
        break;
    }
  })
}

⚠️ 리듀서 코드 작성시 주의해야할 점

  1. 객체를 가르킬 시 객체의 레퍼런스가 아닌 고유한 아이디 값 이용
  2. 리듀서는 순수 함수로 작성해야함(입력이 같을 때 같은 출력을 해야함, 부수효과 없어야 함)


🔸 리듀서 생성함수

📍createReducer사용

import produce from 'immer';

function createReducer(initialState, handlerMap) {
  return function(state = initialState, action) {
    return produce(state, draft => {
      const handler = handlerMap[action.type];
      if (handler) {
        handler(draft, action);
      }
    })
  }
}

const reducer = createReducer(INITIAL_STATE, {
  [ADD]: (state, action) => state.todos.push(action.todo),
  [REMOVE_ALL]: state => (state.todos = []),
  [REMOVE]: (state, action) => state.todos.filter(todo => todo.id !== action.id),
});


🔸 스토어 생성

📍createStore사용

const store = createStore(reducer);

let prevState;
store.subscribe(() => {
  const state = store.getState();
  if (state === prevState) {
    console.log('상태값 같음');
  } else {
    console.log('상태값 변경됨');
  }
  prevState = state;
});
profile
FE developer

0개의 댓글