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기 나무] TodoList with CRUD #36
Conversation
- 파일 끝 endline 추가 https://blog.coderifleman.com/2015/04/04/text-files-end-with-a-newline/ - 불필요 코드 삭제 - let -> const
- 지난 피드백 적용하면서 수정 덜된부분 수정 - TodoCount에서는 count만 받도록 수정
- handle conflict
* todo list에 todoItem을 키보드로 입력하여 추가하기 및 완료 처리하기 * todo list의 x버튼을 이용해서 해당 엘리먼트를 삭제 * todo item 수정 기능 추가 * todo list의 item갯수를 count한 갯수를 리스트의 하단에 보여주기 * keyCode 메소드 대신 key 메소드 사용 * 리스트 개수 선택자 범위를 더 포괄적으로 처리 * data의 불변성을 지켜주기 위해 push가 아닌 concat 메소드 사용
* all except active and completed * 1st week missions acomplished * 리스트 위치 바꿈 * 1 * destroy has a problem * only without local storage * 로컬스토리지에서 불러냈을 때 체크 표시가 화면에 반영 안 됨 / 이것만 하면 1주차 심화까지 완성 Co-authored-by: eastjun <56821976+EastjunDev@users.noreply.github.com>
src/app.js
Outdated
this.removeItem = (index) => { | ||
this.data.splice(index, 1); | ||
|
||
this.filterItems(this.selected); | ||
}; |
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.
필터를 구현하고 나니 아이템 삭제/토글 하는 부분에서 버그
에 대해 고민해보았습니다.
@wooooooood 님께서 적어놓았듯이 해야할 일에 필터가 선택되어 있을 때 보여지는 인덱스와 실제 인덱스가 다르기 때문에 버그가 생기는 것 같습니다.
다른 방법도 많이 있겠지만 저 같은 경우 this.data
객체에 할일의 고유번호인 id 항목을 추가하여 사용하였습니다.
TodoList의 렌더부분에 data-index대신 data-id="${id}"
를 사용하여 map에서 나온 인덱스가 아니라 좀 전의 고유아이디를 적용하였는데요.
Todolist.js
29번째줄을 아래와 같이 수정하여 보여지는 순서에 상관없이 할일의 고유아이디를 얻을 수 있습니다.
if (target.classList.contains('destroy')) {
const {id} = target.closest('.todo-item').dataset;
removeItem(id);
}
위에서 얻은 아이디에 해당하는 할일을 삭제하는 기능을 아래와 같이 구현해 보았습니다.
this.removeItem = (index) => { | |
this.data.splice(index, 1); | |
this.filterItems(this.selected); | |
}; | |
this.removeItem = (id) => { | |
const nextData = this.data.filter((todo) => todo.id.toString() !== id); | |
this.data = nextData; | |
this.filterItems(this.selected); | |
}; |
임시로 삭제부분만 예시를 들었는데 토글도 비슷한 방식으로 적용하면 되지 않을까 생각합니다. 🐸
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.
아하! index를 재활용(?)하고 싶다는 생각밖에 없었는데 말씀해주신 id 방식을 적용하면 해결될것같습니당! 예시까지 작성해주셔서 감사드립니다~👍✨
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.
안녕하세요 나무님! 같은 기수로 참여중인 황준일입니다.
제가 남긴 리뷰에 대해 종합해보자면 다음과 같습니다.
- 컴포넌트의 코드 스타일이 이도 저도 아닌 상태입니다. 그래서 명확한 방향으로 작성하면 좋을 것 같아요!
- ES5 Class: prototype 사용
- ES6 Class: Class 문법 사용
- Functional 사용
- function 내부에서 this에다가 method를 사용하는건 좋지 않은 방법입니다.
- 이벤트와 관련된 메소드가 아니라면 prototype을 통하여 정의해주세요.
- Map을 정확하게 사용해주세요. 지금은 forEach 처럼 사용하고 있습니다.
- 일부 코드에서 DOM을 기준으로 State를 변경하고 있습니다. State를 기준으로 DOM을 변경해주세요.
- state/setState/render 등을 명확하게 분리하여 사용해주세요!
- 지금은 render update 및 각각의 변수명 등으로 흩어져있습니다. 이 상태에서 기능이 더 추가되면 본인이 작성한 코드도 무슨 내용인지 못알아볼 수 있어요!
- state 성격을 띄는 변수는 state 객체 안에 담아주세요.
- state를 변경할땐 꼭 setState를 사용해주세요.
- setState에서만 render를 실행해주세요.
추가로 나무님께서 읽어보시면 좋을 것 같은 글입니다. 같은 기수로 참여중인 @eyabc 님이 작성한 것인데 도움 되는 내용이 많이 있습니다!
추가로 class에 대한 이해가 깊어졌을 때 [es2015+] class문은 특별할까? 이 글도 읽어보시면 좋을 것 같습니다!
아 마지막으로 상태관리에 대한 내용인데 https://www.youtube.com/watch?v=o4meZ7MRd5o 이 동영상도 추천드려요!
제가 같은 팀원은 아니기 때문에 Aprrove나 Request Changes가 아닌 Comment로 남겨놓겠습니다!
src/TodoCount.js
Outdated
export default function TodoCount($todoCount, totalCount) { | ||
this.$todoCount = $todoCount; | ||
this.totalCount = totalCount; | ||
|
||
this.render = (totalCount) => { | ||
this.totalCount = totalCount; | ||
this.$todoCount.innerHTML = `총 <strong>${this.totalCount}</strong> 개`; | ||
}; | ||
|
||
this.render(this.totalCount); | ||
} |
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.
음 지금 이 코드는 매우 애매한 기준으로 작성되었습니다.
무언가 컨셉(?)을 잡고 개선하면 좋을 것 같아요!
- ES5의 class
export default function TodoCount($todoCount, totalCount) {
this.$todoCount = $todoCount;
this.totalCount = totalCount;
this.render(this.totalCount);
}
// prototype을 사용하면 된답니다.
TodoCount.prototype.render = function (totalCount) {
this.totalCount = totalCount;
this.$todoCount.innerHTML = `총 <strong>${this.totalCount}</strong> 개`;
}
- ES6의 class
export default class TodoCount {
$totalCount; totalCount;
constructor ($totalCount, totalCount) {
this.$totalCount = $totalCount;
this.render(totalCount);
}
render (totalCount) {
this.totalCount = totalCount;
this.$todoCount.innerHTML = `총 <strong>${totalCount}</strong> 개`;
}
}
- 함수형
export default function TotalCount ($totalCount, totalCount) {
const render = totalCount => {
$todoCount.innerHTML = `총 <strong>${totalCount}</strong> 개`;
}
render(totalCount);
return { render };
}
// 사용
const totalCount = TotalCount($totalCount, totalCount);
totalCount.render(10);
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/TodoFilter.js
Outdated
@@ -0,0 +1,39 @@ | |||
import {FilterDetails} from './constants.js'; | |||
|
|||
export default function TodoFilter($todoFilter, data, selected, filterItems) { |
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.
TodoFilter 또한 앞서 언급한 TodoCount와 마찬가지입니다.
src/TodoFilter.js
Outdated
let result = ''; | ||
FilterDetails.map(({type, text}) => { | ||
result += ` | ||
<li> | ||
<a class="${type} ${this.selected == type? 'selected': ''}" href="#${type}">${text}</a> | ||
</li> | ||
`; | ||
}); | ||
|
||
this.$todoFilter.innerHTML = result; |
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.
map을 forEach 처럼 사용하고 있네요! map은 새로운 값을 반환해줘야합니다.
let result = ''; | |
FilterDetails.map(({type, text}) => { | |
result += ` | |
<li> | |
<a class="${type} ${this.selected == type? 'selected': ''}" href="#${type}">${text}</a> | |
</li> | |
`; | |
}); | |
this.$todoFilter.innerHTML = result; | |
this.$todoFilter.innerHTML = FilterDetails.map(({type, text}) => ` | |
<li> | |
<a class="${type} ${this.selected == type? 'selected': ''}" href="#${type}">${text}</a> | |
</li> | |
`).join(''); |
즉, 배열의 요소({ type, text })를 string으로 변환하여 새로운 배열을 만든 후에 이어주는거죠!
예를 들자면 다음과 같습니다.
const originArr = [1, 2, 3, 4, 5];
const arr1 = originArr.map(v => v * 10);
const arr2 = originArr.map(v => `${v}${v}`).join('/');
console.log(arr1); // [10, 20, 30, 40, 50]
console.log(arr2); // "11/22/33/44/55"
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.
제가 맵을 이상하게 사용하고 있었네요.. 핃백감사합니다!!!
@@ -0,0 +1,21 @@ | |||
import {KEY} from './constants.js'; | |||
|
|||
export default function TodoInput($todoInput, addItem) { |
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.
TodoCount와 동일합니다.
|
||
export default function TodoInput($todoInput, addItem) { | ||
if (!$todoInput) { | ||
throw new Error('ERROR: Invalid object'); |
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.
throw만 날릴게 아니라 throw를 catch하여 무언가 더 작업하는게 필요할 것 같아요!
src/TodoList.js
Outdated
let result = ''; | ||
this.data.map(({text, isCompleted}, index) => { | ||
result += ` | ||
<li class="todo-item ${isCompleted? 'completed' : ''}" data-index="${index}"> | ||
<div class="view"> | ||
<input class="toggle" type="checkbox" ${isCompleted? 'checked' : ''} /> | ||
<label class="label">${text}</label> | ||
<button class="destroy"></button> | ||
</div> | ||
<input class="edit" value="${text}" /> | ||
</li> | ||
`; | ||
}).join(''); | ||
|
||
this.$todoList.innerHTML = result; |
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.
let result = ''; | |
this.data.map(({text, isCompleted}, index) => { | |
result += ` | |
<li class="todo-item ${isCompleted? 'completed' : ''}" data-index="${index}"> | |
<div class="view"> | |
<input class="toggle" type="checkbox" ${isCompleted? 'checked' : ''} /> | |
<label class="label">${text}</label> | |
<button class="destroy"></button> | |
</div> | |
<input class="edit" value="${text}" /> | |
</li> | |
`; | |
}).join(''); | |
this.$todoList.innerHTML = result; | |
this.$todoList.innerHTML = this.data.map(({text, isCompleted}, index) => ` | |
<li class="todo-item ${isCompleted? 'completed' : ''}" data-index="${index}"> | |
<div class="view"> | |
<input class="toggle" type="checkbox" ${isCompleted? 'checked' : ''} /> | |
<label class="label">${text}</label> | |
<button class="destroy"></button> | |
</div> | |
<input class="edit" value="${text}" /> | |
</li> | |
`).join('');; |
이 부분도 앞서 언급한 것 처럼 이렇게 사용할 수 있습니다!
src/app.js
Outdated
this.filterItems = (type) => { | ||
this.selected = type; | ||
|
||
if (type === FilterOptions.ALL.type) { | ||
this.filteredData = this.data; | ||
} else if (type === FilterOptions.ACTIVE.type) { | ||
this.filteredData = this.data.filter((item) => !item.isCompleted); | ||
} else if (type === FilterOptions.COMPLETED.type) { | ||
this.filteredData = this.data.filter((item) => item.isCompleted); | ||
} | ||
this.todoList.updateItem(this.filteredData); | ||
this.todoCount.render(this.filteredData.length); | ||
}; |
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.
this.filterItems = (type) => { | |
this.selected = type; | |
if (type === FilterOptions.ALL.type) { | |
this.filteredData = this.data; | |
} else if (type === FilterOptions.ACTIVE.type) { | |
this.filteredData = this.data.filter((item) => !item.isCompleted); | |
} else if (type === FilterOptions.COMPLETED.type) { | |
this.filteredData = this.data.filter((item) => item.isCompleted); | |
} | |
this.todoList.updateItem(this.filteredData); | |
this.todoCount.render(this.filteredData.length); | |
}; | |
this.filterItems = (type) => { | |
this.selected = type; | |
this.todoList.updateItem(this.getFilteredItem()); | |
this.todoCount.render(this.getFilteredItem().length); | |
}; | |
this.getFilteredItem = () => | |
this.data.filter(({ isCompleted }) => | |
(this.type === FilterOptions.ALL.type) || | |
(this.type === FilterOptions.COMPLETED.type && isCompleted) || | |
(this.type === FilterOptions.ACTIVE.type && !isCompleted)); |
이런식으로 분리해주면 좋을 것 같습니다!
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.
와 filter
로 이렇게 구현할 수 있다는게 신기하네요,,! 👍
src/app.js
Outdated
const $todoCount = document.querySelector('.todo-count'); | ||
const $todoFilter = document.querySelector('.filters'); | ||
this.data = []; | ||
this.filteredData = []; |
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.
filteredData를 상태로 저장하기보단
data와 selected조합하여 filteredData를 뽑아내는 메소드로 만들면 어떨까요?
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.js
Outdated
this.removeItem = (index) => { | ||
this.data.splice(index, 1); | ||
|
||
this.filterItems(this.selected); | ||
}; |
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.
this.removeItem = (index) => { | |
this.data.splice(index, 1); | |
this.filterItems(this.selected); | |
}; | |
this.removeItem = filteredIndex => { | |
const item = this. filteredData[filteredIndex]; | |
const index = this.data.find(v => v === this.get) | |
this.data.splice(index, 1); | |
this.filterItems(this.selected); | |
}; |
index가 다르더라도 실제 객체는 똑같은걸 공유하기 때문에 이런식으로 작성해주면 된답니다!
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.
혹시 27번줄의 this.get
은 js의 어떤 기능이 있는 코드인가요? 검색해도 나오는 것 같진 않아서 참고로 작성해주신 것이라 생각하고 const index = this.data.findIndex(v => v === item)
으로 수정했습니다 ㅎㅎ;
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.
isCompleted는 다르지만 내용은 동일한 다른 index값을 삭제할 수도 있지 않을까요??
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.
@wooooooood 앗 this.get은 제가 잘못 작성한 것 같아요 ㅠㅠ item이 맞습니다!
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.
this.removeItem = filteredIndex => {
const item = this. filteredData[filteredIndex];
const index = this.data.findIndex(v => v == item)
this.data.splice(index, 1);
this.filterItems(this.selected);
};
제가 졸면서 썼느지 .. 잘못된 부분이 몇 개 있네요 ㅠㅠ find말고 findIndex를 사용해야합니다!
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/constants.js
Outdated
export const FilterDetails = [ | ||
{type: 'all', text: '전체보기'}, | ||
{type: 'active', text: '해야할 일'}, | ||
{type: 'completed', text: '완료한 일'}, | ||
]; | ||
|
||
export const FilterOptions = { | ||
ALL: FilterDetails[0], | ||
ACTIVE: FilterDetails[1], | ||
COMPLETED: FilterDetails[2], | ||
}; |
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 const FilterDetails = [ | |
{type: 'all', text: '전체보기'}, | |
{type: 'active', text: '해야할 일'}, | |
{type: 'completed', text: '완료한 일'}, | |
]; | |
export const FilterOptions = { | |
ALL: FilterDetails[0], | |
ACTIVE: FilterDetails[1], | |
COMPLETED: FilterDetails[2], | |
}; | |
export const FilterOptions = { | |
ALL: 'all', | |
ACTIVE: 'active', | |
COMPLETED: 'completed', | |
}; | |
export const FilterDetails = [ | |
{type: FilterOptions.ALL, text: '전체보기'}, | |
{type: FilterOptions.ACTIVE, text: '해야할 일'}, | |
{type: FilterOptions.COMPLETED, text: '완료한 일'}, | |
]; |
거꾸로 해주는게 좋을 것같아요!
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.
헉 작성하면서도 배열 원소 넣는게 마음에 안들었는데 이런 방법이 있었군요!!😂
🚀 기본 요구사항
🚀🚀 심화 요구사항
🤔 고민
🐞 버그