리스트와 Key

Yeom Jae Seon·2021년 2월 7일
0

React공식문서 공부

목록 보기
7/11
post-thumbnail

지금까지 공부한것들


우리는 컴포넌트가 React Element를 리턴하여 결국 ReactDOM.render() 함수에 해당 React Element를 넣던가 해당 엘리먼트를 리턴하는 컴포넌트를 넣어서 DOM을 구성할수 있다 배웠다. (state를 이용해서 컴포넌트 내에서 자체적으로 리 랜더링시킬수 있다.)
그런데 React Element가 원소인 배열에 대해선 어떻게 렌더링할까?

  • 원소가 React Element인 배열은 렌더링 어떻게 시킬까?

원소가 리액트 엘리먼트인 리스트 렌더링하기


예를들면
[<li>햄버거</li>, <li>피자</li>, <li>치킨</li>, <li>간장게장</li>, <li>삶은달걀</li>]
이 리스트를 어떻게 렌더링 시킬까?

그냥 JSX내의 {}를 이용해서 해당 리스트를 넣으면 된다.

물론 root 태그가 있어야하니 <ul></ul>로 wrapper하자.

import React from "react";
import ReactDOM from "react-dom";

const foods = [
  <li>햄버거</li>,
  <li>피자</li>,
  <li>치킨</li>,
  <li>간장게장</li>,
  <li>삶은달걀</li>
];

ReactDOM.render(<ul>{foods}</ul>, document.getElementById("root"));

즉, React Element가 원소인 배열을 렌더링하려면 JSX 내에서 {}를 이용해서 해당 배열을 넣으면 된다. (JSX내부 표현식 {}를 배웠다.)

  • 원소가 React Element인 배열을 렌더링 시키는건 JSX시간에 공부했던 것처럼 JSX내부의 {}를 이용해서 해당 배열을 넣으면 된다.

일반적인 React Element가 원소인 리스트 렌더링


위에서는 그냥 배열안에 React Element들이 있는 배열을 랜더링 했을 뿐이다.
일반적으로는 배열의 원소가 React Element가 아니고 단순히 어떠한 값(혹은 reference)이고 이러한 배열을 props로 받아 컴포넌트에서 해당 배열을 리턴 즉, React Element로 변경해 리턴한다고 한다.

import React from "react";
import ReactDOM from "react-dom";

const Foods = (props) => {
  const foods = props.foods.map((food) => <li>{food}</li>);
  return <ul>{foods}</ul>;
};

const foods = ["햄버거", "피자", "치킨", "간장게장", "삶은달걀"];

ReactDOM.render(<Foods foods={foods} />, document.getElementById("root"));

Foods컴포넌트는 props로 배열을 받아서 해당 배열을 컴포넌트 내에서 원소가 React Element인 원소인 배열로 변경한다음 wrapper 태그인 ul이용해서 리턴한다.
이 과정에서 map이라는 JS array api가 사용되는데 이 api는 입력과 같은 크기의 새로운 배열을 리턴하는데 원소는 map함수 내에서 리턴하는 값으로 변경해서 리턴한다.
즉, 위 예에선 string인 음식이름을 React Element로 변경해서 해당 원소를 가지는 새로운배열을 리턴하는 과정이다.

일반적으론 이렇게 컴포넌트 내에서 단순한 원소를 가진 리스트를 React Element를 가진 리스트로 변경하고 해당 배열을 리턴한다.

그런데 컴포넌트를 이용해서 배열을 렌더링하던
그냥 배열을 가진 변수를 이용해서 렌더링을 시키던간에 동일한 에러가 나왔다.

배열의 각각의 원소는 유일한 key를 가져야 한다고 워닝이 뜬다.
이 워닝이 뜬 이유는 정말 워닝에서 표현하는 그대로 배열의 원소가 key라는 유일한 값을 갖는 props를 가지고있어야하는데 없어서 뜨는 워닝이다.

여기서 key는 React Element로 구성된 리스트를 만들 때 포함해야하는 특수한 문자열 속성이다.

