2019. 8. 29. 17:40

상태관리

1. Input 컴포넌트(텍스트 입력) 관련 상태 관리

InputTodo 컴포넌트의 부모 컴포넌트인 App.js 에서

  • state에 input값 정의하기
  • input 폼 변경시 변경 이벤트 메서드 생성
//src/components/App.js

import React, { Component } from 'react';
import PageTemplate from './PageTemplate';
import InputTodo from './InputTodo';
import TodoList from './TodoList';

class App extends Component {
    state = {
        input: '',
    };

    onChangeHandler = (e) => {
        const { value } = e.target;
        this.setState({
            input: value,
        });
    };

    render() {
        const { input } = this.state;
        const { onChangeHandler } = this;

        return (
            <PageTemplate>
                <InputTodo onChange={onChangeHandler} value={input} />
                <TodoList />
            </PageTemplate>
        );
    }
}

export default App;

위와같이 state  onChangeHanlder 를 설정하고 InputTodo에 props로 전달한다.

개발 서버를 실행하고 리액트 개발자 도구 React Developer Tools 를 실행하여 입력폼에 데이터가 수정될때 이벤트가 발생하여 state 값이 수정 되는지 확인한다.


 

2. 초기 데이터 정의 하고 렌더링 확인하기

이번에는 TodoList에 props로 초기 데이터를 전달하여보자. state에 todos 객체 배열을 생성하고 초기 데이터를 작성한뒤 TodoList의 props로 전달한다.

//src/components/App.js

import React, { Component } from 'react';
import PageTemplate from './PageTemplate';
import InputTodo from './InputTodo';
import TodoList from './TodoList';

class App extends Component {
    state = {
        input: '',
        todos: [
            { id: 0, text: '첫번째 일정 입니다.', done: true },
            { id: 1, text: '두번째 일정 입니다.', done: false },
        ]
    };

    onChangeHandler = (e) => {
        const { value } = e.target;
        this.setState({
            input: value,
        });
    };

    render() {
        const { input, todos } = this.state;
        const { onChangeHandler } = this;

        return (
            <PageTemplate>
                <InputTodo onChange={onChangeHandler} value={input} />
                <TodoList todos={todos} />
            </PageTemplate>
        );
    }
}

export default App;

TodoList.js 에서는 props 로 전달된 객체배열 todos를 map으로 TodoItem으로 구성된 (랜더링 문법에 맞게) 새로운 배열을 생성하고 이 데이터를 랜더링 한다.

//src/components/TodoList/TodoList.js

import React, { Component } from 'react';
import TodoItem from '../TodoItem';

class TodoList extends Component {
    render() {
        const { todos } = this.props;
        const todoList = todos.map(todo => {
            return <TodoItem key={todo.id} done={todo.done}>{todo.text}</TodoItem>
        });

        return (
            <div>
                {todoList}
            </div>
        );
    }
}

export default TodoList;

개발 서버에 정상적으로 랜더링이 되는지 확인한다.


3. 데이터 추가하기 (To-do item 추가)

input폼에 데이터를 입력하고 추가 버튼을 눌렀을때 list에 추가가 될 수 있게 해보자.

추가 버튼을 누르면 input폼의 값을 기존 todos 객체배열에 추가하고 setState 메소드로 state값을 변경할 수 있는 dataInsertHandler 메서드를 정의 한다.

//src/components/App.js

import React, { Component } from 'react';
import PageTemplate from './PageTemplate';
import InputTodo from './InputTodo';
import TodoList from './TodoList';

class App extends Component {
    state = {
        input: '',
        todos: [
            { id: 0, text: '첫번째 일정 입니다.', done: true },
            { id: 1, text: '두번째 일정 입니다.', done: false },
        ]
    };

    onChangeHandler = (e) => {
        const { value } = e.target;
        this.setState({
            input: value,
        });
    };

    id = 1;
    getId = () => ++this.id;

    dataInsertHandler = () => {
        const { todos, input } = this.state;
        if (input) {
            const newTodos = [
                ...todos,
                {
                    id: this.getId(),
                    text: input,
                    done: false
                }
            ];

            this.setState({
                todos: newTodos,
                input: ''
            });
        }
    };

    render() {
        const { input, todos } = this.state;
        const { onChangeHandler, dataInsertHandler } = this;

        return (
            <PageTemplate>
                <InputTodo onChange={onChangeHandler} value={input} onInsert={dataInsertHandler} />
                <TodoList todos={todos} />
            </PageTemplate>
        );
    }
}

export default App;

객체 내부의 id값을 추가할때마다 증가시킨다.

id = 1;
getId = () => ++this.id;

추가 버튼을 눌렀을때의 핸들러로 input값이 존재할때만 추가를 하며

전개 연산자(…)로 배열을 다루어 좀더 가독성을 높인다.

dataInsertHandler = () => {
        const { todos, input } = this.state;
        if (input) {
            const newTodos = [
                ...todos,
                {
                    id: this.getId(),
                    text: input,
                    done: false
                }
            ];

            this.setState({
                todos: newTodos,
                input: ''
            });
        }
    };

추가가 잘 되나 확인한다.


4. 데이터 수정 (To-do item 완료/미완료 토글)

데이터를 수정하는 기능을 구현해 보자. TodoItem을 클릭했을때 체크 박스 활성화/비활성화를 구현한다.

  • id로 토글 원하는 데이터 찾기
  • 찾은 데이터의 객체를 수정한 새로운 객체배열 만들기
  • state값 업데이트하기
