본문 바로가기

WINK-(Web & App)/React.js 스터디

[2024 React.js 스터디] 박건민 #4주차

반응형

 

오늘은 리액트에 대해서 알아볼게요!

 

01. 리액트는 어쩌다 만들어졌을까?

  • JavaScript를 사용하여 HTML 로 구성한 UI 를 제어한다면, 브라우저의 DOM Selector API 를 사용해서 특정 DOM 을 선택한 뒤, 특정 이벤트가 발생하면 변화를 주도록 설정해야 한다.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Number Counter</title>
</head>
<body>
  <h2 id="number">0</h2>
  <div>
    <button id="increase">+1</button>
    <button id="decrease">-1</button>
  </div>

  <script>
    const number = document.getElementById('number');
    const increase = document.getElementById('increase');
    const decrease = document.getElementById('decrease');

    increase.onclick = () => {
      const current = parseInt(number.innerText, 10);
      number.innerText = current + 1;
    };

    decrease.onclick = () => {
      const current = parseInt(number.innerText, 10);
      number.innerText = current - 1;
    };
  </script>
</body>
</html>

 

  • 위 html/JS 코드는 +1 버튼이 눌리면, id 가 number 인 DOM 을 선택해서 innerText 속성을 1씩 더하는 규칙이 있다.
  • 위와 같은 방식은 인터랙션이 자주 발생한다면 이벤트 처리 로직이 복잡해지고 관리하기 힘들어진다.

 

- DOM이란?

  • Document Object Model의 약자로, HTML요소를 JavaScript Object처럼 조작할 수 있는 Model이다.
  • DOM의 구조는 트리구조로, 아래에 여러 구성요소가 부모-자식 관계를 가지고 있다.
  • DOM에서 제공하는 API를 변경하여 Element의 상태를 변경할 수 있다.
  • DOM이 변경되고 업데이트가 된다는 것은 결국 Browser의 렌더링 엔진 또한 Reflow 및 Repaint 한다는 것을 의미한다.
  • DOM을 수정할 때는 해당 Element를 찾고 해당 Element와 자녀 Element를 제거한 뒤 수정된 Element들로 교체한다. 그리고 CSS와 Layout을 수정하고 Browser에 새롭게 업데이트 하는 과정을 거친다.
  • 매번 DOM을 조작할 때마다 UI를 새롭게 그리는 작업은 비용이 많이 드는 작업이기에 비효율적이다.

DOM의 구조 / Browser의 렌더링 과정

 

- React와 Virtual DOM

  • Browser Reflow 및 Repaint를 최소화 하기 위해 React는 Virtual DOM을 사용한다.
  • Virtual DOM은 실제 DOM에 접근하며 조작하는 대신, 이를 추상화한 자바스크립트 객체를 구성하여 DOM의 상태를 메모리에 저장하고, 변경 전과 변경 후의 상태를 비교 한뒤 최소한의 내용만 반영하여 성능 향상을 이끌어낸다.
  • 상태를 변경하는 작업(Change of State)이 발생하면, React는 Virtual DOM에 저장된 이전 상태와 변경된 현재 상태를 비교한다.
  • 비교 과정에서 React는 Diffing 알고리즘을 사용하여 변경된 부분을 감지한다.
  • 이후 Virtual DOM과 새로운 Virtual DOM을 비교하여 변경이 필요한 부분만 실제 DOM에 업데이트(재조정, Reconciliation) 한다.
  • 이러한 과정을 통해 React는 성능을 최적화하고 불필요한 리랜더링을 최소화할 수 있다.

Virtual DOM을 활용한 DOM 조작 처리 과정

 

 

02. 작업환경 준비

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
$ nvm install --lts

 

 

- 새 프로젝트 만들어보기

  • 터미널에 다음 명령어를 입력하여 새로운 프로젝트 생성
$ npx create-react-app begin-react

 

  • begin-react 디렉터리 생성 및 하위 React 프로젝트 생성
  • 이후 cd 명령어를 사용하여 해당 디렉터리에 들어간 다음 yarn start 명령어를 입력(yarn 이 없다면 npm start).
$ cd begin-react
$ yarn start

 

 

03. 나의 첫 번째 리액트 컴포넌트

  • src 디렉터리에 Hello.js 라는 파일을 다음과 같이 작성

Hello.js

import React from 'react';

function Hello() {
  return <div>안녕하세요</div>
}

export default Hello;

 

  • React 컴포넌트를 만들 때는 "import React from 'react';" 를 통하여 React를 불러와야 한다.
  • React 컴포넌트는 함수형태로 작성 할 수도 있고 클래스형태로도 작성 할 수 있다.
  • 최하단의 "export default hello;" 코드는 Hello 라는 컴포넌트를 내보내겠다는 의미

 

 

