본문 바로가기

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

[2024 React.js 스터디] 김지나 #4주차 "리액트 1~9"

반응형

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

- 처리해야 할 이벤트, 관리해야 할 상태값, DOM이 다양해질수록 업데이트를 하는 규칙도 복잡해짐 

-> Ember, Backbone, AngularJS 등의 프레임워크: 자바스크립트의 특정 값이 바뀌면 특정 DOM의 속성이 바뀌도록 연결, 업데이트 작업 간소화

- 리액트는 어떠한 상태가 바뀌었을 때, DOM을 업데이트 하는 규칙을 정하는 것이 아니라 다 날려버리고 처음부터 모든 걸 새로 만들어서 보여주면 어떨까? 라는 아이디어에서 시작함.

=> DOM을 어떻게 업데이트 해야 할지에 대한 고민을 하지 않아도 됨! 하지만 모든걸 다 날려버리고 새로 만들게 된다면 속도가 굉장히 느려질 것 ==> Virtual DOM 사용

 

Virtual DOM

: 브라우저에 실제로 보여지는 DOM이 아니라 메모리에 가상으로 존재하는 DOM

: 자바스크립트 객체이기 때문에 작동 성능이 실제로 브라우저에서 DOM을 보여주는 것보다 속도가 훨씬 빠름.

- 상태가 업데이트 되면, 업데이트가 필요한 곳의 UI를 Virtual DOM을 통해서 렌더링함 -> 효율적인 비교 알고리즘을 통해 실제 브라우저에서 보여지고 있는 DOM과 비교 -> 차이가 있는 곳을 감지해서 실제 DOM에 패치시킴 

 

02. 작업 환경 준비

1) Node.js: Webpack, Babel 같은 도구들이 Node.js를 기반으로 함.

- Webpack: 모듈 번들러(JavaScript 모듈을 브라우저에서 실행할 수 있는 단일 JavaScript 파일로 묶는데 사용되는 도구)

- Babel: 자바스크립트 컴파일러

 

 

2) Yarn: 개선된 버전의 npm(Node.js를 설치할 때 딸려오는 패키지 매니저 도구), 프로젝트에서 사용되는 라이브러리를 설치하고 해당 라이브러리들의 버전 관리를 할 때. 

- 더 나은 속도, 더 나은 캐싱 시스템을 사용하기 위함

 

3) 코드 에디터: VSCode, Atom, WebStorm, Sublime ...

 

- 새 프로젝트 만들어보기!

$ npx create-react-app begin-react

-> begin-react라는 디렉토리가 생기고 그 안에 리액트 프로젝트가 생성됨

$ cd begin-react
$ yarnn start (npm start)

 

 

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

- Hello.js

- App.js 

- 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'));

- ReactDOM.render: 브라우저에 있는 실제 DOM 내부에 리액트 컴포넌트를 렌더링하겠다는 의미

- id가 root인 DOM을 선택하고 있는데 이 DOM은 public/index.html 내부에 존재 = 리액트 컴포넌트가 렌더링 될 때는 렌더링된 결과물이 위 div 내부에 렌더링됨.

 

04. JSX

: 리액트에서 생김새를 정의할 때 사용하는 문법

(얼핏 보면 HTML 같이 생겼지만 자바스크립트다...)

- 리액트 컴포넌트 파일에서 XML 형태로 코드를 작성하면 babel이 JSX를 JavaScript로 변환해준다

(Babel: 자바스크립트의 문법을 확장해주는 도구. 아직 지원되지 않는 최신 문법이나, 실험적인 자바스크립트 문법들을 자바스크립트 형태로 변환해줌으로써 구형 브라우저 환경에서도 제대로 실행할 수 있게 됨)

 

- JSX가 JavaScript로 제대로 변환이 되려면 지켜야 하는 몇 가지 규칙이 있음!

1) 태그는 꼭 닫혀 있어야 한다 (태그 사이에 내용이 들어가지 않을 때는 Self Closing 태그 사용 ex: Hello() ) 

2) 두가지 이상의 태그는 무조건 하나의 태그로 감싸져있어야 한다 (단순히 감싸기 위하여 불필요한 div로 감싸는게 좋지 않을 때도 있음 -> 리액트의 Fragment 를 사용) // Fragment: 태그를 작성할 때 이름 없이 작성하면 만들어짐, 브라우저 상에서 따로 별도의 엘리먼트로 나타나지 않음

3) JSX 안에 자바스크립트 값 사용하기: JSX 내부에 자바스크립트 변수를 보여줘야 할 때에는 {}으로 감싸서 보여줌

4) style과 className: 인라인 스타일은 객체 형태로 작성해야 하며, -로 구분되어 있는 이름들은 camelCase 형태로 네이밍 해줘야 함

// camelCase: 단어가 합쳐진 부분마다 맨 처음 글자를 대문자로 표기하는 방법

- JSX 내부의 주석은 {/* 주석! */} 형태로 작성, 열리는 태그 내부에서는 // 주석! 형태로도 작성 가능

 

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

props: properties의 줄임말. 어떠한 값을 컴포넌트에게 전달해줘야 할 때 사용

