본문 바로가기

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

[2024 React.js 스터디] 박지민 #4주차 "리액트 입문 1-9"

반응형

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

  • 처리해야 할 이벤트, 관리해야 할 상태값, DOM까지 다양해지게 된다면 이에 따라 업데이트하는 규칙도 복잡 -> 업데이트 간소화 필요
  • 리액트는 어떠한 상태가 바뀌었을때, 그 상태에 따라 DOM을 어떻게 업데이트 할 지 규칙을 정함 X, 아예 다 날려버리고 처음부터 모든걸 새로 만들어서 보여준다는 아이디어에서 시작
  • 정말로 동적인 UI를 보여주기 위해서 모든걸 다 날려버리고 새로 만들게 된다면, 속도가 굉장히 느림 -> 리액트에서는 Virtual DOM이라는 것을 사용

- Virtual DOM

  • 가상의 DOM으로, 브라우저에 실제로 보여지는 DOM X
  • 그냥 메모리에 가상으로 존재하는 DOM - 그냥 JavaScript 객체 -> 작동 성능이 실제로 브러우저에서 DOM을 보여주는 것보다 속도가 훨씬 빠름
  • 리액트에서 Virtual DOM이 사용되는 과정
    • 리액트는 상태가 업데이트 되면, 업데이트가 필요한 곳의 UI 를 Virtual DOM 을 통해서 렌더링
    • 비교 알고리즘을 통하여 실제 브라우저에 보여지고 있는 DOM 과 비교를 한 후, 차이가 있는 곳을 감지하여 이를 실제 DOM 에 패치

 

2. 작업환경 준비

- Node.js

  • Webpack과 Babel 같은 도구들이 자바스크립트 런타임인 Node.js 기반으로 만들어짐
  • Webpack, Babel은 무슨 용도?
    • 리액트 프로젝트를 만들게 되면서, 컴포넌트를 여러가지 파일로 분리해서 저장 & 이 컴포넌트는 일반 자바스크립트가 아닌 JSX 라는 문법으로 작성
    • 여러가지의 파일을 한개로 결합하기 위해서 Webpack 사용
    • JSX 를 비롯한 새로운 자바스크립트 문법들을 사용하기 위해서 Babel 사용

- Yarn

  • 조금 개선된 버전의 npm
  • Node.js를 설치하게 될 때 같이 딸려오는 패키지 매니저 도구
  • 프로젝트에서 사용되는 라이브러리를 설치 & 해당 라이브러리들의 버전 관리
  • 만약 npm 이 이미 익숙하고, yarn 을 설치하기 귀찮다면 생략해도 상관 X

- 코드 에디터

  • ex) VSCode, Atom, WebStorm, Sublime

- Git bash

  • 윈도우의 경우, Git for Windows 를 설치해서 앞으로 터미널에 무엇을 입력하라는 내용이 있으면 함께 설치되는 Git Bash 를 사용
  • 윈도우가 아니라면 설치 X 상관없음

 

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

  • 리액트 컴포넌트는 함수형태 & 클래스 형태로도 작성 가능 (이 글의 단계에서는 함수로 작성하는 방법에 대해서만 알아봄)
  • JSX: 리액트 컴포넌트에서는 XML 형식의 값을 반환 가능
// Hello.js
import React from 'react';  // 리액트 컴포넌트를 만들 때 리액트 불러오기

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

export default Hello;  // Hello 라는 컴포넌트를 내보내겠다는 의미 - 다른 컴포넌트에서 불러와서 사용 가능

 

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


