728x90

overview 

 리덕스의 action 과 reducer를 간단하게 정의해 놓았습니다.

이제 todolist 등록하고, delete하는 기능을 적용하기 위해  reducer를 수정해 줍니다.

// src\Redux\TodoList\index.js

const CREATE = "todolist/ADD";
const READ = "todolist/READ";
const UPDATE = "todolist/UPDATE";
const DELETE = "todolist/DELETE";

export const onCreate = (todoItem) => {
  return {
    type: CREATE,
    item: todoItem,
  };
};
export const onRead = () => {
  return {
    type: READ,
  };
};
export const onUpdate = () => {
  return {
    type: UPDATE,
  };
};
export const onDelete = (idx) => {
  return {
    type: DELETE,
    idx: idx,
  };
};

const initialState = [
  {
    id: 1,
    title: "블로그 작성",
    contents: "리액트 블로그 작성하기",
    done: false,
    regDate: new Date(2022, 8, 10, 11, 12, 24),
  },
  {
    id: 2,
    title: "스터디 참가",
    contents: "리액트 스터디 참가",
    done: true,
    regDate: new Date(2022, 8, 10, 11, 13, 24),
  },
  {
    id: 3,
    title: "이메일 보내기",
    contents: "고객사에게 이메일 보내기",
    done: false,
    regDate: new Date(2022, 8, 10, 11, 14, 24),
  },
];

export const TodoListReducer = (state = initialState, action) => {
  switch (action.type) {
    case CREATE:
      action.item = {
        ...action.item,
        regDate: new Date(),
        id: state.length + 1,
      };
      return [...state, action.item];
    case READ:
      console.log("READ");
      return state;
    case UPDATE:
      console.log("UPDATE");
      return state;
    case DELETE:
      state = state.filter((s, i) => i !== action.idx);
      return state;
    default:
      return state;
  }
};

create 액션 함수는 todolist에 들어가는 객체를 파라미터로 받아옵니다.

delete액션 함수는 객체의 인덱스를 파라미터로 받아 옵니다.

reducer에서는

create일때 받아온 매개변수 = todo 객체에 생성일자 regDate를 더하여 state에 추가 합니다.

delete일때 받아온 매개변수 = index 를 이용해 state에서 해당 index를 filter로 걸러줍니다.

생성 도 해보고 삭제도 해보고!!

redux 툴로 확인해본 결과입니다.

728x90
728x90

overview

등록된 store를 통해서 todolist의 상태를 관리할수 있습니다.

스토어에 등록된 상태를 가져오는 hooks인 useSelect와 상태 변화를 일으키는 hooks인 useDispatch를 사용해 보도록 합니다.

 

1. dummy data

UI를 만들기 위해 작성했던 dummy data를 우선 Redux-TodoList-index.js의 initialState로 옮겨 주고 TodoListReducer의 state의 default값으로 지정해 주겠습니다.

 

// src\Redux\TodoList\index.js

const CREATE = "todolist/ADD";
const READ = "todolist/READ";
const UPDATE = "todolist/UPDATE";
const DELETE = "todolist/DELETE";

export const onCreate = () => {
  return {
    type: CREATE,
  };
};
export const onRead = () => {
  return {
    type: READ,
  };
};
export const onUpdate = () => {
  return {
    type: UPDATE,
  };
};
export const onDelete = () => {
  return {
    type: DELETE,
  };
};

const initialState = [
  {
    id: 1,
    title: "블로그 작성",
    contents: "리액트 블로그 작성하기",
    done: false,
    regDate: new Date(2022, 8, 10, 11, 12, 24),
  },
  {
    id: 2,
    title: "스터디 참가",
    contents: "리액트 스터디 참가",
    done: true,
    regDate: new Date(2022, 8, 10, 11, 13, 24),
  },
  {
    id: 3,
    title: "이메일 보내기",
    contents: "고객사에게 이메일 보내기",
    done: false,
    regDate: new Date(2022, 8, 10, 11, 14, 24),
  },
];

export const TodoListReducer = (state = initialState, action) => {
  switch (action.type) {
    case CREATE:
      console.log("CREATE");
      return state;
    case READ:
      console.log("READ");
      return state;
    case UPDATE:
      console.log("UPDATE");
      return state;
    case DELETE:
      console.log("DELETE");
      return state;
    default:
      return state;
  }
};

2. useSelector

react-redux 에서 제공하는 useSelector를 통해서 현재 리덕스 스토에서 등록된 TodoList의 상태값을 가지고 옵니다.

 

// src/project/t/TodoList/index.js

import React from "react";

import Meta from "@components/meta/Meta";

import { meta } from "@project/t/TodoList/meta";
import { dummy } from "@project/t/TodoList/data";
import { dateToString } from "@project/t/TodoList/components/date";

