2020. 2. 25. 17:02

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를 파라미터로 받지 않아도 된다.
    • mapStateToPropsmapDispatchToProps 에서 반환하는 객체 내부의 값들은 컴포넌트의 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 : 첫 번째 파라미터에는 각 액션에 대한 업데이트 함수, 두 번째 파라미터에는 초기 상태 전달.

 

 

 

'Javascript > Redux' 카테고리의 다른 글

#12 Redux  (0) 2020.02.25
middleware  (0) 2019.09.02
Redux Life Cycle  (0) 2019.08.29
Posted by yongminLEE