React Basic

Lead Software Engineer @NHN Dooray (2021~)

Lead Software Engineer @ProtoPie (2016 ~ 2021)

Microsoft MVP

TypeScript Korea User Group Organizer

Marktube (Youtube)

Woongjae Lee

이 웅재

리액트 기본 및 현업에서의 활용 방안

  • 컴포넌트로 페이지를 만드는 방식을 배웁니다.
  • 리액트로 컴포넌트를 만드는 방식을 세부적으로 배웁니다.
  • SPA 에서의 라우팅 방식과 컴포넌트를 이용하여 라우팅라는 방법을 배웁니다.
  • 리액트의 라이프사이클과 훅에 대해 배웁니다.
  • 하나의 컴포넌트가 아니라 여러개의 컴포넌트를 조합하는 방식에 대해 배웁니다.
  • 리액트가 컴포넌트를 랜더하는 방식과 다시 랜더하지 않는 방식에 대해 배웁니다.

개발 환경 체크

  • Node.js

    • ​Installer
    • nvm
  • Browser (Chrome)

  • Git

  • VSCode

nvm install 16.14.2

nvm use 16.14.2

nvm alias default 16.14.2

Create React App

https://create-react-app.dev

npx create-react-app tic-tac-toe

npx

npm 5.2.0 이상부터 함께 설치된 커맨드라인 명령어

npx create-react-app tic-tac-toe

npx create-react-app 프로젝트이름

이런 곳에 있습니다.

왜 npx 가 필요했을까요??

  • 프로젝트의 로컬에 설치된 패키지의 실행 커맨드를 사용하려면,
    • package.jsonnpm scripts 에 명령어를 추가하여 사용해야 했다.
    • npx 로 바로 실행 가능
  • 전역으로 실행하고 싶은 패키지가 있을 경우,
    • npm i -g 를 이용하여, 전역에 꼭 설치해서 사용해야 가능했다.
    • npx 로 최신 버전의 패키지를 받아 바로 실행 가능 
{
  "name": "tic-tac-toe",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.1.1",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

package.json

  • 리액트 핵심 모듈

    • "react": "^18.0.0"

    • "react-dom": "^18.0.0"

  • cra 를 사용하는데 필요한 모듈

    • "react-scripts": "5.0.1"

  • ​Test 를 도와주는 유틸 모듈

    • "@testing-library/jest-dom": "^5.16.4"

    • "@testing-library/react": "^13.1.1"

    • "@testing-library/user-event": "^13.5.0"

  • ​ 성능 측정을 도와주는 모듈

    • ​"web-vitals": "^2.1.4"

  • npm start

    • react-scripts start
    • Starting the development server...
  • npm run build

    • react-scripts build
    • Creating an optimized production build...​
  • ​npm test

    • ​react-scripts test
    • Jest 와 testing-library 를 통해 test code 를 실행합니다.
  • ​npm run eject

    • ​react-scripts eject

개발 용 서버를 띄웁니다.

소스 코드가 수정되었을 때,

다시 컴파일 하고 웹페이지를 새로고침 합니다.

Project 폴더 바로 아래

build 라는 폴더가 만들어지고,

그 안에 Production 배포를 위한

파일들이 생성됩니다.

  • serve 라는 패키지를 전역으로 설치합니다.

  • serve 명령어를 -s 옵션으로 build 폴더를 지정하여 실행합니다.

    • ​-s 옵션은 어떤 라우팅으로 요청해도 index.html 을 응답하도록 합니다.
npm install serve -g
serve -s build
  • __tests__ 폴더 안의 .js 파일
  • .test.js 로 끝나는 파일
  • .spec.js 로 끝나는 파일

eject 를 이용하면, cra 로 만든 프로젝트에서 cra 를 제거합니다.

이는 돌이킬 수 없기 때문에 결정하기 전에 신중해야 합니다.

보통 cra 내에서 해결이 안되는 설정을 추가해야 할 때 합니다.

{
  "name": "tic-tac-toe",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@babel/core": "^7.16.0",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
    "@svgr/webpack": "^5.5.0",
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.1.1",
    "@testing-library/user-event": "^13.5.0",
    "babel-jest": "^27.4.2",
    "babel-loader": "^8.2.3",
    "babel-plugin-named-asset-import": "^0.3.8",
    "babel-preset-react-app": "^10.0.1",
    "bfj": "^7.0.2",
    "browserslist": "^4.18.1",
    "camelcase": "^6.2.1",
    "case-sensitive-paths-webpack-plugin": "^2.4.0",
    "css-loader": "^6.5.1",
    "css-minimizer-webpack-plugin": "^3.2.0",
    "dotenv": "^10.0.0",
    "dotenv-expand": "^5.1.0",
    "eslint": "^8.3.0",
    "eslint-config-react-app": "^7.0.1",
    "eslint-webpack-plugin": "^3.1.1",
    "file-loader": "^6.2.0",
    "fs-extra": "^10.0.0",
    "html-webpack-plugin": "^5.5.0",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^27.4.3",
    "jest-resolve": "^27.4.2",
    "jest-watch-typeahead": "^1.0.0",
    "mini-css-extract-plugin": "^2.4.5",
    "postcss": "^8.4.4",
    "postcss-flexbugs-fixes": "^5.0.2",
    "postcss-loader": "^6.2.1",
    "postcss-normalize": "^10.0.1",
    "postcss-preset-env": "^7.0.1",
    "prompts": "^2.4.2",
    "react": "^18.0.0",
    "react-app-polyfill": "^3.0.0",
    "react-dev-utils": "^12.0.1",
    "react-dom": "^18.0.0",
    "react-refresh": "^0.11.0",
    "resolve": "^1.20.0",
    "resolve-url-loader": "^4.0.0",
    "sass-loader": "^12.3.0",
    "semver": "^7.3.5",
    "source-map-loader": "^3.0.0",
    "style-loader": "^3.3.1",
    "tailwindcss": "^3.0.2",
    "terser-webpack-plugin": "^5.2.5",
    "web-vitals": "^2.1.4",
    "webpack": "^5.64.4",
    "webpack-dev-server": "^4.6.0",
    "webpack-manifest-plugin": "^4.0.2",
    "workbox-webpack-plugin": "^6.4.1"
  },
  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "jest": {
    "roots": [
      "<rootDir>/src"
    ],
    "collectCoverageFrom": [
      "src/**/*.{js,jsx,ts,tsx}",
      "!src/**/*.d.ts"
    ],
    "setupFiles": [
      "react-app-polyfill/jsdom"
    ],
    "setupFilesAfterEnv": [
      "<rootDir>/src/setupTests.js"
    ],
    "testMatch": [
      "<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
      "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
    ],
    "testEnvironment": "jsdom",
    "transform": {
      "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "<rootDir>/config/jest/babelTransform.js",
      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
      "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
    },
    "transformIgnorePatterns": [
      "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$",
      "^.+\\.module\\.(css|sass|scss)$"
    ],
    "modulePaths": [],
    "moduleNameMapper": {
      "^react-native$": "react-native-web",
      "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
    },
    "moduleFileExtensions": [
      "web.js",
      "js",
      "web.ts",
      "ts",
      "web.tsx",
      "tsx",
      "json",
      "web.jsx",
      "jsx",
      "node"
    ],
    "watchPlugins": [
      "jest-watch-typeahead/filename",
      "jest-watch-typeahead/testname"
    ],
    "resetMocks": true
  },
  "babel": {
    "presets": [
      "react-app"
    ]
  }
}
  • react-scripts 는 사라집니다.
  • 드러내지 않고 cra 에 의해 사용되던
    각종 패키지가 package.json 에 나타납니다.
  • Jest, Babel, ESLint 설정이 추가됩니다.
  • 각종 설정 파일이 config 폴더에 생성됩니다.

webpack

파일 확장자에 맞는 loader 에게 위임

babel-loader

js

jsx

css

.

.

.

css-loader

최종 배포용 파일

babel config

어떤 문법을 번역할건지 설정

ESLint

The pluggable linting utility for JavaScript and JSX

mkdir eslint-test

cd eslint-test

npm init -y

npm install eslint -D

npx eslint --init
{
    "env": {
        "commonjs": true,
        "es6": true,
        "node": true
    },
    "extends": "eslint:recommended",
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
        "ecmaVersion": 2018
    },
    "rules": {
        "semi": [
            "error",
            "always"
        ]
    }
}

rules 을 추가합니다.