import { Wrapper, Title, HLine, Section } from "@resources/globalStyle";

import { useSelector } from "react-redux";
const TodoList = ({}) => {

  
  const TodoListData = useSelector((state) => state.TodoListReducer);
  console.log(TodoListData);


  return (
    <Wrapper>
      <Meta data={meta} />
      <Section>
        <Title>Todo List For Self</Title>
        <HLine />
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            width: "80%",
            gap: "16px",
            margin: "20px 0 0 0",
          }}
        >
          <div>
            <input type="text" />
            <button>등록</button>
          </div>
          {dummy.map((d) => {
            return (
              <div style={{ display: "flex", gap: "8px" }}>
                <div>{d.id}</div>
                <div>{d.title}</div>
                <div>{d.contents}</div>
                <div>{dateToString(d.regDate)}</div>
                <input type="checkbox" defaultChecked={d.done} />
                <button>수장</button>
                <button>삭제</button>
              </div>
            );
          })}
        </div>
      </Section>
    </Wrapper>
  );
};

export default TodoList;

데이터가 잘 불어 와 졌습니다.

 

3. useDispatch

useDispatch hooks를 통해서 dispatch를생성하고, EventHandler를 생성합니다.

// src/project/t/TodoList/index.js

import React, { useCallback } from "react";

import Meta from "@components/meta/Meta";

import { meta } from "@project/t/TodoList/meta";
import { dateToString } from "@project/t/TodoList/components/date";

import { Wrapper, Title, HLine, Section } from "@resources/globalStyle";

import { useSelector, useDispatch } from "react-redux";
import { onCreate, onUpdate, onDelete } from "@Redux/TodoList";
const TodoList = ({}) => {
  const TodoListData = useSelector((state) => state.TodoListReducer);
  const dispatch = useDispatch();
  const onItemAdd = useCallback(() => {
    dispatch(onCreate());
  }, [dispatch]);
  const onItemUpdate = useCallback(() => {
    dispatch(onUpdate());
  }, [dispatch]);
  const onItemDelete = useCallback(() => {
    dispatch(onDelete());
  }, [dispatch]);

  return (
    <Wrapper>
      <Meta data={meta} />
      <Section>
        <Title>Todo List For Self</Title>
        <HLine />
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            width: "80%",
            gap: "16px",
            margin: "20px 0 0 0",
          }}
        >
          <div>
            <input type="text" />
            <button>등록</button>
          </div>
          {TodoListData.map((d) => {
            return (
              <div style={{ display: "flex", gap: "8px" }}>
                <div>{d.id}</div>
                <div>{d.title}</div>
                <div>{d.contents}</div>
                <div>{dateToString(d.regDate)}</div>
                <input type="checkbox" defaultChecked={d.done} />
                <button>수장</button>
                <button>삭제</button>
              </div>
            );
          })}
        </div>
      </Section>
    </Wrapper>
  );
};

export default TodoList;

이제 button 마다 onClick Event에 함수를 적용합니다.

// src/project/t/TodoList/index.js

import React, { useCallback } from "react";

import Meta from "@components/meta/Meta";

import { meta } from "@project/t/TodoList/meta";
import { dateToString } from "@project/t/TodoList/components/date";

import { Wrapper, Title, HLine, Section } from "@resources/globalStyle";

import { useSelector, useDispatch } from "react-redux";
import { onCreate, onUpdate, onDelete } from "@Redux/TodoList";
const TodoList = ({}) => {
  const TodoListData = useSelector((state) => state.TodoListReducer);
  const dispatch = useDispatch();
  const onItemAdd = useCallback(() => {
    dispatch(onCreate());
  }, [dispatch]);
  const onItemUpdate = useCallback(() => {
    dispatch(onUpdate());
  }, [dispatch]);
  const onItemDelete = useCallback(() => {
    dispatch(onDelete());
  }, [dispatch]);

  return (
    <Wrapper>
      <Meta data={meta} />
      <Section>
        <Title>Todo List For Self</Title>
        <HLine />
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            width: "80%",
            gap: "16px",
            margin: "20px 0 0 0",
          }}
        >
          <div>
            <input type="text" />
            <button onClick={onItemAdd}>등록</button>
          </div>
          {TodoListData.map((d) => {
            return (
              <div key={Math.random()} style={{ display: "flex", gap: "8px" }}>
                <div>{d.id}</div>
                <div>{d.title}</div>
                <div>{d.contents}</div>
                <div>{dateToString(d.regDate)}</div>
                <input type="checkbox" defaultChecked={d.done} />
                <button onClick={onItemUpdate}>수정</button>
                <button onClick={onItemDelete}>삭제</button>
              </div>
            );
          })}
        </div>
      </Section>
    </Wrapper>
  );
};

export default TodoList;

 

4. 버튼을 클릭해보자!