APP.js

import React from 'react';
import Hello from './Hello';

function App() {
  return (
    <div>
      <Hello />
    </div>
  );
}

export default App;

 

 

  • 컴포넌트는 재사용이 가능한 각각의 독립된 모듈이다. (재사용 가능한 UI 코드 조각)

 

import React from 'react';
import Hello from './Hello';


function App() {
  return (
    <div>
      <Hello />
      <Hello />
      <Hello />
    </div>
  );
}

export default App;

 

 

 

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
  • 여기서 ReactDOM.render 의 역할은 브라우저에 있는 실제 DOM 내부에 리액트 컴포넌트를 렌더링하겠다는 것을 의
  • id  root 인 DOM 을 선택, public/index.html 내부의 <div id="root"></div>를 지칭
  • React 컴포넌트가 렌더링 될 때에는, React 애플리케이션의 루트 컴포넌트가 주어진 루트 DOM 요소에 렌더링된다.

 

04. JSX의 기본 규칙 알아보기

  • JSX(Javascript Syntax eXtension) 는 리액트에서 생김새를 정의할 때, 사용하는 문법(JavaScript)이다.
  • 리액트 컴포넌트 파일에서 XML 형태로 코드를 작성하면 babel 이 JSX 를 JavaScript 로 변환을 해준다.
  • 아직 지원되지 않는 최신 문법이나, 편의상 사용하거나 실험적인 자바스크립트 문법들을 정식 자바스크립트 형태로 변환해줌으로서 구형 브라우저같은 환경에서도 제대로 실행 할 수 있게 해주는 역할이다.

 

- 태그는 닫혀 있어야 한다

mport React from 'react';
import Hello from './Hello';

function App() {
  return (
    <div>
      <Hello />
      <Hello />
      <Hello />
      <div>
    </div>
  );
}

export default App;

 

  • 위 코드와 같이 <div>를 열고 닫지 않으면 컴파일 에러가 난다.
  • 태그와 태그 사이에 내용이 들어가지 않을 때에는, Self Closing 태그 라는 것을 사용해야 한다.
  • 현재 Hello 컴포넌트를 사용 할 때 Self Closing 태그를 사용
  • 두 개 이상의 태그는 무조건 하나의 태그로 감싸져 있어야 한다.

 

import React from 'react';
import Hello from './Hello';

function App() {
  return (
    <>
      <Hello />
      <div>안녕히계세요</div>
    </>
  );
}

export default App;

 

  • <div>로 감싸기 어려운 경우 React의 Fragment를 사용하여 감쌀 수도 있다.
  • 태그를 이름 없이 작성 시 Fragment가 만들어지는데, Fragment는 Browser상에서 별도의 Element로 나타나지 않는다.

 

- JSX 안에 자바스크립트 값 사용하기

import React from 'react';
import Hello from './Hello';

function App() {
  const name = 'react';
  return (
    <>
      <Hello />
      <div>{name}</div>
    </>
  );
}

export default App;

 

  • JSX 내부에 자바스크립트 변수를 보여줘야 할 때에는 {} 으로 감싸서 보여준다.

 

- style 과 className

  • JSX 에서 태그에 style 과 CSS class 를 설정하는 방법은 HTML 에서 설정하는 방법과 다르다.
  • 우선, 인라인 스타일은 객체 형태로 작성 해야 하며, camelCase 형태로 네이밍 해야 한다.
  • CSS class 를 설정 할 때에는 "class= "가 아닌 "className=" 으로 설정을 해야 한다.
import React from 'react';
import Hello from './Hello';
import './App.css';


function App() {
  const name = 'react';
  const style = {
    backgroundColor: 'black',
    color: 'aqua',
    fontSize: 24, // 기본 단위 px
    padding: '1rem' // 다른 단위 사용 시 문자열로 설정
  }

  return (
    <>
      <Hello />
      <div style={style}>{name}</div>
      <div className="gray-box"></div>
    </>
  );
}

export default App;

 

- 주석

  • JSX에서 주석은 "{/* 주석 */}" 와 같은 형태로 작성할 수 있다.
  • 열리는 태그 내부에서는 "//" 를 사용하여 뒤에 오는 문장을 주석 처리할 수 있다.

 

 

