패캠 React

TonyHan·2021년 8월 8일
0
post-thumbnail

https://slides.com/woongjae/react2021
https://slides.com/woongjae/fds17th-11
https://slides.com/woongjae/fds17th-12
https://slides.com/woongjae/fds17th-13
https://slides.com/woongjae/redux2021#/2
https://slides.com/woongjae/reactts2021#/2
https://slides.com/woongjae/fds17th-14

0. React 시작하기

Angular vs React vs Vue

  • 리액트는 랜더링과 업데이트에만 관여
  • 컴포넌트 기반 개발
  • 가상 돔
  • JSX
  • CSR & SSR

Angular : 웹 프레임워크 안에서 모든 것을 제공해주려고 노력
Vue : React 또는 Angular 처럼 사용하기
React : user interface를 만들기 위한 라이브러리

Component

<!-- HTMLElement -->

<img src="이미지 주소"/>
  
<button class="클래스 이름">버튼</button>

<!-- 내가 만든 컴포넌트 -->

<내가지은이름1 name="Mark" />

<내가지은이름 prop={false}>내용</내가지은이름>

<!--

- src, class, name, props 밖에서 넣어주는 데이터
- 문서(HTML), 스타일(CSS), 동작(JS) 를 합쳐서 내가 만든 일종의 태그

-->

HTML은 이미 정의된 것을 사용한다.
내가 만든 컴포넌트는 내가지은이름을 가지고 거기에 내가 정한 attribute를 표현해줄 수 있는 구성을 이야기 한다.

그래서 위와 같이 우리가 컴포넌트를 정의할 수 있다.

그래서 Component Tree는 DOM Tree와 유사하게 생기었다. 하지만 DOM Tree와는 다르게 내가 만든 Component는 내가 문서, 스타일, 표현, 동작을 정의한 컴포넌트를 만들고 재활용까지 하는 것을 이야기 한다.

Virtual DOM

  • DOM 을 직접 제어하는 경우
    바뀐 부분만 정확히 바꿔야 한다.

  • DOM 을 직접 제어하지 않는 경우
    가상의 돔 트리를 사용해서,
    이전 상태와 이후 상태를 비교하여,
    바뀐 부분을 찾아내서 자동으로 바꾼다.

Virtual DOM은 먼저 diff를 찾아내고 이를 변경해준다.

CSR, SSR

React 실행되기 전까지도 화면을 볼 수 없다. React가 실행되어야 비로소 우리의 웹 애플리케이션을 볼 수 있다.

SSR은 이미 HTML을 다운 받는다. 그 안의 JS를 받는 과정에서는 화면은 보일것이다. 하지만 JS가 실행되지 않았기에 액션은 불가능하다. 그 이후 React가 다운로드 되고 소스코드를 실행하면 앱이 실행될 것이다.

그래서 두개다 차이는 없지만 SSR이 보다 유저에게 빠르게 정보를 제공할 수 있다.

정리

1. 개발환경

다음 환경들이 필요하다.

이미 앞에서 다 다루었기 때문에

nvm install 14.16.1
nvm use 14.16.1
nvm alias default 14.16.1

React 라이브러리

// 1. 리액트 컴포넌트 => HTMLElement 연결하기
import ReactDOM from 'react-dom';

// 2. 리액트 컴포넌트 만들기
import React from 'react';

위의 두가지를 반드시 사용하게 된다.

아래쪽 HelloMessage가 컴포넌트이다. 그리고 ReactDOM이 HelloMessage를 hello-example이라는 곳에 그려라는 행위를 수행하고 있다.

https://reactjs.org/docs/react-api.html
https://ko.reactjs.org/docs/react-api.html

그래서 react 컴포넌트를 만들때 사용하는 api를 보고자 한다면 위의 사이트에 방문해서 확인하자

https://reactjs.org/docs/cdn-links.html
https://ko.reactjs.org/docs/cdn-links.html

시작전에 위의 사이트에서 React CDN을 받아오자


이제 시작전에
what-is-react라는 폴더를 만들어주자

npm init -y 를 해주고

이 폴더를 서버로 사용할 수 있게 해주는
npx serve 명령어를 입력해주자

그럼 로컬호스트가 열리게 된다.

가보면 위와 같이 파일이 존재하는 것을 확인할 수 있다.

위쪽의 dev cdn을 가지고 오자

가져온 스크림트를 가지고 React, ReactDOM을 띄어주면 위와 같이 객체가 뜨는 것을 확인할 수 있다.

실재 DOM에 Component를 연결해주는 것이 ReactDOM이다.
ReactDOM.render(리액트 컴포넌트, 리액트 컴포넌트가 그려지는 곳)

