이덕수가 싫은 림졍은
그를 때려눕히기 위해 오늘도 열심히 내공 다지는중... (는 강의정리하면서 내공냠냠중이죠?)
강의 정리 지지이이이인짜 얼마 안남았으니 언능 빠르게 가보자구요
라쯔고.
React (리액트) 강의 정리 - 숙련 ver. (이이이어이서)
이덕수 내공냠냠
Redux 기반 TodoList 만들기
기초 뼈대
// src > redux > moduels > todos.js
// Reducer
const todos = () => {};
export default todos;
나머지는.. 저번처럼 하면 됩니다. 어때요, 참 쉽죠? (ft. 밥아저씨)
[ src > redux > modules > todos.js ]
// Action value
const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";
const TOGGLE_TODO = "TOGGLE_TODO";
// Action Creator
export const addTodo = (payload) => {
return { type: ADD_TODO, payload };
};
export const deleteTodo = (payload) => {
return { type: DELETE_TODO, payload };
};
export const toggleTodo = (payload) => {
return { type: TOGGLE_TODO, payload };
};
// initial State
const initialState = {
todos: [
{
id: 1,
title: "react를 배워봅시다.",
},
{
id: 2,
title: "redux를 배워봅시다.",
},
],
};
// Reducer
const todos = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload],
};
case DELETE_TODO:
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.payload.id),
};
case TOGGLE_TODO:
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.payload.id
? { ...todo, isDone: !todo.isDone }
: todo
),
};
default:
return state;
}
};
export default todos;
[ AddForm.jsx ]
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { addTodo } from "../redux/modules/todos";
import styled from "styled-components";
const AddForm = () => {
const [title, setTitle] = useState("");
const dispatch = useDispatch();
const onSubmitHandler = (e) => {
e.preventDefault();
if (title === "") return; // 아무것도 입력하지 않았을 때 dispatch 하지 않음
dispatch(
addTodo({
id: new Date().getTime(),
title,
})
);
};
return (
<StFormContainer>
<form onSubmit={onSubmitHandler}>
<label>Todos의 제목을 입력하세요</label>
<br />
<StInput
type="text"
value={title}
onChange={(e) => {
setTitle(e.target.value);
}}
/>
<StButton>추가하기</StButton>
</form>
</StFormContainer>
);
};
export default AddForm;
const StFormContainer = styled.div`
display: flex;
gap: 24px;
padding: 30px;
text-indent: 2rem;
line-height: 40px;
`;
const StButton = styled.button`
border: none;
background-color: #eee;
height: 25px;
cursor: pointer;
width: 120px;
border-radius: 12px;
&:hover {
background-color: orange;
color: white;
border: 2px solid black;
}
`;
const StInput = styled.input`
border: 1px solid #eee;
margin: 0 24px;
height: 25px;
width: 300px;
border-radius: 12px;
outline: none;
padding: 0 10px;
&:focus {
border: 2px solid black;
}
`;
[ TodoList.jsx ]
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import styled from "styled-components";
import { deleteTodo, toggleTodo } from "../redux/modules/todos";
const TodoList = () => {
const { todos } = useSelector((state) => state.todos);
const dispatch = useDispatch();
// handlers
const onDeleteHandler = (id) => {
dispatch(deleteTodo({ id }));
};
const onToggleHandler = (id) => {
dispatch(toggleTodo({ id }));
};
// todoList, doneList
const todoList = [];
const doneList = [];
todos.forEach((todo) => {
if (todo.isDone) {
doneList.push(todo);
} else {
todoList.push(todo);
}
});
return (
<>
<h2>할 일</h2>
<StTodos>
{todoList.map((todo) => (
<StTodo key={todo.id}>
{todo.title}
<StButtonContainer>
<StButton color="green" onClick={() => onToggleHandler(todo.id)}>
완료
</StButton>
<StButton color="red" onClick={() => onDeleteHandler(todo.id)}>
삭제
</StButton>
</StButtonContainer>
</StTodo>
))}
</StTodos>
<h2>완료한 일</h2>
<StTodos>
{doneList.map((todo) => (
<StTodo key={todo.id}>
{todo.title}
<StButtonContainer>
<StButton color="green" onClick={() => onToggleHandler(todo.id)}>
취소
</StButton>
<StButton color="red" onClick={() => onDeleteHandler(todo.id)}>
삭제
</StButton>
</StButtonContainer>
</StTodo>
))}
</StTodos>
</>
);
};
export default TodoList;
const StTodos = styled.div`
display: flex;
gap: 12px;
flex-wrap: wrap;
`;
const StTodo = styled.div`
border: 1px solid #ddd;
width: 20%;
height: 100px;
display: flex;
gap: 12px;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0 24px;
border-radius: 12px;
`;
const StButtonContainer = styled.section`
display: flex;
gap: 6px;
`;
const StButton = styled.button`
border: none;
cursor: pointer;
border-radius: 12px;
padding: 6px 12px;
${(props) =>
props.color === "green"
? "background-color: lightgreen;"
: props.color === "red"
? "background-color: lightcoral;"
: "background-color: #eee;"}
&:hover {
background-color: #f7ed63;
border: 2px solid black;
}
`;
RTK (Redux ToolKit)
리덕스를 개량한 툴킷으로 생각!
- 탄생비화
ducks 패턴의 요소들, 전체적인 코드의 양을 늘린다는 개발자들의 불만이 발생
코드는 더 적게 사용하면서 리덕스는 더 편하게 쓸 수 있는 기능들을 흡수해서 만들었다고..
- 어디가 달라졌나용?
모듈 파일 부분만 바뀌었숨당!
리덕스와 구조 & 패러다임 same, 한땀한땀 적던 ducks 패턴의 코드들이 새로운 API의 추가로 어느정도 자동화가 되었다고 생각
- 툴킷 설치하기
vite로 우선 새로운 프로젝트 생성하고 해당 명령어를 복붙해서 설치합시당
yarn add react-redux @reduxjs/toolkit
- 코드 수정 : 파일위치 및 설명 추가, slice export 제거 (아래 코드 참고)
// src/redux/slices/counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
number: 0,
};
// action + reducer
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
오옹 코드가 짧아졌다... ㅇ0ㅇ!!
Action Value & Action Creator 를 따로 생성 안하고, Reducer 까지 싸그리 몽땅 하나로 합쳐진 것을 알 수 있음..!
Slice API 사용 : 3개 따로 만들 필요 없이 한번에 만들어진다..!
( 필수로 작성해야 하는 값 - name, initialState, reducers )
//createSlice API 뼈대
const counterSlice = createSlice({
name: '', // 이 모듈의 이름
initialState : {}, // 이 모듈의 초기상태 값
reducers : {}, // 이 모듈의 Reducer 로직
})
[정리]
리듀서 로직이 되면서도 동시에 Action Creator도 된다..!
그리고 Action Value 까지 함수 이름으로 자동으로 만들어지니..
∴ Reducer만 만들어주면 됨 ㅇㅇ. (아주 편리하다!!)
// counterSlice.js의 Slice 구조
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
// 리듀서 안에서 만든 함수 자체가 리듀서의 로직이자, 액션크리에이터가 된다.
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
추가) counterSlice.reducer, default로 내보내면 됨. (export로 각각의 Action Creator를 내보내주었던 것처럼)
(리듀서에 로직 추가할 때마다 함수도 추가해서 내보내주도록 하자)
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
- configStore 비교
( combineReducers + createStore 조합 → configureStore로 대체 )
// src/redux/slices/config/configStore.js
import { configureStore } from "@reduxjs/toolkit";
// import 해온 것은 slice.reducer 입니다.
import counter from "../slices/counterSlice";
import todos from "../slices/todosSlice";
/*
- 모듈(Slice)이 여러개인 경우
- 추가할때마다 reducer 안에 각 모듈의 slice.reducer를 추가해줘야 합니다.
- 아래 예시는 하나의 프로젝트 안에서 counter 기능과 todos 기능이 모두 있고,
- 이것을 각각 모듈로 구현한 다음에 아래 코드로 2개의 모듈을 스토어에 연결해준 것 입니다.
*/
// reducer: { counter: counter, todos: todos }, 이런 식으로 모듈을 스토어에 연결해주기....
const store = configureStore({
reducer: { counter: counter, todos: todos },
});
export default store;
Redux Devtools
redux로 개발할 때 사용할 수 있는 강려크한 개발툴
현재 프로젝트의 state 상태라던가, 어떤 액션이 일어났을 때 그 액션이 무엇이고,
그것으로 인해 state가 어떻게 변경되었는지 등 리덕스를 사용하여 개발할 때 아주 편리하게 사용
- 설치 : 구글 웹스토어에서 플러그인 설치하면 된다. (나머진.. 귀찮아..)
- Redux Devtools설치 : 링크 바로가기
뭐 설치하고 리덕스 사용하면 플러그인에 녹색불이 켜진답니다
그러고 개발자도구 탭에서 리덕스 볼 수 있움 ㅇㅇ..
... 나중에 깔아야지
Flux 패턴
Facebook에 의해 개발된 애플리케이션 아키텍처 (주로 React와 함께 사용, 데이터 단방향 흐름 강조)
- 단방향 흐름이 무엇을 뜻하는거여..
그..그게 말이죠
액션 → 디스패처 → 스토어 → 뷰.
이렇게 관리되는 순환적인 흐름 = 애플리케이션의 데이터 흐름 예측 가능하도록 만들어줌
∴ 복잡한 상호작용이 많은 대규모 애플리케이션을 쉽게 관리할 수 있다!
+) 단방향이다보니... 어느 단계에서 문제가 생겼는지 빠르게 파악하고 해결도 가능 ^-^)b
[구성요소]
- Dispatcher: 애플리케이션 내 모든 데이터 흐름을 관리하는 중앙 허브 역할 수행. 액션 발생 시, 스토어로 전달
- Stores: 애플리케이션의 상태(데이터)와 로직 보유. 디스패처 통해 전달된 액션에 반응 → 상태 변경 + 변경 사항 뷰에 알림
- Actions: 상태 변화를 일으킬 때 사용하는 간단한 객체.
사용자 인터페이스에서 발생한 사용자 행동, 액션으로 표현 → 디스패처를 통해 스토어로 전달 - Views (React Components): 사용자 인터페이스를 구성하는 React 컴포넌트들.
스토어에서 상태 변화 시, 이를 반영하여 사용자 인터페이스를 업데이트
Ducks 패턴과 Flux 패턴
둘 다 같은 상태 관리 라이브러리에서 코드 구조 정리를 위한 패턴
( = 애플리케이션의 상태 관리와 데이터 흐름의 체계를 잡기 위한 방법론! )
각각 코드의 구조화 및 데이터 관리 방식 부분에서 서로 다른 접근 제공 (차별화 존재)
Ducks는 주로 코드의 구조를 단순화, Flux는 애플리케이션의 데이터 흐름을 체계화하는 데 중점을 둠
1) Ducks 패턴 (다시 정리)
Redux의 파일 및 코드 관리 문제를 해결하기 위해 고안된 패턴
파일을 actions, reducers, types 등으로 분리하는 방식으로 코드를 관리
-> 규모가 커지면 관리가 어렵고 파일이 분산되어 코드의 가독성이 떨어짐
이를 해결하기 위해 고안된 패턴이 Ducks 패턴( 기능별로 모든 관련 코드를 하나의 파일에 모아 관리 )
- Ducks 패턴의 특징
- 하나의 기능(예: todos)에 관련된 action type, action creator, reducer를 한 파일에 묶음.
- 코드 구조가 간결해지며, 파일 개수가 줄어들어 유지보수와 파일 관리가 쉬워짐.
- Redux의 구조적인 복잡성을 줄이기 위해 나온 패턴.
→ 기능별로 코드를 나누는 것 = 유지보수 쉬움 + Redux의 초기 설정 복잡성을 줄일 수 있음
2) Flux 패턴 (얘도 다시 정..리)
Facebook에서 애플리케이션의 데이터 흐름을 단방향으로 관리하기 위해 제안한 아키텍처
Redux도 Flux 아키텍처에서 영향을 받은 라이브러리, Flux 패턴을 따른다고 볼 수 있음.
Flux 패턴의 핵심은 단방향 데이터 흐름과 중앙 집중식 스토어 관리 ( 사용 시, 데이터 흐름이 예측 및 버그 추적이 쉬워짐. )
- Flux 패턴의 구성 요소
- Action: 사용자나 시스템이 일으킨 이벤트로, 데이터 변경을 요청하는 역할 담당
- Dispatcher: 액션을 받아서 스토어로 전달하는 중앙 허브 역할. 모든 액션은 반드시 디스패처를 통해 전달
- Store: 상태와 비즈니스 로직을 포함, 애플리케이션의 중앙 상태를 관리
- View: 사용자 인터페이스를 담당하며, 스토어의 상태를 읽고 렌더링함. 사용자가 상호작용 시, 액션을 생성하여 디스패처에 전달
- Flux 패턴의 데이터 흐름
- 사용자, View와 상호작용을 통해 Action 생성
- Action, Dispatcher를 통해 Store로 전달
- Store, 상태 업데이트 → View에 변화를 반영하도록 알림.
- View, 새로운 상태를 받아 UI를 다시 렌더링
→ 단방향 데이터 흐름을 강조, 상태 관리의 예측 가능성을 높이는 데 도움을 줌.
마무리 - 짤방이 이것밖에 없냐구요? ye.
TIL 쓴거 다시 되돌아보고오니 도란스뽀머 지분이 엄청나네요..
아 다른 짤들도 써야 하나 싶지만...
근데 얘네 자막도 웃기고 얼굴도 웃기고 작붕도 웃겨서.. 아직 못잃...겠네요
아니 진짜 영상을 보세요 얘네 자막 진짜로 약간 은은하게 돌아있다니까요???
(제가 괜히 쓰는거 아닙니다... ㄱ....그렇다구요.)
그러니까 결론은 뭐냐구요?
우리 도란스뽀머 많이 사랑해주세요 헤헿 '-^)b
(참고로 남자친구도 이해 못하는 제 취미랍니다.. 쿸... ⭐️)
그럼 이만... 총총총
오 늘의 KPT 회고
- Keep : 이덕수 뎀벼
- Problem : ...개인과제 구현 언제 다 하지 (짤방의 한계도..)
- Try : 어웅 하기싫어 (이거 여기다가 적는거 맞죠?)
ㄲ
ㅡ
ㅅ.
'React TIL' 카테고리의 다른 글
[React] Day_40 데일리 정리 (0) | 2024.11.13 |
---|---|
[React] Day_39 데일리 정리 (5) | 2024.11.12 |
[React] Day_37 데일리 정리 (2) | 2024.11.10 |
쉬어가는 TIME - 개인과제 중간점검(?) (feat. 꿀팁) (1) | 2024.11.09 |
[React] Day_36 데일리 정리 (4) | 2024.11.08 |