본문 바로가기

Computer Programming/[리액트를 다루는 기술]

[리액트를 다루는 기술] 6. 컴포넌트의 반복 (map, key, 데이터 추가 및 삭제, concat, filter)

1. map 함수 사용하기

map 함수는 기존 배열로 새로운 배열을 만들어 반환한다. 각 요소들을 원하는대로 가공할 수 있다.

const IterationSample = () => {

    const names = ['눈사람', '얼음', '눈', '바람']
    const nameList = names.map(item => <li>{item}</li>)
    return (
        <div>
            <ul>
                {nameList}
            </ul>
        </div>
    )
}

export default IterationSample

 

하지만 위의 코드를 실행하면 콘솔에 warning이 뜬다. 바로 key가 없다는 것

 

 

 

2. Key의 중요성

리액트에서 key는 컴포넌트 '배열'을 렌더링 했을때 어떤 원소에 변동이 있었는지 알아내려고 사용한다. 즉 원소의 생성, 제거, 수정을 알아낼 수 있다. 

만약 key가 없다면 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교하면서 변동을 감지한다. 그런데 key가 존재한다면 순차적으로 모두 알아낼 필요 없이 key로 먼저 비교하기 때문에 훨씬 빠르게 변화를 알아내 렌더링 할 수 있다.

 

key가 identifier 역할을 한다는 정도는 알았지만 리액트에서는 virtual DOM에 직접적인 연관이 있어 효율성에도 좋다는 것을 처음 알게 되었다.

 

key 사용법!

map 함수의 인자로 전달되는 함수내에서 props 처럼 작성하면 된다. 아래에서는 자동 생성되는 index를 활용해 key에 전달했다.

 

* 사실 map의 index를 key로 생성하는것은 그다지 좋은 방법이 아니다. 배열의 원소가 추가되거나 삭제된다면 re-rendering 시 index가 다시 mapping 되고, 그러면 index가 서로 바뀌기 때문에 사용하지 않는 것이 좋다. 데이터가 변할 일이 없다면 사용해도 되지만 다른 방법을 고려해보자 ...

 

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

이렇듯 고유하게 식별할 수 있는 문자열을 사용하는 것이 좋다. 대부분의 경우에는 데이터의 ID를 key로 사용한다.

 

 

 

3. 데이터 추가하기 (concat)

import { useState } from "react"

const IterationSample = () => {
    const [names,setNames] = useState([
        {id:1, text:'눈사람'},
        {id:2, text:'얼음'},
        {id:3, text:'눈'},
        {id:4, text:'바람'},
    ])
    const [inputText, setInputText] = useState('')
    const [nextId, setNextId] = useState(5)
    const changeHandler = (e) => setInputText(e.target.value)
    const clickHandler = () => {
        const newName = names.concat({
            id: nextId,
            text: inputText
        })
        setNextId(nextId + 1)
        setNames( newName )
        setInputText('')
    }

    const nameList = names.map(name => <li key={name.id}>{name.text}</li> )

    return (
        <div>
            <input type="text" value={inputText} onChange={changeHandler} />
            <button onClick={clickHandler}>추가</button>
            <ul>{nameList}</ul>
        </div>
    )
}

export default IterationSample

 

데이터를 추가할때 push 를 사용해도 된다. 하지만 렌더링 최적화를 생각하면 불변성 유지가 중요하기 때문에 원본 배열을 변경하지 않는 concat을 사용하는 것도 방법이다. push는 기존 배열 자체를 변경해주지만 concat은 새로운 배열을 만들어준다. 

 

 

4. 데이터 삭제 (filter)

같은 맥락으로 불변성 유지를 위해 삭제 또한 새로운 배열을 리턴하는 filter()을 사용하는 것이 좋다. filter을 통해 원하는 배열 요소만 골라 새로운 배열을 만들 수 있다.

 

해당 요소를 더블클릭하면 삭제되는 코드를 작성해보자

import { useState } from "react"

const IterationSample = () => {
    const [names,setNames] = useState([
        {id:1, text:'눈사람'},
        {id:2, text:'얼음'},
        {id:3, text:'눈'},
        {id:4, text:'바람'},
    ])
    const [inputText, setInputText] = useState('')
    const [nextId, setNextId] = useState(5)

    const changeHandler = (e) => setInputText(e.target.value)

    const clickHandler = () => {
        const newName = names.concat({
            id: nextId,
            text: inputText
        })
        setNextId(nextId + 1)
        setNames( newName )
        setInputText('')
    }

    const doubleClickHandler = (id) => {
        const newnames = names.filter(name => name.id !== id)
        setNames(newnames)
    }

    const nameList = names.map(name => 
        <li key={name.id} onDoubleClick={() => doubleClickHandler(name.id)}>
            {name.text}
        </li>
    )

    return (
        <div>
            <input type="text" value={inputText} onChange={changeHandler} />
            <button onClick={clickHandler}>추가</button>
            <ul>
                {nameList}
            </ul>
        </div>
    )
}

export default IterationSample

 

 

 

 

불변성 유지 중요!