그리고 Component를 정의해 주어야 한다.
createElemt(태그, 성분, 내용)

const Component = props => {
	return React.createElement('p', null, `${props.message}: ${props.count}`)
}

이렇게 작성된 Componet를 ReactDOM에 넣어주자.

ReactDOM.reander(
  React.createElement(Component, {message: 'init', count: 0}, null), document.querySelector("#root")
);

React.Component를 상속받은 클래스를 정의하면 그 안에 반드시 render이라는 함수를 정의해주어야한다. 이 함수는 꼭 반환해주어야하면 반환 성분은 react element이어야 한다.

2. React Component 만드는 법

Hook이 나오기 전에는 만약에 컴포넌트 내부에 유지되고 변경되는 상태가 있다면 class 컴포넌트를 만들어서 사용

만약 상태가 없다면 라이프사이클을 기준으로 class/function 컴포넌트를 나누었다.

Hook이 나온 이후에는 class 컴포넌트와 function 컴포넌트를 이전과 같은 기준으로 나누어 사용하지 않는다. 그래서 앞으로는 function 컴포넌트만으로 코딩이 가능해졌다.

Class 컴포넌트

https://babeljs.io/setup#installation

시작전에 babel도 넣어주자.

클래스를 정의해주면 반드시 render을 정의해주고 return도 해주어야 한다. 그럼 위와 같은 그림이 되는데 이는 컴포넌트를 어딘가에서 사용하면 반환값처럼 표현하겠다는 의미이다.

그리고 reactDOM.render()로 해당 컴포넌트를 root에 넣어서 사용하면 위의 이미지와 같이 정상 출력되는 것을 확인할 수 있다.

Function 컴포넌트

위와 같이 컴포넌트를 정의하면 된다.

혹은 위와 같이 화살표로 작성해도 된다.

컴포넌트 만들기

<!-- ex5.html : React.createElement 로 컴포넌트를 만들기 -->
<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script type="text/javascript">
      //   React.createElement(
      //     type, // 태그 이름 문자열 | React 컴포넌트 | React.Fragment
      //     [props], // 리액트 컴포넌트에 넣어주는 데이터 객체
      //     [...children] // 자식으로 넣어주는 요소들
      //   );

      // 1. 태그 이름 문자열 type
      //   ReactDOM.render(
      //     React.createElement('h1', null, `type 이 "태그 이름 문자열" 입니다.`),
      //     document.querySelector('#root'),
      //   );

      // 2. React 컴포넌트 type
      //   const Component = props => {
      //     return React.createElement('p', null, `type 이 "React 컴포넌트" 입니다.`);
      //   };

      //   ReactDOM.render(
      //     React.createElement(
      //       Component,
      //       null,
      //       null
      //     ),
      //     document.querySelector("#root")
      //   );

      // 3. React Fragment type
      //   ReactDOM.render(
      //     React.createElement(
      //       React.Fragment,
      //       null,
      //       `type 이 "React Fragment" 입니다.`
      //     ),
      //     document.querySelector("#root")
      //   );
      태그없이 데이터가 입력된다.

      // 4-0 복잡한 리액트 엘리먼트 모임
      // createElement의 한계 : 단순하게만 만들 수 있다.
      
      // 4. props 를 통해 데이터를 주입
      //   const Component = props => {
      //     return React.createElement(
      //       'p',
      //       null,
      //       `message 는 "${props.message}" 입니다.`,
      //     );
      //   };

      //   ReactDOM.render(
      //     React.createElement(
      //       Component,
      //       { message: '이것은 메세지 입니다.' },
      //       null,
      //     ),
      //     document.querySelector('#root'),
      //   );

      // 5. props 에 들어가는 children
      //   const Component = props => {
      //     return React.createElement(
      //       'p',
      //       null,
      //       `message 는 "${props.message}" 입니다.`,
      //       `props.children 은 "${props.children}" 입니다.`,
      //     );
      //   };

      //   ReactDOM.render(
      //     React.createElement(
      //       Component,
      //       { message: '이것은 메세지 입니다.' },
      //       '이것은 children 입니다.',
      //     ),
      //     document.querySelector('#root'),
      //   );

      // 6. 리액트 엘리먼트에 style 추가
      //   ReactDOM.render(
      //     React.createElement(
      //       'h1',
      //       { style: { color: 'red' } },
      //       `type 이 "태그 이름 문자열" 입니다.`,
      //     ),
      //     document.querySelector('#root'),
      //   );

      // 7. 복잡한 컴포넌트
      //   ReactDOM.render(
      //     React.createElement(
      //       'div',
      //       { style: { backgroundColor: 'red', width: 100, height: 100 } },
      //       React.createElement(
      //         'div',
      //         { style: { backgroundColor: 'green', width: 50, height: 50 } },
      //         null,
      //       ),
      //       React.createElement(
      //         'div',
      //         { style: { backgroundColor: 'yellow', width: 50, height: 50 } },
      //         null,
      //       ),
      //     ),
      //     document.querySelector('#root'),
      //   );
    </script>
  </body>
