// 리액트 앱 만들기
// .은 현재 디렉토리에서 프로젝트를 만들 것이라는 뜻
npm init react-app .
// or
npm init react-app [폴더 이름]
// 프로젝트 실행(개발 모드 시행)
npm run start
//개발 모드 종료
Crtl + c
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>안녕 리액트!</h1>);
render 메소드는 첫 번째 아규먼트 값을 활용해서 HTML요소를 만들고, 두 번째 아규먼트 값에 그 요소를 넣어 주는 역할을 함.
JSX는 자바스크립트로 HTML과 같은 문법을 사용할 수 있도록 만들어주는 편리한 문법이지만, 그만큼 꼭 지켜야 할 규칙들도 있습니다.
JSX 문법에서도 태그에 속성을 지정해 줄 수 있다. 단, 여러 단어가 조합된 몇몇 속성들을 사용할 때는 반드시 카멜 케이스(Camel Case)로 작성해야 한다.
단, 예외적으로 HTML에서 비표준 속성을 다룰 때 활용하는 data-*
속성은 카멜 케이스(Camel Case)가 아니라 기존의 HTML 문법 그대로 작성해야 한다.
<div>
상태 변경:
<button className="btn" data-status="대기중">대기중</button>
<button className="btn" data-status="진행중">진행중</button>
<button className="btn" data-status="완료">완료</button>
</div>
JSX 문법도 결국은 자바스크립트 문법이기 때문에, for
나 class
처럼 자바스크립트의 문법에 해당하는 예약어와 똑같은 이름의 속성명은 사용할 수 없다.
그래서 HTML의 for
의 경우에는 자바스크립트의 반복문 키워드 for
와 겹치기 때문에 htmlFor
로, HTML의 class
속성도 자바스크립트의 클래스 키워드 class
와 겹치기 때문에 className
으로 작성해야 한다.
<form>
<label htmlFor="name">이름</label>
<input id="name" className="name-input" type="text" />
</form>
JSX 문법을 활용할 때는 반드시 하나의 요소로 감싸야 한다.
Fragment
로 감싸주면 의미 없는 부모 태그를 만들지 않아도 여러 요소를 작성할 수 있다.
ReactDOM.render(
<Fragment>
<p>안녕</p>
<p>리액트!</p>
</Fragment>
참고로 Fragment
는 아래 코드처럼 빈 태그로 감싸는 단축 문법으로 활용할 수도 있다.
<>
<p>안녕</p>
<p>리액트!</p>
</>
JSX 문법에서 중괄호({})를 활용하면 자바스크립트 표현식을 넣을 수 있다.
import ReactDOM from 'react-dom';
const product = '맥북';
ReactDOM.render(
<h1>나만의 {product} 주문하기</h1>,
document.getElementById('root')
);
단, JSX 문법에서 중괄호는 자바스크립트 표현식을 다룰 때 활용하기 때문에, 중괄호 안에서 for, if문 등의 문장은 다룰 수 없다는 점!
그런데도 만약 JSX 문법을 활용할 때 조건문이 꼭 필요하다면 조건 연산자를, 반복문이 꼭 필요하다면 배열의 반복 메소드를 활용할 수는 있다…
JSX 문법으로 작성한 요소는 결과적으로 자바스크립트 객체가 된다.
import ReactDOM from 'react-dom';
const element = <h1>안녕 리액트!</h1>;
console.log(element);
ReactDOM.render(element, document.getElementById('root'));
{$$typeof: Symbol(react.element), type: "h1", key: null, ref: null, props: {…}, …}
ReactDOM.render
함수의 아규먼트로 전달하게 되면, 리액트가 객체 형태의 값을 해석해서 HTML 형태로 브라우저에 띄워주는 것.Hello
함수가 바로 하나의 컴포넌트다.import ReactDOM from 'react-dom';
function Hello() {
return <h1>안녕 리액트</h1>;
}
const element = (
<>
<Hello />
<Hello />
<Hello />
</>
);
ReactDOM.render(element, document.getElementById('root'));
element
변수 안의 JSX 코드에서 볼 수 있듯 컴포넌트 함수 이름을 통해 하나의 태그처럼 활용할 수 있다.JSX 문법에서 컴포넌트를 작성할 때 컴포넌트에도 속성을 지정할 수 있다. 리액트에서 이렇게 컴포넌트에 지정한 속성들을 Props라고 부른다.
props
에는 children
이라는 프로퍼티(prop, 프롭)가 있다.
JSX 문법으로 컴포넌트를 작성할 때 컴포넌트를 단일 태그가 아니라 여는 태그와 닫는 태그의 형태로 작성하면, 그 안에 작성된 코드가 바로 이 children
값에 담기게 된다.
Button.js
function Button({ children }) {
return <button>{children}</button>;
}
export default Button;
App.js
import Button from './Button';
import Dice from './Dice';
function App() {
return (
<div>
<div>
<Button>던지기</Button>
<Button>처음부터</Button>
</div>
<Dice color="red" num={2} />
</div>
);
}
export default App;
props
의 속성값을 주로 활용하고,children
값을 활용한다.props
는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 데 사용되는 객체이며, children
은 React 컴포넌트의 태그 내에 포함된 내용을 나타냄useState
라는 함수를 쓴다.
import { useState } from 'react';
// ...
const [num, setNum] = useState(1);
// ...
useState
함수가 초깃값을 아규먼트로 받고 그에 따른 실행 결과로 요소 2개를 가진 배열의 형태로 리턴을 하기 때문.num
)을 지어주고, 두 번째 변수에는 state 이름 앞에 set을 붙인 다음 카멜 케이스로 이름을 지어주는 것(setNum
)이 일반적이다.setter 함수를 활용해서 이벤트 핸들러를 등록해두면, 이벤트가 발생할 때마다 상태가 변하면서 화면이 새로 그려지는 것이다.
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;
자바스크립트의 자료형은 크게 기본형(Primitive type)과 참조형(Reference type)로 나뉜다.
// ...
const [gameHistory, setGameHistory] = useState([]);
const handleRollClick = () => {
const nextNum = random(6);
gameHistory.push(nextNum);
setGameHistory(gameHistory); // state가 제대로 변경되지 않는다!
};
// ...
gameHistory
에 push
메소드를 이용해서 배열의 값을 변경한 다음, 변경된 배열을 setter 함수로 state를 변경하려고 하면 코드가 제대로 동작하지 않는다.gameHistory
state는 배열 값 자체를 가지고 있는 게 아니라 그 배열의 주솟값을 참조하고 있는 것이다. 때문에 push
메소드로 배열 안에 요소를 변경했다고 하더라도 결과적으로 참조하는 배열의 주솟값은 변경된 것이 아니다.gameHistory
state가 참조하는 주솟값은 여전히 똑같기 때문에 상태(state)가 바뀌었다고 판단하지 않는 것이다.가장 간단한 방법은 Spread 문법(...
) 을 활용하는 것!!
// ...
const [gameHistory, setGameHistory] = useState([]);
const handleRollClick = () => {
const nextNum = random(6);
setGameHistory([...gameHistory, nextNum]); // state가 제대로 변경된다!
};
// ...
참조형 state를 활용할 땐 반드시 새로운 참조형 값을 만들어서 state를 변경해야 한다는 점!!
기본적으로 HTML 요소들은 DOM 트리라고 하는 자료구조로 저장되어 있다.
리액트는 가상 DOM이라는 자료구조를 사용한다.
그래서 엘리먼트를 새로 렌더링할 때 리액트는 그 모습을 실제 DOM트리에 바로 반영하는 것이 아니라 가상 DOM에 적용한다.
리액트는 state 변경 전의 가상 DOM과 변경 후의 가상 DOM을 비교한다. 그래서 바뀐 부분만 찾아내고 각각에 해당하는 실제 DOM 노드를 변경한다.
가상 DOM을 활용해 효율적인 화면을 처리할 수 있다.
DOM | Virtual DOM | |
---|---|---|
업데이트 | 느림 | 빠름 |
메모리 | 낭비 심함 | 낭비 덜함 |
새로운 element 업데이트 방식 | 새로운 DOM 생성 | 가상 DOM 생성 후 이전 가상 DOM과 비교 후 바뀐 부분만 DOM에 업데이트 |
이미지 파일은 import
구문을 통해 불러오고, 불러온 이미지 주소를 src
속성으로 사용.
import diceImg from './assets/dice.png';
function Dice() {
return <img src={diceImg} alt="주사위 이미지" />;
}
export default App;
리액트에서 인라인 스타일은 문자열이 아닌 객체형으로 사용한다. 프로퍼티 이름은 CSS 속성 이름으로, 프로퍼티 값은 CSS 속성 값으로 쓰는데, 이때 프로퍼티 이름은 아래의 boarderRadius
처럼 대시 기호 없이 카멜 케이스로 써야 한다.
import diceImg from './assets/dice.png';
const style = {
borderRadius: '50%',
width: '120px',
height: '120px',
};
function Dice() {
return <img style={style} src={diceImg} alt="주사위 이미지" />;
}
export default App;
import
구문으로 파일을 불러오고 from
키워드 없이 쓰기
import diceImg from './assets/dice.png';
import './Dice.css';
function Dice() {
return <img src={diceImg} alt="주사위 이미지" />;
}
export default App;
CSS 파일에 정의된 클래스명을 className
prop에 문자열로 넣어주면 됩니다. 이때 재사용성을 위해 className
prop을 부모 컴포넌트에서 받으면 더 좋다.
import diceImg from './assets/dice.png';
import './Dice.css';
function Dice({ className = '' }) {
const classNames = `Dice ${className}`;
return <img className={classNames} src={diceImg} alt="주사위 이미지" />;
}
export default App;
개수가 늘어날수록 아래처럼 알아보기 힘들어진다는 문제점이 있다.
템플릿 문자열을 사용한 예
function Button({ isPending, color, size, invert, children }) {
const classNames = `Button ${isPending ? 'pending' : ''} ${color} ${size} ${invert ? 'invert' : ''}`;
return <button className={classNames}>{children}</button>;
}
export default Button;
배열을 사용한 예
function Button({ isPending, color, size, invert, children }) {
const classNames = [
'Button',
isPending ? 'pending' : '',
color,
size,
invert ? 'invert' : '',
].join(' ');
return <button className={classNames}>{children}</button>;
}
export default Button;
지저분하게 느껴지고, 매번 반복되는 코드를 작성한다는 번거로움이 있다. 개발자들은 이럴 때 라이브러리라는 걸 쓰는데, 다른 개발자가 미리 만들어 놓은 코드를 이용해서 편하게 개발하는 것이다.
클래스네임의 경우에도 편리하게 사용할 수 있는 라이브러리가 많이 있다. → 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 run build
npx serve build
npm init react-app .
터미널에서 원하는 디렉토리에 들어가서 npm init react-app .
를 입력하면 현재 디렉토리에 리액트 프로젝트를 생성
npm start (npm run start)
터미널에서 npm run start
를 입력하면 개발 모드 서버가 실행
ctrl + c
서버가 실행 중인 터미널에서 ctrl + c
를 입력하면 서버가 종료
npm run build
터미널에서 npm run build
를 입력하면 빌드를 시작
npx serve build
터미널에서 npx serve build
를 입력하면 serve 프로그램을 다운 받고 build 폴더에서 서버가 실행