Redux - 사용


Redux 사용하기

이전 포스트에서는 Redux에서 사용하는 5가지를 실제 구현해봤습니다. (에러가 있었지만..)

yarn create react-app redux_test로 프로젝트를 생성하고 redux_test 폴더에서 yarn add redux react-redux로 redux를 사용할 준비를 합니다.

redux를 이용해 개발하면 action, constant, reducer 별로 관리하는 경우가 있습니다. 이러한 경우, 수정할 때 각각 수정해야하는 현상이 발생합니다.

이를 개선하여 하나의 폴더 즉, modules 폴더를 만들고 관리하게 됩니다.

Counter 모듈 만들기

  • 초기 상태 만들기
[modules/Counter.js]

const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

export const increase = () => ({type: INCREASE});
export const decrease = () => ({type: DECREASE});

값을 증가, 감소 시키는 초기 상태의 코드를 작성합니다. 각 type이 존재합니다.

  • reducer 만들기
[modules/Counter.js]

(생략)

const initialState = {
    number: 0
};

const counter = (state = initialState, action) => {
    switch (action.type) {
        case INCREASE:
            return {
                number: state.number + 1
            };
        case DECREASE:
            return {
                number: state.number - 1
            };
        default:
            return state;
    }
}

export default counter;

state와 aciton을 받아 해당 type에 따른 상태변화를 위한 reducer를 만듭니다.

todos 모듈 만들기

이번에는 todo 모듈을 만들어보겠습니다.

마찬가지로, 초기상태와 reducer를 만듭니다.

[modules/Todos.js]

const CHANGE_INPUT = "todos/CHANGE_INPUT";
const INSERT = "todos/INSERT";
const TOGGLE = "todos/TOGGLE";
const REMOVE = "todos/REMOVE";

export const changeInput = (input) => (
    {
        type: CHANGE_INPUT,
        input
    }
);

let id = 3;
export const insert = (text) => (
    {
        type: INSERT,
        todo: {
            id: id++,
            text,
            done: false
        }
    }
);

export const toggle = (id) => (
    {
        type: TOGGLE,
        id
    }
);

export const remove = (id) => (
    {
        type: REMOVE,
        id
    }
);

const initialState = {
    input: "",
    todos: [
        {
            id: 1,
            text: "redux test 1",
            done: true
        },
        {
            id: 2, 
            text: "redux test 2",
            done: false
        }
    ]
};

const todos = (state = initialState, action) => {
    switch(action.type) {
        case CHANGE_INPUT:
            return {
                ...state,
                input: action.input
            }
        case INSERT:
            return {
                ...state,
                todos: state.todos.concat(action.todo)
            }
        case TOGGLE:
            return {
                ...state,
                todos: state.todos.map(todo => 
                    todo.id === action.id ? {...todo, done: !todo.done} : todo
                )
            }
        case REMOVE:
            return {
                ...state,
                todos: state.todos.filter(todo => todo.id !== action.id)
            }
        default:
            return state;
    }
}

export default todos;

상위 reducer 만들기

현재 Todos.js와 Counter.js에 각각 reducer가 만들어져 있습니다. 나중에 store를 만들게 될때 reducer를 하나만 만들어야 하는데, 이때 여러개의 reducer를 하나로 묶어주는 combineReducers라는 함수를 사용하면 됩니다.

[modules/index.js]

import { combineReducers } from "redux";
import Counter from "./Counter";
import Todos from "./Todos";

const rootReducer = combineReducers(
    {
        Counter,
        Todos,
    }
);

export default rootReducer;

위 처럼 combinReducers 함수를 이용해 각 모듈을 추가해 사용할 수 있습니다.

store 만들기

이제, reducer를 하나로 묶었으니 store를 생성합니다.

[src/index.js]

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import rootReducer from './modules';
import { createStore } from "redux";
import { Provider } from "react-redux";

const store = createStore(rootReducer);

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

modules/index.js가 아닌 src/index.js에 코드를 추가합니다.

또한, 리액트 컴포넌트에서 store를 사용할 수 있도록 App 컴포넌트를 Provider 컴포넌트로 감싸주고 사용할 때, store를 props로 전달합니다.

Counter 컨테이너 컴포넌트 만들기