function App() {
  return (
    <div>
      <Hello />  // 컴포넌트는 일종의 UI 조각 - 쉽게 재사용 가능
      <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 을 선택하고 있는데, 이 DOM은 public/index.html 내부에 존재
  • 결국, 리액트 컴포넌트가 렌더링 될 때에는, 렌더링된 결과물이 위 div 내부에 렌더링되는 것

 

4. JSX

  • 리액트에서 생김새를 정의할 때, 사용하는 문법 - HTML 같이 생겼지만 실제로는 JavaScript
  • 리액트 컴포넌트 파일에서 XML 형태로 코드를 작성하면 babel 이 JSX 를 JavaScript 로 변환
  • Babel 은 자바스크립트의 문법을 확장해주는 도구 - 편의상 사용하거나 실험적인 자바스크립트 문법들을 정식 자바스크립트 형태로 변환해줌으로서 구형 브라우저같은 환경에서도 제대로 실행 할 수 있게 해주는 역할
  • JSX 가 JavaScript 로 제대로 변환이 되려면 지켜주어야 하는 규칙
    1. 꼭 닫혀야 하는 태그
      • 태그를 열었으면 꼭, <div></div> 이렇게 닫아주어야 함
      • 태그와 태그 사이에 내용이 들어가지 않을 때에는, Self Closing 태그 라는 것을 사용 - 열리고, 바로 닫히는 태그를 의미
    2. 꼭 감싸져야 하는 태그
      • 두 개 이상의 태그는 무조건 하나의 태그로 감싸져 있어야 함
      • 스타일 관련 설정을 하다가 복잡해지게 되는 상황도 올 수 있고, table 관련 태그를 작성 할 때에도 내용을 div 같은걸로 감싸기엔 애매 - 그럴 땐, 리액트의 Fragment 라는 것을 사용
      • 태그를 작성 할 때 이름 없이 작성을 하게 되면 Fragment 만들어짐 - 브라우저 상에서 따로 별도의 엘리먼트로 나타나지 않음
    3. JSX 안에 자바스크립트 값 사용하기
      • JSX 내부에 자바스크립트 변수를 보여줘야 할 때에는 {} 으로 감싸서 보여줌
    4. sytle과 className
      • 인라인 스타일은 객체 형태로 작성 -> - 로 구분되어 있는 이름들은 camelCase 형태로 네이밍
        • ex) background-color -> backgroundColor
      • CSS class 를 설정 할 때에는 class= 가 아닌 className= 으로 설정
    5. 주석
      • JSX 내부의 주석은 {/* 이런 형태로 */} 작성
      • 열리는 태그 내부에서는 // 주석 처럼 작성 가능

 

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

  • props 는 properties 의 줄임말

- props의 기본 사용법

  • App 컴포넌트에서 Hello 컴포넌트를 사용 할 때 name 이라는 값을 전달해주고 싶다고 가정
// App.js
import React from 'react';
import Hello from './Hello';

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

export default App;
  • Hello 컴포넌트에서 name 값을 사용 하고 싶을 때
// Hello.js
import React from 'react';

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

export default Hello;
  • 컴포넌트에게 전달되는 props 는 파라미터를 통하여 조회 X
  • props 는 객체 형태로 전달 -> 만약 name 값을 조회하고 싶다면 props.name 을 조회

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

  • Hello 컴포넌트에 또 다른 props 를 전달
// App.js
import React from 'react';
import Hello from './Hello';

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

export default App;
  • Hello 컴포넌트에서 color 값을 조회해서 폰트의 색상으로 설정
// Hello.js
import React from 'react';

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

export default Hello;
  • props 내부의 값을 조회할 때마다 props. 을 입력하고 있다. 함수의 파라미터에서 비구조화 할당(혹은 구조 분해) 문법을 사용 -> 조금 더 코드를 간결하게 작성 가능
// Hello.js
import React from 'react';

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

export default Hello;

 

- defaultProps로 기본값 설정

  • 컴포넌트에 props 를 지정하지 않았을 때 기본적으로 사용 할 값을 설정하고 싶다면 컴포넌트에 defaultProps 라는 값을 설정
// Hello.js
import React from 'react';

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

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

export default Hello;
  • App 에서 name 이 없는 Hello 컴포넌트를 렌더링 -> "이름없음"이 name으로 뜸

- props.children

  • 컴포넌트 태그 사이에 넣은 값을 조회하고 싶을 땐, props.children 을 조회
  • Wrapper 태그 내부에 Hello 컴포넌트를 넣고 브라우저 확인하면 컴포넌트 보임 X -> 내부의 내용이 보여지게 하기 위해서는 Wrapper에서 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;

 

6. 조건부 렌더링

- 특정 조건에 따라 다른 결과물을 렌더링 하는 것

  • App 컴포넌트에서 Hello 컴포넌트를 사용 할 때, isSpecial 이라는 props 를 설정
// 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}/>  // true는 자바스크립트 값 -> 중괄호로 감싸줌
      <Hello color="pink" />
    </Wrapper>
  )
}

export default App;
  • Hello 컴포넌트에서는 isSpecial 이 true 이냐 false 이냐에 따라서 컴포넌트의 좌측에 * 표시 - 삼항연산자 사용
// 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;

 

  • isSpecial 값이 true 라면 <b>*</b> 를, 그렇지 않다면 null 보여줌
    • 참고로 JSX 에서 null, false, undefined 를 렌더링하게 된다면 아무것도 나타나지 않음
  • 보통 삼항연산자를 사용한 조건부 렌더링을 주로 특정 조건에 따라 보여줘야 하는 내용이 다를 때 사용
  • 단순히 특정 조건이 true 이면 보여주고, 그렇지 않다면 숨겨주는 상황 -> && 연산자를 사용해서 처리하는 것이 더 간편
function Hello({ color, name, isSpecial }) {
  return (
    <div style={{ color }}>
      {isSpecial && <b>*</b>}
      안녕하세요 {name}
    </div>
  );
}
  • isSpecial && <b>*</b> 의 결과는 isSpecial  false 일땐 false 이고, isSpecial true 일 땐 <b>*</b>

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

  • 컴포넌트의 props 값을 설정하게 될 때 만약 props 이름만 작성하고 값 설정을 생략한다면, 이를 true 로 설정한 것으로 간주
// App.js
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;

 

  • isSpecial 이름만 넣어주면 isSpecial={true} 와 동일한 의미

 

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

- 컴포넌트에서 보여줘야 하는 내용이 사용자 인터랙션에 따라 바뀌어야 할 때 어떻게 구현할 수 있는가

