[P3_S1] React 웹 개발 시작하기

보리·2024년 4월 8일
0

codeit-sprint

목록 보기
16/22
// 리액트 앱 만들기
// .은 현재 디렉토리에서 프로젝트를 만들 것이라는 뜻
npm init react-app .

// or
npm init react-app [폴더 이름]

// 프로젝트 실행(개발 모드 시행)
npm run start

//개발 모드 종료
Crtl + c

✨JSX란?

  • JSX는 자바스크립트의 확장 문법
  • 리액트로 코드를 작성할 때 HTML 문법과 비슷한 이 JSX 문법을 활용하면 훨씬 더 편리하게 화면에 나타낼 코드를 작성할 수가 있다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>안녕 리액트!</h1>);

render 메소드는 첫 번째 아규먼트 값을 활용해서 HTML요소를 만들고, 두 번째 아규먼트 값에 그 요소를 넣어 주는 역할을 함.

✨JSX 문법

JSX는 자바스크립트로 HTML과 같은 문법을 사용할 수 있도록 만들어주는 편리한 문법이지만, 그만큼 꼭 지켜야 할 규칙들도 있습니다.

HTML과 다른 속성명

1. 속성명은 카멜 케이스로 작성하기

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>

2. 자바스크립트 예약어와 같은 속성명은 사용할 수 없다

JSX 문법도 결국은 자바스크립트 문법이기 때문에, forclass처럼 자바스크립트의 문법에 해당하는 예약어와 똑같은 이름의 속성명은 사용할 수 없다.
그래서 HTML의 for의 경우에는 자바스크립트의 반복문 키워드 for와 겹치기 때문에 htmlFor로, HTML의 class 속성도 자바스크립트의 클래스 키워드 class와 겹치기 때문에 className으로 작성해야 한다.

  <form>
    <label htmlFor="name">이름</label>
    <input id="name" className="name-input" type="text" />
  </form>

반드시 하나의 요소로 감싸기 - Fragment

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 형태로 브라우저에 띄워주는 것.
  • 리액트 엘리먼트는 리액트로 화면을 그려내는데 가장 기본적인 요소다.

리액트 컴포넌트

  • 리액트 컴포넌트는 리액트 엘리먼트를 조금 더 자유롭게 다루기 위한 하나의 문법이다.
  • 컴포넌트를 만드는 가장 간단한 방법은 자바스크립트의 함수를 활용하는 것임.
  • 아래 코드에서 JSX 문법으로 작성된 하나의 요소를 리턴하는 Hello 함수가 바로 하나의 컴포넌트다.
import ReactDOM from 'react-dom';

function Hello() {
  return <h1>안녕 리액트</h1>;
}

const element = (
  <>
    <Hello />
    <Hello />
    <Hello />
  </>
);

ReactDOM.render(element, document.getElementById('root'));
  • 컴포넌트를 작성하면, 위 코드에서 element 변수 안의 JSX 코드에서 볼 수 있듯 컴포넌트 함수 이름을 통해 하나의 태그처럼 활용할 수 있다.
  • 이런 특성을 모듈 문법으로 활용하면 훨씬 더 독립적으로 컴포넌트 특성에 집중해서 코드를 작성할 수 있다.
  • 리액트 컴포넌트의 이름은 반드시 첫 글자를 대문자로 작성해야 한다는 것.

✨Props

JSX 문법에서 컴포넌트를 작성할 때 컴포넌트에도 속성을 지정할 수 있다. 리액트에서 이렇게 컴포넌트에 지정한 속성들을 Props라고 부른다.

  • Props는 Properties의 약자
  • 컴포넌트에 속성을 지정해주면 각 속성이 하나의 객체로 모여서 컴포넌트를 정의한 함수의 첫 번째 파라미터로 전달된다.

✨Children

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;
  • JSX 문법으로 컴포넌트를 작성할 때 어떤 정보를 전달할 때는 일반적인 props의 속성값을 주로 활용하고,
  • 화면에 보여질 모습을 조금 더 직관적인 코드로 작성하고자 할 때 children 값을 활용한다.
  • props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 데 사용되는 객체이며, children은 React 컴포넌트의 태그 내에 포함된 내용을 나타냄

✨State

  • state는 상태가 바뀔 때마다 화면을 새롭게 그려내는 방식으로 동작을 한다.
  • 리액트에서 state를 만들고, state를 바꾸기 위해 useState라는 함수를 쓴다.

import { useState } from 'react';