</html>

JSX

createElement의 한계를 극복하기 위해 나온 것이 JSX이다.

우리가 작성한 코드를 순수하게 실행될 수 있는 자바스크립트로 바꾸어 주어야 하는데 이것이 babel을 이용해서 작동하게 된다.

babel이 JSX 문법을 이해해서 순수하게 실행할 수 있는 자바스크립트로도 변환을 해준다.

https://babeljs.io/

먼저 바벨 사이트로 가자

실재로 이렇게 해보니까 매우 잘 나온 것을 확인해볼 수 있다.

여기에다가 속성을 적게 되면 props로 입력이 된다.

그래서 JSX를 쓰는 이유는 가독성과 문법적 오류를 체크하기 위함이다.

최상위 요소가 하나여야 한다는 오류는 위와 같은 형태를 이야기 한다.

이러한 형태를 그대로 출력하고자 한다면 <>, </>을 사용하면 된다.

이러한 것을 Fragment 문법이라고 부른다.

표현식을 사용하려면 위와 같이 코드를 입력하면 된다.

if 문은 되지 않기 때문에 삼항 연산자 혹은 &&를 사용하게 된다.

class 대신 className을 사용해 class를 적용할 수 있다.

자식요소가 있으면 반드시 닫아야하고 자식요소가 없으면 열면서 닫아야한다.

props

props는 컴포넌트 외부에서 컴포넌트에게 주는 데이터이다.
state는 컴포넌트 내부에서의 상태이다.

둘 중 무엇이든 변경이 발생하면, 랜더가 다시 일어난다.

<!-- ex8-1.html : 함수로 리액트 컴포넌트 만들기 -->
<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      
      //{message : "안녕하세요!!!"}
      function Component(props) {
        return (
          <div>
            <h1>{props.message} 이것은 함수로 만든 컴포넌트 입니다.</h1>
          </div>
        );
      }

      ReactDOM.render(
        <Component message="안녕하세요!!!" />,
        document.querySelector('#root'),
      );
    </script>
  </body>
</html>

<!-- ex8-2.html : 클래스로 리액트 컴포넌트 만들기 -->
<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      class Component extends React.Component {
        render() {
          return (
            <div>
              <h1>
                {this.props.message} 이것은 클래스를 상속하여 만든 컴포넌트
                입니다.
              </h1>
            </div>
          );
        }
      }

      ReactDOM.render(
        <Component message="안녕하세요!!!" />,
        document.querySelector('#root'),
      );
    </script>
  </body>
</html>

<!-- ex9.html : defaultProps 설정 -->
<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      class Component extends React.Component {
        static defaultProps = {
          message: '안녕하세요!!!',
        };
      
        render() {
          return (
            <div>
              {this.props.message} 이것은 클래스를 상속하여 만든 컴포넌트
              입니다.
            </div>
          );
        }
      }

      //   Component.defaultProps = {
      //     message: '안녕하세요!!!',
      //   };

      ReactDOM.render(<Component />, document.querySelector('#root'));
    </script>
  </body>
</html>

State

클래스에서 state는 항상 객체로 되어 있어야 한다.

State는 반드시 객체로 정의해주어야 한다. 보통 위의 두가지 방식이 존재하는데 보통은 직접 써주는 방식으로 구현한다.

위와 같이 componentDidMount() 함수를
호출해주는 것으로 state 상태를 바꿀 수 있다.

이때 componentDidMount를 사용해서 state를 바꾸는 방식도 두가지가 존재한다. 하나는 직접 state를 바꾸어주거나 함수를 넣는 방식이 존재한다.

Event Handling

HTML DOM에 클릭과 같은 이벤트가 발생했을 경우 JSX에 이벤트를 설정해줄 수 있다.

Event명은 camelCase로 하고 반드시 앞에 on을 붙여 주어야 한다. 그리고 뒷부분을 element에서 우리가 사용할 수 있는 걸로 붙여주어야 한다.
반드시 이벤트는 함수로 처리되어야 한다.

