리액트, 리덕스 연습 겸 TodoList 를 만들어보았다.
디자인은 styled components로 간단하게... 만들었다.
배운 내용을 토대로 id를 nextId 로 따로 만들어서 state 로 빼줘서 처리해줬다.
삭제와 완료는 모두 해당 id 를 활용했으며 filter 메서드를 사용했다.
// 초기 상태
const initState = {
todoList: [],
};
// id 제공을 위한 길이
let count = initState.todoList.length;
initState['nextId'] = count;
// 액션 타입 선언
const ADD = 'todo/ADD';
const REMOVE = 'todo/REMOVE';
const DONE = 'todo/DONE';
// 액션 함수 선언
export function add(todo) {
return {
type: ADD,
payload: todo,
};
}
export function remove(id) {
return {
type: REMOVE,
id,
};
}
export function done(id) {
return {
type: DONE,
id,
};
}
// 리듀서
export default function todo(state = initState, action) {
switch (action.type) {
// 추가 액션
case ADD:
return {
...state,
todoList: [
...state.todoList,
{
id: action.payload.id,
text: action.payload.text,
done: false,
},
],
nextId: action.payload.id + 1,
};
// 완료 액션
case DONE:
return {
...state,
todoList: state.todoList.map((e) => {
if (action.id === e.id) {
return {
...e,
done: true,
};
} else {
return e;
}
}),
};
// 삭제 액션
case REMOVE:
return {
...state,
todoList: state.todoList.filter((e) => {
if (action.id !== e.id) return { ...e };
}),
};
default:
return state;
}
}
스토어 설정이 위처럼 완료되면 cpnfigureStore() 와 Provider 로 지정해준다.
const reduxDevTool =
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = configureStore({ reducer: rootReducer }, reduxDevTool);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<GlobalStyle />
<App />
</Provider>,
);
// ListContainer
const MyListContainer = styled.div`
max-width: 1100px;
margin: auto;
`;
const MyContainer = styled.div`
display: flex;
justify-content: space-around;
margin-top: 30px;
`;
export default function ListContainer() {
return (
<MyListContainer>
<TodoInput />
<MyContainer>
<TodoList />
<DoneList />
</MyContainer>
</MyListContainer>
);
}
// TodoInput
const MyContainer = styled.div`
text-align: center;
margin-top: 50px;
`;
const MyTitle = styled.h2`
font-size: 2rem;
font-weight: 800;
margin-bottom: 10px;
`;
const MyButton = styled.button`
width: 70px;
height: 30px;
box-sizing: border-box;
font-size: 1.1rem;
border-radius: 6px;
background-color: #ffdb29;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 700;
`;
const MyInput = styled.input`
width: 240px;
height: 30px;
box-sizing: border-box;
border: 1px solid #777;
padding: 2px 6px;
border-radius: 6px;
margin-right: 4px;
outline: none;
&:focus {
border-color: #ffdb29;
}
`;
export default function TodoInput() {
const nextId = useSelector((state) => state.todo.nextId);
const dispatch = useDispatch();
const inputValue = useRef();
const addTodo = () => {
dispatch(
add({
id: nextId,
text: inputValue.current.value,
done: false,
}),
);
inputValue.current.value = '';
};
return (
<MyContainer>
<MyTitle>TodoList</MyTitle>
<MyInput
required
type="text"
ref={inputValue}
placeholder="할일을 입력하세요."
/>
<MyButton onClick={addTodo}>추가</MyButton>
</MyContainer>
);
}
// TodoList
const MyH4 = styled.h4`
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 25px;
text-align: center;
`;
const MyContainer = styled.div`
width: 300px;
border: 1px solid #777;
border-radius: 9px;
padding: 25px;
`;
const MyLi = styled.li`
background-color: #e7f3fe;
padding: 0 8px;
height: 32px;
display: flex;
position: relative;
margin-bottom: 10px;
box-shadow: 1px 1px 1px #aaa;
`;
const MyContent = styled.p`
line-height: 32px;
`;
const MyDeleteButton = styled.button`
position: absolute;
width: 50px;
height: 25px;
right: 5px;
top: 50%;
transform: translateY(-50%);
background-color: #7ea9d2;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 700;
`;
const MyDoneButton = styled.button`
position: absolute;
width: 50px;
height: 25px;
right: 60px;
top: 50%;
transform: translateY(-50%);
background-color: #7ed28d;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 700;
`;
export default function TodoList() {
const list = useSelector((state) => state.todo.todoList).filter(
(e) => !e.done,
);
const dispatch = useDispatch();
return (
<MyContainer>
<MyH4>🔨Todo</MyH4>
{list.length === 0 ? (
<div>할일을 입력해주세요 🥺</div>
) : (
<ul>
{list.map((e) => (
<MyLi key={e.id}>
<MyContent>{'📌 ' + e.text}</MyContent>
<MyDeleteButton onClick={() => dispatch(remove(e.id))}>
삭제
</MyDeleteButton>
<MyDoneButton onClick={() => dispatch(done(e.id))}>
완료
</MyDoneButton>
</MyLi>
))}
</ul>
)}
</MyContainer>
);
}
// DoneList
const MyH4 = styled.h4`
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 25px;
text-align: center;
`;
const MyContainer = styled.div`
width: 300px;
border: 1px solid #777;
border-radius: 9px;
padding: 25px;
`;
const MyLi = styled.li`
background-color: #e7f3fe;
padding: 0 8px;
height: 32px;
display: flex;
position: relative;
margin-bottom: 10px;
box-shadow: 1px 1px 1px #aaa;
`;
const MyContent = styled.p`
line-height: 32px;
text-decoration: line-through;
`;
const MyDeleteButton = styled.button`
position: absolute;
width: 50px;
height: 25px;
right: 5px;
top: 50%;
transform: translateY(-50%);
background-color: #7ea9d2;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 700;
`;
export default function DoneList() {
const doneList = useSelector((state) => state.todo.todoList).filter(
(e) => e.done,
);
const dispatch = useDispatch();
return (
<MyContainer>
<MyH4>✨Done</MyH4>
{doneList.length === 0 ? (
<div>모두 완료! 🎉</div>
) : (
<ul>
{doneList.map((e) => (
<MyLi key={e.id}>
<MyContent>{e.text}</MyContent>
<MyDeleteButton onClick={() => dispatch(remove(e.id))}>
삭제
</MyDeleteButton>
</MyLi>
))}
</ul>
)}
</MyContainer>
);
}
'Web Study' 카테고리의 다른 글
useSWR 정리 (0) | 2023.04.23 |
---|---|
리덕스 정리 - 1 (0) | 2023.04.10 |
리덕스 사용을 위한.. useReducer 개념 (0) | 2023.04.07 |
useContext ? (0) | 2023.04.04 |
React.memo()에 대해 (0) | 2023.04.03 |