React

리액트(ReactJS) 전화번호부 만들기 (3)

코드사냥꾼 2021. 5. 4. 17:10

해당 게시물은 인프런의 Velopert님의 유료 강의를 듣고 개인적으로 정리한 내용을 담고 있습니다👩‍💻

 

데이터 삭제하기

기존의 배열 데이터를 건들이지 않으면서 데이터를 제거하기 위해서는, 여러가지 방법이 있을 수 있다. 먼저 자바스크립트 배열을 가지고 연습해보자.

const arr = [1, 2, 3, 4, 5];

arr 배열에서 3을 제거해보자. 기존의 배열은 그대로 유지하고 새 배열을 만들어서 3을 제외시키겠다. 첫번째 방법은 slice와 concat을 이용하는 것이다. 3을 기준으로 좌측의 배열과 우측의 배열을 서로 합쳐주는 것이다.

array.slice(0,2).concat(array.slice(3,5)) //[1, 2, 4, 5]

배열 전개 연산자(...)를 사용하면 다음과 같이 구현할 수도 있다.

[ ...array.slice(0,2), ...array.slice(3,5) ];

하지만 이것보다 훨씬 간단한 방법도 가능하다. 배열에는 filter라는 내장함수가 있는데, 이 함수는 특정 조건에 부합되는 원소들만 뽑아서 새 배열을 만들어준다. 따라서 3을 제외한 배열을 만들기 위해서는 이러한 코드를 작성할 수 있다.

array.filter(num => num !== 3); //[1, 2, 4, 5]

이렇게 하면, 3이 아닌 것들만 필터링되어 새 배열을 만들어준다. 

 

동일한 방식으로, 전화번호 정보를 데이터에서 제외시키는 기능을 구현해보자. id를 파라미터로 받아오는 hadleRemove 함수를 만들고, PhoneInfoList로 전달하자.

//src/App.js

import React, { Component } from 'react';
import PhoneForm from './components/PhoneForm';
import PhoneInfoList from './components/PhoneInfoList';

class App extends Component {
  id = 2
  state = {
    information: [
      {
        id: 0,
        name: '김민준',
        phone: '010-0000-0000'
      },
      {
        id: 1,
        name: '홍길동',
        phone: '010-0000-0001'
      }
    ]
  }
  
  handleCreate = (data) => {
    const { information } = this.state;
    this.setState({
      information: information.concat({ id: this.id++, ...data })
    })
  }
  
  handleRemove = (id) => {
    const { information } = this.state;
    this.setState({
      information: information.filter(info => info.id !== id)
    })
  }
  
  render() {
    const { information } = this.state;
    return (
      <div>
        <PhoneForm
          onCreate={this.handleCreate}
        />
        <PhoneInfoList 
          data={information}
          onRemove={this.handleRemove}
        />
      </div>
    );
  }
}

export default App;

PhoneInfoList에서는 props로 전달받은 onRemove를 그대로 전달해주겠다. 이 함수가 전달되지 않았을 경우를 대비하여 해당 props를 위한 defaultProps를 설정할 것이다. (항상 크래시 예방을 해줘야하나보다..!)

//src/components/PhoneInfoList.js

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

class PhoneInfoList extends Component {
  static defaultProps = {
    list: [],
    onRemove: () => console.warn('onRemove not defined'),
  }