//src/components/App.js

import React, { Component } from 'react';
import PageTemplate from './PageTemplate';
import InputTodo from './InputTodo';
import TodoList from './TodoList';

class App extends Component {
    (...)

    toggleHandler = (id) => {
        const { todos } = this.state;
        const index = todos.findIndex(todo => todo.id === id);

        const toggled = {
            ...todos[index],
            done: !todos[index].done
        };

        const newTodos = [
            ...todos.slice(0, index),
            toggled,
            ...todos.slice(index + 1, todos.length)
        ];
        this.setState({
            todos: newTodos
        })

    };

    render() {
        const { input, todos } = this.state;
        const { onChangeHandler, dataInsertHandler, toggleHandler } = this;

        return (
            <PageTemplate>
                <InputTodo onChange={onChangeHandler} value={input} onInsert={dataInsertHandler}/>
                <TodoList todos={todos} onToggle={toggleHandler}/>
            </PageTemplate>
        );
    }
}

export default App;

 

//src/components/TodoList/TodoList.js

import React, {Component} from 'react';
import TodoItem from '../TodoItem';

class TodoList extends Component {
    render() {
        const { todos, onToggle } = this.props;
        const todoList = todos.map(todo => {
            return <TodoItem
                key={todo.id}
                done={todo.done}
                onToggle={() => onToggle(todo.id)}>
                {todo.text}
            </TodoItem>
        });

        return (
            <div>
                {todoList}
            </div>
        );
    }
}

export default TodoList;

토글이 정상적으로 작동하는지 확인해본다.


5. 데이터 제거 (To-do item 삭제하기)

src/components/App.js

import React, { Component } from 'react';
import PageTemplate from './PageTemplate';
import InputTodo from './InputTodo';
import TodoList from './TodoList';

class App extends Component {
    (...)

    dataRemoveHandler = (id) => {
        const { todos } = this.state;
        const index = todos.findIndex(todo => todo.id === id);

        const newTodos = [
            ...todos.slice(0, index),
            ...todos.slice(index + 1, todos.length)
        ];
        this.setState({
            todos: newTodos
        })
    };

    toggleHandler = (id) => {
        const { todos } = this.state;
        const index = todos.findIndex(todo => todo.id === id);

        const toggled = {
            ...todos[index],
            done: !todos[index].done
        };

        const newTodos = [
            ...todos.slice(0, index),
            toggled,
            ...todos.slice(index + 1, todos.length)
        ];
        this.setState({
            todos: newTodos
        })

    };

    render() {
        const { input, todos } = this.state;
        const {
            onChangeHandler,
            dataInsertHandler,
            toggleHandler,
            dataRemoveHandler
        } = this;

        return (
            <PageTemplate>
                <InputTodo
                    onChange={onChangeHandler}
                    value={input}
                    onInsert={dataInsertHandler}/>
                <TodoList
                    todos={todos}
                    onToggle={toggleHandler}
                    onRemove={dataRemoveHandler}/>
            </PageTemplate>
        );
    }
}

export default App;

 

//src/components/TodoList/TodoList.js

import React, {Component} from 'react';
import TodoItem from '../TodoItem';

class TodoList extends Component {
    render() {
        const { todos, onToggle, onRemove } = this.props;
        const todoList = todos.map(todo => {
            return <TodoItem
                key={todo.id}
                done={todo.done}
                onToggle={() => onToggle(todo.id)}
                onRemove={() => onRemove(todo.id)}>
                {todo.text}
            </TodoItem>
        });

        return (
            <div>
                {todoList}
            </div>
        );
    }
}

export default TodoList;

데이터 수정과 마찬가지로 id로 해당 인덱스를 찾고 해당 인덱스의 객체를 제거한 새로운 객체배열을 생성하여 state값을 업데이트한다.

개발 서버를 실행하여 [지우기] 버튼을 눌르면 예상한것처럼 데이터가 삭제되는것이 아니라 토글이 작동하게 된다.

이와같이 부모요소와 자식요소에 onClick 이벤트가 함께 설정되어 있으면 자식 → 부모 순으로 메서드를 실행한다. 즉, 토글의 onClick 이벤트와 [지우기]의 onClick 이벤트가 겹쳐서 우리가 예상한 동작을 하지 않는것이다. 이것을 propagation 이라고 한다. 이를 방지하려면 자식 요소의 onClick 처리 함수 내부에서 e.stopPropagation 함수를 호출해주어야 한다.

//src/components/TodoItem/TodoItem.js

import React, {Component} from 'react';
import styles from './TodoItem.scss';
import classNames from 'classnames/bind';

const cx = classNames.bind(styles);

class TodoItem extends Component {
  render() {
    const {done, children, onToggle, onRemove} = this.props;

    return (
      <div className={cx('todo-item')} onClick={onToggle}>
        <input className={cx('tick')} type="checkbox" checked={done} readOnly/>
        <div className={cx('text', {done})}>{children}</div>
        <div className={cx('delete')} onClick={(e) => {
          onRemove();
          e.stopPropagation();
        }}>[지우기]</div>
      </div>
    );
  }
}

export default TodoItem;

위와같이 onClick 이벤트에 e.stopPropagation() 을 호출하여준다음에 지우기 버튼이 잘 동작하는지 확인한다.

 

출처:https://juicyjusung.github.io/2019/03/27/react/React-To-do-list-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B6%94%EA%B0%80-%EC%88%98%EC%A0%95-%EC%82%AD%EC%A0%9C/

Posted by yongminLEE