등록 , 수정,삭제 버튼을 클릭하면 console에 잘 찍히는 것을 확인할 수 있습니다.

 

이제 다음에는 console부분에 실질적인 todolist의 item을 등록 수정 삭제 해보는 기능을 추가해 보겠습니다.

728x90
728x90

Overview

Redux 는 기본적으로 액션 - 리듀서 - 스토어 - 상태 로 이루어져 있다고 이해하고 있다.
추가적으로 구독이나 디스패치, 미들웨어 등이 있다.
중요한 개념은 스토어에 등록된 리듀서에서 디스패치된 액션 타입 별로 상태를 변화 시켜 주는것으로 이해된다.

나는 Redux 디렉토리를 만들고 그 안에 Redux를 사용하는 프로젝트 별로 액션과 리듀서를 작성하고,
CombineReducers를 index.js에 선언에 RootReducer를 Export하는 방식으로 사용할 계획을 가지고 시작한다.

1. 디렉토리 및 파일 만들기


src 디렉토리에 Redux 를 디렉토리를 만들고, 그안에 TodoList 디렉토리를 만들어 준다.
각각 index.js 파일을 만들어 준다.

 

그리고 webpack.config.js 에 resolve alias에 @Redux를 추가해 주었다.

  resolve: {
    extensions: [".js", ".json", ".wasm"],
    alias: {
      "@pages": path.resolve(__dirname, "src", "pages"),
      "@resources": path.resolve(__dirname, "src", "resources"),
      "@components": path.resolve(__dirname, "src", "components"),
      "@project": path.resolve(__dirname, "src", "project"),
      "@Redux": path.resolve(__dirname, "src", "Redux"),
    },
  },

 

2. 액션과 액션 함수

 

이번 TodoList 에서는 액션-> 리듀서 순으로 작성하고, 필요한 기능들을 그때그때 추가 하는 식으로 진행을 해볼까한다.

액션은 상태 변화가 필요할때 사용되는 객체이다. 

type을 필수적으로 가지고 있어야한다.

액션함수는 이러한 액션을 만들어 주는 함수이며, 파라미터로 받는 값들을 액션 객체에 담아 줄수 있다.

가장 기본적인 CRUD 기능을 액션으로 만들어 줍니다.

각 액션 type에 들어갈 문자열을 변수에 담아 액션 함수를 만들어 줍니다.

const CREATE = "todolist/ADD";
const READ = "todolist/READ";
const UPDATE = "todolist/UPDATE";
const DELETE = "todolist/DELETE";

export const onCreate = () => {
  return {
    type: CREATE,
  };
};
export const onRead = () => {
  return {
    type: READ,
  };
};
export const onUpdate = () => {
  return {
    type: UPDATE,
  };
};
export const onDelete = () => {
  return {
    type: DELETE,
  };
};

 

 액션함수명 앞에는 on을 붙여 줍니다.

각 액션함수를 export 합니다.

 

3. 리듀서

리듀서는 상태와 액션을 파라미터로 받아 액션 타입에 맞는 변화가 일어나고, 새로운 상태값을 리턴해 줍니다.

Store에등록된 상태로 컴포넌트에서 발생한 dispatch+액션으로 상태를 변화시킵니다.

export const TodoListReducer = (state = [], action) => {
  switch (action.type) {
    case CREATE:
      console.log("CREATE");
      return state;
    case READ:
      console.log("READ");
      return state;
    case UPDATE:
      console.log("UPDATE");
      return state;
    case DELETE:
      console.log("DELETE");
      return state;
    default:
      return state;
  }
};

우선 switch 문을 통해 case에 해당하는 액션이 들어왔을때 실행하는 함수를 정의해 주는 형태로 사용됩니다. 하지만, 아직 아무런 기능에 대한 정의가 없으므로 console만 찍어 줍니다.

 

export!

 

3. CombineReducers & RootReducers

스토어는 1개만 가능 합니다.

정의된 상태나 리듀서가 많을 경우에는 CombineReducers 를 통해 하나의 Reducer로 만든후 store에 등록합니다.

import { combineReducers } from "redux";
import { TodoListReducer } from "@Redux/TodoList";

const rootReducer = combineReducers({
  TodoListReducer,
});

export default rootReducer;

위에서 만든 TodoListReducer를 넣어 rootReducer를 만들어 주고, export 합니다.

 

4. CreateStore

이제 이렇게 만든 Reducer를 createStore를 통해 store로 만들어 반영합니다.

// index.js
// modules
import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import ReactDOM from "react-dom";
import { legacy_createStore as createStore } from "redux";
import { Provider } from "react-redux";

// components
import App from "./src/App";
import rootReducer from "@Redux";


const store = createStore(rootReducer);

