리액트 문서의 주요개념의 엘리멘트 렌더링 부분부터 보기 시작했다.
첫번째 페이지에 바로 주의
가 나오는데 컴포넌트와 엘리멘트를 혼동할 수 있다고 나오길래 정확히 어떤 차이가 있는지 궁금해졌다.
element는 불변 객체이고, 요소에 메소드가 없지만 component는 hooks를 사용해 상태를 바꿀수 있고, 생명주기가 있다.
라고 나오는데 이걸 보니 더 헷갈려졌다.
구글링을 계속 하다가 우연히 TypeScript의 코드를 보고 차이점을 명확히 깨달았는데,
위 코드를 보면 App이라는 컴포넌트는 Element를 반환해주고 있다.
즉, 컴포넌트는 여러 상태(State)에 따라 element를 구성하고 반환해주는 함수인것이었다.
여기까지 이해하고나니 주의에 적혀있던 마지막 문장이 눈에 보였다.
엘리먼트는 컴포넌트의 구성요소이다
처음부터 이 문장부터 읽었더라면 시간을 좀 더 아낄수 있지 않았을까,,,
함수 컴포넌트는 Props를 가지고 element를 구성하여 반환해줄 수 있다.
아래는 공식문서에서 가져온 Props를 사용하여 element를 구성한 함수 컴포넌트이다.
하지만 우리는 타입스크립트를 가지고 공부를 하고 있기때문에 공식문서처럼 props라고만 쓰면 안된다.
Practice01에서 사용했던 코드를 가져와서 Props를 받을수 있도록 고쳐보자
interface AppProps {
propsString: string
};
const App = (props: AppProps): JSX.Element => {
return (
<div>
{'Hello, World!'}
{props.propsString}
</div>
)
}
props가 어떤 타입으로 정의되어있는지 interface를 사용하여 정의해주었다.
이렇게 고치게 되면 App 컴포넌트는 반드시 Props를 받아야하기 때문에 index.tsx도 고쳐주어야 한다.
이렇게 화가나있는 코드를 아래와 같은 코드로 바꿔주자.
root.render(<App propsString='practice02 props string' />);
실행시켜보면 아래와 같이 잘 바뀐것을 확인할 수 있다.
잘 실행은 되었지만 컴포넌트의 Props 객체는 하나인데 꼭 props.propsString과 같이 써야하나? 그냥 propsString만 사용하면 좋겠다 라는 생각이 들어서 구조 분해 할당
을 해주기로 하였다.
const App = ({ propsString }: AppProps): JSX.Element => {
return (
<div>
{'Hello, World!'}
{propsString}
</div>
)
}
위 코드처럼 props 대신 props에 들어가는 key들을 객체 안에 써주면 바로 사용 할수 있다.
props.propsString
으로 사용하는것 보다 훨씬 보기 좋아진 것 같다.
리스트에 담긴 데이터를 Props로 주는 컴포넌트를 반복적으로 만들고 싶을때는 어떻게 해야할까.
자바스크립트에는 map 함수
가 있는데 원래 리스트의 데이터를 매개변수로 받는 콜백 함수의 리턴값에 따라 다시 계산해준 다음 새로운 리스트 객체로 만들어 리턴해주는 함수이다.
const arr1 = [1,2,3,4,5];
const arr2 = arr1.map((n) => n * 2);
// arr2 == [2,4,6,8,10];
위 코드에서 map함수의 콜백 함수는 리스트의 데이터를 2배씩 늘려주는 함수이다. 따라서 [1,2,3,4,5]
를 각각 2배씩 한 리스트인 [2,4,6,8,10]
을 arr2
에 리턴해주게 된다.
이러한 기능을 하는 map 함수를 사용하면 리스트의 데이터를 사용하여 반복적으로 여러 컴포넌트를 만들어 줄 수가 있다.
import React from 'react';
interface ListComponentProps{
array: number[],
}
const ListComponent = ({ array }: ListComponentProps): JSX.Element => {
const listItem = array.map((n: number) => <li>{n}</li>);
return (<ul>{listItem}</ul>);
}
export default ListComponent;
위 코드는 props로 들어온 배열을 <li>
태그로 감싸주고 감싸진 <li>
태그 엘리먼트들을 <ul>
태그로 감싸서 리턴해주는 컴포넌트를 구현한 것이다.
위와 같은 컴포넌트를 하나 만들어주고 App 컴포넌트에서 아래 코드처럼 사용하게 되면 다음과 같은 결과를 얻을수 있다.
const App = ({ propsString }: AppProps): JSX.Element => {
const numArray = [1,2,3,4,5];
return (
<div>
<div>
{'Hello, World!'}
{propsString}
</div>
<ListComponent array={numArray}/>
</div>
)
}
결과화면은 잘 나오지만 크롬의 console 창을 보면 warning이 발생하고 있는걸 확인 할 수있다.
리스트 엘리먼트를 만들어줄때 key를 넣어달라는 경고인데 이러한 경고가 발생하는 이유가 뭘까
ListComponent 에서 리스트 데이터중 일부가 바뀐다면 <li>
태그의 엘리먼트들은 어떻게 될까?
리액트에서는 최대한 바뀐 데이터의 엘리먼트만을 바꾸려고 노력한다.
그리고 그것을 구분지어주는게 key라는 친구이다.
App 컴포넌트를 다음과 같이 바꿔보자
const App = ({ propsString }: AppProps): JSX.Element => {
const [ numArray, setNumArray ] = useState<number[]>([1,2,3,4,5]);
return (
<div>
<div>
{'Hello, World!'}
{propsString}
<button onClick={() => setNumArray([0,1,2,3,4,5])}>배열 바꾸는 버튼</button>
</div>
<ListComponent array={numArray}/>
</div>
)
}
버튼을 클릭하면 리스트가 [1,2,3,4,5]
에서 [0,1,2,3,4,5]
로 바뀌는 코드이다.
해당 상태에서 최대한 엘리먼트를 안바꾸려면 1,2,3,4,5 의 <li>
는 그대로 두고 0을 데이터로 가지는 <li>태그
만 추가해주면 될것이다.
하지만 실제로 실행시켜보면 다음과 같이 <li>태그
의 모든 엘리먼트가 새로 변경되는것을 확인할 수 있다.
왜 이런결과가 생기는 것일까
현재의 ListComponent에는 key값이 주어지지 않았는데 이러한 경우 리액트는 기본 키값으로 리스트의 인덱스를 받는다.
즉, 맨 앞쪽에 0이 추가가 되었기 떄문에 리액트 입장에서는 각 키값에 대해 모든 요소들이 바뀐것으로 판단하여 모든 엘리먼트를 리렌더링 해주게 된것이다.
이 문제를 해결하기 위해서는 다음과 같이 코드를 변경하면 된다.
const ListComponent = ({ array }: ListComponentProps): JSX.Element => {
const listItem = array.map((n: number) => <li key={n}>{n}</li>);
return (<ul>{listItem}</ul>);
}
좋은 예제는 아니지만 위의 코드처럼 리스트의 데이터 자체를 키값으로 바꿔주면 같은 키값에 대해 1~5까지는 전혀 바뀌지 않았기때문에 원래 의도했던 것처럼 <li>0</li>
만 추가가 된다.
이렇게 key값을 주고 나니 console창의 경고도 사라진 모습을 확인 할 수 있다.
이런식으로 리스트의 데이터를 가지고 반복적으로 엘리먼트를 만드려고 할 때는 해당 데이터의 고유값을 가지고 key값을 설정해주어야 불필요한 요소들의 렌더링을 막을 수 있다.