본문 바로가기
Front-End/Javascript

[React]내 멋대로 만드는 Todolist-4(feat.Parcel)

by kimik 2019. 5. 11.

1.완료 체크 기능 추가

 

Todo list에 완료체크 기능을 위해 app.js에서 함수를 하나 만듭니다.

handleCheck = (index) => {
  let todos = this.state.todo;
  todos[index].check = !todos[index].check;
  this.setState({
  	todo:todos
  })
}

(todos라는 변수에 기존의 state.todo를 복사하고 todos라는 변수를 수정하여 setState로 담는 것은 state 자체를 직접 수정하면 안되기 때문입니다.)

 

매개변수로 클릭한 li의 index를 넘겨서 그 index값과 매칭된 배열의 check부분을 수정하여 setState를 하는 로직입니다. 

 

그렇다면 해당 함수를 

<TodoList list={this.state.todo} onRemove={this.handleDelete} onCheck={this.handleCheck}/>

Props로 위와같이 넘겨줍니다.

 

그리고 todo.js파일을 열어 

<li className={`todo-text ${ check ? ' checked' : '' }`} onClick={() => {this.props.onCheck(index)}} key={index}>

li 부분을 위와같이 수정해줍니다. className부분에서 todo-text는 언제나 존재하는 class명이고 checked라는 클래스명은 이제 해당 배열의 check값이 true이냐 false이냐에 따라서 항상 다르게 표시 될 겁니다. 

 

클래스명에 따라 li의 모습이 바뀌어야하니 scss파일도 수정하도록하겠습니다.