const rootDomElement = document.getElementById("App");
const rootRenderElement = createRoot(rootDomElement);
const rootJsxElement = (
  <BrowserRouter>
    <Provider store={store}>
      <App />
    </Provider>
  </BrowserRouter>
);

// Rendering

if (rootDomElement.hasChildNodes()) {
  ReactDOM.hydrate(rootJsxElement, rootDomElement);
} else {
  rootRenderElement.render(rootJsxElement);
}

redux에서 제공하는 createStore에 rootReducers를 넣어 store를 생성한뒤 Provider 컴포넌트의 props로 넣어주고, Provider를 App 컴포넌트의 상위에 위치 시켰습니다.

 

이제 정의되 액션을 디스패치하여 상태 변화를 일으켜 보도록하는 과정을 진행하면 될것같습니다.

728x90
728x90

Overview


리액트 관련 프로젝트에서 많이 사용하는 상태 관리 라이브러리로서 Redux(리덕스)를 사용해 보면서 정리하는 글을 작성해 보고자한다.
간단한 Todo List의 상태를 Redux로 상태 관리하는 것을 통해 정리해보고자한다.

먼저, Dummy data를 통해 초기 페이지 UI를 간단하게 만들어 본다.

Todo List UI 세팅

1. Data Structure

TodoList에 필요한 데이터는 아이디 , 제목, 내용, 생성날짜,완료 체크 이다. 추후에 추가될수도있지만, 우선은 이포맷으로 더미 데이터 3개 정도를 만들어 준다.

// ./data.js

export const dummy = [
  {
    id: 1,
    title: "블로그 작성",
    contents: "리액트 블로그 작성하기",
    done:false,
    regDate: "2022-08-10 11:12:23",
  },
  {
    id: 2,
    title: "스터디 참가",
    contents: "리액트 스터디 참가",
    done:true,
    regDate: "2022-08-10 11:13:23",
  },
  {
    id: 3,
    title: "이메일 보내기",
    contents: "고객사에게 이메일 보내기",
    done:false,
    regDate: "2022-08-10 11:14:23",
  },
];

최종적으로는 위와 같은 화면으로 나오게 됩니다.

 

2. 전체 코드 

 

// src/project/t/TodoList/index.js

import React from "react";

import Meta from "@components/meta/Meta";

import { meta } from "@project/t/TodoList/meta";
import { Section, TodoItem, TodoWrapper } from "@project/t/TodoList/style";
import { dummy } from "@project/t/TodoList/data";
import { dateToString } from "@project/t/TodoList/components/date";

import { Wrapper, Title, HLine } from "@resources/globalStyle";

const TodoList = ({}) => {
  return (
    <Wrapper>
      <Meta data={meta} />
      <Section>
        <Title>Todo List For Self</Title>
        <HLine />
        <TodoWrapper>
          {dummy.map((d) => {
            return (
              <TodoItem key={Math.random()}>
                <div>{d.id}</div>
                <div>{d.title}</div>
                <div>{d.contents}</div>
                <div>{dateToString(d.regDate)}</div>
                <input type="checkbox" defaultChecked={d.done} />
              </TodoItem>
            );
          })}
        </TodoWrapper>
      </Section>
    </Wrapper>
  );
};

export default TodoList;

 

import styled from "styled-components";
import { FlexLayout } from "@resources/globalStyle";

export const Section = styled.div`
  padding: 24px;
`;
export const TodoWrapper = styled(FlexLayout)`
  flex-direction: column;
  justify-content: center;
  gap: 16px;
  margin-top: 16px;
`;
export const TodoItem = styled(FlexLayout)`
  width: 80%;
  gap: 8px;
  border-radius: 16px;
  box-shadow: 1px 1px 1px 1px rgba(200, 200, 200, 0.3),
    -1px -1px 1px 1px rgba(150, 150, 150, 0.3);
  padding: 8px 16px;
  font-size: 1.2rem;
  align-items: center;
  cursor: pointer;
  > div:nth-child(1) {
    width: 8px;
  }
  > div:nth-child(2) {
    width: 128px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
  }
  > div:nth-child(3) {
    flex-grow: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  > div:nth-child(4) {
    width: 64px;
  }
  > div:nth-child(5) {
    width: 12px;
  }
  :hover {
    background-color: rgba(165, 165, 165, 0.2);
  }
`;
export const dateToString = (date) => {
  const y = date.getFullYear(),
    m = date.getMonth() + 1,
    d = date.getDate(),
    H = date.getHours(),
    M = date.getMinutes(),
    S = date.getSeconds();
  return `${NumPadStart(y)}-${NumPadStart(m)}-${NumPadStart(d)} ${NumPadStart(
    H
  )}:${NumPadStart(M)}:${NumPadStart(S)}`;
};

export const NumPadStart = (num) => {
  return String(num).padStart(2, "0");
};

 

728x90

+ Recent posts