// index.js
console.log("hello")
{
  "name": "tic-tac-toe",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "3.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app",
    "rules": {
      "semi": [
        "error",
        "always"
      ]
    }
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

.eslintrc.json

Prettier

An opinionated code formatter

mkdir prettier-test

cd prettier-test

npm init -y

npm i prettier -D
// index.js
console.log('hello')
// index.js
console.log("hello")

* replace *

Command + Shift + P

>Format Document

"editor.formatOnSave": true

Prettier 에서 불필요하거나, Prettier 와 충돌할 수 있는 모든 규칙을 끕니다.
이 구성은 규칙을 끄기만 하기 때문에 다른 설정과 함께 사용하는 것이 좋습니다.

{
  ...
  "eslintConfig": {
    "extends": [
      "react-app",
      "prettier"
    ]
  },
  ...
}

husky

Git hooks made easy

mkdir husky-test

cd husky-test

npm init -y

git init

npm i huskey -D

npm set-script prepare "husky install"

npm run prepare

npx husky add .husky/pre-commit "CI=true npm test"

git add -A

git commit -m "Keep calm and commit"

lint-staged

Run linters on git staged files

npm i lint-staged -D
{
  ...,
  "scripts": {
    ...,
    "lint-staged": "lint-staged"
  },
  ...,
  "lint-staged": {
    "*.{js,jsx}": [
      "eslint --fix",
      "prettier --write"
    ]
  }
}

React Developer Tools

개발 모드

React Concept

Keyword

  • Angular vs React vs Vue

  • View 를 다루는 라이브러리

  • Only Rendering & Update

    • NOT included another functionality (ex. http client, ...)

  • Component Based Development

    • 독립적인 코드 블럭 (HTML + CSS + JavaScript)

    • 작업의 단위

  • Virtual DOM

    • 이제는 DOM 을 직접 다루지 않음.

  • JSX

    • NOT Templates

    • transpile to JS (Babel, TypeScript)

  • CSR & SSR

Component ??

<!-- HTMLElement -->

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

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

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

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

<!--

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

-->

Component Based Development - Version

Component Based Development - Links

Component Based Development - Title

Component Based Development - Content

Component Based Development - Card

Component Based Development - Card

Component Based Development - Card

Component Tree => DOM Tree

Why Virtual DOM ?

  • DOM 을 직접 제어하는 경우

    • 바뀐 부분만 정확히 바꿔야 한다.

  • DOM 을 직접 제어하지 않는 경우

    • 가상의 돔 트리를 사용해서,

    • 이전 상태와 이후 상태를 비교하여,

    • 바뀐 부분을 찾아내서 자동으로 바꾼다.

Virtual DOM - diff 로 변경

  • 정적인 파일을 내려주기

    • ​html
  • 동적으로 생성해서 데이터 내려주기

    • ​jsp

React Server Side Rendering

React Client Side Rendering

CSR vs SSR

  • CSR

    • JS 가 전부 다운로드 되어 리액트 애플리케이션이 정상 실행되기 전까지는
      화면이 보이지 않음.

    • JS 가 전부 다운로드 되어 리액트 애플리케이션이 정상 실행된 후,
      화면이 보이면서 유저가 인터렉션 가능

  • SSR

    • JS 가 전부 다운로드 되지 않아도,
      일단 화면은 보이지만 유저가 사용 할 수 없음.

    • JS 가 전부 다운로드 되어 리액트 애플리케이션이 정상 실행된 후,
      유저가 사용 가능

React 라이브러리

리액트가 하는 일

리액트의 핵심 모듈 2개로 리액트가 하는 일 알아보기

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

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

< HTMLElement >

{ React 컴포넌트 } - JS, JSX

ReactDOM.render(
  <HelloMessage name="Taylor" />,
  
  document.getElementById('hello-example'),
);
class HelloMessage extends React.Component {
  render() {
    return (
      <div>
        Hello {this.props.name}
      </div>
    );
  }
}

ReactDOM.render(
  <HelloMessage name="Taylor" />,
  document.getElementById('hello-example')
);

< HTMLElement >

{ React 컴포넌트 } - JS, JSX

"만들어진 리액트 컴포넌트"

실제 HTMLElement 에 연결할 때

ReactDOM 라이브러리를

이용합니다.

{ React 컴포넌트 } 만들기

리액트 컴포넌트를 만들 때 사용하는 API 모음

Use React, ReactDOM Library with CDN

CDN 을 통한 리액트 라이브러리 사용

mkdir what-is-react

cd what-is-react

npm init -y

npx serve
<!-- ex1.html : CDN 을 통해 React, ReactDOM 가져오기 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <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">
      // Global 에 React 와 ReactDOM 객체가 생성
      console.log(React);
      console.log(ReactDOM);
    </script>
  </body>
</html>

고전 프론트엔드

HTML 로 문서 구조를 잡고,

CSS 로 스타일을 입히고,

JavaScript 로 DOM 을 조작합니다.

<!-- ex2.html : 고전 프론트엔드 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>
      * {margin: 0;padding: 0;border: 0;}
      #root p {color: white;font-size: 20px;background-color: green;text-align: center;width: 200px;}
      #btn_plus {background-color: red;border: 2px solid #000000;font-size: 15px;width: 200px;}
    </style>
  </head>
  <body>
    <div id="root"></div>
    <button id="btn_plus">+</button>
    <script type="text/javascript">
      const root = document.querySelector("#root");
      const btn_plus = document.querySelector("#btn_plus");

      let i = 0;

      root.innerHTML = `<p>init : 0</p>`;

      btn_plus.addEventListener("click", () => {
        root.innerHTML = `<p>init : ${++i}</p>`;
      });
    </script>
  </body>
</html>

컴포넌트를 활용한 프론트엔드

컴포넌트를 정의하고,

실제 DOM 에 컴포넌트를 그려준다.

<!-- ex3.html : 컴포넌트를 만들고, 실제 DOM 에 그린다. -->
<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <div id="root"></div>
    <button id="btn_plus">+</button>
    <script type="text/javascript">
      // react 라이브러리가 하는 일
      const component = {
        message: "init",
        count: 0,
        render() {
          return `<p>${this.message} : ${this.count}</p>`;
        }
      };

      // react-dom 라이브러리가 하는 일
      function render(dom, component) {
        // 컴포넌트를 render 하고, DOM 에 그려준다.
        root.innerHTML = component.render();
      }

      render(document.querySelector("#root"), component);

      document.querySelector("#btn_plus").addEventListener("click", () => {
        // 외부에서 컴포넌트의 값을 변경하는 행위
        component.message = "update";
        component.count = component.count + 1;

        render(document.querySelector("#root"), component);
      });
    </script>
  </body>
</html>

React 프론트엔드

컴포넌트를 정의하고,

실제 DOM 에 컴포넌트를 그려준다.

브라우저 지원

<!-- ex4.html : React 로 컴포넌트를 만들고, 실제 DOM 에 그린다. -->
<!DOCTYPE html>
<html lang="en">
  <head>...</head>
  <body>
    <div id="root"></div>
    <button id="btn_plus">+</button>
    <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">
      const Component = props => {
        return React.createElement(
          "p",
          null,
          `${props.message} ${props.count}`
        );
      };
      
      let i = 0;

      ReactDOM.render(
        React.createElement(Component, { message: "init", count: i }, null),
        document.querySelector("#root")
      );
      
      document.querySelector("#btn_plus").addEventListener("click", () => {
        i++;
        ReactDOM.render(
          React.createElement(Component, { message: "update", count: i }, null),
          document.querySelector("#root")
        );
      });
    </script>
  </body>
</html>

JSX

React.createElement 로

컴포넌트 만들기

React.createElement

순수 JavaScript (그렇다면 순수하지 않은 것은??)

<!-- 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. 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

JSX 문법으로 작성된 코드는 순수한 JavaScript 로 컴파일 하여 사용한다.

누가 해주나요?? => babel

JSX

JSX 문법 => React.createElement

<!-- ex6.html : React.createElement => JSX -->
<!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">
      //   React.createElement(
      //     type, // 태그 이름 문자열 | React 컴포넌트 | React.Fragment
      //     [props], // 리액트 컴포넌트에 넣어주는 데이터 객체
      //     [...children] // 자식으로 넣어주는 요소들
      //   );

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

      //   ReactDOM.render(
      //     <h1>type 이 "태그 이름 문자열" 입니다.</h1>,
      //     document.querySelector('#root'),
      //   );

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

      //   ReactDOM.render(
      //     React.createElement(
      //       Component,
      //       null,
      //       `type 이 "React 컴포넌트" 입니다.`
      //     ),
      //     document.querySelector("#root")
      //   );

      //   const Component = props => {
      //     return <p>type 이 "React 컴포넌트" 입니다.</p>;
      //   };

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

      // 3. React Fragment type
      //   ReactDOM.render(
      //     React.createElement(
      //       React.Fragment,
      //       null,
      //       `type 이 "React Fragment" 입니다.`
      //     ),
      //     document.querySelector("#root")
      //   );

      //   ReactDOM.render(
      //     <>`type 이 "React Fragment" 입니다.</>,
      //     document.querySelector('#root'),
      //   );

      // 4. props 를 통해 데이터를 주입
      //   const Component = props => {
      //     return React.createElement(
      //       'p',
      //       null,
      //       `message 는 "${props.message}" 입니다.`,
      //     );
      //   };

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

      //   const Component = props => {
      //     return <p>message 는 "{props.message}" 입니다.</p>;
      //   };

      //   ReactDOM.render(
      //     <Component message="이것은 메세지 입니다." />,
      //     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'),
      //   );

      //   const Component = props => {
      //     return (
      //       <p>
      //         message 는 "{props.message}" 입니다. props.children 은 "
      //         {props.children}" 입니다.
      //       </p>
      //     );
      //   };

      //   ReactDOM.render(
      //     <Component message="이것은 메세지 입니다.">
      //       이것은 children 입니다.
      //     </Component>,
      //     document.querySelector('#root'),
      //   );

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

      //   ReactDOM.render(
      //     <h1 style={{ color: 'red' }}>type 이 "태그 이름 문자열" 입니다.</h1>,
      //     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'),
      //   );

      //   ReactDOM.render(
      //     <div style={{ backgroundColor: 'red', width: 100, height: 100 }}>
      //       <div style={{ backgroundColor: 'green', width: 50, height: 50 }} />
      //       <div style={{ backgroundColor: 'yellow', width: 50, height: 50 }} />
      //     </div>,
      //     document.querySelector('#root'),
      //   );
    </script>
  </body>
</html>

왜 JSX 을 쓰나요??

  • React.createElement VS JSX
    • 가독성 완승
  • babel 과 같은 컴파일 과정에서 문법적 오류를 인지하기 쉬움

JSX 문법

  • 최상위 요소가 하나여야 합니다.
  • 최상위 요소 리턴하는 경우, ( ) 로 감싸야 합니다.
  • 자식들을 바로 랜더링하고 싶으면, <>자식들</> 를 사용합니다. => Fragment
  • 자바스크립트 표현식을 사용하려면, {표현식} 를 이용합니다.
  • if 문은 사용할 수 없습니다.
    • 삼항 연산자 혹은 && 를 사용합니다.
  • style 을 이용해 인라인 스타일링이 가능합니다.
  • class 대신 className 을 사용해 class 를 적용할 수 있습니다.
  • 자식요소가 있으면, 꼭 닫아야 하고, 자식요소가 없으면 열면서 닫아야 합니다.
    • <p>어쩌구</p>
    • <br />

JSX 문법

<!-- ex7.html : JSX 문법 -->
<!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">
      // 1. 최상위 요소가 하나여야 합니다.
      // 2. 최상위 요소 리턴하는 경우, ( ) 로 감싸야 합니다.
      //   const Comp1 = props => {
      //       return (
      //         <h1>제목</h1>
      //         <h2>부제목</h2>
      //       );
      //   }
      //   const Comp2 = props => {
      //     return (
      //       <div>
      //         <h1>제목</h1>
      //         <h2>부제목</h2>
      //       </div>
      //     );
      //   };

      // 3. 자식들을 바로 랜더링하고 싶으면, <>자식들</> 를 사용합니다. => Fragment
      //   const Comp3 = props => {
      //     return (
      //       <>
      //         <h1>제목</h1>
      //         <h2>부제목</h2>
      //       </>
      //     );
      //   };

      // 4. 자바스크립트 표현식을 사용하려면, {표현식} 를 이용합니다.
      //   const Comp4 = props => {
      //     return (
      //       <div>
      //         <h1>제목</h1>
      //         <h2>{props.children}</h2>
      //       </div>
      //     );
      //   };

      // 5. if 문은 사용할 수 없습니다.
      // 삼항 연산자 혹은 && 를 사용합니다.
      //   const Comp5 = props => {
      //     return (
      //       <div>
      //         <h1>제목</h1>
      //         <h2>{props.children}</h2>
      //         {props.isShow ? '있다' : '없다.'}
      //         {props.isShow && '있을 때만 나온다'}
      //       </div>
      //     );
      //   };

      // 6. style 을 이용해 인라인 스타일링이 가능합니다.
      //   const Comp6 = props => {
      //     return (
      //       <div>
      //         <h1
      //           style={{
      //             color: 'red',
      //           }}
      //         >
      //           제목
      //         </h1>
      //         <h2>부제목</h2>
      //       </div>
      //     );
      //   };

      // 7. class 대신 className 을 사용해 class 를 적용할 수 있습니다.
      //   const Comp7 = props => {
      //     return (
      //       <div>
      //         <h1 className="title">제목</h1>
      //         <h2>부제목</h2>
      //       </div>
      //     );
      //   };

      // 8. 자식요소가 있으면, 꼭 닫아야 하고, 자식요소가 없으면 열면서 닫아야 합니다.
      // <p>어쩌구</p>
      // <br />
      //   const Comp7 = props => {
      //     return (
      //       <div>
      //         <h1 className="title">제목</h1>
      //         <br />
      //         <h2>부제목</h2>
      //       </div>
      //     );
      //   };
    </script>
  </body>
</html>

React Component 만드는 법

Hooks 이전

  • 컴포넌트 내부에 상태가 있다면 ?

    • class

  • 컴포넌트 내부에 상태가 없다면 ?

    • ​라이프사이클을 사용해야 한다면 ?

      • class

    • ​라이프사이클에 관계 없다면 ?

      • ​function

Hooks 이후

  • class

  • function

Class 컴포넌트

import React from 'react';

// 정의
class ClassComponent extends React.Component {
  render() {
    return (<div>Hello</div>);
  }
}

// 사용
<ClassComponent />

Function 컴포넌트

import React from 'react';

// 정의 1
function FunctionComponent() {  
  return <div>Hello</div>;
}

// 정의 2
const FunctionComponent = () => <div>Hello</div>;

// 사용
<FunctionComponent />

Props 와 State

Props 와 State

Props 는 컴포넌트 외부에서 컴포넌트에게 주는 데이터입니다.

State 는 컴포넌트 내부에서 변경할 수 있는 데이터입니다.

둘 다 변경이 발생하면, 랜더가 다시 일어날 수 있습니다.

Component

Props

State

Render 함수

Props 와 State 를 바탕으로 컴포넌트를 그립니다.

그리고 Props 와 State 가 변경되면, 컴포넌트를 다시 그립니다.

컴포넌트를 그리는 방법을 기술하는 함수가 랜더 함수 입니다.

Component

Props

State

Component

Props

State

Props

State

function 컴포넌트(props) { retutn JSX; }

<!-- 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">
      function Component(props) {
        return (
          <div>
            <h1>{props.message} 이것은 함수로 만든 컴포넌트 입니다.</h1>
          </div>
        );
      }

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

<Component p="프롭스" />

props 설정

function Component(props) {
  return (
    <div>
      <h1>{props.message} 이것은 함수로 만든 컴포넌트 입니다.</h1>
    </div>
  );
}

props.p 로 접근

props 사용

class 컴포넌트 extends React.Component

<!-- 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>

<Component p="프롭스" />

props 설정

class Component extends React.Component {
  render() {
    return (
      <div>{this.props.p}</div>
    );
  }
}

this.props.p 로 접근

props 사용

defaultProps

<!-- 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 = {}; constructor

state 초기값 설정

class Component extends React.Component {
  state = {
    s: '스테이트'
  };
  render() {
    return (
      <div>{this.state.s}</div>
    );
  }
}

this.state.s 로 접근

state 사용

class Component extends React.Component {
  constructor(props) {
    super(props);
    this.state = {s: '스테이트'};
  }
  render() {
    return (
      <div>{this.state.s}</div>
    );
  }
}

this.setState({s: '새 스테이트'});

state 값 업데이트

class Component extends React.Component {
  state = {
    s: '스테이트'
  };
  render() {
    return (
      <div onClick={() => {
        this.setState({s: '새 스테이트'});
      }}>{this.state.s}</div>
    );
  }
}

Event Handling

Event Handling

  • HTML DOM 에 클릭하면 이벤트가 발생하고, 발생하면 그에 맞는 변경이 일어나도록 해야합니다.
  • JSX 에 이벤트를 설정할 수 있습니다.
class Comp extends React.Component {
  render() {
    return (
      <div>
        <button onClick={() => {
          console.log('clicked');
        }}>클릭</button>
      </div>
    );
  }
}

Event Handling

  • camelCase 로만 사용할 수 있습니다.
    • onClick, onMouseEnter
  • 이벤트에 연결된 자바스크립트 코드는 함수입니다.
    • 이벤트={함수} 와 같이 씁니다.
  • 실제 DOM 요소들에만 사용 가능합니다.
    • 리액트 컴포넌트에 사용하면, 그냥 props 로 전달합니다.
<!-- ex10.html : 이벤트를 이용하여 state 바꾸기 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>
      * { margin: 0; padding: 0; border: 0; }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/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 {
        state = { message: 'init', count: 0 };
        render() {
          return (
            <>
              <p
                style={{
                  color: 'white',
                  fontSize: 20,
                  backgroundColor: 'green',
                  textAlign: 'center',
                  width: 200,
                }}
              >
                {this.state.message} {this.state.count}
              </p>
              <button
                style={{
                  backgroundColor: 'red',
                  border: '2px solid #000000',
                  fontSize: 15,
                  width: 200,
                }}
                onClick={() => {
                  this.setState({
                    message: 'update',
                    count: this.state.count + 1,
                  });
                }}
              >
                +
              </button>
            </>
          );
        }
      }

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

Component Lifecycle

리액트 컴포넌트는 탄생부터 죽음까지

여러지점에서 개발자가 작업이 가능하도록

메서드를 오버라이딩 할 수 있게 해준다.

Declarative 디클레러티브

Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.

Component 생성 및 마운트 (< v16.3)

constructor

componentWillMount

render (최초 랜더)

componentDidMount

class App extends React.Component {
  _interval;

  constructor(props) {
    console.log('App constructor');
    super(props);
    this.state = {
      age: 37,
    };
  }

  componentWillMount() {
    console.log('App componentWillMount');
  }

  componentDidMount() {
    console.log('App componentDidMount');
    this._interval = window.setInterval(() => {
      this.setState({
        age: this.state.age + 1,
      });
    }, 1000);
  }

  componentWillUnmount() {
    console.log('App componentWillUnmount');
    clearInterval(this._interval);
  }

  render() {
    console.log('App render');
    return (
      <div>
        <h2>
          Hello {this.props.name} - {this.state.age}
        </h2>
      </div>
    );
  }
}

constructor

componentWillMount

render

componentDidMount

Component props, state 변경 (< v16.3)

componentWillReceiveProps

shouldComponentUpdate

componentWillUpdate
render

componentDidUpdate

  componentWillReceiveProps(nextProps) {
    console.log(
      `App componentWillReceiveProps : ${JSON.stringify(
        this.props
      )} => ${JSON.stringify(nextProps)}`
    );
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log(
      `App shouldComponentUpdate : ${JSON.stringify(
        this.props
      )} => ${JSON.stringify(nextProps)}, ${JSON.stringify(
        this.state
      )} => ${JSON.stringify(nextState)}`
    );
    return true;
  }

  componentWillUpdate(nextProps, nextState) {
    console.log(
      `App componentWillUpdate : ${JSON.stringify(
        this.props
      )} => ${JSON.stringify(nextProps)}, ${JSON.stringify(
        this.state
      )} => ${JSON.stringify(nextState)}`
    );
  }

  componentDidUpdate(prevProps, prevState) {
    console.log(
      `App componentDidUpdate : ${JSON.stringify(
        prevProps
      )} => ${JSON.stringify(this.props)}, ${JSON.stringify(
        prevState
      )} => ${JSON.stringify(this.state)}`
    );
  }

componentWillReceiveProps

shouldComponentUpdate

componentWillUpdate

render

componentDidUpdate

componentWillReceiveProps

  • props 를 새로 지정했을 때 바로 호출됩니다.

  • 여기는 state 의 변경에 반응하지 않습니다.

    • 여기서 props 의 값에 따라 state 를 변경해야 한다면,

      • setState 를 이용해 state 를 변경합니다.

      • 그러면 다음 이벤트로 각각 가는것이 아니라 한번에 변경됩니다.

shouldComponentUpdate

  • props 만 변경되어도

  • state 만 변경되어도

  • props & state 둘다 변경되어도

  • newProps 와 new State 를 인자로 해서 호출

  • return type 이 boolean 입니다.

    • true 면 render

    • false 면 render 가 호출되지 않습니다.

    • 이 함수를 구현하지 않으면, 디폴트는 true

componentWillUpdate

  • 컴포넌트가 재 랜더링 되기 직전에 불립니다.

  • 여기선 setState 같은 것을 쓰면 아니됩니다.

componentDidUpdate

  • 컴포넌트가 재 랜더링을 마치면 불립니다.

Component 언마운트 (< v16.3)

componentWillUnmpunt

class App extends React.Component {
  _interval;

  constructor(props) {
    console.log('App constructor');
    super(props);
    this.state = {
      age: 37,
    };
  }

  componentDidMount() {
    console.log('App componentDidMount');
    this._interval = window.setInterval(() => {
      this.setState({
        age: this.state.age + 1,
      });
    }, 1000);
  }
  
  componentWillUnmount() {
    console.log('App componentWillUnmount');
    clearInterval(this._interval);
  }

  render() {
    console.log('App render');
    return (
      <div>{this.state.age < 50 && <Button />}</div>
    );
  }
}

componentWillUnmount

class Button extends React.Component {
  componentWillUnmount() {
    console.log('Button componentWillUnmount');
  }

  render() {
    return <>hello</>;
  }
}

Component 라이프사이클 변경 (v16.3)

constructor

componentWillMount => getDerivedStateFromProps

render

componentDidMount

 

componentWillReceiveProps => getDerivedStateFromProps

shouldComponentUpdate

render

componentWillUpdate => getSnapshotBeforeUpdate

(dom 에  적용)

componentDidUpdate

 

componentWillUnmount

Component 생성 및 마운트 (v16.3)

constructor

static getDerivedStateFromProps

render (최초 랜더)

componentDidMount

import React from 'react';

class App extends React.Component {
  state = {
    age: 0,
  };

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log(nextProps, prevState);
    if (prevState.age !== nextProps.age) {
      return { age: nextProps.age };
    }

    return null;
  }

  render() {
    console.log('App render');
    return <div>{this.state.age}</div>;
  }
}