05. props를 통해 컴포넌트에게 값 전달하기

  • props는 상위 컴포넌트가 하위 컴포넌트에 값을 전달할 때 사용하는 속성
  • 상위 컴포넌트가 하위 컴포넌트에 값을 전달하기 때문에  단방향 데이터 흐름을 갖는다.
  • 부모 컴포넌트는 수정 가능하지만, 자식 컴포넌트는 읽기만 가능 
  • props는 properties의 줄임말

 

- props의 기본 사용법

 

App.js

import React from 'react';
import Hello from './Hello';

function App() {
  return (
    <Hello name="react" />
  );
}

export default App;

 

 

Hello.js

import React from 'react';

function Hello(props) {
  return <div>안녕하세요 {props.name}</div>
}

export default Hello;

 

  • 컴포넌트에게 전달되는 props 는 파라미터를 통하여 조회 할 수 있다.
  • props 는 객체 형태로 전달되며, name 값을 조회하고 싶다면 props.name 을 조회하면 된다.

 

- 여러개의 props, 비구조화 할당

 

App.js

import React from 'react';
import Hello from './Hello';

function App() {
  return (
    <Hello name="react" color="red"/>
  );
}

export default App;

 

 

Hello.js

import React from 'react';

function Hello(props) {
  return <div style={{ color: props.color }}>안녕하세요 {props.name}</div>
}

export default Hello;

 

  • Hello 컴포넌트에서 color 값을 조회해서 폰트의 색상으로 설정하는 코드
  • 함수의 파라미터에서 비구조화 할당 (혹은 구조 분해) 문법을 사용하면 조금 더 코드를 간결하게 작성 가능
  • 비구조화할당이란, 배열이나 객체 속성을 해체하여 개별 변수에 값을 담을 수 있는 JavaScript 표현식을 말한다.
  • 위의 코드에서 { color, name }은 객체 구조 분해를 나타낸다.
  • 함수의 매개변수로 전달되는 객체에서 colorname 속성을 추출하여 새로운 변수에 할당한다.

 

- defaultProps 로 기본값 설정

 

수정된 Hello.js 코드

import React from 'react';

function Hello({ color, name }) {
  return <div style={{ color }}>안녕하세요 {name}</div>
}

Hello.defaultProps = {
  name: '이름없음'
}

export default Hello;

 

  • 컴포넌트에 props 를 지정하지 않았을 때  기본 값을 설정하고 싶다면 컴포넌트에 defaultProps 라는 값을 설정하면 된다.

 

- props.children

 

Wrapper.js

import React from 'react';

function Wrapper({ children }) {
  const style = {
    border: '2px solid black',
    padding: '16px',
  };
  return (
    <div style={style}>
      {children}
    </div>
  )
}

export default Wrapper;

 

 

App.js

import React from 'react';
import Hello from './Hello';
import Wrapper from './Wrapper';

function App() {
  return (
    <Wrapper>
      <Hello name="react" color="red"/>
      <Hello color="pink"/>
    </Wrapper>
  );
}

export default App;

 

  • 컴포넌트 태그 사이에 넣은 값을 조회하고 싶을 땐, props.children 을 조회하면 된다.
  • 내부의 내용이 보여지게 하기 위해 Wrapper 에서 props.children 을 렌더링해주어야 함.

 

06. 조건부 렌더링

 

App.js

import React from 'react';
import Hello from './Hello';
import Wrapper from './Wrapper';


function App() {
  return (
    <Wrapper>
      <Hello name="react" color="red" isSpecial={true}/>
      <Hello color="pink" />
    </Wrapper>
  )
}

export default App;

 

 

Hello.js

import React from 'react';

function Hello({ color, name, isSpecial }) {
  return (
    <div style={{ color }}>
      { isSpecial ? <b>*</b> : null }
      안녕하세요 {name}
    </div>
  );
}

Hello.defaultProps = {
  name: '이름없음'
}

export default Hello;

 

  • 조건부 렌더링이란, 특정 조건에 따라 다른 결과물을 렌더링 하는 것을 의미
  •  App 컴포넌트에서 Hello 컴포넌트를 사용 할 때, isSpecial 이라는 props 를 설정
  • true 는 자바스크립트 값이기 때문에 중괄호로 감싸줌
  • Hello 컴포넌트에서는 isSpecial 이 true 이냐 false 이냐에 따라 컴포넌트의 좌측에 * 출력
  • isSpecial 값이 true 라면 <b>*</b> 를, 그렇지 않다면 null 을 보여준다.
  • JSX 에서 null, false, undefined 를 렌더링하면 아무것도 나타나지 않는다.

 

- props 값 설정을 생략하면 ={true}

import React from 'react';
import Hello from './Hello';
import Wrapper from './Wrapper';

