function App() {
const texts = [<p>Hello</p>, <p>World</p>]
return <div className="App">{texts}</div>
}
위의 코드를 실행하면 브라우저에 이러한 경고문이 뜬다.
이 경고는 key 속성을 설정해주면 사라진다.
function App() {
const texts = [<p key="1">Hello</p>, <p key="2">World</p>]
return <div className="App">{texts}</div>
}
리액트 컴포넌트의 Attributes 타입 선언문을 보자.
interface Attributes {
key?: Key | null | undefined;
}
경고문을 표시할 정도라면 그냥 필수 속성으로 해놓지 왜 선택 속성으로 해놓았을까?
아, 그러면 배열에 담겨있지 않은 컴포넌트에도 일일이 key
속성을 추가해줘야 하네...
Key는 어떠한 타입인지 보자.
type Key = string | number;
숫자나 문자열 둘 중 하나로 표현된다.
function App() {
const texts = ['hello', 'world'].map((text, index) => <p key={index}>{text}</p>)
return <div className="App">{texts}</div>
}
map
의 콜백함수에는 index
매개변수가 제공되는데 이를 이용하여 key
값을 설정할 수 있다.
그런데 이러한 방식은 비추한다.
리액트는 컴포넌트에 설정된 key로 컴포넌트를 구분한다.
['hello', 'world'].map((text, index) => <p key={index}>{text}</p>)
위와 같은 컴포넌트가 있을 때 반환값은 아래와 같다.
<p key=0>hello</p>
<p key=1>world</p>
hello 컴포넌트에 key가 0, world 컴포넌트에 key가 1가 대응된다.
동적으로 배열에 아이템을 추가했다고 가정해보자.
['hello', 'world', 'people'].map((text, index) => <p key={index}>{text}</p>)
<p key=0>hello</p>
<p key=1>world</p>
<p key=2>people</p>
리액트는 key가 0, 1인 컴포넌트는 변한것이 없다고 판단해서 key가 2인 컴포넌트만 랜더링한다.
하지만 배열에 아이템을 뒤쪽이 아닌 앞쪽으로 추가했다고 가정해보자.
['people', 'hello', 'world'].map((text, index) => <p key={index}>{text}</p>)
<p key=0>people</p>
<p key=1>hello</p>
<p key=2>world</p>
리액트는 key가 0, 1인 컴포넌트가 변했다고 판단해서 0, 1, 2인 키를 가진 모두를 재 랜더링한다.
아이템이 만개가 넘는다고 가정한다면 성능의 저하가 일어날 수있다.
순서에 따라 달라지는 key가 아닌 아이템별로 고유한 key값을 설정하는 것이 필수다.
children 속성의 타입을 보자.
children?: ReactNode | undefined;
아 물론, children
은 자식 요소를 포함할 수 있는 컴포넌트에서만 사용할 수 있다.
children
속성에 자식 요소를 설정할땐 다음과 같이 사용한다.
function App() {
const texts = ['hello', 'world'].map((text, index) => <p key={index} children={text} />)
return <div className="App" children={texts} />
}
위에서는 리액트 컴포넌트인 p
에 children
속성을 설정했다.
리액트 컴포넌트를 수정하는건 좀 그러니 사용자 컴포넌트 P
를 만들고 P
에 children
속성을 설정해보자.
function App() {
const texts = ['hello', 'world'].map((text, index) => <P key={index} children={text} />)
return <div className="App" children={texts} />
}
그리고 P
에서 children
속성이 어떻게 사용되는지 알아보자.
type PProps = {
children?: ReactNode
}
function P(props: PProps) {
const {children} = props
return <p children={children} />
}
type PProps = {
children?: ReactNode
}
const P: FC<PProps> = props => {
const {children} = props
return <p children={children} />
}
// 또는
const P: FC<PProps> = ({children}) => {
return <p children={children} />
}
리액트18 버전부터는 FC 타입에서 children 속성을 제거하고 PropsWithChildren 이라는 새로운 제네릭 타입을 제공한다.
따라서 리액트18 버전부터는 아래와 같이 사용하도록 하자.
import type {FC, PropsWithChildren} from 'react'
type PProps = {}
const P: FC<PropsWithChildren<PProps>> = ({children}) => {
return <p children={children} />
}
그런데 이렇게 따로 정의되어 있는 타입을 사용할 때는 어떠한 속성이 들어있는지 명확하게 파악하기가 힘들다.
위에만 보아도 PropsWithChildren
타입이 childrend
속성을 포함시켰다.
위의 PropsWithChildren<PProps>
타입의 경우엔 그래도 좀 간단한 편이라서 괜찮지만 다른 타입들을 props로 사용한다고 했을 때 각각의 속성들을 매개변수에 입력해주지 않으면 타입에러가 난다.
따라서 그냥 props
라는 하나의 객체로 받거나 나머지 매개변수를 사용하면된다.
const P: FC<PropsWithChildren<PProps>> = ({children, ...props}) => {
return <p children={children} />
}
JSX는 {...props}
구문을 제공한다.
바로 위의 코드에서 매개변수 props
를 p
컴포넌트의 속성으로 그대로 전달하고 싶을 때 사용한다.
const P: FC<PropsWithChildren<PProps>> = ({children, ...props}) => {
return <p children={children} {...props} />
}
type PProps = {}
class P extends Component<PropsWithChildren<PProps>> {
render(): React.ReactNode {
const {children} = this.props
return <p children={children} />
}
}