내가한 예제코드에서 설명하면
배열의 원소인<li>음식..</li> 이러한 React Element가 React에서 기본적으로 제공하는 props인 key를 가지고있지 않기 때문이다. (이 문제는 배열을 컴포넌트를 이용해서 렌더링 하던 React Element의 원소를 가지고있는 배열의 변수를 JSX에 넣어서 렌더링하던 동일하게 등장한다.)
즉, React에서는 React Element를 원소로 가지고있는 리스트를 렌더링시키면 뜨는 에러이다.
이를 해결해주기 위해선 각각의 원소(React Element)에 key라는 props를 줘야한다.

각각의 React Element원소에 key속성을 부여하는 작업도 map api를 이용해서 해결해보자.

import React from "react";
import ReactDOM from "react-dom";

const Foods = (props) => {
  const foods = props.foods.map((food) => <li key={food}>{food}</li>);
  return <ul>{foods}</ul>;
};

const foods = ["햄버거", "피자", "치킨", "간장게장", "삶은달걀"];

ReactDOM.render(<Foods foods={foods} />, document.getElementById("root"));

짜잔
이런식으로 key라는 props를 각 원소인 React Element에 줬더니 해결됬다. 여기서는 key props를 줬다기 보단 JSX에 key라는 속성값을 준거겠찌?
<li>음식..</li>는 컴포넌트가 아니닌까! (컴포넌트의 속성과 children을 props로 준다했다..)

  • 일반적으로 React Element를 원소로가진 배열을 렌더링할땐 컴포넌트 내에서 해당 배열을 렌더링 한다.
  • React Element를 원소로 가진 배열을 만들 어 렌더링 할 때 key라는 기본적으로 부여되는 속성을 각 원소(React Element)마다 부여해야 워닝이 사라진다
  • 배열을 렌덜이할때 JS Array api인 map을 이용하면 편하다.

Key


그렇다면 왜 리스트의 React Element원소에 Key속성을 부여하지않으면 리액트는 워닝을 뜨게 하는가?

Key는 React가 리스트 내의 어떤 원소를 추가 또는 삭제등의 행동을 할때 각 원소를 식별하는 것을 도와준다. 즉, Key를 통해서 각 원소에 안정적인 고유성을 부여한다.

왜 안정적인 고유성을 부여하는데?

  • Key를 제공하면 원소에 접근하여 무언가 할때 식별이 되어있어서 더 효율적으로 원소를 관리할수 있기 때문이다.

그럼 Key에 어떤 값을 넣어야하는데? number? boolean?

  • 해당 원소(React Element)를 고유하게 식별가능하게할 문자열을 사용하는게 가장 좋다. 대부분의 경우 데이터의 ID를 key로 이용한다.

참고로 리스트의 원소(React Element)마다 고유한 ID, 문자열이 없다면 기본적으로 제공되는 배열의 index를 이용할수 있다.(0부터 시작하는 그거!)
const foods = props.foods.map((food, index) => <li key={index}>{food}</li>);

그치만 각원소에 key로 리스트의 인덱스를 제공하는 것은 고유하게 식별가능한 key를 제공하는건 아니다. 단순히 임시방편이라할까?

만약 배열의 원소가 몇개 삭제되거나 추가가되어 index가 변하면 다시 key로 제공되는 index는 변경이 된다. 즉, index만으론 고유한 식별이 불가능하다.
그래서 React에선 Key로 index의 전달은 권장되지 않는다.

  • 리스트의 React Element원소에 제공되야하는 Key는 각 원소마다 안정적인 고유성을 부여받기 위해 제공된다.
  • Key는 변하지않는 고유한 문자열로 제공하는것이 적절하다

Key로 컴포넌트 추출하기


하나의 음식에 대해서 하나의 음식만 <li>음식이름..</li>으로 만드는 Food컴포넌트를 만들어 Foods컴포넌트에 제공하는 컴포넌트 추출을 이용해보자.

import React from "react";
import ReactDOM from "react-dom";

const Food = (props) => {
  return <li>{props.food}</li>;
};

