- React, TypeScript, and Redux from 독학
- styled-components from 노마드코더
5월 한 달 동안 doit 모던 리액트 타입스크립트 책을 보며 리덕스까지 공부했다.
최근 노마드 코더 리액트 스터디에 등록해서 styled-components를 배웠다.
앞서 배운 것과 이 두 가지를 모두 활용해서 Bucket List App을 만들기로 결정!
➡︎ 전체 코드는 여기에!
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;
위 TodoList.tsx 끝자락에 TodoItem 컴포넌트가 보이는가..!
쓰다보니 너무 길어져서 다른 컴포넌트로 옮기기로 했다.
이 과정에서 이름을 Todo로 만드는 바람에 Todo 객체 타입과 겹쳐서 오류가 생겼다.
(TodoItem으로 바꿈, 이름 짓는 것도 꽤나 힘든 일)
여튼 스타일과 JSX를 반갈라 놨더니 좀 보기 좋아졌다.
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;
그 다음은 보여줄 todos를 만드는 일이다.
우선 액션 타입, 액션 생성 함수, 리듀서를 만들어준다.
// 액션 타입 및 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;
// 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,
},
});
store를 만든 뒤 index.tsx에 Provider와 ConfigureStore을 사용해서 전달했다.
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;
}
};
첫 번째 TodoList에서 본 것처럼 코드를 만들어준다. (확인은 위에서!)