function App() {
  return (
    <Wrapper>
      <Hello name="react" color="red" isSpecial />
      <Hello color="pink"/>
    </Wrapper>
  );
}

export default App;

 

  • 컴포넌트의 props 값을 설정하게 될 때 만약 props 이름만 작성하고 값 설정을 생략한다면, 이를 true 로 설정한 것으로 간주한다.
  • 위 코드와 같이 isSpecial 이름만 넣어주면 isSpecial={true} 와 동일한 의미

 

07. useState 를 통해 컴포넌트에서 바뀌는 값 관리하기

 

Counter.js

import React from 'react';

function Counter() {
  const onIncrease = () => {
    console.log('+1')
  }
  const onDecrease = () => {
    console.log('-1');
  }
  return (
    <div>
      <h1>0</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

 

App.js

import React from 'react';
import Counter from './Counter';

function App() {
  return (
    <Counter />
  );
}

export default App;

 

  • onIncrease  onDecrease 는 화살표 함수를 사용하여 구현
  • 함수를 만들고, button 의 onClick 으로 각 함수를 연결
  • 리액트에서 엘리먼트에 이벤트를 설정해줄때에는 on이벤트이름={실행하고싶은함수} 형태로 설정해야 한다.

 

- 동적인 값 끼얹기, useState

 

useState를 사용한 Counter.js

import React, { useState } from 'react';

function Counter() {
  const [number, setNumber] = useState(0);

  const onIncrease = () => {
    setNumber(number + 1);
  }

  const onDecrease = () => {
    setNumber(number - 1);
  }

  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

 

  • 컴포넌트에서 동적인 값을 상태(state)라고 부른다.
  • useState 함수를 사용하여 컴포넌트에서 상태 관리 가능
  • Setter 함수는 파라미터로 전달 받은 값을 최신 상태로 설정

 

- 함수형 업데이트

 

함수형 업데이트 Counter.js

import React, { useState } from 'react';

function Counter() {
  const [number, setNumber] = useState(0);

  const onIncrease = () => {
    setNumber(prevNumber => prevNumber + 1);
  }

  const onDecrease = () => {
    setNumber(prevNumber => prevNumber - 1);
  }

  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

 

  • onIncrease  onDecrease 에서 setNumber 를 사용 할 때 그 다음 상태를 파라미터로 넣어준것이 아니라, 값을 업데이트 하는 함수를 파라미터로 전달
  • 함수형 업데이트는 주로 컴포넌트를 최적화 할 때 사용

 

 

08. input 상태 관리하기

 

InputSample.js

import React, { useState } from 'react';

function InputSample() {
  const [text, setText] = useState('');

  const onChange = (e) => {
    setText(e.target.value);
  };

  const onReset = () => {
    setText('');
  };

  return (
    <div>
      <input onChange={onChange} value={text}  />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: {text}</b>
      </div>
    </div>
  );
}

export default InputSample;

 

App.js

import React from 'react';
import InputSample from './InputSample';

function App() {
  return (
    <InputSample />
  );
}

export default App;

 

  • useState를 사용하여 동적 상태 관리
  • Input 의 onChange 라는 이벤트 사용
  • 이벤트 등록 함수에서 이벤트 객체 e 를 파라미터로 받아와 사용 할 수 있다.
  • 이 객체의 e.target 은 이벤트가 발생한 DOM 인 input DOM 을 가르킨다.
  • 이 DOM 의 value 값, 즉 e.target.value 를 조회하면 현재 input 에 입력한 값이 무엇인지 알 수 있다.

 

 

09. 여러개의 input 상태 관리하기

 

InputSample.js

import React, { useState } from 'react';

function InputSample() {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: ''
  });

  const { name, nickname } = inputs; // 비구조화 할당을 통해 값 추출

  const onChange = (e) => {
    const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
    setInputs({
      ...inputs, // 기존의 input 객체를 복사한 뒤
      [name]: value // name 키를 가진 값을 value 로 설정
    });
  };

  const onReset = () => {
    setInputs({
      name: '',
      nickname: '',
    })
  };


  return (
    <div>
      <input name="name" placeholder="이름" onChange={onChange} value={name} />
      <input name="nickname" placeholder="닉네임" onChange={onChange} value={nickname}/>
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b>
        {name} ({nickname})
      </div>
    </div>
  );
}

export default InputSample;

 

  • React 상태에서 객체를 수정해야 할 때  "inputs[name] = value;"와 같이 직접 수정하면 안된다.
  • 새로운 객체를 생성한 뒤 객체에 변화를 주고, 이를 상태로 사용해야 한다.

 

이상으로 리액트에 대해서 알아봤습니다.

 

반응형