const Foods = (props) => {
  const foods = props.foods.map((food) => <Food food={food} />);
  return <ul>{foods}</ul>;
};

const foods = ["햄버거", "피자", "치킨", "간장게장", "삶은달걀"];

ReactDOM.render(<Foods foods={foods} />, document.getElementById("root"));

Food컴포넌트를 Foods컴포넌트에서 추출해서 가독성을 죄금 높였따.

물론 컴포넌트 구조는 바뀌었지만 JSX구조는 똑같다.
즉, DOM에 결국 렌더링되는 DOM Element구조는 똑같다.
개발자 도구창에서 확인해보자.

그럼 여기에서 어디에 Key속성을 부여해야할까? 결국 DOM Element에 반영되는 li JSX에 붙여야할까?

아니다.

Key는 주변 배열의 context에서만 의미가 있다.
말이 좀어려운데 map함수 사용할때 key를 붙이면 된다고 생각하면 쉽다.

  • React Element가 원소인 리스트에 대해 컴포넌트 추출할 때 Key는 map 함수 내부에서 제공하자.

Key는 자신의 배열내, 형제 원소 사이에서만 고유한 값이다.


React Element가 원소인 배열이 두개 있다 치면
두개의 배열의 각 원소간의 Key는 상관이 없다는 뜻이다.
자신을 가지고 있는 배열 안에서 형제 원소사이에서만 고유하면 된다.

이는 Key는 리스트 원소를 추가, 삭제등 여러 행동을 할때 효율성을 도모하기 위해 고유성을 부여한다 생각하면 다른 배열과의 관계는 상관없는건 자명하다.

import React from "react";
import ReactDOM from "react-dom";

const Food = (props) => {
  return <li>{props.food}</li>;
};

const Foods = (props) => {
  const foods = props.foods.map((food) => <Food key={food} food={food} />);
  return <ul>{foods}</ul>;
};

const Drinks = (props) => {
  const drinks = props.drinks.map((drink) => <li key={drink}>{drink}</li>);
  return <ul>{drinks}</ul>;
};

const foods = ["햄버거", "피자", "치킨", "간장게장", "삶은달걀"];
const drinks = ["콜라", "사이다", "물", "오렌지쥬스", "쥬시쿨"];

ReactDOM.render(
  <>
    <Foods foods={foods} /> <Drinks drinks={drinks} />{" "}
  </>,
  document.getElementById("root")
);

Foods 내부에서 만드는 foods 배열과, Drinks내부에서 만드는 drinks배열의 Key는 모두 다르다.

  • Key는 자신이 속한 배열내의 형제 원소 사이에서만 고유하면 되지 다른 배열과는 상관없다.

JSX 내에서 map을 이용해서 바로 배열 렌더링 시키기


JSX내부에서 {}를 이용해 다양한 JS표현식을 사용할수 있다는걸 우린 알고있다.
이걸 이용하잔 의미이다.

예를들면 전시간 조건부렌더링에서 &&나 삼항연산자를 JSX내부에서 표현하는 것과 같은 원리이다.

진짜 하나도안어렵고 위에 내용이랑 똑같다

import React from "react";
import ReactDOM from "react-dom";

const Foods = (props) => {
  return (
    <ul>
      {props.foods.map((food) => (
        <li key={food}>{food}</li>
      ))}
    </ul>
  );
};

const foods = ["햄버거", "피자", "치킨", "간장게장", "삶은달걀"];

ReactDOM.render(<Foods foods={foods} />, document.getElementById("root"));

단순히 map함수를 JSX내에서 {}내부에서 사용하여 바로 리턴한것뿐. 끝!
이 내용을 사용할땐 어떤 방법이 가독성이 좋은지 고민해서 사용하자.
(변수로 받아서 리턴할지, 바로 map을 JSX내부에서 사용할지.)

  • JSX내에서 {}를 이용해서 이 안에 map을 이용해서 바로 배열 렌더링 가능

위 내용은 공식문서를 보고 공부한 내용입니다.
https://ko.reactjs.org/docs/lists-and-keys.html

0개의 댓글