[항해99 4주차, 장문주의] - Redux 개인과제 (3) [코드 뜯어보기]
아무래도 코드설명을 하면
글이 길어지다보니
간단한 투두리스트 프로젝트도
블로그 포스팅이 나눠질 수 밖에 없는 듯.....
이제 남은 컴포넌트인
TodoItem과 Detail 그리고 모듈폴더의 todos.js 스토어를 살펴보자
<TodoItem.js>
const TodoItem = ({todo, i}) => {
const dispatch = useDispatch();
const onDelete = (id) => dispatch(deleteTodo(id))
const onToggle = (id) => dispatch(toggleTodo(id))
const navigate = useNavigate();
const isDone = todo.isDone;
return (
<>
<StItem isDone={isDone}>
<StP onClick={() => {navigate("/detail/" + todo.id)}}>
상세보기
</StP>
<span>{todo.title}</span>
<span>{todo.content}</span>
<StDiv>
<StButton onClick={() => { onDelete(todo.id) }}>삭제하기</StButton>
<StButton onClick={() => { onToggle(todo.id)}}>
{isDone ? "취소!" : "완료!"}
</StButton>
</StDiv>
</StItem>
</>
)
}
제일 처음 App.js에서 봤던
detail라우팅이 여기에서 사용된다.
상세보기버튼을
클릭할 시 navigate('/detail/' + tood.id)
경로로 이동하게 되는데
이 컴포넌트에서 사용되는 모든 todo는
그 전의 TodoList에서 map함수를 사용하여 얻은 인자를 뜻한다.
즉 이 Item컴포넌트는 배열안의 객체 하나하나를 뜻하는 것이고
props로 받아온 todo는
해당 객체를 뜻한다!
todo.id가 1인 투두리스트의
상세보기버튼을 누르게 되면
navigate로
http://localhost:3000/detail/1
위와 같은 경로이동이 되는 것이다!
새로고침없이 페이지가
부드럽게 이동되게 하는
리액트의 강력한 무기 중 하나라고 보면된다.
위 투두리스트 메인페이지에서
<TodoItem>컴포넌트는
이 리스트 하나를 뜻하는 것이다!
삭제하기와 완료를 누르면
각각 todos.js 스토어에 있는
toggleTodo와 deleteTodo가 실행되는데
id값을 파라미터로 넘겨 실행해준다.
<Detail.js>
const Detail = () => {
const { id } = useParams();
const todo_state = useSelector((state) => state.todo.todo_1); // 추가해주세요.
const navigate = useNavigate();
let todos = todo_state.find(data => data.id === Number(id));
const isDone = todos.isDone;
return (
<>
<GlobalStyle/>
<StWrapper>
<StItem isDone={isDone}>
<h3>id : {todos.id}</h3>
<Stbutton onClick={() => {navigate("/")}} style={{cursor:"pointer"}}>메인으로</Stbutton>
<h1>{todos.title}</h1>
<h3>{todos.content}</h3>
</StItem>
</StWrapper>
</>
);
};
리덕스 스토어에서
마찬가지로 todo객체들이 있는 배열을 state로 가져오고
그 배열을 find함수를 이용해
해당 객체의 id와 URL에 입력된 숫자 {id}가 같다면
Detail메인화면을 띄워주는데
useParams를 이용해 가져온 id값이
문자열로 받아와져서 Number나 parseInt를 이용해 숫자로 형변환시켜줘야한다.
해당 객체의
id와 title그리고 content를 가져와 렌더링해주면
상세보기를 클릭할 때
http://localhost:3000/detail/1
위와 같은 URL로 이동되며
화면엔
이러한 네모박스가 뜬다. ( 다음부턴 디자인신경좀...)
이제 대망의 하이라이트
리덕스 스토어 파일을 보자....
차근차근 하나하나 뜯어먹어보자!
<todos.js>
const ADD_TODO = "todos/ADD_TODO";
const DELETE_TODO = "todos/DELETE_TODO";
const TOGGLE_TODO = "todos/TOGGLE_TODO";
액션을 변수로 선언해 준 후
이 액션을
액션 creator에 type으로 지정해 줄 것이다.
let nextId = 1; // todo 데이터에서 사용 할 고유 id
export const addTodo = (title, content) => ({
type: ADD_TODO,
todo: {
id: nextId++, // 새 항목을 추가하고 nextId 값에 1을 더해줍니다.
title,
content,
isDone: false,
},
});
export const deleteTodo = (id) => ({
type: DELETE_TODO,
id,
});
export const toggleTodo = (id) => ({
type: TOGGLE_TODO,
id,
});
addTodo와 deleteTodo, toggleTodo가 필요한
각기다른 컴포넌트에서
이 함수를 호출하여 사용하고 있는데
<TodoForm.js>컴포넌트에서 추가하기버튼을 누를 시
dispatch(addTodo(title, content)) 함수가 실행되는걸 확인했었는데
title과 content 파라미터를 받아와
todo: { } 객체안에 id값과 isDone과 함께 넣어준다!
위의 export const로 되어있는 세 함수들을
Action Creator라고 한다..!
const initialState = {
todo_1: [],
};
initialState는 초기상태값이라고 표현하는데
객체로 지정해줘도 되고
배열로해줘도되고
원시데이터로 지정해줘도 된다.
그렇지만 객체를 사용하면
각각 다른 데이터타입을가진
다양한 스테이트를 사용할 수 있다.
필자는 지금
todo_1의 이름을가진 빈배열하나만
초기상태값의 객체안으로 넣어주었다.
const todos = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
const addState = { ...state, todo_1: [...state.todo_1, action.todo] };
return addState;
case DELETE_TODO:
const deleteFilter = { ...state, todo_1: state.todo_1.filter(todo => todo.id !== action.id)}
return deleteFilter;
case TOGGLE_TODO:
const toggleState = { ...state, todo_1: state.todo_1.map(todo => todo.id === action.id ? { ...todo, isDone: !todo.isDone} : todo)}
return toggleState;
default:
return state;
}
};
자 이제 심호흡하고
후웁....
위의 함수를 Reducer라고 한다.
실제로 state가 이 리듀서에서 변경된다
switch case 조건문을 여기서 이용하게 될줄이야...
파라미터로는 state와 action을 받아오는데
state에 initialState를 할당한다.
리듀서 내에서 state를 불변성을 유지하며 변경해줄 수 있다.
switch case문을 통해
action.type이 ADD_TODO, DELETE_TODO, TOGGLE_TODO
일 때 각각 정해진 로직이 실행되는 것이다.
case ADD_TODO:
const addState = { ...state, todo_1: [...state.todo_1, action.todo] };
return addState;
두번째 줄 로직은
현재 파라미터로 받아온 state가
const initialState = {
todo_1: [],
};
위와 같기 때문에
todo_1이 아닌 다른 state들은 불변성을 ...state로 유지한 채
todo_1 안의 값만 변경해준다!
그런데 액션크리에이터에서
객체를 생성해줬으니까
todo_1 배열안에서
그 생성된 객체 외에 다른 객체들은
마찬가지로 ...state.todo_1 이렇게 불변성을 유지해준채
새로운 객체만 추가해주는 것이다.!
case DELETE_TODO:
const deleteFilter = { ...state, todo_1: state.todo_1.filter(todo => todo.id !== action.id)}
return deleteFilter;
두번째 액션타입이 DELETE_TODO일 때 실행되는 로직은
마찬가지로 ...state로 불변성을 유지해준다.
state.todo_1의 배열을 filter함수로 돌린다.
클릭한 객체의 id 이외의 다른 id값을 가진 객체들을
반환해주면 클릭한 객체만 지우는 효과가 되는 것!
case TOGGLE_TODO:
const toggleState = { ...state, todo_1: state.todo_1.map(todo => todo.id === action.id ? { ...todo, isDone: !todo.isDone} : todo)}
return toggleState;
클릭한 객체의 isDone값을 false와 true로 토글해주는 로직이다
map함수를 이용해 클릭한 객체의 id와 같은 값을 가진 객체의 isDone만
현재상태의 반대로 뒤집어 주는 것이다!
이렇게 했을 때
완료!버튼과 취소버튼을 누를 때마다
원하는 곳에 배치할 수 있게 된다.
이렇게 한번 쭉 훑어봤는데
이제 결과물을 봐보자....
다음 프로젝트는 CSS좀 이쁘게 꾸며서
보기 좋게 만들어봐야겠다...
지금보다 더..
react-redux는 이쯤하고
현재 투두리스트에 리덕스 툴킷을 덮어쓰기 하여
돌아오겠읍니다...