- 리액트 16.8 에서 Hooks 라는 기능이 도입되면서 함수형 컴포넌트에서도 상태를 관리 가능

 

- ex) 버튼을 누르면 숫자가 바뀌는 Counter 컴포넌트

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

function Counter() {
  return (
    <div>
      <h1>0</h1>
      <button>+1</button>
      <button>-1</button>
    </div>
  );
}

export default Counter;
  • 이벤트 설정: Counter 에서 버튼이 클릭되는 이벤트가 발생 했을 때, 특정 함수가 호출되도록 수정
// Counter.js
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>
  );
}
  • onIncrease 와 onDecrease 는 화살표 함수를 사용하여 구현
  • 함수를 만들고, button 의 onClick 으로 각 함수를 연결 - 리액트에서 엘리먼트에 이벤트를 설정해줄때에는 on이벤트이름={실행하고싶은함수} 형태로 설정

- 동적인 값 끼얹기, useState

  • 컴포넌트에서 동적인 값을 상태(state)
  • 리액트에 useState 라는 함수가 있는데요, 이것을 사용하면 컴포넌트에서 상태를 관리 가능
// Counter.js
import React, { useState } from 'react';  // 리액트 패키지에서 useState 함수 불러옴

function Counter() {
  // 상태의 기본값을 파라미터로 넣어서 호출
  const [number, setNumber] = useState(0);  // 배열 반환: 첫번째 원소는 현재 상태, 두번째 원소는 Setter 함수

  // 비구조화 할당을 통화여 각 원소를 추출
  // Setter 함수는 파라미터로 전달 받은 값을 최신 상태로 설정
  const onIncrease = () => {
    setNumber(number + 1);
  }

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

  return (
    <div>
      <h1>{number}</h1>  // h1 태그에서는 이제 0 이 아닌 {number} 값을 보여주어야 함
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

 

- 함수형 업데이트

  • 지금은 Setter 함수를 사용 할 때, 업데이트 하고 싶은 새로운 값을 파라미터로 넣어주고 있음 - 그 대신에 기존 값을 어떻게 업데이트 할 지에 대한 함수를 등록하는 방식으로도 값을 업데이트 가능
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를 사용 할 때 그 다음 상태를 파라미터로 넣어준것이 아니라, 값을 업데이트 하는 함수를 파라미터로 넣어줌
  • 함수형 업데이트는 주로 나중에 컴포넌트를 최적화를 하게 될 때 사용

 

8. input 상태 관리하기

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

function InputSample() {
  return (
    <div>
      <input />
      <button>초기화</button>
      <div>
        <b>값: </b>
      </div>
    </div>
  );
}

export default InputSample;
  • input 에 입력하는 값이 하단에 나타나게 하고, 초기화 버튼을 누르면 input 이 값이 비워지도록 구현할 것
  • input 의 onChange 라는 이벤트를 사용 - 이벤트에 등록하는 함수에서는 이벤트 객체 e 를 파라미터로 받아와서 사용 할 수 있는데 이 객체의 e.target 은 이벤트가 발생한 DOM 인 input DOM 을 가르킴
  • 이 DOM 의 value 값, 즉 e.target.value 를 조회하면 현재 input 에 입력한 값이 무엇인지 알 수 있음
  • 이 값을 useState 를 통해서 관리
// InputSample.js 수정
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>
  );
}
  • input 의 상태를 관리할 때에는 input 태그의 value 값도 설정해주는 것이 중요
  • 그렇게 해야, 상태가 바뀌었을때 input 의 내용도 업데이트

 

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

// InputSample.js
import React, { useState } from 'react';

function InputSample() {
  const onChange = (e) => {
  };

  const onReset = () => {
  };


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

export default InputSample;
  • input 의 개수가 여러개가 됐을때는, 단순히 useState 를 여러번 사용하고 onChange 도 여러개 만들어서 구현 가능
  • 더 좋은 방법은, input 에 name 을 설정하고 이벤트가 발생했을 때 이 값을 참조하는 것
  • useState 에서는 문자열이 아니라 객체 형태의 상태를 관리
// InputSample.js 수정
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>
  );
}
  • 리액트 상태에서 객체를 수정해야 할 때에는, inputs[name] = value; 처럼 직접 수정 X
  • 그 대신, 새로운 객체를 만들어서 새로운 객체에 변화를 주고, 이를 상태로 사용
setInputs({
  ...inputs,
  [name]: value
});
  • 여기서 사용한 ... 문법은 spread 문법: 객체의 내용을 모두 "펼쳐서" 기존 객체를 복사
  • 이러한 작업을, "불변성을 지킨다" 라고 부름 - 불변성을 지켜주어야만 리액트 컴포넌트에서 상태가 업데이트가 됐음을 감지 할 수 있고 이에 따라 필요한 리렌더링이 진행
  • 기존 상태를 직접 수정하게 되면, 값을 바꿔도 리렌더링 X
  • 추가적으로, 리액트에서는 불변성을 지켜주어야만 컴포넌트 업데이트 성능 최적화를 제대로 가능
반응형