우리는 컴포넌트가 React Element를 리턴하여 결국 ReactDOM.render()
함수에 해당 React Element를 넣던가 해당 엘리먼트를 리턴하는 컴포넌트를 넣어서 DOM을 구성할수 있다 배웠다. (state를 이용해서 컴포넌트 내에서 자체적으로 리 랜더링시킬수 있다.)
그런데 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들이 있는 배열을 랜더링 했을 뿐이다.
일반적으로는 배열의 원소가 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로 준다했다..)
map
을 이용하면 편하다.그렇다면 왜 리스트의 React Element원소에 Key
속성을 부여하지않으면 리액트는 워닝을 뜨게 하는가?
Key
는 React가 리스트 내의 어떤 원소를 추가 또는 삭제등의 행동을 할때 각 원소를 식별하는 것을 도와준다. 즉, Key
를 통해서 각 원소에 안정적인 고유성을 부여한다.
왜 안정적인 고유성을 부여하는데?
Key
를 제공하면 원소에 접근하여 무언가 할때 식별이 되어있어서 더 효율적으로 원소를 관리할수 있기 때문이다.그럼 Key에 어떤 값을 넣어야하는데? number? boolean?
참고로 리스트의 원소(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의 전달은 권장되지 않는다.
하나의 음식에 대해서 하나의 음식만 <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는 상관이 없다는 뜻이다.
자신을 가지고 있는 배열 안에서 형제 원소사이에서만 고유하면 된다.
이는 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
는 모두 다르다.
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내부에서 사용할지.)
{}
를 이용해서 이 안에 map
을 이용해서 바로 배열 렌더링 가능위 내용은 공식문서를 보고 공부한 내용입니다.
https://ko.reactjs.org/docs/lists-and-keys.html