// ...

  const [num, setNum] = useState(1);

// ...
  • 보통 Destructuring 문법으로 작성한다.
  • useState 함수가 초깃값을 아규먼트로 받고 그에 따른 실행 결과로 요소 2개를 가진 배열의 형태로 리턴을 하기 때문.
  • 첫 번째 요소가 바로 state이고, 두 번째 요소가 이 state를 바꾸는 setter 함수
  • 첫 번째 변수는 원하는 state의 이름(num)을 지어주고, 두 번째 변수에는 state 이름 앞에 set을 붙인 다음 카멜 케이스로 이름을 지어주는 것(setNum)이 일반적이다.
  • state는 변수에 새로운 값을 할당하는 방식으로 변경하는 것이 아니라 이 setter 함수를 활용해야 한다. setter 함수는 호출할 때 전달하는 아규먼트 값으로 state 값을 변경해 준다.

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;

✨참조형 State

자바스크립트의 자료형은 크게 기본형(Primitive type)과 참조형(Reference type)로 나뉜다.

// ...

  const [gameHistory, setGameHistory] = useState([]);

  const handleRollClick = () => {
    const nextNum = random(6);
    gameHistory.push(nextNum);
    setGameHistory(gameHistory); // state가 제대로 변경되지 않는다!
  };

// ...
  • 배열 값을 가진 gameHistorypush 메소드를 이용해서 배열의 값을 변경한 다음, 변경된 배열을 setter 함수로 state를 변경하려고 하면 코드가 제대로 동작하지 않는다.
  • gameHistory state는 배열 값 자체를 가지고 있는 게 아니라 그 배열의 주솟값을 참조하고 있는 것이다. 때문에 push 메소드로 배열 안에 요소를 변경했다고 하더라도 결과적으로 참조하는 배열의 주솟값은 변경된 것이 아니다.
  • 결과적으로 리액트 입장에서는 gameHistory state가 참조하는 주솟값은 여전히 똑같기 때문에 상태(state)가 바뀌었다고 판단하지 않는 것이다.
  • 참조형 state를 활용할 때는 반드시 새로운 참조형 값을 만들어 state를 변경해야 한다.

가장 간단한 방법은 Spread 문법(...) 을 활용하는 것!!


// ...

  const [gameHistory, setGameHistory] = useState([]);

  const handleRollClick = () => {
    const nextNum = random(6);
    setGameHistory([...gameHistory, nextNum]); // state가 제대로 변경된다!
  };

// ...

참조형 state를 활용할 땐 반드시 새로운 참조형 값을 만들어서 state를 변경해야 한다는 점!!

✨컴포넌트

📘컴포넌트 장점

  • 반복적인 개발 줄어듦.
  • 오류를 고치기 쉽다.
  • 일을 쉽게 나눌 수 있다.

✨리액트가 렌더링하는 방식

기본적으로 HTML 요소들은 DOM 트리라고 하는 자료구조로 저장되어 있다.

Untitled

리액트는 가상 DOM이라는 자료구조를 사용한다.

그래서 엘리먼트를 새로 렌더링할 때 리액트는 그 모습을 실제 DOM트리에 바로 반영하는 것이 아니라 가상 DOM에 적용한다.

리액트는 state 변경 전의 가상 DOM과 변경 후의 가상 DOM을 비교한다. 그래서 바뀐 부분만 찾아내고 각각에 해당하는 실제 DOM 노드를 변경한다.

  • 개발자가 직접 DOM 노드를 신경쓸 필요가 없어 단순하고 깔끔한 코드를 작성할 수 있다.
  • 변경 사항들을 리액트가 적당히 모아 적당히 나눠서 브라우저에 전달한다. → 변경사항들을 효율적으로 관리할 수 있다.

가상 DOM을 활용해 효율적인 화면을 처리할 수 있다.

⚠️DOM vs Virtual DOM

DOMVirtual 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;

📘CSS 파일 불러오기

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라는 라이브러리

클래스네임에만 집중할 수 있어 훨씬 읽기 편해진다.

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 폴더에서 서버가 실행

  • 브라우저는 jsx를 바로 이해하지 못한다
  • 그래서 Babel 같은 트랜스파일러가 우리의 jsx 코드를 트랜스파일해 줘야 한다
  • 리액트 프로젝트 배포 전 '빌드'라는 과정을 거쳐야 하는데, 빌드 과정 중에 트랜스파일링도 이루어진다
profile
정신차려 이 각박한 세상속에서

0개의 댓글