styled-components와 Redux를 사용해서 Bucketlist-app 만들기

LEEJAEJUN·2023년 5월 30일
0

react-typescript

목록 보기
6/7

5월 스터디 총정리!

  • React, TypeScript, and Redux from 독학
  • styled-components from 노마드코더

5월 한 달 동안 doit 모던 리액트 타입스크립트 책을 보며 리덕스까지 공부했다.
최근 노마드 코더 리액트 스터디에 등록해서 styled-components를 배웠다.

앞서 배운 것과 이 두 가지를 모두 활용해서 Bucket List App을 만들기로 결정!
➡︎ 전체 코드는 여기에!

의식의 흐름 (순서)

1. 한 컴포넌트에 몰아넣기

  • TodoList.tsx
    지금은 많이 바뀐 상태지만 처음 시작할 때는 폴더 구조를 어떻게 나눠야할지 몰랐다.
    그래서 일단 위 컴포넌트에 너무 길다고 느끼기 전까지 모든 코드를 적었다.
    1. 스타일링 할 컴포넌트 - div, line, text 등
    2. 사이트에 보여질 JSX구문
import { ChangeEvent, FormEvent, useCallback, useState } from 'react';
import styled from 'styled-components';
import { Title } from './TodoItem';
import TodoItem from './TodoItem';
import { AppState, Todo } from '../store/types';
import { useDispatch, useSelector } from 'react-redux';
import { addTodo } from '../store/actions';

// Component Styling
export const Div = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

const Input = styled.input`
  padding: 10px 20px;
  margin: 15px;
`;

// Main Component
const TodoList = () => {
  const todos = useSelector<AppState, Todo[]>((state) => state.todos);
  const [title, setTitle] = useState('');

  const dispatch = useDispatch();

  const onSubmit = useCallback(
    (e: FormEvent<HTMLFormElement>) => {
      e.preventDefault();
      dispatch(addTodo(title));
      console.log(title, 'Dispatch completed');
      setTitle('');
    },
    [dispatch, title]
  );

  const onAddTodo = (e: ChangeEvent<HTMLInputElement>) => {
    e.preventDefault();
    setTitle(e.target.value);
  };

  return (
    <>
      <Div>
        <Title style={{ marginTop: '50px' }}>BUCKET LIST APP</Title>
        <form onSubmit={onSubmit}>
          <Input type="text" value={title} onChange={onAddTodo} placeholder="월 천 만원 벌기" />
          <button type="submit">추가하기</button>
        </form>
      </Div>
      <TodoItem todos={todos} />
    </>
  );
};

export default TodoList;

2. 컴포넌트 나누기

위 TodoList.tsx 끝자락에 TodoItem 컴포넌트가 보이는가..!
쓰다보니 너무 길어져서 다른 컴포넌트로 옮기기로 했다.

이 과정에서 이름을 Todo로 만드는 바람에 Todo 객체 타입과 겹쳐서 오류가 생겼다.
(TodoItem으로 바꿈, 이름 짓는 것도 꽤나 힘든 일)

여튼 스타일과 JSX를 반갈라 놨더니 좀 보기 좋아졌다.

  • TodoItem.tsx
import { Todo } from '../store/types';
import styled from 'styled-components';
import { Div } from './TodoList';

const LineWrapper = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 30px;
  padding: 40px;
`;

const Line = styled.div`
  position: relative;
  height: 1px;
  width: 80vw;
  margin-top: 30px;
  background-color: black;
  opacity: 0.2;
  &::after {
    content: '';
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background-color: black;
  }
`;

export const Title = styled.h1`
  font-size: 30px;
  font-family: ${(props) => props.theme.fontLogo};
  font-weight: ${(props) => props.theme.weightBold};
`;

const SubTitle = styled(Title)`
  font-size: 18px;
  font-weight: ${(props) => props.theme.weightRegular};
`;

interface Props {
  todos: Todo[];
}

const TodoItem = ({ todos }: Props) => {
  return (
    <>
      <LineWrapper>
        {todos.length > 0
          ? todos.map((todo) => (
              <Div key={todo.id}>
                <SubTitle>{todo.title}</SubTitle>
                <Line />
              </Div>
            ))
          : 'No items'}
      </LineWrapper>
    </>
  );
};

export default TodoItem;

3. Redux store 만들기(?)

그 다음은 보여줄 todos를 만드는 일이다.
우선 액션 타입, 액션 생성 함수, 리듀서를 만들어준다.

  • types.ts
// 액션 타입 및 Todo 타입 선언
import { Action } from 'redux';

export interface Todo {
  title: string;
  id: number;
  done: boolean;
}

export type AppState = {
  todos: Todo[];
};

// action types
export const ADD_TODO = 'todos/ADD_TODO';

export type AddTodoAction = Action<typeof ADD_TODO> & {
  type: string;
  payload: Todo;
};

// actions
export type Actions = AddTodoAction;
  • actions.ts
// id는 1씩 올라가게 만듦 나중에 nanoId 사용할 예정
// payload 부분이 살짝 헷갈렸음
import { ADD_TODO } from './types';

let nextId = 1;

export const addTodo = (title: string) => ({
  type: ADD_TODO,
  payload: {
    title: title,
    id: nextId++,
    done: false,
  },
});
  • reducers.ts
    리듀서는 간단하게 추가하는 것 하나만 만들었다.
    다음에 삭제, 업데이트, 상세페이지 등도 만들 예정이다.

store를 만든 뒤 index.tsx에 ProviderConfigureStore을 사용해서 전달했다.

import { ADD_TODO, Actions } from './types';
import { Todo } from './types';

const initialState: Todo[] = [];

export const todosReducer = (state = initialState, action: Actions) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, action.payload];
    default:
      return state;
  }
};

4. useSelector, useDispatch로 todos가져오고 바꾸기

첫 번째 TodoList에서 본 것처럼 코드를 만들어준다. (확인은 위에서!)

느낀점 및 어려웠던 점

  • useSelector 타입과 인자 설정이 아직 익숙치 않다. 실습이 조금 더 필요하다.
  • 폴더 구조를 나누는 게 만만치 않다. 아직도 여기저기 더럽게 쓴 곳이 많다.
  • 그래도 한 번 실습해보니 눈으로 보는 것보단 훨씬 잘 이해된다.
    리덕스 영원히 이해 못 할줄 알았다. (ㅠㅠ)
  • styled-components 쓸 때 스타일만 따로 모아둔 파일을 만들어야할지 고민이다.
  • styled-components 쓸 때 얼마나 세세하게 컴포넌트를 나눌지 고민이다.
    어떤 건 그냥 써도 될 것 같은데, 여기선 굳이 다 만들어봤다.
  • 리듀서, 액션, 컴포넌트 이름 짓기 은근 쉽지 않다. 겹친다.

다음에 할 일

  • 리스트 삭제
  • 리스트 업데이트 (수정)
  • 리스트 성취여부
  • 상세 페이지 (사진, 설명, 마감일 등) - 라우터 써야하나?
  • 리스트를 DB에 저장해서 불러오기 - MongoDB 쓸 듯?
profile
always be fresh

0개의 댓글