export default App;

getDerivedStateFromProps

Component props, state 변경 (v16.3)

static getDerivedStateFromProps​ (props 변경)

shouldComponentUpdate (state 변경)
render

getSnapshotBeforeUpdate

(dom 에 적용)

componentDidUpdate

import React from "react";
import "./App.css";

let i = 0;

export default class App extends React.Component {
  state = { list: [] };

  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevState.list.length === this.state.list.length) return null;
    const list = document.querySelector("#list");
    return list.scrollHeight - list.scrollTop;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot === null) return;
    const list = document.querySelector("#list");
    list.scrollTop = list.scrollHeight - snapshot;
  }

  componentDidMount() {
    setInterval(() => {
      this.setState({
        list: [...this.state.list, i++],
      });
    }, 1000);
  }

  render() {
    return (
      <div id="list" style={{ height: 100, overflow: "scroll" }}>
        {this.state.list.map((i) => (
          <div>{i}</div>
        ))}
      </div>
    );
  }
}

getSnapshotBeforeUpdate

Component 언마운트 (v16.3)

componentWillUnmount

Component 에러 캐치

componentDidCatch

import React from 'react';

class Button extends React.Component {
  render() {
    test();
    return <div>hello</div>;
  }
}

class App extends React.Component {
  state = {
    hasError: false,
  };

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    // logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <div>에러 화면</div>;
    }
    return (
      <div>
        <Button />
      </div>
    );
  }
}

export default App;

componentDidCatch

React 의 라우팅 이해하기

https://github.com/nhn-kai/react-router-dom-v6​

/

/profile

/about

Client (Browser)

Server

Single Page Application

/

/profile

/about

Client (Browser)

SPA 라우팅 과정

  1. 브라우저에서 최초에 '/' 경로로 요청을 하면,

  2. React Web App 을 내려줍니다.

  3. 내려받은 React App 에서 '/' 경로에 맞는 컴포넌트를 보여줍니다.

  4. React App 에서 다른 페이지로 이동하는 동작을 수행하면,

  5. 새로운 경로에 맞는 컴포넌트를 보여줍니다.

npm i react-router-dom
  • cra 에 기본 내장된 패키지가 아닙니다.

  • react-router-dom 은 Facebook 의 공식 패키지는 아닙니다.

  • 가장 대표적인 라우팅 패키지입니다.

특정 경로에서 보여줄 컴포넌트를 준비합니다.

  • '/'  =>  Home 컴포넌트

  • '/profile'  =>  Profile 컴포넌트

  • '/about'  =>  About 컴포넌트

// src/pages/Home.jsx

export default function Home() {
  return <div>Home 페이지 입니다.</div>;
}
// src/pages/Profile.jsx

