-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[3주차] 주효정 미션 제출합니다. #15
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
효정님 안녕하세요, 프론트엔드 멘토 정시원입니다.
꼼꼼하게 모든 객체를 타이핑해주신 점 정말 좋은 것 같아요. reducer 패턴과 custom hook를 이용해서 state의 안정성과 관심사의 분리를 달성하려고 하신 점이 특히 인상깊습니다.
말씀드린 제안 사항 한번 살펴보시고, 코멘트 부탁드립니다 :)
"@types/jest": "^27.4.1", | ||
"@types/node": "^17.0.23", | ||
"@types/react": "^17.0.43", | ||
"@types/react-dom": "^17.0.14", | ||
"@types/styled-components": "^5.1.24", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타입 라이브러리의 경우 dev-dependencies에 추가해주면 배포 때 번들 사이즈를 줄일 수 있을 것 같아요!
[https://ingorae.tistory.com/1754](참고 링크)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 처음 안 사실이네요! 앞으로 참고해서 사용하겠습니다!
src/App.tsx
Outdated
const _saveLocalStorage = useCallback( | ||
(type: string, list: Array<ITodoItem>): void => { | ||
localStorage.setItem(type, JSON.stringify(list)); | ||
}, | ||
[], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 Hooks 내부에 정의할 필요는 따로 없을 것 같네요 ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그렇네요! 밖으로 뺄 생각을 못했네요. 감사합니다!!
src/App.tsx
Outdated
const Container = styled.div` | ||
height: 100vh; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사람마다 다른데, 최상위 컴포넌트는 Wrapper
로 이름 짓는 경우가 많더라구요 ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어쩐지 네이밍이 마음에 안 들더라구요.. 알려주셔서 감사합니다!
const TodoProvider = ({ children }: IContextProps) => { | ||
const [state, dispatch] = useReducer(reducer, initialState); | ||
|
||
return ( | ||
<TodoContext.Provider | ||
value={{ | ||
todoList: state.todoList, | ||
doneList: state.doneList, | ||
dispatch, | ||
}} | ||
> | ||
{children} | ||
</TodoContext.Provider> | ||
); | ||
}; | ||
|
||
export { TodoContext, TodoProvider }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
custom hook을 잘 �활용하셨네요!
useTodo()
라는 커스텀 훅 내부에 로직을 숨겨 조금 더 추상화해보면 어떨까요?
const { todoList, doneList, dispatch } = useContext(TodoContext); // 로 쓰는 것보다
const { todoList, doneList, addTodo, deleteTodo, addDone, deleteDone } = useTodo() // context을 사용하는 것도 useTodo 속에 숨겨봅시다
이렇게 관심사의 분리를 적용해봅시다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 관심사의 분리 적용하면 코드 이해하기도 훨씬 편할 것 같네요. 꼭 적용해보겠습니다:thumbsup:
import { TodoContext, TodoProvider } from "./TodoContext"; | ||
|
||
export { TodoContext, TodoProvider }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import 할 때 깔끔하고 좋군요!
다만, 파일을 추가하고 바꿀때마다 수정해줘야 하니 유지 보수 비용이 늘어날 수 있겠네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하 그렇네요.. 조금 더 고민하고 효율적으로 사용해봐야 할 것 같네요!
} | ||
|
||
export interface ITodoItemProps { | ||
type: "todo" | "done"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typescript는 enum 타입을 제공합니다. enum을 활용해보시는 것도 좋을 것 같네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typescript 너무 어렵네요.. enum 사용해보겠습니다!
src/interface.tsx
Outdated
todoList: Array<ITodoItem>; | ||
doneList: Array<ITodoItem>; | ||
dispatch: React.Dispatch<IAction>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
하나도 놓치지 않는 꼼꼼한 타이핑이 인상적이네요.
Array<T>
대신 T[]
를 사용할 수 있답니다
todoList: Array<ITodoItem>; | |
doneList: Array<ITodoItem>; | |
dispatch: React.Dispatch<IAction>; | |
todoList: ITodoItem[]; | |
doneList: ITodoItem[]; | |
dispatch: React.Dispatch<IAction>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 감사합니다!
src/components/TodoInputForm.tsx
Outdated
if (text.trim()) { | ||
// list에 todo item 추가 | ||
dispatch({ | ||
type: "ADD_TODO", | ||
todo: { text }, | ||
}); | ||
} | ||
_resetText(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 앞서 말씀드린 useTodo 훅에 넣어도 될 것 같네요!
const { addTodo } = useTodo()
const handleTodoInputChange = (event: React.FormEvent) => {
addTodo({text: event.target.value})
}
// ... addTodo는 useTodo내에서 구현
이렇게 하면 todoInputForm의 이벤트 처리 로직과 todo 상태 변경 로직의 분리를 달성할 수 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 커스텀 훅에 적용해보도록 하겠습니다~!
const TodoItem = ({ | ||
type, | ||
todo, | ||
idx, | ||
deleteCurrentList, | ||
addToggleList, | ||
}: ITodoItemProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const TodoItem = ({ | |
type, | |
todo, | |
idx, | |
deleteCurrentList, | |
addToggleList, | |
}: ITodoItemProps) => { | |
const TodoItem = ({ | |
type, | |
todo, | |
idx, | |
deleteCurrentList, | |
addToggleList, | |
}: ITodoItemProps): React.FC<ITodoItemProps> => { |
보통은 이런 식으로 직접 컴포넌트를 타이핑해준답니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하 넵 저걸 빼먹었네요!
${TodoDeleteBtn} { | ||
opacity: 1; | ||
transition: 0.1s; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
styled-components의 고급 기능을 잘 활용하셨네요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
효정님 안녕하세요
프론트엔드 멘토 김선종입니다.
코드 정말 깔끔하네요... flux가 왜 좋은지는 이런 코드를 볼 때 크게 와닿는 것 같습니다. 타입스크립트가 어렵지는 않으셨나요? 개인적으로 저도 타입스크립트가 걸음마 단계라서 크게 코멘트를 드리지는 못했습니다. 다만 몇가지 참고하시면 좋을거 같은 내용을 적어봤어요.
과제 하느라 고생하셨습니다
src/interface.tsx
Outdated
export interface IAction { | ||
type: "ADD_TODO" | "DELETE_TODO" | "ADD_DONE" | "DELETE_DONE"; | ||
todo?: { text: string }; | ||
idx?: number; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
action은 명확하게 나눠줘야 합니다. 현재 코드에서는 ADD_DONE
타입의 action에 payload로 idx
를 넘겨준다고 해도 컴파일타임에 에러가 없습니다. (하지만 명백히 잘못된 payload 전달이죠) 타입스크립트의 목적중 하나가 런타임에 에러를 방지하려는 것임을 고려해보면 꼭 지켜져야 하는 부분입니다.
export interface IAction { | |
type: "ADD_TODO" | "DELETE_TODO" | "ADD_DONE" | "DELETE_DONE"; | |
todo?: { text: string }; | |
idx?: number; | |
} | |
export interface IAction { | |
type: "ADD_TODO"; | |
todo: { text: string }; | |
} | | |
{ | |
type: "DELETE_TODO"; | |
idx: number; | |
} | | |
{ | |
type: "ADD_DONE"; | |
todo: { text: string }; | |
} | | |
{ | |
type: "DELETE_DONE"; | |
idx: number; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
action에 따라 다른 payload를 받는 것을 어떻게 선언해줘야 할 지 몰라서 저런 애매한 코드를 작성해버렸네요.. 말씀해주신대로 명확하게 작성하도록 하겠습니다. 감사합니다!
src/contexts/TodoContext.tsx
Outdated
case "ADD_TODO": | ||
return { | ||
...state, | ||
todoList: todo ? [...state.todoList, todo] : state.todoList, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 액션은 "해야할 일 목록에 TO-DO 아이템 추가"라는 명시적인 목적을 수행할 수 있어야 합니다. todo
상태에 따라 추가가 될지 안될지를 리듀서에서 결정하는 것은 좋지 못한 패턴입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 수정하도록 하겠습니다!
src/contexts/TodoContext.tsx
Outdated
const initialState = { | ||
todoList: loadTodo, | ||
doneList: loadDone, | ||
dispatch: () => {}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
초기값에 dispatch
함수가 포함될 필요는 없어보이네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dispatch를 초기값에 설정해주지 않으니까 type에 맞지 않다고 오류가 나더라구요.. 다른 방법을 생각해보도록 하겠습니다!
src/contexts/TodoContext.tsx
Outdated
const initialState = { | ||
todoList: loadTodo, | ||
doneList: loadDone, | ||
dispatch: () => {}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const initialState = { | |
todoList: loadTodo, | |
doneList: loadDone, | |
dispatch: () => {}, | |
}; | |
const initialState: IState = { | |
todoList: loadTodo, | |
doneList: loadDone, | |
dispatch: () => {}, | |
}; |
상태는 타입을 꼭 명시해주세요...^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아이코 저걸 빼먹었네요.. 감사합니다!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요, 3주차 리뷰파트너 한규진입니다!!
1주차 과제부터 효정님 코드보고 배워가는게 참 많습니다. useReducer를 사용하니 확실히 데이터 흐름을 보기가 쉬워지네요. 제가 이번에 타입스크립트를 완전히 처음 접해서 무언가 조언을 드릴 수 있는 게 없어 아쉽습니다. 궁금한점과 배워가는부분에 감사인사 정도 드리고 가겠습니다!!
const { todoList, addDone, deleteTodo } = useTodo(); | ||
|
||
// done item 추가 | ||
const _addDoneList = useCallback( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
함수 네이밍에 _(언더바)가 어떤 의미를 갖는지 궁금합니다!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
처음에는 변수명과 함수를 구분하기 위해 사용했던게 습관이 되었던 것 같습니다. 그런데 앞에 정시원 멘토님이 조언해주신 말씀을 보고 찾아보니 private 함수 앞에 붙여주는 일종의 자바스크립트 컨벤션이더라구요!
import { useEffect, useContext } from "react"; | ||
import styled from "styled-components"; | ||
import { TodoInputForm, TodoList, DoneList } from "components"; | ||
import { TodoContext } from "contexts"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contexts에서 index.tsx에서 따로 export 해온게 이부분에서 더 깔끔하게 보이기 위해서라고 이해하면 될까요?? 혹시 다른 이유가 또 있을까요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
특별한 이유는 없고 한 폴더에서 여러 컴포넌트를 import 할 때 한 줄로 깔끔하게 import하기 위해서 자주 사용했습니다..
|
||
const TodoContext = createContext<IState>(initialState); | ||
|
||
const reducer = (state: IState, action: IAction): IState => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
전 useReducer 훅을 사용해본 적이 없어서 고려해보지 못했는데, 코드가 굉장히 정리된 느낌을 주는것 같습니다. 종종 사용해봐야겠네요.
const ListItem = styled.li` | ||
margin-bottom: 13px; | ||
:hover { | ||
${TodoDeleteBtn} { | ||
opacity: 1; | ||
transition: 0.1s; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hover 안에 다른 요소를 넣을 수 도 있군요..! 앞으로 저도 자주 쓰게 될 것 같습니다.
안녕하세요. 프론트 15기 주효정입니다! 지난주차랑 이어지는 미션이라 시간이 오래 안 걸릴 줄 알았는데 typescript로 변환하면서 생각보다 시간이 많이 걸리더라구요. Context api도 너무 오랜만에 사용해서 굉장히 배울 점이 많은 주차였습니다:joy:
배포 링크
https://react-todo-15th-jhj2713.vercel.app/
신경 쓴 부분
자꾸 오류가 떠서 any를 쓸까말까 한참을 고민했는데 최대한 안 쓰려고 노력했습니다.
감사합니다!