React

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

코드사냥꾼 2021. 5. 4. 16:53

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

 

데이터를 추가해보자

App 컴포넌트 state에 information이라는 배열을 만들고, 그 안에 샘플 데이터 두 개를 추가할 것이다. 각 전화번호는 다음과 같은 형식으로 담긴다.

{
  id: 0,
  name: '이름',
  phone: '010-0000-0000'
}

 

여기서 id는 각 데이터를 식별하기 위해 사용되는 고유값이다. 수정 및 삭제 기능을 구현할 때 필요하다. 그리고 id 값은 데이터를 추가할 때마다 증가해야 하기 때문에 +1씩 더해줄 것이다.

// src/App.js

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

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 })
    })
  }
  
  render() {
    const { information } = this.state;
    return (
      <div>
        <PhoneForm
          onCreate={this.handleCreate}
        />
        {JSON.stringify(information)}
      </div>
    );
  }
}

export default App;

id 값의 경우, App 컴포넌트의 일반 클래스 내부 변수로 선언했다. 컴포넌트 내부에서 필요한 값들 중 렌더링 되는 것과 상관없는 값들은 굳이 state에 넣어줄 필요가 없기 때문이다. render 함수에서는 information 값을 문자열로 변환하여 보이도록 했다. (이후에 컴포넌트 형태로 렌더링 할 것이다.)

결과는 다음과 같다.

데이터 렌더링

이제 위 배열을 컴포넌트로 변환해보겠다. 여러 개의 컴포넌트를 렌더링하기 위해서는 자바스크립트 배열의 내장 함수인 map을 사용하면 된다.

+ map 함수 알아보기

예를 들어 다음과 같은 배열이 있다고 가정해보자

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

 

배열 a의 원소들에 2를 곱한 값을 출력하고 싶다면 어떻게 해야할까

const a = [1,2,3,4,5];
const b = [];

a.forEach(number => b.push(number * 2));

 

위와 같이 forEach를 사용해서 구현할 수 있지만, map을 사용하면 조금 더 쉽게 해결할 수 있다.

const a = [1,2,3,4,5];
const b = a.map(number => number * 2);

 

정보 표시 컴포넌트 만들기

각 전화번호의 정보를 보여주는 컴포넌트인 PhoneInfo, 여러 개의 PhoneInfo 를 보여주는 컴포넌트인 PhoneInfoList를 생성해보자.

// src/components/PhoneInfo.js

import React, { Component } from 'react';

class PhoneInfo extends Component {
  static defaultProps = {
    info: {
      name: '이름',
      phone: '010-0000-0000',
      id: 0
    }
  }
  
  render() {
    const style = {
      border: '1px solid black',
      padding: '8px',
      margin: '8px'
    };

    const {
      name, phone, id
    } = this.props.info;
    
    return (
      <div style={style}>
        <div><b>{name}</b></div>
        <div>{phone}</div>
      </div>
    );
  }
}

export default PhoneInfo;

이름과 전화번호 값이 담긴 info라는 객체를 props로 받아와서 렌더링해줄 것이다. 그런데 실수로 info 값을 전달해주는 것을 잊어버리게 된다면 컴포넌트가 크래시될 것이다. info가 undefined 일 때는 비구조화 할당을 통해 내부의 값을 받아올 수 없기 때문이다. 따라서 defaultProps를 통하여 info의 기본값을 설정해주었고, 크래시를 예방할 수 있다.

그 다음에는 PhoneInfoList 컴포넌트를 만들어 볼 것이다.

// src/components/PhoneInfoList.js

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

class PhoneInfoList extends Component {
  static defaultProps = {
    data: []
  }

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

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

export default PhoneInfoList;

이 컴포넌트에서는 data라는 배열을 가져와 map을 통하여 JSX로 변환해준다. 이 과정에서 key라는 값도 설정되었는데, 여기서 key는 리액트에서 배열을 렌더링할 때 꼭 필요한 값이다. 리액트는 배열을 렌더링할 때 값을 통하여 업데이트 성능을 최적화하는데 다음 예를 살펴보자.

<div>A</div>
<div>B</div>
<div>C</div>
<div>D</div>

만약에 key를 부여하지 않으면, 배열의 index값이 자동으로 key로 설정되는데, key가 배열의 인덱스로 설정되어있는 상태는 이러하다.

<div key={0}>A</div>
<div key={1}>B</div>
<div key={2}>C</div>
<div key={3}>D</div>

여기서 B와 C 사이에 X를 집어넣는다고 가정해보면

<div key={0}>A</div>
<div key={1}>B</div>
<div key={2}>X</div> [C -> X]
<div key={3}>C</div> [D -> C]
<div key={4}>D</div> [새로 생성됨]

굉장히 비효율적으로 만들어진다. 사실상 중간에 끼워넣기만 하면 되는 것인데, 배열의 index를 key로 사용하게 되어 중간에 값이 들어가면 index도 함께 바뀌어버리기 떄문에 X 아래로 값이 다 바뀌게 되는 것이다.

key를 배열의 index 값으로 사용하는 것이 아니라, 데이터를 추가할 때마다 정적인 고유 값을 부여해주면 리액트가 변화를 감지해내고 업데이트를 하게 될 때 조금 더 똑똑하게 처리할 수 있게 된다.

<div key={0}>A</div>
<div key={1}>B</div>
<div key={2}>C</div>
<div key={3}>D</div>

이번에 다시 B와 C 사이에 다시 X를 넣어보겠다. 이번에 key 값은 고정된 고유값(id)이다.

<div key={0}>A</div>
<div key={1}>B</div>
<div key={5}>X</div> [새로 생성됨]
<div key={2}>C</div> [유지됨]
<div key={3}>D</div> [유지됨]

결국 새로운 DOM은 하나만 생성되고, 나머지는 그대로 유지된다. key값은 언제나 고유한 값으로 유지해야한다. 실제 프로젝트를 예로들자면, DB에 데이터를 추가하면 주로 해당 데이터를 가리키는 고유 id가 있다. 그러한 데이터를 리액트에서 렌더링하게 된다면 그 고유 id를 가지고 key로 사용하면 된다. 지금 경우네는 전화번호 정보에서 id값을 key값을 사용해줬다.

이제 PhoneInfoList 컴포넌트를 App에서 렌더링하고, data값을 props로 전달해보자

// 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 })
    })
  }
  
  render() {
    return (
      <div>
        <PhoneForm
          onCreate={this.handleCreate}
        />
        <PhoneInfoList data={this.state.information}/>
      </div>
    );
  }
}

export default App;

 

결과는 다음과 같다.

가끔씩은 데이터에 고유한 값이 없을 수도 있다. key값이 없는 상태로 렌더링해도 동작은 되지만, 개발자도구 콘솔에서 경고창이 뜨게된다. 그 경고가 보고싶지 않다면

    const list = data.map(
      (info, index) => (<PhoneInfo key={index} info={info}/>)
    );

과 같이 작업하면 되나, 이것은 단순히 경고만 감출 뿐이다.

 

velopert.com/3636

 

누구든지 하는 리액트 7편: 배열 다루기 (1) 생성과 렌더링 | VELOPERT.LOG

이 튜토리얼은 10편으로 이뤄진 시리즈입니다. 이전 / 다음 편을 확인하시려면 목차를 확인하세요. 이번에는 리액트 프로젝트에서 배열을 다루는 방법을 알아보겠습니다. 리액트에서는 배열을

velopert.com