export default function Profile() {
  return <div>Profile 페이지 입니다.</div>;
}
// src/pages/About.jsx

export default function About() {
  return <div>About 페이지 입니다.</div>;
}

   /

   /profile

   /about

// src/App.js

import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="profile" element={<Profile />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
- Route 컴포넌트에 경로(path) 와 컴포넌트(element) 를 설정하여 나열해줍니다.

- Routes 로 Route 들을 감싸줍니다.

- 브라우저에서 요청한 경로에 Route 의 path 가 들어있으면 해당 component 를 보여줍니다.

Dynamic 라우팅

/profile/1

import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />} />
        <Route path="profile/:id" element={<Profile />} />
        <Route path="about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
<Route path="/profile/:id" element={<Profile />} />

/profile/1

import { useParams } from "react-router-dom";

const Profile = () => {
  const { id } = useParams();
  console.log(id, typeof id);
  return (
    <div>
      <h2>Profile 페이지입니다.</h2>
      {id && <p>id 는 {id} 입니다.</p>}
    </div>
  );
};

export default Profile;
const { id } = useParams();

typeof id => 'string'

/about?name=mark

import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />} />
        <Route path="profile/:id" element={<Profile />} />
        <Route path="about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
<Route path="/about" element={<About />} />

/about?name=mark

// src/pages/About.jsx

import { useLocation } from "react-router";

const About = () => {
  const location = useLocation();
  console.log(location.search);
  
  return <div>About 페이지 입니다.</div>;
};

export default About;
const location = useLocation();

/about?name=mark

// src/pages/About.jsx

import { useLocation } from "react-router";

const About = () => {
  const { search } = useLocation();
  const searchParams = new URLSearchParams(search);
  const name = searchParams.get("name");
  console.log(name);

  return (
    <div>
      <h2>About 페이지 입니다.</h2>
      {name && <p>name 은 {name} 입니다.</p>}
    </div>
  );
};

export default About;
new URLSearchParams(location.search);

/about?name=mark

// src/pages/About.jsx

import { useLocation } from "react-router";
import qs from "qs";

const About = () => {
  const { search } = useLocation();
  const query = qs.parse(search, { ignoreQueryPrefix: true });
  const { name } = query;
  console.log(name);

  return (
    <div>
      <h2>About 페이지 입니다.</h2>
      {name && <p>name 은 {name} 입니다.</p>}
    </div>
  );
};

export default About;
const query = qs.parse(search, { ignoreQueryPrefix: true });

NotFound

path="*"