class로 작성하면 위와 같이 된다. 앞에서 state변경방법도 배웠기에 이참에 state도 변경해보았다. 위의 문법에서 확인되는 것은 state 객체를 받아서 새로운 state 객체에 복사해주는데 이떄 state.count에 있는 값을 하나 증가해서 반환해주는 형태이다.

위와 같이 이미 있는 이벤트 명을 MouseEnter로 바꾸어도 보았다.

하지만 보통 이렇게 안 사용하기 때문에 click함수만 따로 때내었고 하는 중에 click의 this도 정의해주었다.

그런데 일부터 constructor을 쓰는 것이 코드의 비효율성을 야기하기 때문에 위와 같이 arrow 함수를 사용해서 간단하게 넘기어주는 방법도 존재한다.

Component Lifecycle Hook

리액트 컴포넌트는 탄생부터 죽음까지 여러지점에서 개발자가 작업이 가능하도록 메서드를 오버라이딩 할 수 있게 해준다.

처음으로 그려진다(initialization, mounting) -> 죽음 (unmounting)

처음 호출시 props와 state가 설정되고 브라우저에 그려지는 것을 render이라고 한다. 그리고 render 이전 이후로 willMount와 DidMount가 존재한다.

update는 props나 state가 바뀌는 것을 이야기 한다.

그래서 이것을 활용하여 불필요하게 render 되는 것을 막는데 도움이 된다. 그래서 리액트 컴포넌트 성능 최적화에 큰 도움이 되는 라이프사이클 훅이다.

하지만 위에서 표현된 라이프사이클은 사실 과거의 순차이다. 하지만 알아두어서 지금은 어떻게 바뀌었는지 확인해보자.

그래서 위에서 확인한 라이프사이클에 따라서 WillMount와 DidMount를 오버라이딩하면 위와 같이 WillMount가 먼저 출력되고 그다음에 DidMount가 출력되는 것을 확인할 수 있다.

하지만 동시에 콘솔창에서 보면 WillMount가 이전 버전에 존재했던 것이기 때문에 위험하다는 경고신호도 보내준 것을 확인할 수 있다.

여기에서 setInterval 함수를 추가한 결과 1초마다 state가 변경되고 이에 따라 render가 다시 이루어지는 것을 확인할 수 있다.

이러한 방식으로 과거 버전에서는 state 변경에 따른 render가 실행되게 된다.

이후의 라이프사이클을 확인해보자. 16.3 버전부터는 Will과 관련되었던 함수들이 getDerivedStateFromProps와 getSnapshotBeforeUpdate로 바뀐 것을 확인할 수 있다.

실재로 componentDidMount 위에 이 함수를 static으로 작성하면 위와 같이 화면에 표시되는 내용이 바뀐것을 확인할 수 있다.

그런데 390의 값이 바뀌지 않고 계속 남아있는 것을 확인할 수 있다. 하지만 이 함수는 render가 호출되기 전에 계속 호출되기 때문에 age가 390의 값이 바뀌지 않고 유지되는 것을 확인할 수 있다.

그렇다보니 이 함수는 매우 특수한 경우에만 사용하게 된다. 보통 시간의 흐름에 따라 변하는 props에 state가 의존하는 경우에 사용되게 된다.

getSnapshotBeforeUpdate를 사용해보자.

만약 위와 같이 i를 계속해서 증가하면서 화면에 뿌려주는 클래스가 존재하고 DidMount를 이용해서 1초마다 i의 값을 증가시킨다고 하자.

하지만 height가 맞지 않아서 화면이 막힌 느낌이 든다. 이것을 막기위해 getSnapshotBeforeUpdate를 사용해보자

업데이트 되기전에 Snapshot을 어딘가에 저장하겠다는 의미이다.

이번에는 getSnapshotBeforeUpdate를 사용해서 이전과 현재 list의 길이가 같다면 null을 반환하고 그게 아니면 높이차이를 반환해주어서 componentDidUpdate에 반환해주었다.

이번에는 snapshot에 따라서 화면이 다르게 출력되도록 만들어주었다.


과거와 다르게 현재는 componentDidCatch를 가지고 에러를 잡아줄 수 있다. 에러가 났다면 부모클래스에서 다른 것을 보여주는 기능이 있다.

예를 들어서 위와 같이 에러가 발생했을 경우 WevService라는 컴포넌트를 반환해준다. 이러한 에러는 componentDidCatch에서 받아들인다. 그래서 에러가 발생시 에러 발생에 대한 페이지를 보여주는 컴포넌트를 error boundary라고 부른다.

profile
신촌거지출신개발자(시리즈 부분에 목차가 나옵니다.)

0개의 댓글