  render() {
    const { data, onRemove } = this.props;
    const list = data.map(
      info => (
        <PhoneInfo
          key={info.id}
          info={info}
          onRemove={onRemove}
        />)
    );

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

export default PhoneInfoList;

그 다음에는 PhoneInfo 쪽에서 삭제 기능을 구현해준다. 삭제 버튼을 만들어서 해당 버튼에 이벤트를 설정하겠다.

//src/components/PhoneInfo.js

import React, { Component } from 'react';

class PhoneInfo extends Component {
  static defaultProps = {
    info: {
      name: '이름',
      phone: '010-0000-0000',
      id: 0
    },
  }

  handleRemove = () => {
    // 삭제 버튼이 클릭되면 onRemove 에 id 넣어서 호출
    const { info, onRemove } = this.props;
    onRemove(info.id);
  }

  render() {
    const style = {
      border: '1px solid black',
      padding: '8px',
      margin: '8px'
    };

    const {
      name, phone
    } = this.props.info;
    
    return (
      <div style={style}>
        <div><b>{name}</b></div>
        <div>{phone}</div>
        <button onClick={this.handleRemove}>삭제</button>
      </div>
    );
  }
}

export default PhoneInfo;

 

 

데이터 수정하기

수정할때도 마찬가지로 불변성을 지켜줘야한다. 기존의 배열과, 그 내부에 있는 객체를 절대로 직접적으로 수정하면 안된다. 

예를 들어서 다음과 같은 객체로 이루어진 배열이 있다고 가정해보자.

const array = [
  { id: 0, text: 'hello', tag: 'a' },
  { id: 1, text: 'world' , tag: 'b' },
  { id: 2, text: 'bye', tag: 'c' }
];

여기서 기존의 값은 건들이지 않고, id가 1인 객체의 text값을 'Korea'라는 값으로 바꾼 새로운 배열을 만들어보겠다.

const modifiedArray = array.map(item => item.id === 1
  ? ({ ...item,. text: 'Korea' }) // id 가 일치하면 새 객체를 만들고, 기존의 내용을 집어넣고 원하는 값 덮어쓰기
  : item) // 바꿀 필요 없는것들은 그냥 기존 값 사용

 

같은 원리로 handleUpdate라는 함수를 만들어 전화번호 정보를 수정해보자. 이 함수는 id와 data라는 파라미터를 받아와서 필요한 정보를 업데이트 해준다. 이 handleUpdate는 PhoneInfoList와 onUpdate로 전달해준다.

//src/App.js

import React, { Component } from 'react';
import PhoneForm from './components/PhoneForm';
import PhoneInfoList from './components/PhoneInfoList';

class App extends Component {
  id = 2
  state = {
    information: [
      {
        id: 0,
        name: '김민준',
        phone: '010-0000-0000'
      },
      {
        id: 1,
        name: '홍길동',
        phone: '010-0000-0001'
      }
    ]
  }
  
  handleCreate = (data) => {
    const { information } = this.state;
    this.setState({
      information: information.concat({ id: this.id++, ...data })
    })
  }
  
  handleRemove = (id) => {
    const { information } = this.state;
    this.setState({
      information: information.filter(info => info.id !== id)
    })
  }
  
  handleUpdate = (id, data) => {
    const { information } = this.state;
    this.setState({
      information: information.map(
        info => id === info.id
          ? { ...info, ...data } // 새 객체를 만들어서 기존의 값과 전달받은 data 을 덮어씀
          : info // 기존의 값을 그대로 유지
      )
    })
  }
  
  render() {
    const { information } = this.state;
    return (
      <div>
        <PhoneForm
          onCreate={this.handleCreate}
        />
        <PhoneInfoList 
          data={information}
          onRemove={this.handleRemove}
          onUpdate={this.handleUpdate}
        />
      </div>
    );
  }
}

export default App;

이제 PhoneInfoList 컴포넌트를 업데이트 해준다.

//src/components/PhoneInfoList.js

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

class PhoneInfoList extends Component {
  static defaultProps = {
    data: [],
    onRemove: () => console.warn('onRemove not defined'),
    onUpdate: () => console.warn('onUpdate not defined'),
  }

  render() {
    const { data, onRemove, onUpdate } = this.props;
    const list = data.map(
      info => (
        <PhoneInfo
          key={info.id}
          info={info}
          onRemove={onRemove}
          onUpdate={onUpdate}
        />)
    );

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

export default PhoneInfoList;

그리고 데이터를 컴포넌트로 렌더링하는 과정에서 PhoneInfo에 onUpdate를 그대로 전달해주었다. 이제 PhoneInfo 컴포넌트를 업데이트 할 차례이다. 이번에 수정할 코드는 꽤 많은데, 주석을 읽어가면서 코드를 작성하자.

//src/components/PhoneInfo.js

import React, { Component } from 'react';

class PhoneInfo extends Component {
  static defaultProps = {
    info: {
      name: '이름',
      phone: '010-0000-0000',
      id: 0
    },
  }

  state = {
    // 우리는 수정 버튼을 눌렀을 떄 editing 값을 true 로 설정해줄것입니다.
    // 이 값이 true 일 때에는, 기존에 텍스트 형태로 보여주던 값들을
    // input 형태로 보여주게 됩니다.
    editing: false,
    // input 의 값은 유동적이겠지요? input 값을 담기 위해서 각 필드를 위한 값도
    // 설정합니다
    name: '',
    phone: '',
  }

  handleRemove = () => {
    // 삭제 버튼이 클릭되면 onRemove 에 id 넣어서 호출
    const { info, onRemove } = this.props;
    onRemove(info.id);
  }

  // editing 값을 반전시키는 함수입니다
  // true -> false, false -> true
  handleToggleEdit = () => {
    const { editing } = this.state;
    this.setState({ editing: !editing });
  }

  // input 에서 onChange 이벤트가 발생 될 때
  // 호출되는 함수입니다
  handleChange = (e) => {
    const { name, value } = e.target;
    this.setState({
      [name]: value
    });
  }


  componentDidUpdate(prevProps, prevState) {
    // 여기서는, editing 값이 바뀔 때 처리 할 로직이 적혀있습니다.
    // 수정을 눌렀을땐, 기존의 값이 input에 나타나고,
    // 수정을 적용할땐, input 의 값들을 부모한테 전달해줍니다.

    const { info, onUpdate } = this.props;
    if(!prevState.editing && this.state.editing) {
      // editing 값이 false -> true 로 전환 될 때
      // info 의 값을 state 에 넣어준다
      this.setState({
        name: info.name,
        phone: info.phone
      })
    }

    if (prevState.editing && !this.state.editing) {
      // editing 값이 true -> false 로 전환 될 때
      onUpdate(info.id, {
        name: this.state.name,
        phone: this.state.phone
      });
    }
  }
  
  render() {
    const style = {
      border: '1px solid black',
      padding: '8px',
      margin: '8px'
    };

    const { editing } = this.state;

    
    if (editing) { // 수정모드
      return (
        <div style={style}>
          <div>
            <input
              value={this.state.name}
              name="name"
              placeholder="이름"
              onChange={this.handleChange}
            />
          </div>
          <div>
            <input
              value={this.state.phone}
              name="phone"
              placeholder="전화번호"
              onChange={this.handleChange}
            />
          </div>
          <button onClick={this.handleToggleEdit}>적용</button>
          <button onClick={this.handleRemove}>삭제</button>
        </div>
      );
    }


    // 일반모드
    const {
      name, phone
    } = this.props.info;
    
    return (
      <div style={style}>
        <div><b>{name}</b></div>
        <div>{phone}</div>
        <button onClick={this.handleToggleEdit}>수정</button>
        <button onClick={this.handleRemove}>삭제</button>
      </div>
    );
  }
}

export default PhoneInfo;

 

코드를 다 작성하였다면 다음과 같은 화면에 수정이 성공적으로 작동할 것이다.

 

 

velopert.com/3638

 

누구든지 하는 리액트 8편: 배열 다루기 (2) 제거와 수정 | VELOPERT.LOG

이 튜토리얼은 10편으로 이뤄진 시리즈입니다. 이전 / 다음 편을 확인하시려면 목차를 확인하세요. 우리는 지난 섹션에서 배열에 데이터를 추가하는 방법과 배열 내부의 내용들을 화면에 보여

velopert.com