import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";
import NotFound from "./pages/NotFound";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />} />
        <Route path="profile/:id" element={<Profile />} />
        <Route path="about" element={<About />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

NotFound

const NotFound = () => <div>페이지를 찾을 수 없습니다.</div>;

export default NotFound;

JSX 링크로 라우팅 이동하기

<Link to="/">Home</Link>
<a href="/">Home</a>
  • 앱을 새로고침하면서 경로를 이동합니다.

  • import { Link } from 'react-router-dom';

  • 브라우저의 주소를 바꾸고,

  • 맞는 Route 로 화면을 변경합니다.

import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";
import NotFound from "./pages/NotFound";
import Links from "./components/Links";

function App() {
  return (
    <BrowserRouter>
      <Links />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />} />
        <Route path="profile/:id" element={<Profile />} />
        <Route path="about" element={<About />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
import { Link } from "react-router-dom";

const Links = () => (
  <ul>
    <li>
      <Link to="/">Home</Link>
    </li>
    <li>
      <Link to="/profile">Profile</Link>
    </li>
    <li>
      <Link to="/profile/1">Profile/1</Link>
    </li>
    <li>
      <Link to="/about">About</Link>
    </li>
    <li>
      <Link to="/about?name=mark">About?name=mark</Link>
    </li>
  </ul>
);

export default Links;
  • import { NavLink } from 'react-router-dom';

  • className, style 는 객체가 아니라 함수로 isActive 를 통한 스타일 지정이 가능합니다.

  • Route 가 동시에 매치될 수 있기 때문에 end 가 있습니다.

import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";
import NotFound from "./pages/NotFound";
import Links from "./components/Links";
import NavLinks from "./components/NavLinks";

function App() {
  return (
    <BrowserRouter>
      <Links />
      <NavLinks />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />} />
        <Route path="profile/:id" element={<Profile />} />
        <Route path="about" element={<About />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
import { NavLink, useLocation } from "react-router-dom";

const activeStyle = { color: "green" };

const NavLinks = () => {
  const { search } = useLocation();
  return (
    <ul>
      <li>
        <NavLink
          to="/"
          style={({ isActive }) => (isActive ? activeStyle : undefined)}
        >
          Home
        </NavLink>
      </li>
      <li>
        <NavLink
          to="/profile"
          style={({ isActive }) => (isActive ? activeStyle : undefined)}
          end
        >
          Profile
        </NavLink>
      </li>
      <li>
        <NavLink
          to="/profile/1"
          style={({ isActive }) => (isActive ? activeStyle : undefined)}
        >
          Profile/1
        </NavLink>
      </li>
      <li>
        <NavLink
          to="/about"
          style={({ isActive }) =>
            isActive && search === "" ? activeStyle : undefined
          }
        >
          About
        </NavLink>
      </li>
      <li>
        <NavLink
          to="/about?name=mark"
          style={({ isActive }) =>
            isActive && search === "?name=mark" ? activeStyle : undefined
          }
        >
          About?name=mark
        </NavLink>
      </li>
    </ul>
  );
};

export default NavLinks;

Redirect

<Navigate />

import { Navigate } from 'react-router-dom';

// jsx
<Navigate to="/" />
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";
import Links from "./components/Links";
import NavLinks from "./components/NavLinks";

function App() {
  return (
    <BrowserRouter>
      <Links />
      <NavLinks />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />} />
        <Route path="profile/:id" element={<Profile />} />
        <Route path="about" element={<About />} />
        <Route path="*" element={<Navigate to="/" replace={true} />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

JS 로 라우팅 이동하기

import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Profile from "./pages/Profile";
import Links from "./components/Links";
import NavLinks from "./components/NavLinks";
import Login from "./pages/Login";

function App() {
  return (
    <BrowserRouter>
      <Links />
      <NavLinks />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route path="profile" element={<Profile />} />
        <Route path="profile/:id" element={<Profile />} />
        <Route path="about" element={<About />} />
        <Route path="*" element={<Navigate to="/" replace={true} />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
const navigate = useNavigate();

/login

// src/pages/Login.jsx

import { useNavigate } from "react-router-dom";

const Login = () => {
  const navigate = useNavigate();

  function login() {
    setTimeout(() => {
      navigate("/");
    }, 1000);
  }
  return (
    <div>
      <h2>Login 페이지 입니다.</h2>
      <button onClick={login}>로그인하기</button>
    </div>
  );
};

export default Login;

React Component Styling

Style Loaders

webpack

파일 확장자에 맞는 loader 에게 위임

babel-loader

.js

.jsx

.css

.

.

.

style-loader
css-loader

최종 배포용 파일

babel config

어떤 문법을 번역할건지 설정

.

.

.

npx create-react-app style-loaders-example

cd style-loaders-example

npm run eject
            // "postcss" loader applies autoprefixer to our CSS.
            // "css" loader resolves paths in CSS and adds assets as dependencies.
            // "style" loader turns CSS into JS modules that inject <style> tags.
            // In production, we use MiniCSSExtractPlugin to extract that CSS
            // to a file, but in development "style" loader enables hot editing
            // of CSS.
            // By default we support CSS Modules with the extension .module.css
            {
              test: cssRegex, // /\.css$/
              exclude: cssModuleRegex, // /\.module\.css$/
              use: getStyleLoaders({
                importLoaders: 1,
                sourceMap: isEnvProduction && shouldUseSourceMap,
              }),
              // Don't consider CSS imports dead code even if the
              // containing package claims to have no side effects.
              // Remove this when webpack adds a warning or an error for this.
              // See https://github.com/webpack/webpack/issues/6571
              sideEffects: true,
            },

CSS (webpack.config.js)

import './App.css';
            // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
            // using the extension .module.css
            {
              test: cssModuleRegex, // /\.module\.css$/
              use: getStyleLoaders({
                importLoaders: 1,
                sourceMap: isEnvProduction && shouldUseSourceMap,
                modules: true,
                getLocalIdent: getCSSModuleLocalIdent,
              }),
            },

CSS Module (webpack.config.js)

import styles from './App.module.css';
            // Opt-in support for SASS (using .scss or .sass extensions).
            // By default we support SASS Modules with the
            // extensions .module.scss or .module.sass
            {
              test: sassRegex, // /\.(scss|sass)$/
              exclude: sassModuleRegex, // /\.module\.(scss|sass)$/
              use: getStyleLoaders(
                {
                  importLoaders: 2,
                  sourceMap: isEnvProduction && shouldUseSourceMap,
                },
                'sass-loader'
              ),
              // Don't consider CSS imports dead code even if the
              // containing package claims to have no side effects.
              // Remove this when webpack adds a warning or an error for this.
              // See https://github.com/webpack/webpack/issues/6571
              sideEffects: true,
            },

Sass (webpack.config.js)

import './App.scss';
import './App.sass';
            // Adds support for CSS Modules, but using SASS
            // using the extension .module.scss or .module.sass
            {
              test: sassModuleRegex, // /\.module\.(scss|sass)$/
              use: getStyleLoaders(
                {
                  importLoaders: 2,
                  sourceMap: isEnvProduction && shouldUseSourceMap,
                  modules: true,
                  getLocalIdent: getCSSModuleLocalIdent,
                },
                'sass-loader'
              ),
            },

Sass Module (webpack.config.js)

import styles from './App.module.scss';
import styles from './App.module.sass';

CSS, SASS

// App.js

import './App.css';
<div className="App">
  <header className="App-header">
    <img src={logo} className="App-logo" alt="logo" />
    <p>
      Edit <code>src/App.js</code> and save to reload.
    </p>
    <a
      className="App-link"
      href="https://reactjs.org"
      target="_blank"
      rel="noopener noreferrer"
    >
      Learn React
    </a>
  </header>
</div>

App.css

.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #09d3ac;
}
  • .App

  • .App-header

  • .App-logo

  • .App-link

App.css

.App {
  text-align: center;
}

.App .logo {
  height: 40vmin;
}

.App .header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App .link {
  color: #09d3ac;
}
  • .App

  • .App .header

  • .App .logo

  • .App .link

<div className="App">
  <header className="header">
    <img src={logo} className="logo" alt="logo" />
    <p>
      Edit <code>src/App.js</code> and save to reload.
    </p>
    <a
      className="link"
      href="https://reactjs.org"
      target="_blank"
      rel="noopener noreferrer"
    >
      Learn React
    </a>
  </header>
</div>

App.scss

.App {
  text-align: center;

  .logo {
    height: 40vmin;
  }

  .header {
    background-color: #282c34;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: calc(10px + 2vmin);
    color: white;
  }

  .link {
    color: #09d3ac;
  }
}
  • .App

  • .App .header

  • .App .logo

  • .App .link

<div className="App">
  <header className="header">
    <img src={logo} className="logo" alt="logo" />
    <p>
      Edit <code>src/App.js</code> and save to reload.
    </p>
    <a
      className="link"
      href="https://reactjs.org"
      target="_blank"
      rel="noopener noreferrer"
    >
      Learn React
    </a>
  </header>
</div>
npm i sass

CSS module

SASS module

import styles from './App.module.css';

console.log(styles);

[filename]\_[classname]\_\_[hash]

.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #09d3ac;
}
import styles from './App.module.scss';

console.log(styles);

[filename]\_[classname]\_\_[hash]

.App {
  text-align: center;

  .logo {
    animation: App-logo-spin infinite 20s linear;
    height: 40vmin;
    pointer-events: none;
  }

  .header {
    background-color: #282c34;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: calc(10px + 2vmin);
    color: white;
  }

  .link {
    color: #61dafb;
  }
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

styles[클래스]

import logo from "./logo.svg";
import styles from "./App.module.css";

const App = () => {
  console.log(styles);
  return (
    <div className={styles["App"]}>
      <header className={styles["App-header"]}>
        <img src={logo} className={styles["App-logo"]} alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className={styles["App-link"]}
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
};

export default App;

components

- Button.module.css
- Button.jsx

import styles from './Button.module.css';

const Button = props => <button className={styles.button} {...props} />;

export default Button;
.button {
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 20px;
}

Button.jsx

import styles from './Button.module.css';

export default class Button extends React.Component {
  state = {
    loading: false,
  };

  startLoading = () => {
    console.log('start');
    this.setState({ loading: true });
    setTimeout(() => {
      this.setState({ loading: false });
    }, 1000);
  };

  render() {
    const { loading } = this.state;
    return (
      <button
        className={
          loading ? `${styles.button} ${styles.loading}` : styles.button
        }
        {...this.props}
        onClick={this.startLoading}
      />
    );
  }
}
.button {
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 20px;
}

.loading {
  border: 2px solid grey;
  color: grey;
}
  • 조건부 className
  • 여러 className
npm i classnames
import classNames from 'classnames';

console.log(classNames('foo', 'bar')); // "foo bar"
console.log(classNames('foo', 'bar', 'baz')); // "foo bar baz"

console.log(classNames({ foo: true }, { bar: true })); // "foo bar"
console.log(classNames({ foo: true }, { bar: false })); // "foo"
console.log(classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, '')); // "bar 1"
console.log(classNames(styles.button, styles.loading)); // Button_button__2Ce79 Button_loading__XEngF

import classNames from 'classnames';

Button.jsx

import styles from './Button.module.css';
import classNames from 'classnames';

export default class Button extends React.Component {
  state = {
    loading: false,
  };

  startLoading = () => {
    console.log('start');
    this.setState({ loading: true });
    setTimeout(() => {
      this.setState({ loading: false });
    }, 1000);
  };

  render() {
    const { loading } = this.state;
    return (
      <button
        className={
          loading ? classNames(styles.button, styles.loading) : styles.button
        }
        {...this.props}
        onClick={this.startLoading}
      />
    );
  }
}
import classNames from 'classnames/bind';

const cx = classNames.bind(styles);

console.log(cx('button', 'loading')); // Button_button__2Ce79 Button_loading__XEngF
console.log(cx('button', { loading: false })); // Button_button__2Ce79

import classNames from 'classnames/bind';

Button.jsx

import styles from './Button.module.css';
import classNames from 'classnames/bind';

const cx = classNames.bind(styles);

export default class Button extends React.Component {
  state = {
    loading: false,
  };

  startLoading = () => {
    console.log('start');
    this.setState({ loading: true });
    setTimeout(() => {
      this.setState({ loading: false });
    }, 1000);
  };

  render() {
    const { loading } = this.state;
    return (
      <button
        className={cx('button', { loading })}
        {...this.props}
        onClick={this.startLoading}
      />
    );
  }
}

Styled Components

npx create-react-app styled-components-example

cd styled-components-example

npm i styled-components

code .

npm start
import logo from './logo.svg';
import './App.css';
import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          <StyledButton>버튼</StyledButton>
        </p>
      </header>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button``;

export default StyledButton;

styled.<태그>`스타일`

import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
`;

export default StyledButton;

styled.<태그>`스타일`

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>버튼</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled, { css } from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;

  ${props =>
    props.primary &&
    css`
      background: palevioletred;
      color: white;
    `};
`;

export default StyledButton;

${props => css`스타일`}

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>버튼</StyledButton>
        <StyledButton primary>Primary 버튼</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled, { css } from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
`;

const PrimaryStyledButton = styled(StyledButton)`
  background: palevioletred;
  color: white;
`;

export default PrimaryStyledButton;

styled(StyledButton)

import PrimaryStyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <PrimaryStyledButton>버튼</PrimaryStyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 1em;
  display: inline-block;

  text-decoration: none;
`;

export default StyledButton;

as="태그"

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton as="a" href="/">
          a 태그 버튼
        </StyledButton>
        <StyledButton>버튼</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 1em;
  display: inline-block;
`;

export default StyledButton;

as={컴포넌트}

import StyledButton from './components/StyledButton';

const UppercaseButton = props => (
  <button {...props} children={props.children.toUpperCase()} />
);

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton as={UppercaseButton}>button</StyledButton>
        <StyledButton>button</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

function MyButton({ className, children }) {
  return <button className={className}>MyButton {children}</button>;
}

const StyledButton = styled(MyButton)`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 1em;
`;

export default StyledButton;

styled(컴포넌트)

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>button</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled('button')`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 1em;
`;

export default StyledButton;

styled('태그') = styled.태그

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>button</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid ${props => props.color || 'palevioletred'};
  color: ${props => props.color || 'palevioletred'};
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 1em;
`;

export default StyledButton;

${props => props.color || ''}

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>button</StyledButton>
        <StyledButton color="red">red button</StyledButton>
        <StyledButton color="green">green button</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;

  :hover {
    border: 2px solid red;
  }
`;

export default StyledButton;

:hover {스타일}

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>버튼</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;

  ::before {
    content: '@';
  }
`;

export default StyledButton;

::before {스타일}

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>버튼</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;

  &:hover {
    border: 2px solid red;
  }
`;

export default StyledButton;

&:hover {스타일}

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>버튼</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;

  & ~ & {
    border: 2px solid red;
  }

  & + & {
    border: 2px solid green;
  }
`;

export default StyledButton;

& ~ & {스타일}, & + & {스타일}

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>버튼</StyledButton>
        <StyledButton>버튼</StyledButton>
        <StyledButton>버튼</StyledButton>
        <button>버튼</button>
        <StyledButton>버튼</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;

  &.orange {
    border: 2px solid orange;
  }
`;

export default StyledButton;

&.클래스 {스타일}

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton className="orange">버튼</StyledButton>
      </p>
      <p className="orange">
        <StyledButton>버튼</StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;

  .orange {
    color: orange;
  }
`;

export default StyledButton;

.클래스 {스타일}

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>
          <a className="orange">버튼</a>
        </StyledButton>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  border: 1px solid palevioletred;
`;

export default StyledButton;

createGlobalStyle`스타일`

import StyledButton from './components/StyledButton';
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  button {
    color: palevioletred;
  }
`;

function App() {
  return (
    <div className="App">
      <p>
        <GlobalStyle />
        <StyledButton>버튼</StyledButton>
        <button>버튼</button>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledButton = styled.button`
  border: 1px solid palevioletred;
`;

export default StyledButton;

createGlobalStyle`스타일`

import StyledButton from './components/StyledButton';
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  button${StyledButton} {
    color: palevioletred;
  }
`;

function App() {
  return (
    <div className="App">
      <p>
        <GlobalStyle />
        <StyledButton>버튼</StyledButton>
        <button>버튼</button>
      </p>
    </div>
  );
}

export default App;
import styled from 'styled-components';

const StyledA = styled.a.attrs(props => ({
  href: props.href || 'https://www.fastcampus.co.kr',
  color: props.color || 'palevioletred',
  target: '_BLANK',
}))`
  color: ${props => props.color};
`;

export default StyledA;

styled.태그.attr(props => ({속성들}))

import StyledA from './components/StyledA';

function App() {
  return (
    <div className="App">
      <p>
        <StyledA>링크</StyledA>
        <StyledA color="red">링크</StyledA>
      </p>
    </div>
  );
}

export default App;
import styled, { keyframes } from 'styled-components';

const slide = keyframes`
  from {
    margin-top: 0em;
  }

  to {
    margin-top: 1em;
  }
`;

const StyledButton = styled.button`
  display: inline-block;
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
  animation: ${slide} 0.3s ease-in;
`;

export default StyledButton;

keyframes`키프레임`

import StyledButton from './components/StyledButton';

function App() {
  return (
    <div className="App">
      <p>
        <StyledButton>Slide Button</StyledButton>
      </p>
    </div>
  );
}

export default App;

React Shadow

npx create-react-app react-shadow-example

cd react-shadow-example

npm i react-shadow

code .

npm start
/* index.css */

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
    monospace;
}

p {
  color: red;
}
.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
import logo from "./logo.svg";
import root from "react-shadow";

const styles = `...`;

function App() {
  return (
    <root.div>
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
      <style type="text/css">{styles}</style>
    </root.div>
  );
}

export default App;

HOC

Higher Order Component

  • advanced technique in React for reusing component logic.

  • not part of the React API

  • a pattern that emerges from React’s compositional nature.

HOC = function(컴포넌트) { return 새로운 컴포넌트; }

HOC

<컴포넌트> 를 인자로 받아

<새로운 컴포넌트> 를 리턴하는

함수

컴포넌트

props

UI

HOC

컴포넌트

새로운

컴포넌트

you already know HOC !

import React from "react";
import { withRouter } from "react-router-dom";

const LoginButton = props => {
  console.log(props);
  function login() {
    setTimeout(() => {
      props.history.push("/");
    }, 1000);
  }
  return <button onClick={login}>로그인하기</button>;
};

export default withRouter(LoginButton);

withRouter()

보통 with 가 붙은 함수가 HOC 인 경우가 많다.

export default withRouter(LoginButton);

사용하는 법

  • Use HOCs For Cross-Cutting Concerns

  • Don’t Mutate the Original Component. Use Composition.

  • Pass Unrelated Props Through to the Wrapped Component

  • Maximizing Composability

  • Wrap the Display Name for Easy Debugging

withRouter(LoginButton)

주의할 점

  • Don’t Use HOCs Inside the render Method

  • Static Methods Must Be Copied Over

  • Refs Aren’t Passed Through (feat. React.forwardRef)

render() {
  // A new version of EnhancedComponent is created on every render
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // That causes the entire subtree to unmount/remount each time!
  return <EnhancedComponent />;
}
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  // Must know exactly which method(s) to copy :(
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}
// Define a static method
WrappedComponent.staticMethod = function() {/*...*/}
// Now apply a HOC
const EnhancedComponent = enhance(WrappedComponent);

// The enhanced component has no static method
typeof EnhancedComponent.staticMethod === 'undefined' // true
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}
// Instead of...
MyComponent.someFunction = someFunction;
export default MyComponent;

// ...export the method separately...
export { someFunction };

// ...and in the consuming module, import both
import MyComponent, { someFunction } from './MyComponent.js';

Controlled Component

Uncontrolled Component

npx create-react-app controlled-uncontrolled-example

상태를 가지고 있는 엘리먼트

  • input

  • select

  • textarea

  • ...

엘리먼트의 '상태' 를 누가 관리하느냐

  • 엘리먼트를 가지고 있는 컴포넌트가 관리

    • Controlled

  • 엘리먼트의 상태를 관리하지 않고, 엘리먼트의 참조만 컴포넌트가 소유

    • Uncontrolled

import React from 'react';

export default class Controlled extends React.Component {
  state = { value: '' };

  render() {
    return (
      <div>
        <input />
      </div>
    );
  }
}

components/Controlled.jsx (1)

import React from 'react';

export default class Controlled extends React.Component {
  state = { value: '' };

  render() {
    const { value } = this.state;
    return (
      <div>
        <input value={value} onChange={this._change} />
      </div>
    );
  }

  _change = e => {
    // console.log(e.target.value);
    this.setState({ value: e.target.value });
  };
}

components/Controlled.jsx (2)

import React from 'react';

export default class Controlled extends React.Component {
  state = { value: '' };

  render() {
    const { value } = this.state;
    return (
      <div>
        <input value={value} onChange={this._change} />
        <button onClick={this._click}>전송</button>
      </div>
    );
  }

  _change = e => {
    // console.log(e.target.value);
    this.setState({ value: e.target.value });
  };

  _click = () => {
    console.log('최종 결과', this.state.value);
  };
}

components/Controlled.jsx (3)

import React from 'react';

export default class Controlled extends React.Component {
  state = { value: '' };

  render() {
    const { value } = this.state;
    return (
      <div>
        <input value={value} onChange={this._change} />
        <button onClick={this._click}>전송</button>
      </div>
    );
  }

  _change = e => {
    // console.log(e.target.value);
    this.setState({ value: e.target.value });
  };

  _click = () => {
    console.log('최종 결과', this.state.value);
  };
}

components/Controlled.jsx (3)

import React from 'react';

export default class Uncontrolled extends React.Component {
  _input = React.createRef();

  render() {
    return (
      <div>
        <input ret={this._input} />
      </div>
    );
  }
}

components/Uncontrolled.jsx (1)

import React from 'react';

export default class Uncontrolled extends React.Component {
  _input = React.createRef();

  render() {
    return (
      <div>
        <input ref={this._input} />
        <button onClick={this._click}>전송</button>
      </div>
    );
  }

  _click = () => {
    console.log('최종 결과', this._input.current.value);
  };
}

components/Uncontrolled.jsx (2)

import React from 'react';

const Uncontrolled = () => {
  const inputRef = React.createRef();

  function click() {
    console.log('최종 결과', inputRef.current.value);
  }
  
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={click}>전송</button>
    </div>
  );
};

export default Uncontrolled;

components/Uncontrolled.jsx (2)

Hooks & Context

1) Basic Hooks

2) Custom Hooks

3) Additional Hooks

Basic Hooks

useState

useEffect

useContext (Context API 에서 다룹니다.)

npx create-react-app react-hooks-example
import React from 'react';

export default class Example1 extends React.Component {
  state = {
    count: 0,
  };

  render() {
    const { count } = this.state;
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={this.click}>Click me</button>
      </div>
    );
  }

  click = () => {
    this.setState({ count: this.state.count + 1 });
  };
}

this.setState

import React, { useState } from 'react';

const Example2 = () => {
  const [count, setCount] = useState(0);

  function click() {
    setCount(count + 1);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={click}>Click me</button>
    </div>
  );
};

export default Example2;

const [count, setCount] = useState(0);

const [스테이트 값, 스테이트 변경 함수] = useState(스테이트 초기값);

import React, { useState } from 'react';

const Example3 = () => {
  const [state, setState] = useState({ count: 0 });

  function click() {
    setState({ count: state.count + 1 });
  }

  return (
    <div>
      <p>You clicked {state.count} times</p>
      <button onClick={click}>Click me</button>
    </div>
  );
};

export default Example3;

const [state, setState] = useState({count: 0});

const [스테이트 값, 스테이트 변경 함수] = useState(스테이트 초기값);

Functional Component

= Stateless Component

= Stateless Functional Component

Functional Component

!= Stateless Component

 

because state hook

  • 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵습니다.

    • ​컨테이너 방식 말고, 상태와 관련된 로직

  • 복잡한 컴포넌트들은 이해하기 어렵습니다.

  • Class 는 사람과 기계를 혼동시킵니다.

    • ​컴파일 단계에서 코드를 최적화하기 어렵게 만든다.

  • ​this.state 는 로직에서 레퍼런스를 공유하기 때문에 문제가 발생할 수 있다.

    • ​좋은 것일까 ?

  • useState

    • state 를 대체 할 수 있다.

  • useEffect

    • 라이프 사이클 훅을 대체 할 수 있다.

      • componentDidMount

      • componentDidUpdate

      • componentWillUnmount

import React from 'react';

export default class Example4 extends React.Component {
  state = { count: 0 };
  componentDidMount() {
    console.log('componentDidMount', this.state.count);
  }

  componentDidUpdate() {
    console.log('componentDidUpdate', this.state.count);
  }

  render() {
    const { count } = this.state;
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={this.click}>Click me</button>
      </div>
    );
  }

  click = () => {
    this.setState({ count: this.state.count + 1 });
  };
}

componentDidMount, componentDidUpdate

import React, { useState, useEffect } from 'react';

const Example5 = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('componentDidMount & componentDidUpdate', count);
  });

  function click() {
    setCount(count + 1);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={click}>Click me</button>
    </div>
  );
};

export default Example5;

useEffect(() => {});

import React from 'react';

export default class Example6 extends React.Component {
  state = {
    time: new Date(),
  };
  _timer = null;

  componentDidMount() {
    this._timer = setInterval(() => {
      this.setState({ time: new Date() });
    }, 1000);
  }

  componentWillUnmount() {
    clearInterval(this._timer);
  }

  render() {
    const { time } = this.state;
    return <div>{time.toISOString()}</div>;
  }
}

componentWillUnmount

import React, { useState, useEffect } from 'react';

const Example7 = () => {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const timer = setInterval(() => {
      setTime(new Date());
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);

  return <div>{time.toISOString()}</div>;
};

export default Example7;

useEffect(() => {return () => {}, [])

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  
  function click() {
    setCount(count + 1);
  }
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={click}>Click me</button>
    </div>
  );
}

useEffect 자세히 알아보기

첫 번째 랜더링 (1)

from 리액트

"컴포넌트야,

state가 0 일 때의 UI를 보여줘."

첫 번째 랜더링 (2)

from 컴포넌트

  • "여기 랜더링 결과물로 <p>You clicked 0 times</p> 가 있어."
  • "그리고 모든 처리가 끝나고 이 이펙트를 실행하는 것을 잊지 마."
    () => { document.title = 'You clicked 0 times' }

첫 번째 랜더링 (3)

from 리액트

"좋아. UI를 업데이트 하겠어.
이봐 브라우저, 나 DOM에 뭘 좀 추가하려고 해."

첫 번째 랜더링 (4)

from 브라우저

"좋아, 화면에 그려줄게."

첫 번째 랜더링 (5)

리액트

좋아, 이제 컴포넌트 컴포넌트가 준 이펙트를 실행할거야.

() => { document.title = 'You clicked 0 times' } 를 실행.

클릭 후, 랜더링 (1)

from 컴포넌트

"이봐 리액트, 내 상태를 1 로 변경해줘."

클릭 후, 랜더링 (2)

from 리액트

"상태가 1 일때의 UI를 줘."

클릭 후, 랜더링 (3)

from 컴포넌트

  • "여기 랜더링 결과물로 <p>You clicked 1 times</p> 가 있어."

  • "그리고 모든 처리가 끝나고 이 이펙트를 실행하는 것을 잊지 마."
    () => { document.title = 'You clicked 1 times' }.

클릭 후, 랜더링 (4)

from 리액트

"좋아. UI를 업데이트 하겠어.
이봐 브라우저, 나 DOM에 뭘 좀 추가하려고 해."

 

클릭 후, 랜더링 (5)

from 브라우저

"좋아, 화면에 그려줄게."

클릭 후, 랜더링 (6)

리액트

좋아, 이제 컴포넌트 컴포넌트가 준 이펙트를 실행할거야.

() => { document.title = 'You clicked 1 times' } 를 실행.

Custom Hooks

useSomething

// hooks/useWindowWidth.js

import { useState, useEffect } from 'react';

export default function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => {
      window.removeEventLister('resize', onResize);
    };
  }, []);

  return width;
}

나만의 훅 만들기

useHasMounted vs withHasMounted

// hocs/withHasMounted.js

import React from 'react';

export default function withHasMounted(Component) {
  class WrapperComponent extends React.Component {
    state = {
      hasMounted: false,
    };
    componentDidMount() {
      this.setState({
        hasMounted: true,
      });
    }
    render() {
      const { hasMounted } = this.state;
      return <Component {...this.props} hasMounted={hasMounted} />;
    }
  }

  WrapperComponent.displayName = `withHasMounted(${Component.name})`;

  return WrapperComponent;
}

withHasMounted (HOC)

// hooks/useHasMounted.js

import { useState, useEffect } from 'react';

export default function useHasMounted() {
  const [hasMounted, setHasMounted] = useState(false);
  useEffect(() => {
    setHasMounted(true);
  }, []);

  return hasMounted;
}

useHasMounted (Custom Hook)

Additional Hooks

useReducer

useCallback, useMemo

useRef, useImperativeHandle,

useLayoutEffect

useDebugValue

useReducer

  • 다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우

  • 다음 state가 이전 state에 의존적인 경우

  • Redux 를 안다면 쉽게 사용 가능

import React, { useReducer, useEffect } from 'react';

const Example8 = ({ count }) => {
  const [state, dispatch] = useReducer(reducer, { count });
  useEffect(() => {
    setTimeout(() => {
      dispatch({ type: 'PLUS' });
    }, 2000);
  }, []);

  function click() {
    dispatch({ type: 'PLUS' });
  }

  return (
    <div>
      <p>You clicked {state.count} times</p>
      <button onClick={click}>Click me</button>
    </div>
  );
};

export default Example8;

const [state, dispatch] = useReducer(reducer, initialState, init?)

const reducer = (state, action) => {
  if (action.type === 'PLUS') {
    return {
      count: state.count + 1,
    };
  }
  return state;
};
import React, { useState } from 'react';

function sum(persons) {
  console.log('sum...');
  return persons.map(person => person.age).reduce((l, r) => l + r, 0);
}

const Example9 = () => {
  const [value, setValue] = useState('');
  const [persons] = useState([{ name: 'Mark', age: 38 }, { name: 'Hanna', age: 27 }]);

  function change(e) {
    setValue(e.target.value);
  }

  const count = sum(persons);

  return (
    <div>
      <input value={value} onChange={change} />
      <p>{count}</p>
    </div>
  );
};

export default Example9;

실제로 변하지 않더라도, 매번 실행되는 어떤 값

import React, { useState, useMemo } from 'react';

function sum(persons) {
  console.log('sum...');
  return persons.map(person => person.age).reduce((l, r) => l + r, 0);
}

const Example9 = () => {
  const [value, setValue] = useState('');
  const [persons] = useState([{ name: 'Mark', age: 38 }, { name: 'Hanna', age: 27 }]);

  function change(e) {
    setValue(e.target.value);
  }

  const count = useMemo(() => sum(persons), [persons]);

  return (
    <div>
      <input value={value} onChange={change} />
      <p>{count}</p>
    </div>
  );
};

export default Example9;

const 디펜던시 변경 없으면 고정 = useMemo(함수, 디펜던시)

import React, { useState } from 'react';

const Example10 = () => {
  const [value, setValue] = useState('');
  const [persons, setPersons] = useState([
    { id: 0, name: 'Mark', age: 38 },
    { id: 1, name: 'Hanna', age: 27 },
  ]);

  function change(e) {
    setValue(e.target.value);
  }

  function click(id) {
    setPersons(
      persons => persons.map(person =>
        person.id === id
          ? { ...person, age: person.age + 1 }
          : { ...person },
      )
    );
  }

  return (
    <div>
      <input value={value} onChange={change} />
      {persons.map(person => (
        <Person {...person} key={person.id} click={click} />
      ))}
    </div>
  );
};

export default Example10;

함수가 매번 새로 생성되는 경우, 최적화의 어려움 (feat. React.memo)

const Person = React.memo(({ id, name, age, click }) => {
  console.log('Person...');
  function onClick() {
    click(id);
  }
  return (
    <div>
      {name}, {age} <button onClick={onClick}>+</button>
    </div>
  );
});
import React, { useState } from 'react';

const Example10 = () => {
  const [value, setValue] = useState('');
  const [persons, setPersons] = useState([
    { id: 0, name: 'Mark', age: 38 },
    { id: 1, name: 'Hanna', age: 27 },
  ]);

  function change(e) {
    setValue(e.target.value);
  }

  const click = useCallback(id => {
    setPersons(persons => {
      return persons.map(person =>
        person.id === id
          ? {
              ...person,
              age: person.age + 1,
            }
          : {
              ...person,
            },
      );
    });
  }, []);

  return (
    <div>
      <input value={value} onChange={change} />
      {persons.map(person => (
        <Person {...person} key={person.id} click={click} />
      ))}
    </div>
  );
};

export default Example10;

useCallback

const Person = React.memo(({ id, name, age, click }) => {
  console.log('Person...');
  function onClick() {
    click(id);
  }
  return (
    <div>
      {name}, {age} <button onClick={onClick}>+</button>
    </div>
  );
});
import React, { useRef, useEffect, useState } from 'react';

const Example11 = () => {
  const [count, setCount] = useState(0);
  const inputCreateRef = React.createRef();
  const inputUseRef = useRef();
  console.log(inputCreateRef.current);
  console.log(inputUseRef.current);
  useEffect(() => {
    setTimeout(() => {
      setCount(count => count + 1);
    }, 1000);
  });
  return (
    <div>
      <p>{count}</p>
      <input ref={inputCreateRef} />
      <input ref={inputUseRef} />
    </div>
  );
};

export default Example11;

createRef vs useRef

Context API

useContext

컴포넌트 간 통신

npx create-react-app component-communication

하위 컴포넌트를 변경하기

A 의 button 를 클릭하여 E 를 변경하려면

  1. <A /> 컴포넌트에서 button 에 onClick 이벤트를 만들고,

  2. button 을 클릭하면, <A /> 의 state 를 변경하여, <B /> 로 내려주는 props 를 변경

  3. <B /> 의 props 가 변경되면, <C /> 의 props 에 전달

  4. <C /> 의 props 가 변경되면, <D /> 의 props 로 전달

  5. <D /> 의 props 가 변경되면, <E /> 의 props 로 전달

// A 컴포넌트
<div>
    <B />
    <button>클릭</button>
</div>
// B 컴포넌트
<div>
    <C />
</div>
// C 컴포넌트
<div>
    <D />
</div>
// D 컴포넌트
<div>
    <E />
</div>
// E 컴포넌트
<div>
    {props.value}
</div>
import React from "react";

class A extends React.Component {
  state = {
    value: "아직 안바뀜"
  };

  render() {
    console.log("A render");
    return (
      <div>
        <B {...this.state} />
        <button onClick={this._click}>E 의 값을 바꾸기</button>
      </div>
    );
  }

  _click = () => {
    this.setState({
      value: "E 의 값을 변경"
    });
  };
}

export default A;
const B = props => (
  <div>
    <p>여긴 B</p>
    <C {...props} />
  </div>
);

const C = props => (
  <div>
    <p>여긴 C</p>
    <D {...props} />
  </div>
);

const D = props => (
  <div>
    <p>여긴 D</p>
    <E {...props} />
  </div>
);

const E = props => (
  <div>
    <p>여긴 E</p>
    <h3>{props.value}</h3>
  </div>
);

A 의 button 를 클릭하여 E 를 변경하려면

상위 컴포넌트를 변경하기

  1. <A /> 에 함수를 만들고, 그 함수 안에 state 를 변경하도록 구현, 그 변경으로 인해 p 안의 내용을 변경.

  2. 만들어진 함수를 props 에 넣어서, <B /> 로 전달

  3. <B /> 의 props 의 함수를 <C /> 의 props 로 전달

  4. <C /> 의 props 의 함수를 <D /> 의 props 로 전달

  5. <D /> 의 Props 의 함수를 <E /> 의 props 로 전달, <E /> 에서 클릭하면 props 로 받은 함수를 실행

E 의 button 를 클릭하여 A 의 p 를 변경하려면

// A 컴포넌트
<div>
    <B />
    <p>{state.value}</p>
</div>
// B 컴포넌트
<div>
    <C />
</div>
// C 컴포넌트
<div>
    <D />
</div>
// D 컴포넌트
<div>
    <E />
</div>
// E 컴포넌트
<div>
    <button>클릭</button>
</div>
import React from "react";

class A extends React.Component {
  state = {
    value: "아직 안바뀜"
  };

  render() {
    console.log("A render");
    return (
      <div>
        <h3>{this.state.value}</h3>
        <B change={this.change} />
      </div>
    );
  }

  change = () => {
    this.setState({
      value: "A 의 값을 변경"
    });
  };
}

export default A;

E 의 button 를 클릭하여 A 의 p 를 변경하려면

const B = props => (
  <div>
    <p>여긴 B</p>
    <C {...props} />
  </div>
);

const C = props => (
  <div>
    <p>여긴 C</p>
    <D {...props} />
  </div>
);

const D = props => (
  <div>
    <p>여긴 D</p>
    <E {...props} />
  </div>
);

const E = props => {
  function click() {
    props.change();
  }
  return (
    <div>
      <p>여긴 E</p>
      <button onClick={click}>클릭</button>
    </div>
  );
};
npx create-react-app react-context-example

하위 컴포넌트 전체에 데이터를 공유하는 법

  • 데이터를 Set 하는 놈

    • 가장 상위 컴포넌트 => 프로바이더

  • 데이터를 Get 하는 놈

    • 모든 하위 컴포넌트에서 접근 가능

      • ​컨슈머로 하는 방법

      • 클래스 컴포넌트의 this.context 로 하는 방법

      • 펑셔널 컴포넌트의 useContext 로 하는 방법

데이터를 Set 하기

  1. 일단 컨텍스트를 생성한다.

  2. 컨텍스트.프로바이더 를 사용한다.

  3. ​value 를 사용

import React from 'react';

const PersonContext = React.createContext();

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

const persons = [
  { id: 0, name: 'Mark', age: 38 },
  { id: 1, name: 'Hanna', age: 27 },
];

ReactDOM.render(
  <PersonContext.Provider value={persons}>
    <App />
  </PersonContext.Provider>,
  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();

데이터를 Get 하기 (1) - Consumer

  1. 컨텍스트를 가져온다.

  2. 컨텍스트.컨슈머를 사용한다.

  3. ​value 를 사용

import React from 'react';
import PersonContext from '../contexts/PersonContext';

const Example1 = () => (
  <PersonContext.Consumer>
    {value => <ul>{JSON.stringify(value)}</ul>}
  </PersonContext.Consumer>
);

export default Example1;
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Example1 from './components/Example1';

export default function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Example1 />
      </header>
    </div>
  );
}

데이터를 Get 하기 (2) - class

  1. static contextType 에 컨텍스트를 설정한다.

  2. this.context => value 이다.

import React from 'react';
import PersonContext from '../contexts/PersonContext';

export default class Example2 extends React.Component {
  static contextType = PersonContext;

  render() {
    return <ul>{JSON.stringify(this.context)}</ul>;
  }
}

// Example2.contextType = PersonContext;
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Example1 from './components/Example1';
import Example2 from './components/Example2';

export default function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Example1 />
        <Example2 />
      </header>
    </div>
  );
}

데이터를 Get 하기 (3) - functional

  1. useContext 로 컨텍스트를 인자로 호출한다.

  2. useContext 의 리턴이 value 이다.

import React, { useContext } from 'react';
import PersonContext from '../contexts/PersonContext';

const Example3 = () => {
  const value = useContext(PersonContext);

  return <ul>{JSON.stringify(value)}</ul>;
};

export default Example3;
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Example1 from './components/Example1';
import Example2 from './components/Example2';
import Example3 from './components/Example3';

export default function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Example1 />
        <Example2 />
        <Example3 />
      </header>
    </div>
  );
}

Optimizing Performance

필요할 때만 랜더한다.

Reconciliation

  • 랜더 전후의 일치 여부를 판단하는 규칙

  • 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.

  • 개발자가 key prop 을 통해,
    여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.

class App extends React.Component {
  state = {
    count: 0
  };
  componentDidMount() {
    setInterval(() => {
      this.setState({
        count: this.state.count + 1
      });
    }, 1000);
  }
  render() {
    if (this.state.count % 2 === 0) {
      return (
        <div>
          <Foo />
        </div>
      );
    }
    return (
      <span>
        <Foo />
      </span>
    );
  }
}
class Foo extends React.Component {
  componentDidMount() {
    console.log("Foo componentDidMount");
  }

  componentWillUnmount() {
    console.log("Foo componentWillUnmount");
  }

  render() {
    return <p>Foo</p>;
  }
}

엘리먼트의 타입이 다른 경우

class App extends React.Component {
  state = {
    count: 0
  };
  componentDidMount() {
    setInterval(() => {
      this.setState({
        count: this.state.count + 1
      });
    }, 1000);
  }
  render() {
    if (this.state.count % 2 === 0) {
      return <div className="before" title="stuff" />;
    }
    return <div className="after" title="stuff" />;
  }
}

DOM 엘리먼트의 타입이 같은 경우 (1)

class App extends React.Component {
  state = {
    count: 0
  };
  componentDidMount() {
    setInterval(() => {
      this.setState({
        count: this.state.count + 1
      });
    }, 1000);
  }
  render() {
    if (this.state.count % 2 === 0) {
      return <div style={{ color: "red", fontWeight: "bold" }} />;
    }
    return <div style={{ color: "green", fontWeight: "bold" }} />;
  }
}

DOM 엘리먼트의 타입이 같은 경우 (2)

class App extends React.Component {
  state = {
    count: 0
  };
  componentDidMount() {
    setInterval(() => {
      this.setState({
        count: this.state.count + 1
      });
    }, 1000);
  }
  render() {
    if (this.state.count % 2 === 0) {
      return <Foo name="Mark" />;
    }
    return <Foo name="Anna" />;
  }
}

같은 타입의 컴포넌트 엘리먼트

class Foo extends React.Component {
  state = {};

  componentDidMount() {
    console.log("Foo componentDidMount");
  }

  componentWillUnmount() {
    console.log("Foo componentWillUnmount");
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log("Foo getDerivedStateFromProps", nextProps, prevState);
    return {};
  }

  render() {
    console.log("Foo render");
    return <p>Foo</p>;
  }
}
class App extends React.Component {
  state = {
    count: 0
  };
  componentDidMount() {
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1
      });
    }, 3000);
  }
  render() {
    if (this.state.count % 2 === 0) {
      return (
        <ul>
          <Foo>first</Foo>
          <Foo>second</Foo>
        </ul>
      );
    }
    return (
      <ul>
        <Foo>first</Foo>
        <Foo>second</Foo>
        <Foo>third</Foo>
      </ul>
    );
  }
}

자식에 대한 재귀적 처리 (1)

class Foo extends React.Component {
  state = {};

  componentDidMount() {
    console.log("Foo componentDidMount", this.props.children);
  }

  componentWillUnmount() {
    console.log("Foo componentWillUnmount");
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log("Foo getDerivedStateFromProps", nextProps, prevState);
    return {};
  }

  render() {
    console.log("Foo render", this.props.children);
    return <p>{this.props.children}</p>;
  }
}
class App extends React.Component {
  state = {
    count: 0
  };
  componentDidMount() {
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1
      });
    }, 3000);
  }
  render() {
    if (this.state.count % 2 === 0) {
      return (
        <ul>
          <Foo>second</Foo>
          <Foo>third</Foo>
        </ul>
      );
    }
    return (
      <ul>
        <Foo>first</Foo>
        <Foo>second</Foo>
        <Foo>third</Foo>
      </ul>
    );
  }
}

자식에 대한 재귀적 처리 (2)

class Foo extends React.Component {
  state = {};

  componentDidMount() {
    console.log("Foo componentDidMount", this.props.children);
  }

  componentWillUnmount() {
    console.log("Foo componentWillUnmount");
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log("Foo getDerivedStateFromProps", nextProps, prevState);
    return {};
  }

  render() {
    console.log("Foo render", this.props.children);
    return <p>{this.props.children}</p>;
  }
}
class App extends React.Component {
  state = {
    count: 0
  };
  componentDidMount() {
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1
      });
    }, 3000);
  }
  render() {
    if (this.state.count % 2 === 0) {
      return (
        <ul>
          <Foo key="2">second</Foo>
          <Foo key="3">third</Foo>
        </ul>
      );
    }
    return (
      <ul>
        <Foo key="1">first</Foo>
        <Foo key="2">second</Foo>
        <Foo key="3">third</Foo>
      </ul>
    );
  }
}

자식에 대한 재귀적 처리 (3)

class Foo extends React.Component {
  state = {};

  componentDidMount() {
    console.log("Foo componentDidMount", this.props.children);
  }

  componentWillUnmount() {
    console.log("Foo componentWillUnmount");
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log("Foo getDerivedStateFromProps", nextProps, prevState);
    return {};
  }

  render() {
    console.log("Foo render", this.props.children);
    return <p>{this.props.children}</p>;
  }
}
class App extends React.Component {
  state = {
    text: "",
    persons: [
      { id: 1, name: "Mark", age: 37 },
      { id: 2, name: "Anna", age: 26 },
    ]
  };
  render() {
    console.log("App render");
    const { text, persons } = this.state;
    return (
      <div>
        <input type="text" value={text} onChange={this._change} />
        <button onClick={this._click}>click</button>
        <ul>
          {persons.map(p => (
            <Person {...p} key={p.id} />
          ))}
        </ul>
      </div>
    );
  }

  _change = e => {
    this.setState({
      ...this.state,
      text: e.target.value
    });
  };

  _click = () => {
    console.log(this.state.text);
  };
}

setState 와 함께 일어나는 일

class Person extends React.Component {
  render() {
    console.log("Person render");

    const { name, age } = this.props;
    return (
      <ul>
        {name} / {age}
      </ul>
    );
  }
}
class App extends React.Component {
  state = {
    text: "",
    persons: [
      { id: 1, name: "Mark", age: 37 },
      { id: 2, name: "Anna", age: 26 },
    ]
  };
  render() {
    console.log("App render");
    const { text, persons } = this.state;
    return (
      <div>
        <input type="text" value={text} onChange={this._change} />
        <button onClick={this._click}>click</button>
        <ul>
          {persons.map(p => (
            <Person {...p} key={p.id} />
          ))}
        </ul>
      </div>
    );
  }

  _change = e => {
    this.setState({
      ...this.state,
      text: e.target.value
    });
  };

  _click = () => {
    console.log(this.state.text);
  };
}

shouldComponentUpdate

class Person extends React.Component {
  shouldComponentUpdate(previousProps) {
    for (const key in this.props) {
      if (previousProps[key] !== this.props[key]) {
        return true;
      }
    }
    return false;
  }
  render() {
    console.log("Person render");

    const { name, age } = this.props;
    return (
      <ul>
        {name} / {age}
      </ul>
    );
  }
}
class App extends React.Component {
  state = {
    text: "",
    persons: [
      { id: 1, name: "Mark", age: 37 },
      { id: 2, name: "Anna", age: 26 },
    ]
  };
  render() {
    console.log("App render");
    const { text, persons } = this.state;
    return (
      <div>
        <input type="text" value={text} onChange={this._change} />
        <button onClick={this._click}>click</button>
        <ul>
          {persons.map(p => (
            <Person {...p} key={p.id} />
          ))}
        </ul>
      </div>
    );
  }

  _change = e => {
    this.setState({
      ...this.state,
      text: e.target.value
    });
  };

  _click = () => {
    console.log(this.state.text);
  };
}

PureComponent

class Person extends React.PureComponent {
  render() {
    console.log("Person render");

    const { name, age } = this.props;
    return (
      <ul>
        {name} / {age}
      </ul>
    );
  }
}
class App extends React.Component {
  state = {
    text: "",
    persons: [
      { id: 1, name: "Mark", age: 37 },
      { id: 2, name: "Anna", age: 26 },
    ]
  };
  render() {
    console.log("App render");
    const { text, persons } = this.state;
    return (
      <div>
        <input type="text" value={text} onChange={this._change} />
        <button onClick={this._click}>click</button>
        <ul>
          {persons.map(p => (
            <Person {...p} key={p.id} onClick={() => {}} />
          ))}
        </ul>
      </div>
    );
  }

  _change = e => {
    this.setState({
      ...this.state,
      text: e.target.value
    });
  };

  _click = () => {
    console.log(this.state.text);
  };
}

onClick={() => {}}

class Person extends React.PureComponent {
  render() {
    console.log("Person render");

    const { name, age } = this.props;
    return (
      <ul>
        {name} / {age}
      </ul>
    );
  }
}
class App extends React.Component {
  state = {
    text: "",
    persons: [
      { id: 1, name: "Mark", age: 37 },
      { id: 2, name: "Anna", age: 26 },
    ]
  };
  render() {
    console.log("App render");
    const { text, persons } = this.state;
    return (
      <div>
        <input type="text" value={text} onChange={this._change} />
        <button onClick={this._click}>click</button>
        <ul>
          {persons.map(p => (
            <Person {...p} key={p.id} onClick={() => {}} />
          ))}
        </ul>
      </div>
    );
  }

  _change = e => {
    this.setState({
      ...this.state,
      text: e.target.value
    });
  };

  _click = () => {
    console.log(this.state.text);
  };
}

React.memo

const Person = React.memo(props => {
  console.log("Person render");

  const { name, age } = props;
  return (
    <ul>
      {name} / {age}
    </ul>
  );
});

React.createPortal

public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <div id="modal"></div>
  </body>
</html>

src/index.css

body {...}

#modal {
  position: absolute;
  top: 0;
  left: 0;
}

src/components/Modal.jsx

import ReactDOM from 'react-dom';

const Modal = ({ children }) =>
  ReactDOM.createPortal(children, document.querySelector('#modal'));

export default Modal;

src/pages/Home.jsx

const Home = () => {
  const [visible, setVisible] = useState(false);
  const show = () => setVisible(true);
  const hide = () => setVisible(false);
  return (
    <div>
      <h1>Home</h1>
      <button onClick={click}>open</button>

      <NaviContainer />
      <BooksContainer />

      {visible && (
        <Modal>
          <div
            style={{
              width: '100vw',
              height: '100vh',
              background: 'rgba(0, 0, 0, 0.5)',
            }}
            onClick={hide}
          >
            Hello
          </div>
        </Modal>
      )}
    </div>
  );
};

React.forwardRef

SPA 프로젝트 배포 이해하기

SPA Deploy

git clone https://github.com/xid-mark/tic-tac-toe.git

cd tic-tac-toe

npm ci

npm run build

npm run build (in create-react-app Project)

  • npm run build

    • production 모드로 빌드되어, 'build' 폴더에 파일 생성

      • ​이렇게 만들어진 파일들을 웹서버를 통해 사용자가 접근할 수 있도록 처리​

    • build/static 폴더 안에 JS, CSS 파일들이 생성

{Project} 폴더 바로 아래

build 라는 폴더가 만들어지고,

그 안에 Production 배포를 위한

파일들이 생성됩니다.

SPA Deploy 의 특징

  • 모든 요청을 서버에 하고 받아오는 형태가 아님

  • 라우팅 경로에 상관없이 리액트 앱을 받아 실행

  • 라우팅은 받아온 리액트 앱을 실행 후, 적용

  • static 파일을 제외한 모든 요청을 index.html 로 응답해 주도록 작업

serve 패키지로

React Wep App 배포하기

  • serve 라는 패키지를 전역으로 설치합니다.

  • serve 명령어를 -s 옵션으로 build 폴더를 지정하여 실행합니다.

    • ​-s 옵션은 어떤 라우팅으로 요청해도 index.html 을 응답하도록 합니다.
npm install serve -g
serve -s build

AWS S3 에

React Wep App 배포하기

Amazon S3 정적 웹 사이트 호스팅

버킷 생성

정적 웹사이트 호스팅 설정

퍼블릭 액세스 차단 해제

버킷 정책 설정

버킷 정책 설정

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
	  "Principal": "*",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::react-camp/*"
      ]
    }
  ]
}

build => s3

s3 static webhosting endpoint

NginX 로

React Wep App 배포하기

Ubuntu 에 NginX 최신 버전 설치하기

sudo apt-get update
sudo apt-get upgrade

wget http://nginx.org/keys/nginx_signing.key

sudo apt-key add nginx_signing.key
sudo rm -rf nginx_signing.key

sudo nano /etc/apt/sources.list

```
deb http://nginx.org/packages/mainline/ubuntu/ trusty nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ trusty nginx
```

sudo apt-get update
sudo apt-get upgrade

sudo apt-get install nginx
nginx -v

NginX 설치 완료

sudo service nginx start

/etc/nginx/conf.d/default.conf

server {
    listen       80;
    server_name  localhost;

    root   /home/ubuntu/tic-tac-toe/build;
    index  index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

ec2 endpoint

node.js express 로

React Wep App 배포하기

npm i express

tic-tac-toe/server.js

const express = require('express');
const path = require('path');
const app = express();

app.use(express.static(path.join(__dirname, 'build')));

// app.get('/', function(req, res) {
app.get('*', function(req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(9000);

node server.js - http://127.0.0.1:9000

서버사이드 렌더링 이해하기

Server Side Rendering

  • 서버에서 응답을 가져올때, 기존 처럼 static file 만을 가져오는 것이 아니고, 먼저 서버에서 응답 값을 만들어서 내려주고, 그 후에 static file 을 내려줍니다.​

  • static file 을 다 내려받고, 리액트 앱을 브라우저에서 실행한 뒤에는 SPA 처럼 동작하게 됩니다.

React Server Side Rendering

  • React Component 를 브라우저가 아니라 Node.js 에서 사용

  • ReactDOMServer.renderToString(<App />);

    • 결과가 문자열

    • 이것을 응답으로 내려준다.

  • 라우팅, 리덕스 와 같은 처리를 서버에서 진행하고 내려준다.

    • 복잡, 어렵

  • ​JSX 가 포함된 리액트 코드를 서버에서 읽을 수 있도록 babel 설정을 해야 한다.

리액트 기본 및 현업에서의 활용 방안

By Woongjae Lee

리액트 기본 및 현업에서의 활용 방안

리액트 기업교육

  • 1,507