본문 바로가기
Frontend

Redux in React

by yongmin.Lee 2020. 10. 24.

0. 작업환경설정

1. UI 구성

2. 프레젠테이셔널 컴포넌트 생성

3. 리덕스 생성

4. 리액트 앱에 리덕스 적용

5. 컨테이너 컴포넌트 생성

6. redux-actions라이브러리 적용

 

 

  0. 작업환경설정

$ yarn create react-app react-redux-tutorial
$ cd react-redux-tutorial
$ yarn add redux react-redux
// redux 라이브러리 : createStore함수를 사용하여 스토어생성
// react-redux 라이브러리 : connect함수와 Provider컴포넌트를 사용하여 리액트에서 리덕스 관련작업 처리

 

 

1. UI 구성

리덕스를 사용한 리액트 애플리케이션 UI 구조

  • 프레젠테이셔널 컴포넌트 : 상태관리가 이루어지지 않고, 그저 props를 받아 와서 화면에 UI를 보여 주기만 하는 컴포넌트
  • 컨테이너 컴포넌트 : 리덕스와 연동되어 있는 컴포넌트. 리덕스로 부터 상태를 받아오기도 하고 리덕스 스토어에 액션을 디스패치 함.

 

2. 프레젠테이셔널 컴포넌트 생성

// src/components/Todos.js

import React from 'react';

const TodoItem = ({ todo, onToggle, onRemove }) => {
  return (
    <div>
      <input type="checkbox" />
      <span>text</span>
      <button>remove</button>
    </div>
  );
};

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

  return (
    <>
      <form onSubmit={onSubmit}>
        <input />
        <button type="submit">register</button>
      </form>
      <div>
        <TodoItem />
        <TodoItem />
        <TodoItem />
        <TodoItem />
      </div>
    </>
  );
};

export default Todos;

 

// src/App.js

import React from 'react';
import Todos from './components/Todos';
import './App.css';

function App() {
  return (
    <div>
      <Todos />
    </div>
  );
}

export default App;

 

3. 리덕스 생성

  • 일반적인 디렉토리 구조 : actions, constatns, reducers 3개의 디렉토리를 만들고 그안에 기능별로 파일을 하나씩 만든다
  • Ducks패턴 : 액션타입, 액션생성함수, 리듀서 모두를 '모듈'이라는 하나의 파일에 몰아서 작성하는 방식
// 모듈 생성
// src/modules/Todos.js : Ducks패턴

//action type
const CHANGE_INPUT = 'todos/CHANGE_INPUT';
const INSERT = 'todos/INSERT';
const TOGGLE = 'todos/TOGGLE';
const REMOVE = 'todos/REMOVE';

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

let id = 3; //insert가 호출될 때마다 1씩 증가
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,
});

// initial state
const initialState = {
  input: '',
  todos: [
    {
      id: 1,
      text: 'todo job 1',
      done: true,
    },
    {
      id: 2,
      text: 'todo job2',
      done: false,
    },
  ],
};

//reducer
function 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), //concat : 배열에 원소추가
      };
    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), // filter의 파라미터로 전달된 함수의 조건을 만족하는 요소들로만 구성된 새로운 배열 반환
      };
    default:
      return state;
  }
}

export default todos;

 

// 루트 리듀서 생성
// src/modules/index.js

import { combineReducers } from 'redux';
import Todos from './Todos';

// 스토어를 만들 때는 리듀서를 하나만 사용해야 하므로
// 리듀서가 여러개인 경우 combinereducers함수를 통해 하나로 묶는다.
const rootReducer = combineReducers({
  Todos,
});

export default rootReducer;

 

4. 리액트 앱에 리덕스 적용

// 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 { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';

const store = createStore(rootReducer);	// 스토어 생성

// Provider 컴포넌트로 리액트 프로젝트에 리덕스 적용
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();

 

5. 컨테이너 컴포넌트 생성

  • 컨테이너 컴포넌트와 리덕스를 연동하려면 react-redux에서 제공하는 connect함수를 사용
    => connect( mapStateToProps, mapDispatchToProps)(연동할컴포넌트)
    • mapStateToProps : 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위한 함수. 현재 스토어 안의 state를 파라미터로 받는다
    • mapDispatchToProps : store의 내장함수 dispatch를 파라미터로 받아 이를 이용하여 액션생성함수를 컴포넌트의 props로 넘겨주기 위한 함수. 액션생성함수를 객체형태로 전달만 해도 connect 함수가 자동bindActionCreators 작업을 수행하므로 dispatch를 파라미터로 받지 않아도 된다.
    • mapStateToProps  mapDispatchToProps 에서 반환하는 객체 내부의 값들은 컴포넌트의 props로 전달된다.
// src/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}
    />
  );
};

const mapStateToProps = state => {
  return {
    input: state.todos.input,
    todos: state.todos.todos,
  };
};

// dispatch를 파라미터로 받지 않고, 액션생성함수를 객체형태로 전달만 해도
// connect 함수가 자동bindActionCreators 작업을 수행한다.
const mapDispatchToProps = {
  changeInput,
  insert,
  toggle,
  remove,
};

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

 

 

// src/App.js

import React from 'react';
import TodosContainer from './containers/TodosContainer';
import './App.css';

function App() {
  return (
    <div>
      <TodosContainer />
    </div>
  );
}

export default App;

 

// src/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}
      />
      <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={() => onRemove(todo.id)}>remove</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 (
    <>
      <form onSubmit={onSubmit}>
        <input value={input} onChange={onChange} />
        <button type="submit">register</button>
      </form>
      <div>
        {todos.map(todo => (
          <TodoItem
            todo={todo}
            key={todo.id}
            onToggle={onToggle}
            onRemove={onRemove}
          />
        ))}
      </div>
    </>
  );
};

export default Todos;

 

6. redux-actions 라이브러리 적용

  • 액션생성함수, 리듀서를 작성할 때 redux-actions 라이브러리 활용하여 리액트 앱에서 리덕스를 훨씬 편하게 사용할 수 있다.
  • createAction : 매번 객체를 직접 만들어 줄 필요 없이 간단하게 액션 생성 함수 선언 가능
  • handleAction : 첫 번째 파라미터에는 각 액션에 대한 업데이트 함수, 두 번째 파라미터에는 초기 상태 전달.

 



'Frontend' 카테고리의 다른 글

1. basic react  (0) 2020.10.24
0. React intro  (0) 2020.10.24
Redux  (0) 2020.10.24
middleware  (0) 2020.10.24
Redux Life Cycle  (0) 2020.10.24