본문 바로가기

FE/React & TS

redux-actions, immer, hooks(useSelector, useDispatch)를 사용한 container component 생성

$yarn add redux-actions


redux-actions: 액션 생성 함수를 더 편리하게 .

createAction 함수: 매번 객체 직접 생성할 필요 없이 간단하게 액션 생성 함수 선언 가능

 

modules/counter.js

import { createAction, handleActions } from "redux-actions";

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE'

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

const initialState = {number: 0};
const counter = handleActions(
    {
        [INCREASE] : (state, action) => ({number: state.number+1}),
        [DECREASE] : (state, action) => ({number: state.number-1}),
    },
    initialState,
);
export default counter;

 

handleActions로 리듀서 재작성

createAction으로 만든 액션 생성 함수 => action.payload라는 이름 넣어주기 

-> 기존 업데이트 로직에서도 모두 action.payload 값을 조회하여 업데이트하도록 구현 필요 

액션 생성 함수: 액션에 필요한 추가 데이터 모두 payload라는 이름으로 사용

action.id , action.todo 조회하는 대신 모두 action.payload 값을 조회하도록 리듀서 구현

 

const todos = handleActions(
    {
        [CHANGE_INPUT]: (state, action) => ({...state, input: action.payload}),
        [INSERT]: (state, action) => ({
            ...state,
            todos: state.todos.concat(action.payload),
        }),
        [TOGGLE]: (state, action) => ({
            ...state,
            todos: state.todos.map(todo =>
            todos.id === action.payload ? {...todo, done: !todo.done} : todo),
        }),
        [REMOVE]: (state, action) => ({
            ...state,
            todos: state.todos.filter(todos => todos.id !== action.payload),
        }),
    },
    initialState,
);

 

객체 비구조화 할당 문법 사용 

const todos = handleActions(
    {
        [CHANGE_INPUT]: (state, {payload: input}) => ({
            ...state, input: input}),
        [INSERT]: (state, {payload: todo}) => ({
            ...state,
            todos: state.todos.concat(action.payload),
        }),
        [TOGGLE]: (state, {payload: id}) => ({
            ...state,
            todos: state.todos.map(todo =>
            todos.id === id ? {...todo, done: !todo.done} : todo),
        }),
        [REMOVE]: (state, {payload: id}) => ({
            ...state,
            todos: state.todos.filter(todos => todos.id !== id),
        }),
    },
    initialState,
);

 

immer 

$yarn add immer

const todos = handleActions(
    {
        [CHANGE_INPUT]: (state, {payload: input}) =>
            produce(state, draft => {
                draft.input = input;
            }),
        [INSERT]: (state, {payload: todo}) =>
            produce(state, draft => {
                draft.todos.push(todo);
            }),
        [TOGGLE]: (state, {payload: id}) =>
            produce(state, draft => {
                const todo = draft.todos.find(todo => todo.id === id);
                todo.done = !todo.done;
            }),
       
        [REMOVE]: (state, {payload: id}) =>
            produce(state, draft => {
                const index = draft.todos.findIndex(todo => todo.id === id);
                draft.todos.splice(index, 1);
            }),
       
    },
    initialState,

 

useSelector

connect 사용하지 않고도 리덕스 상태 조회 가능 

const 결과 = useSelector(상태 선택 함수);

 

CounterContainer.js 

import { useSelector } from "react-redux";
const CounterContainer = () => {
    const number = useSelector(state => state.counter.number);
    return <Counter number={number} />;
};

export default CounterContainer;

counter.number 값 조회 => Counter에게 props 넘겨줌 

 

useDispatch

const dispatch = useDispatch();
dispatch({type: 'SAMPLE_ACTION'});

 

const CounterContainer = () => {
    const number = useSelector(state => state.counter.number);
    const dispatch = useDispatch();
    return <Counter
        number={number}
        onIncrease={() => dispatch(increase())}
        onDecrease={() => dispatch(decrease())}
    />;
};

 

useCallback으로 액션 디스패치 함수 감싸주기

why? 컴포넌트 성능 최적화 

const CounterContainer = () => {
    const number = useSelector(state => state.counter.number);
    const dispatch = useDispatch();
    //컴포넌트 리렌더링 될 때마다 증가, 감소함수 각각 새롭게 만들어짐.
    //useCallback을 사용하여 컴포넌트 최적화 필요 
    const onIncrease = useCallback(() => dispatch(increase()), [dispatch()]);
    const onDecrease = useCallback(() => dispatch(decrease()), [dispatch()]);
    
    return <Counter
        number={number}
        onIncrease={onIncrease()}
        onDecrease={onDecrease()}
    />;
};

 

containers/TodoContainer.js

import {bindActionCreators} from "redux";
import {useSelector, useDispatch} from "react-redux";
import {connect} from 'react-redux';
import Todos from '../components/Todos';
import {changeInput, insert, toggle, remove} from "../modules/todos";
import {useCallback} from "react";

const TodosContainer = () => {
    const {input, todos} = useSelector(({todos}) => ({
        input: todos.input,
        todos: todos.todos
    }));
    const dispatch = useDispatch();
    const onChangeInput = useCallback(input => dispatch(changeInput(input)),[
        dispatch
    ]);
    const onInsert = useCallback(text => dispatch(insert(text)),[
        dispatch
    ]);
    const onToggle = useCallback(id => dispatch(toggle(id)),[
        dispatch
    ]);
    const onRemove = useCallback(id => dispatch(remove(id)),[
        dispatch
    ]);
    return (
        <Todos
            input = {input}
            todos = {todos}
            onChangeInput={onChangeInput}
            onInsert={onInsert}
            onToggle={onToggle}
            onRemove={onRemove}
        />
    );
};

export default TodosContainer;

 

connect 함수와 hook 사용 차이점

connect 함수: 자동으로 리렌더링 방지, 성능 최적화

useSelector 사용시 -> React.memo 사용하여 추가적으로 최적화 작업 필요 

'FE > React & TS' 카테고리의 다른 글

[Socket.io] socket.emit과 socket.on  (0) 2024.03.29
[Vite+React] 깃허브 페이지 배포 이미지 경로 에러  (0) 2024.03.16
Redux를 활용한 투두리스트 구현  (0) 2023.10.10
Todo app  (0) 2023.07.21
Sass / styled-components  (0) 2023.05.16