.list-area {
    ul {margin-top: 20px;}
    li {list-style:none; font-size: 18px; display:flex; line-height: 45px; border-top: 1px solid #ddd;align-items: center; min-height:46px;
        &:first-child {border-top:0;}
        &.checked {
            &:before {content:"✓"; color: #3bc9db;}
            span {text-decoration: line-through; color:#ccc; padding-left:15px;}
            .edit {display: none;}
        }
        input {line-height: 26px; height:30px; box-sizing: border-box;}
        span,input {flex-grow: 8; vertical-align: middle;font-size: 18px; }
        button {flex-grow: 1;font-size: 18px;line-height: 30px; height:30px; border: 1px solid #ccc;margin-left: 10px; vertical-align: middle;  justify-content: center;min-width: 100px; max-width:100px;}
    }
}

list-area부분을 통채로 바꾸었습니다. (이전 포스트 1,2,3에서는 scss에 대한 내용이없고 git에만 올라가 있습니다.)

 

위와같이 css부분을 처리하고 li부분을 누르게되면 

할일목록1처럼 체크가 될 것 입니다. (저는 완료된 목록은 편집할필요없다고 생각되어 none처리 하였습니다.)

 

하지만 위와같이 li에 onclick이벤트를 주게되면 자식요소인 버튼들과 이벤트가 겹치기 때문에 삭제/편집 버튼에서 

onClick 이벤트를 실행할때는 

<button className="del" onClick={(e) => {e.stopPropagation(); this.props.onRemove(index)}}>삭제</button>

위와같이 e.stopPropagation(); 를 넣어 부모요소의 이벤트와 중첩되지 않게 처리해야합니다. 

 

2. 편집기능 넣기

왠지모르게 다른 Todo list 만들기 포스트를 보면 편집 기능이 없는게 늘 아쉬웠습니다. (물론 있는것도 있지만) 

그래서 제멋대로 한번 만들어보았는데 잘 만든소스는 아니니, 여러분도 참고만 하시고 입맛대로 만드시는걸 추천드립니다.

 

일단 해당 목록의 편집버튼을 눌렀을때 해당 목록이 편집중이다라는것을 알아야 하므로, todo 배열에 check 변수외에 modify라는 변수를 만듭니다.

this.state = {
  input:'',
  todo:[{text:"할일목록1",check:false,modify:false},
  {text:"할일목록2",check:false,modify:false}]
}

편집중일때 true값 이어야하므로, false로 처리하고 편집기능을 위한 함수를 만듭니다.

handleModify = (index,e) => {
    let todos = this.state.todo;
    if(e){
        todos[index].text = e.target.value;
    }else{
        todos[index].modify = !todos[index].modify;
    }
    this.setState({
        todo:todos
    })
}

이 함수는 두가지 역할을 하는데, 

1. 편집기능을 눌렀을때 해당 목록의 index만을 전달하여 해당 배열의 modify 부분을 true로 바꾸는 역할

2.편집기능을 눌렀을때 추가된 input태그에 어떤 내용을 입력했을때 해당 목록의 index와 input의 내용(위의 함수에서 매개변수 e)을 전달함으로써 해당배열의 text부분을 바꾸는 역할을 합니다.

 

즉 편집버튼을 클릭해도 handleModify가 실행되고, 편집버튼을 눌렀을때의 input에 onChange에서도 handleModify가 실행된다는 것입니다.

(취향에 따라 분리하셔도 상관없습니다.)

<TodoList list={this.state.todo} onRemove={this.handleDelete} onModify={this.handleModify} onCheck={this.handleCheck} />

완료체크기능과 마찬가지로 Props로 해당함수를 전달하고, 

<ul>
    {list.length && list.map(({text,check,modify},index) => 
        <li className={`todo-text ${ check ? ' checked' : '' }`} onClick={() => {this.props.onCheck(index)}} key={index}>
        {modify ? <input ref={this.props.input} type="text" defaultValue={text} onChange={(e,i) => {this.props.onModify(index,e)}} onClick={(e) => {e.stopPropagation();}} />
            : <span>{text}</span>}
            <button className="del" onClick={(e) => {e.stopPropagation(); this.props.onRemove(index)}}>삭제</button>
            <button className="edit" onClick={(e) => {e.stopPropagation(); this.props.onModify(index)}}>{modify ? '완료' : '편집'}</button></li>
    )}
</ul>

todo.js를 위와같이 수정합니다. jsx내에서 modify라는 변수가 true일때와 false일때에 대한 조건문때문에 좀 복잡해보입니다만, 크게 어려운건 없습니다. 편집버튼을 눌렀을때 Props로 전달받은 함수를 실행해서 해당 목록의 modifty변수가 true가 되고, 그럼 기존에 있던 span태그 대신 input태그가 렌더링 되며 input태그에 내용을 입력할떄 해당 배열의 text가 수정될 것입니다.

 

수정 버튼을 누른 모습

이제 기능적으로는 거의 구현이 완료되었습니다, 다만 편집에서도 목록 추가 버튼과 마찬가지로 Enter버튼을 눌렀을 때 입력이 되면 편할 것 같으니 추가해보도록 하겠습니다. 새로운 함수를 만드는것도 나쁘진 않지만, 이미 app.js에 handleKey라는 함수가 존재하므로, 조금만 수정하면 같이 사용 할 수 있습니다.

handleKey = (e,i) => {
    if(e.key === 'Enter') {
        i ? this.handleModify(i) : this.handleCreate()
    }
}

목록을 추가하는 버튼에는 input의 내용만 전달하면 되지만, 편집 버튼에는 해당 목록에 대한 index가 꼭 필요하므로 그것을 조건문으로추가하면 간단하게 하나의 함수로 처리 할 수 있습니다.

<TodoList list={this.state.todo} onRemove={this.handleDelete} onModify={this.handleModify} onKey={this.handleKey} onCheck={this.handleCheck} />

위와같이 onKey라는 Props로 함수를 넘기고, todo.js에서는 

{modify ? <input ref={this.props.input} type="text" defaultValue={text} onChange={(e,i) => {this.props.onModify(index,e)}} onClick={(e) => {e.stopPropagation();}} onKeyPress={(e) => {this.props.onKey(e,index)}}/>
: <span>{text}</span>}

위와같이 수정하면 enter버튼을 통한 수정이 가능해집니다.

 

3.LocalStorage를 활용하여 Todo list 저장하기

 

모든 기능은 구현됬지만 새로고침만 하면 모든 데이터가 날아가버리니 실사용이 어렵습니다. 단순 포트폴리오로 만드는 것도 좋지만, 이왕 만들었으니 실사용이 가능하도록 업그레이드를 해봅시다.

if(localStorage.todo == 0 || localStorage.todo == undefined){
  localStorage.setItem('todo', JSON.stringify([]));
}

localStorage는 모든 브라우저에 존재하는 저장소입니다. localStorage안에 todo라는 객체가 없다면(처음엔 당연히 없겠죠?) 그냥 빈 배열을 넣어주는 조건문을 app.js 상단에 작성합니다.

constructor(props) {
    super(props);
    this.state = {
        input:'',
        todo:JSON.parse(localStorage.todo)
    }
}

그리고 todo에 해당 localStorage의 todo 객체를 가져옵니다. (JSON parse를 하는 이유는 위의 조건문에서 stringify로 넣었기 때문입니다.)

 

그럼 state.todo의 내용이 변경될때 저장해주는 부분도 있어야겠죠? 하지만 어느시점에 저장을 해야할지 애매합니다.

목록을 추가할때? 그럼 특정 목록을 완료체크하고 새로고침하면 그부분은 저장되지 않을것 같고, 그렇다고 localstorage에 저장하는 함수를 여기저기서 실행하는 것도 좋은 방법은 아닌 것 같습니다. 그래서

render() {
    localStorage.setItem('todo', JSON.stringify(this.state.todo));
    return (
        <div className="container">
        <div className="head-area">
            <h1>React To do list</h1>
        </div>
        <div className="content">
            <TodoInput onInput={this.handleInput} value={this.state.input} onKey={this.handleKey} onCreate={this.handleCreate}/>
            <TodoList list={this.state.todo} onRemove={this.handleDelete} onModify={this.handleModify} onKey={this.handleKey} onCheck={this.handleCheck}/>
        </div>
        </div>
    )
}

위와같이 render되는 시점에 저장을 한다면, setState가 일어나거나 어떤 변화가 생길때 마다 localStorage에 setItem으로 저장을 해준다면 dom의 변화가 있을 때마다 저장할 수 있습니다. 

 

여기까지의 작업은 https://github.com/kimik-hyum/react-todo/tree/modify-check-localStorage

 

마치며..

 

정말 제멋대로 만든것이라 부족한점도 많습니다. 또한 강의형 포스트라기보단 혼자 만들면서 기록용도라 설명이 부족한점도 이해해 주시길 바랍니다. 다른 작업들도 진행중이라 리팩토링 및 수정을 하게 된다면 다시 정리하여 포스팅 하도록 하겠습니다. 

댓글