ex) 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 는 파라미터를 통하여 조회 가능. 

- props 는 객체 형태로 전달되며, name 값을 조회하고 싶다면 props.name을 조회하면 됨! 

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

 

1) Hello 컴포넌트에 또 다른 props 전달하기! 

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

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

export default App;

- color 값 설정

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

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

export default Hello;

Hello 컴포넌트에서 color 값을 조회해서 폰트의 색상으로 설정

- props 내부 값을 조회할 때마다 props.를 입력하고 있는데, 함수의 파라미터에서 비구조화 할당 문법을 사용하면 좀 더 간결하게 작성할 수 있음.

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

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

export default Hello;

- 컴포넌트에 props 를 지정하지 않았을 때 기본적으로 사용할 값을 설정하고 싶다면 컴포넌트에 defalutProps를 설정하면 됨

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

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

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

export default Hello;

- 컴포넌트 태그 사이에 넣은 값을 조회하고 싶을 때, props.children 을 조회하면 됨

- props.children 사용하는 새로운 컴포넌트 만들기

import React from 'react';

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

    </div>
  )
}

export default Wrapper; //Wrapper.js를 src 디렉토리에 만들기
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; 
//Wrapper 태그 내부에 Hello 컴포넌트 두 개를 넣었지만 브라우저에서는 보이지 않음

- 내부의 내용이 보여지게 하기 위해서는 Wrapper 에서 props.children 을 렌더링해줘야 함

import React from 'react';

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

export default Wrapper;

 

06. 조건부 렌더링

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

ex) 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}/>
      <Hello color="pink" />
    </Wrapper>
  )
}

export default App;

- true 는 자바스크립트 값이니 중괄호로 감싸주기

- 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 면 보여주고, 그렇지 않다면 숨기는 상황에서는 && 연산자를 사용하는 것이 더 간편

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

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

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

export default Hello;

 

- 컴포넌트의 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}

 

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

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

- 리액트 16.8 이전 버전에서는 함수형 컴포넌트에서는 상태를 관리할 수 없었는데 Hooks 기능이 도입되면서 함수형 컴포넌트에서도 관리가 가능하게 됨

- useState 함수 사용 <- 리액트의 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;
//App에서 Counter 렌더링
import React from 'react';
import Counter from './Counter';

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

export default App;

- 이벤트 설정

Counter 에서 버튼이 클릭되는 이벤트가 발생했을 때, 특정 함수가 호출되도록 설정

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

리액트에서 엘리먼트에 이벤트를 설정해줄때에는 on이벤트이름={실행하고싶은함수}

(⚠️함수형태를 넣어야함. 함수를 실행하면 렌더링되는 시점에서 함수가 호출되어버림)

 

- 동적인 값 넣기(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;

- useState를 사용할 때에는 상태의 기본값을 파라미터로 넣어서 호출

- 이 함수를 호출하면 배열이 반환되는데, 첫 번째 원소는 현재 상태, 두 번째 원소는 Setter 함수

Setter 함수: 파라미터로 전달받은 값을 최신 상태로 설정

 

- 함수형 업데이트

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;

- 함수형 업데이트는 컴포넌트를 최적화하게 될때 사용

 

08. input 상태 관리하기

//inputSample.js 생성
import React from 'react';

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

export default InputSample;
//App에 렌더링
import React from 'react';
import InputSample from './InputSample';

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

export default App;

- input에 입력하는 값이 하단에 나타나게 하고, 초기화 버튼을 누르면 input의 값이 비워지도록 구현

=> useState 사용. input의 onChange 이벤트 사용

이벤트에 등록하는 함수에서는 이벤트 객체 e를 파라미터로 받아와서 사용할 수 있는데, 이 객체의 e.target은 이벤트가 발생한 DOM인 input DOM을 가리킴

이 DOM 의 value 값 (=e.target.value) 을 조회하면 현재 input에 입력한 값이 무엇인지 알 수 있음

이 값을 useState를 통해서 관리해주면 됨

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;

- input 상태를 관리할 때에는 input 태그의 value 값도 설정해주는 것이 중요!

 

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

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에서는 문자열이 아니라 객체 형태의 상태를 관리해줘야 함

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;

- 리액트 상태에서 객체를 수정해야 할 때에는, 직접 수정하지 말고 새로운 객체를 만들어 변화를 주고 이를 상태로 사용해줘야 함

inputs[name] = value;
//이런식으로하면안됩니다
setInputs({
  ...inputs,
  [name]: value
});
//이런식으로하기~

- ...은 spread 문법. 객체의 내용을 모두 펼쳐서 기존 객체를 복사

- 이러한 작업을 불변성을 지킨다고 하는데, 불변성을 지켜주어야만 리액트 컴포넌트에서 상태가 업데이트됐음을 감지할 수 있고 이에 따라 필요한 렌더링이 진행됨. 만약 직접 수정하게 되면, 값을 바꿔도 리렌더링이 되지 않음. 또 불변성을 지켜주어야만 컴포넌트 업데이트 성능 최적화를 제대로 할 수 있음!

반응형