src 밑에 containers 디렉토리를 생성하고 그 안에 CounterContainer.js를 생성합니다.

[containers/CounterContainer.js]

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

const CounterContainer = ({ number, increase, decrease }) => {
    return (
        <Counter number={number} onIncrease={increase} onDecrease={decrease}></Counter>
    );
};

const mapStateToProps = state => (
    {
        number: state.Counter.number
    }
)

const mapDispatchToProps = dispatch => (
    {
        increase: () => {
            console.log("increase");
        },

        decrease: () => {
            console.log("decrease");
        }
    }
)

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(CounterContainer);

위 컴포넌트를 Redux와 연동하기 위해서 connect함수를 사용합니다. 여기에서 사용되는 mapStateToProps는 store 안의 상태를 컴포넌트의 props로 넘겨주는 함수이고, mapDispatchToProps는 action 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수입니다.

실제 사용하기 위해서 App.js를 아래와 같이 수정합니다.

[App.js]

import React from 'react';
import Todos from "./components/Todos";
import CounterContainer from "./containers/CounterContainer";

const App = () => {
  return (
    <div>
      <CounterContainer></CounterContainer>

      <hr/>

      <Todos></Todos>
    </div>
  )
}

export default App;

위 코드를 모두 작성하면 +1, -1 버튼을 클릭하면 콘솔창에 increase, decrease 문자가 출력되는 것을 볼 수 있습니다.

여기서, CounterContainer.js를 개선해 보겠습니다.

[containers/Countercontainer.js]

import React from "react";
import { connect } from "react-redux";
import Counter from "../components/Counter";
import { bindActionCreators } from "redux";
import { increase, decrease } from "../modules/Counter"

const CounterContainer = ({ number, increase, decrease }) => {
    return (
        <Counter number={number} onIncrease={increase} onDecrease={decrease}></Counter>
    );
};


export default connect(
    state => (
        {
            number: state.Counter.number,
        }
    ),

    dispatch => bindActionCreators(
        {
            increase,
            decrease,
        },
        dispatch,
    ),
)(CounterContainer);

기존의 mapStateToProps, mapDispatchToProps를 지우고 connect 함수에서 모두 처리해 주었습니다.

Todoscontainer 만들기

[containers/TodosContainer.js]

import React from "react";
import { connect } from "react-redux";
import { changeInput, insert, toggle, remove } from "../modules/Todos";
import Todos from "../components/Todos";

const TodosContainer = ({input, todos, changeInput, insert, toggle, remove}) => {
    return (
        <Todos
            input={input}
            todos={todos}
            onChangeInput={changeInput}
            onInsert={insert}
            onToggle={toggle}
            onRemove={remove}
        ></Todos>
    )
}

export default connect(
    ({Todos}) => (
        {
            input: Todos.input,
            todos: Todos.todos,
        }
    ),
    {
        changeInput,
        insert,
        toggle,
        remove
    }
)(TodosContainer);

위에서 작성한 todosContainer.js에서 전달하고 components/Todos.js에서 받아 props로 구현합니다.

[components/Todos.js]

import React from "react";

const TodoItem = ({ todo, onToggle, onRemove }) => {
    return (
        <div>
            <input type="checkbox"
                onClick={() => onToggle(todo.id)}
                checked={todo.done}
                readOnly={true}
            >
            </input>
            
            <span style=>{todo.text}</span>
            <button onClick={() => onRemove(todo.id)}>delete</button>
        </div>
    )
};

const Todos = ( {input, todos, onChangeInput, onInsert, onToggle, onRemove}) => {
    const onSubmit = (e) => {
        e.preventDefault();
        onInsert(input);
        onChangeInput("");
    };

    const onChange = (e) => {
        onChangeInput(e.target.value); 
    }

    return (
        <div>
            <form onSubmit={onSubmit}>
                <input value={input} onChange={onChange}></input>
                <button type="submit">Add</button>
            </form>
            <div>
                {todos.map(todo => (
                    <TodoItem
                        todo={todo}
                        key={todo.id}
                        onToggle={onToggle}
                        onRemove={onRemove}
                    >
                    </TodoItem>
                ))}
            </div>
        </div>
    )
};

export default Todos;





© 2017. by k3y6reak

Powered by k3y6reak