배열 안에는 여러 개의 요소가 있을 수 있다. 이 요소들이 각기 다른 key 값을 가지면, 리액트는 각 요소를 고유하게 식별할 수 있다. 이는 마치 친구들의 이름처럼 각 요소를 구분하는 역할을 한다.
리액트는 배열의 요소가 바뀔 때, 어떤 요소가 추가되거나 삭제되었는지를 알아야 한다. key가 있으면 리액트는 변화가 생긴 부분만 빠르게 업데이트할 수 있는데 이는 성능을 향상시키고 불필요한 렌더링을 줄여준다.
만약 배열의 요소가 이전과 같은 key를 가지고 있다면, 리액트는 그 요소의 상태(입력값, 클릭 상태 등)를 유지할 수 있다. 반면 key가 없거나 중복된다면, 상태가 잘못될 수 있다.
key가 없으면 리액트는 어떤 요소가 어떤 것인지 헷갈릴 수 있다. 이로 인해 렌더링 결과가 이상해질 수 있는데 각 요소를 정확하게 식별하기 위해서는 key가 필요하다.
//key가 없다면 리액트는 어떤 요소가 어떤 상태를 가지고 있었는지를 알기 어렵다.
[
{ id: 1, text: '사과', isChecked: false },
{ id: 1, text: '바나나', isChecked: false } // 중복된 key
]
//만약 사용자가 첫 번째 항목(사과)의 체크박스를 클릭하면,
//리액트는 두 항목이 동일한 key를 가지고 있기 때문에 두 항목 모두 체크된 것으로 잘못 인식할 수 있다.
💡배열이 아니라 노드 / 컴포넌트에 key 값을 부여한다고 이해해도 괜찮다. 리액트 뿐만 아니라 뷰 등 대부분 프레임워크에서 key 값을 사용한다.
리액트는 자바스크립트 라이브러리로 사용자 인터페이스(User Inerface 줄여서 UI)를 구축하기 위해 사용한다.
리액트는 컴포넌트 기반으로 설계되어 있어 재사용성과 유지보수성이 높다. 리액트를 사용하면 복잡한 UI를 쉽게 관리하고 업데이트할 수 있다.
라이브러리(Library)?
-라이브러리는 특정 기능이나 작업을 수행하기 위해 미리 작성된 코드의 집합 -프로그래밍에서 라이브러리를 사용하면 반복적으로 작성해야 하는 코드를 줄이고, 개발 시간을 단축할 수 있다.
리액트에서 특정 부분이 어떻게 생길지 정하는 선언체를 컴포넌트(Component)라고 한다.
컴포넌트(Component)?
컴포넌트는 리액트에서 UI를 구성하는 기본 단위이다. 각각의 컴포넌트는 독립적으로 동작하며, UI의 특정 부분을 나타낸다. 예를 들어, 버튼, 입력창, 아이템 등과 같은 요소들이 각각의 컴포넌트로 구현될 수 있다.
1. 재사용성: 한 번 작성한 컴포넌트를 여러 곳에서 재사용할 수 있다. 2. 독립성: 각 컴포넌트는 자신의 상태(state)와 속성(props)을 가질 수 있어, 서로 독립적으로 동작한다. 3. 구조화: 복잡한 UI를 작은 단위로 나누어 관리할 수 있어, 코드의 가독성과 유지보수성을 높인다.
컴포넌트는 다른 프레임워크에서 UI를 다룰 때 사용하는 템플릿과는 조금 다르다. 템플릿은 보통 데이터셋이 주어지면 HTML 태그 형식을 문자열로 반환하는데, 컴포넌트는 좀 더 복합접인 개념이다. 재사용이 가능한 API로 수 많은 기능들을 내장하고 있으며, 컴포넌트 하나에 해당 컴포넌트의 생김새와 작동 방식을 정의한다.
API (Application Programming Interface)
소프트웨어 간의 상호작용을 가능하게 하는 일종의 인터페이스
다른 프로그램이나 서비스와 데이터를 주고받거나 기능을 사용할 수 있도록 해주는 규칙이나 프로토콜
클라이언트가 요청을 보내고 서버가 응답하여 클라이언트로 보내면, 클라이언트는 화면에서 서버가 응답한 답을 볼 수 있는데 "클라이언트가 A라는 요청을 보내면 B라는 응답 값을 주세요."라고 약속한 내용을 API라고 할 수 있다.
클라이언트 (정보요청) → API → (정보요청) 서버 ❘❘ 클라이언트 (데이터 전달) ← API ← (데이터 전달) 서버
렌더링(rendering)
웹 페이지나 앱에서 화면에 내용을 보여주는 과정을 말한다. 쉽게 말하면, 그림을 그릴 때 종이에 그리는 것처럼, 컴퓨터가 화면에 글자, 그림, 버튼 등을 그리는 과정을 렌더링이라고 생각하면 된다.
1. 먼저, 컴퓨터는 어떤 내용을 보여줄지 정한다. 예를 들어, "안녕하세요!"라는 글자와 그림이 필요하다고 생각한다면 (준비 단계) 2. 그 다음, 컴퓨터는 이 내용을 화면에 그린다. 이때 글자의 색깔이나 크기, 그림의 위치 등을 정해서 예쁘게 보여준다. (그림을 그리는 단계) 3. 그러다 만약 버튼을 클릭하거나 다른 행동을 하면, 컴퓨터는 다시 내용을 준비하고 다시 그리는 과정을 거치는데 이를 통해 화면이 바뀌고, 새로운 내용이 나타난다. (변화하는 단계)
이런 과정을 통해 웹 페이지에서 필요한 정보를 볼 수 있게 되는데 이걸 렌더링이라고 한다.
어떤 프레임워크, 라이브러리를 사용하든지 간에 맨 처음에 어떻게 보일지를 정하는 초기 렌더링이 필요하다.
초기 렌더링
리액트에서는 렌더링을 다루는 render라는 함수가 있다.
render() {...}
이 함수는 컴포넌트가 어떻게 생겼는지 정의하는 역할을 하는데 html 형식의 문자열을 반환하지 않고 뷰가 어떻게 생겼고 작동하는지에 대한 정보를 지닌 객체를 반환한다.
또, 컴포넌트 내부에는 또 다른 컴포넌트들이 들어갈 수 있는데 이 때 render 함수를 실행하면 그 내부에 있는 컴포넌트들도 재귀적(자기 자신을 호출)으로 렌더링한다. 이렇게 최상위 컴포넌트의 렌더링 작업이 끝나면 지니고 있는 정보들을 사용해서 HTML 마크업을 만들고 이를 우리가 정의하는 실제 페이지의 DOM 요소 안에 주입한다.
컴포넌트를 실제 페이지에 렌더링할 때는 분리된 두가지 절차를 따르는데 먼저 문자열 형태의 HTML 코드를 생성한 후 특정 DOM에 해당 내용을 주입하면 이벤트가 적용된다.
리액트의 조화과정 (리액트가 화면에 보여줄 내용을 업데이트하는 방법.)
1. 초기 렌더링 처음에 리액트는 만들고 싶은 웹 페이지의 기본 모습을 그린다. 예를 들어, "안녕하세요! "라는 글자와 버튼이 있는 화면을 상상한다고 친다면, 이걸 처음으로 그리는 것을 "렌더링"이라고 한다.
2. 상태 변화 감지하기 이제 사용자가 버튼을 클릭하거나 다른 행동을 하면, 리액트는 "어, 누군가 클릭했는데?"라고 생각한다. 이때 리액트는 상태(state)라는 것을 확인하는데, 상태는 컴포넌트가 가지고 있는 정보를 의미한다.
3. 변경할 내용 정하기 리액트는 상태가 바뀌었는지 확인한 후, 어떤 부분을 바꿔야 할지를 정한다. 예를 들어, 버튼을 클릭했을 때 "안녕하세요!" 대신 "환영합니다!"로 바꿔야 한다고 생각해보자.
4. 조화 과정 시작하기 그럼 이제 리액트는 "조화"라는 과정을 시작한다. 조화는 리액트가 새로운 내용을 화면에 적용하는 과정을 말한다. 이때 리액트는 이전에 그린 내용과 새로운 내용을 비교해 낡은 내용은 지우고 새로운 내용을 그린다.
5. 새로운 화면 보여주기 마지막으로, 리액트는 준비한 새로운 내용을 화면에 보여준다. 그러면 버튼을 클릭한 후 "환영합니다!"라는 글자가 나타난다.
이렇게 리액트는 사용자의 행동에 따라 화면을 빠르고 효율적으로 업데이트할 수 있다. 이 과정 덕분에 웹 페이지에서 항상 최신 정보를 볼 수 있게 되는 것이다.
Virtual DOM (가상의 돔)
가상 DOM은 리액트가 화면을 더 빠르고 효율적으로 업데이트할 수 있도록 돕는 특별한 도구라고 생각하면 된다.
DOM(Documet Object Model)?
먼저, DOM은 웹 페이지의 구조를 표현하는 방식으로 브라우저는 HTML을 DOM으로 변환해서 화면에 보여준다.
이 DOM은 우리가 웹 페이지의 내용과 구조를 조작할 수 있도록 해주는 일종의 지도 같은 것이다.
DOM의 문제점
하지만 DOM을 직접 수정하는 것은 속도가 느릴 수 있다.
만약 웹 페이지에 많은 변화가 생기면, 브라우저는 DOM을 매번 업데이트해야 하는데 그러면 시간이 오래 걸리고 성능이 떨어질 수 있는 문제점이 발생하게 된다.
DOM 자체는 빠르다. DOM 자체를 읽고 쓸 때의 성능은 자바스크립트 객체를 처리할 때의 성능과 비교하여 다르지 않지만 웹 브라우저 단에서 DOM에 변화가 일어나 웹 브라우저가 CSS를 다시 연산하고, 레이아웃을 구성하고, 페이지를 리페인트 하는 과정에서 시간이 허비되는 것이다.
가상 DOM의 등장
그래서 가상 DOM이 등장했다. 가상 DOM은 실제 DOM의 가벼운 복사본이라고 생각하면 되는데 리액트는 이 가상 DOM을 사용해서 변화를 계산하고, 실제 DOM과 비교해 어떤 부분만 바꿔야 하는지를 정한다.
가상 DOM 동작 과정
변화 감지: 사용자가 버튼을 클릭하면, 리액트는 상태가 바뀌었다고 감지한다.
가상 DOM 업데이트: 리액트는 새로운 상태를 바탕으로 가상 DOM을 업데이트하는데 이때는 실제 DOM을 건드리지 않아서 빠르게 처리된다.
비교하기: 리액트는 이전의 가상 DOM과 새로운 가상 DOM을 비교해 어떤 부분이 달라졌는지를 확인한다.
최소한의 업데이트: 달라진 부분만 실제 DOM에 적용한다. 필요한 부분만 수정하니 성능이 좋아질 수 밖에 없다.
위 예시 코드처럼 지저분하게 느껴지고, 매번 반복되는 코드를 작성한다는 번거로움이 있다. 개발자들은 이럴 때 라이브러리라는 걸 사용하는데, 다른 개발자가 미리 만들어 놓은 코드를 이용해서 편하게 개발하는 것이다.
클래스네임의 경우에도 편리하게 사용할 수 있는 라이브러리가 많이 있다. 그중에서도 이번에 사용할 라이브러리는 바로 classnames라는 라이브러리이다. 아래 예시 코드를 보시면 알겠지만, 클래스네임에만 집중할 수 있어 훨씬 읽기 편하다. 이렇게 적절한 라이브러리를 쓰면 개발 생산성이 엄청 좋아진다.
3. classnames 라이브러리를 사용
import classNames from 'classnames';
function Button({ isPending, color, size, invert, children }) {
return (
<button
className={classNames(
'Button',
isPending && 'pending',
color,
size,
invert && 'invert',
)}>
{ children }
</button >
);
}
export default Button;
classnames 은 NPM이라는 프로그램을 통해 설치할 수 있다. 터미널에서 npm install classnames 을 입력하고 설치한 다음에, 위 예시처럼 import 로 불러와서 사용하면 된다. NPM 저장소 사이트로 들어가면 사용 방법과 설명이 나와있으니, 아래 링크를 한 번 살펴보고 사용해보는 것도 좋다.
보통 이렇게 Destructuring 문법으로 작성하는데 useState 함수가 초기값을 아규먼트로 받고 그에 따른 실행 결과로 요소 2개를 가진 배열의 형태로 리턴을 하기 때문이다.
이때 첫 번째 요소가 바로 state이고, 두 번째 요소가 이 state를 바꾸는 setter 함수인데, 참고로 위 코드에서도 볼 수 있듯 첫 번째 변수는 원하는 state의 이름(num)을 지어주고, 두 번째 변수에는 state 이름 앞에 set을 붙인 다음 카멜 케이스로 이름을 지어주는 것(setNum)이 일반적이다.
state는 변수에 새로운 값을 할당하는 방식으로 변경하는 것이 아니라 반드시 setter 함수를 활용해야 하는데 setter 함수는 호출할 때 전달하는 아규먼트 값으로 state 값을 변경해 준다. (그래서 const 키워드로 state를 만든다.)
그래서 아래 코드처럼 setter 함수를 활용해서 이벤트 핸들러를 등록해두면, 이벤트가 발생할 때마다 상태가 변하면서 화면이 새로 그려지게 된다.
//App.js
import { useState } from 'react';
import Button from './Button';
import Dice from './Dice';
function App() {
const [num, setNum] = useState(1);
const handleRollClick = () => {
setNum(3); // num state를 3으로 변경!
};
const handleClearClick = () => {
setNum(1); // num state를 1로 변경!
};
return (
<div>
<Button onClick={handleRollClick}>던지기</Button>
<Button onClick={handleClearClick}>처음부터</Button>
<Dice color="red" num={num} />
</div>
);
}
export default App;
//Button.js
import { children } from "react";
function Button({ children, onClick }) {
return <button onClick={onClick}>{children}</button>;
}
export default Button;
//App.js
import { useState } from "react";
import Button from "./Button";
import Dice from "./Dice";
function random(n) { //파라미터 n으로 숫자값을 전달받아서
return Math.ceil(Math.random() * n); //1부터 n까지의 랜덤한 정수를 반환하는 함수
}
function App() {
const [num, setNum] = useState(1);
const handleRollClick = () => {
const nextNum = random(6); //random6을 할당해준 다음 이 값으로 setNum 함수를 호출하면
setNum(nextNum); //던지기 버튼을 누를 때마다 랜덤한 숫자로 num State가 변경
};
const handleClearClick = () => {
setNum(1);
};
return (
<div>
<div>
<Button onClick={handleRollClick}>던지기</Button>
<Button onClick={handleClearClick}>처음부터</Button>
</div>
<Dice color="red" num={num} />
</div>
);
}
export default App;
참조형 State
자바스크립트의 자료형은 크게 기본형(Primitive type)과 참조형(Reference type)로 나눌 수 있다. 특히 참조형 값들은 조금 독특한 특성을 가지고 있어서 변수로 다룰 때도 조금 주의해야 할 부분들이 있었는데 state를 활용할 때도 마찬가지이다.
JSX는 자바스크립트의 확장 문법이다. 리액트로 코드를 작성할 때 HTML 문법과 비슷한 이 JSX 문법을 활용하면 훨씬 더 편리하게 화면에 나타낼 코드를 작성할 수 있다.
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>안녕 리액트! 나는 코린이야!!!</h1>);
JSX 문법
JSX는 자바스크립트로 HTML과 같은 문법을 사용할 수 있도록 만들어주는 편리한 문법이지만, 그만큼 꼭 지켜야 할 규칙들도 있다.
HTML과 다른 속성명
1. 속성명은 카멜 케이스로 작성하기!
JSX 문법에서도 태그에 속성을 지정해 줄 수 있다. 단, 여러 단어가 조합된 몇몇 속성들을 사용할 때는 반드시 카멜 케이스(Camel Case)로 작성해야 한다. 여러 단어가 조합된 HTML 속성들이 많진 않지만, 예를 들면 `onclick`, `onblur`, `onfocus` 등과 같은 이벤트 속성이나, `tabindex` 같은 속성들이 있다. 이런 속성들은 모두 `onClick`, `onBlur`, `onFocus`, `onMouseDown`, `onMouseOver`, `tabIndex` 처럼 작성해야한다.
JSX 문법도 결국은 자바스크립트 문법이기 때문에, `for`나 `class`처럼 자바스크립트의 문법에 해당하는 예약어와 똑같은 이름의 속성명은 사용할 수 없다. 그래서 HTML의 `for`의 경우에는 자바스크립트의 반복문 키워드 `for`와 겹치기 때문에 `htmlFor`로, HTML의 `class` 속성도 자바스크립트의 클래스 키워드 `class`와 겹치기 때문에 `className`으로 작성해 주어야 한다. [React 공식 문서 - 어트리뷰트의 차이]
하지만 이렇게 작성한다면 때로는 꼭 필요하지 않은 부모 태그가 작성될 수 있다. 그럴 땐 `Fragment`로 감싸주면 의미 없는 부모 태그를 만들지 않아도 여러 요소를 작성할 수 있다. 참고로 Fragnent는 빈 태그로 감싸는 단축 문법으로도 활용할 수 있다.
import { Fragment } from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<> //혹은 <Fragnent>
<h1>안녕 리액트! 나는 코린이야!!!</h1>
<p>리액트 너를 산산조각 내기 위해 왔다!</p>
</> //혹은 </Fragnent>
);
자바스크립트 표현식 넣기
JSX 문법에서 중괄호{}를 활용하면 자바스크립트 표현식을 넣을 수 있다. 잘 활용하면, 중괄호 안에서 문자열을 조합할 수도 있고 변수에 이미지 주소를 할당해서 `img` 태그의 `src` 속성값을 전달해 줄 수도 있고, 이벤트 핸들러를 좀 더 편리하게 등록할 수도 있다.
import { Fragment } from 'react';
import ReactDOM from 'react-dom/client';
const product = 'MacBook';
const model = 'Air';
const imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/MacBook_with_Retina_Display.png/500px-MacBook_with_Retina_Display.png'
function handleClick(e) {
alert('곧 도착합니다!');
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
<h1>{product + ' ' + model} 주문하기</h1>
<img src={imageUrl} alt="제품 사진" />
<button onClick={handleClick}>확인</button>
</>
);
코드 창과 결과화면
단, JSX 문법에서 중괄호는 자바스크립트 표현식을 다룰 때 활용하기 때문에, 중괄호 안에서 for, if문 등의 문장은 다룰 수 없다. 그런데도 만약 JSX 문법을 활용할 때 조건문이 꼭 필요하다면 조건 연산자를, 반복문이 꼭 필요하다면 배열의 반복 메소드를 활용해 볼 수 있다.