본문 바로가기

React

20210614 React13 : createPortal(특정 요소에 하위 요소 추가하기), forwardRef(후손 ref 사용하기), SPA 배포(Deploy)특징&이해, Serve패키지 배포, AWS S3 배포, Express 배포, SSR 이해, express를 통한 SSR

React 13



React.createPortal : 요소 보내기

  • React : Portals
  • 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법
    • 의도하는 실제 DOM Element(부모)에 자식, 후손 요소들을 어디에서 든지 createPortal로 만들어진 Component를 통해 넣을 수 있음
    • 서로 동등한 위치의 A, B Element라도 A에서 작성한 후손 Element를 B의 후손 Element로 들어 갈수 있게 A에서 작성 가능함
  • 다른 곳에서 집어 넣었다고 하더라도 이벤트 버블링은 portal인지 아닌지 상관 없이 부모 Component로(React 기준으로, 실제 DOM 기준이 아니고) 전달 됨
// Modal.jsx : Portal 사용
import ReactDOM from "react-dom";

const Modal = ({ children }) =>
  // Component 사용시 children을 modal DOM의 후손 요소로 넣음
  ReactDOM.createPortal(children, document.querySelector("#modal"));

export default Modal;
// App.js : Portal기능의 Modal 사용
import "./App.css";
import React, { useState } from "react";
import Modal from "./components/Modal";

function App() {
  const [visible, setVisible] = useState(false);
  const open = () => {
    setVisible(true);
  };
  const close = () => {
    setVisible(false);
  };
  return (
    <div>
      <button onClick={open}>open</button>
      {/* open 클릭시 Modal Component 안으로 해당 children이 들어감 */}
      {visible && (
        <Modal>
          <div
            style={{
              width: "100vw",
              height: "100vh",
              background: "rgba(0, 0, 0, 0.5)",
            }}
            onClick={close}
          >
            Hello
          </div>
        </Modal>
      )}
    </div>
  );
}

export default App;
<!-- 실제 DOM 화면 -->
<div id="root">
  <div><button>open</button></div>
</div>
<div id="modal">
  <div style="width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.5);">
    Hello
  </div>
</div>



React.forwardRef : 후손 ref 사용하기

  • 하위 컴포넌트의 Reference를 상위 컴포넌트에서 이용하게 해줌
  • REACT : Fowarding Refs
  • 특정 input에 대해서 component를 만드는 이유로 하위 컴포넌트에 reference를 지정하는 경우 forwardRef를 이용해서 전달 가능함
  • forwardRef()를 가져와서 해당 component 함수를 넣어주면 됨
  • Function Component에서 첫번째 매개변수는 props , 두번째 매개변수는 ref임
    • 그래서 특정 prop을 받을 경우엔, {}을 사용해서 첫번째 매개변수에 작성하여 가져오는 것임
    • 두번째 매개변수에 {ref} 를 사용하면 안되고, 그대로 ref를 사용해야 함
// MyInput.jsx
import React from "react";

// input을 사용하는 Component 지정
export default React.forwardRef(function MyInput(props, ref) {
  return (
    <div>
      <p>MyInput</p>
      {/* input에 ref를 사용함 */}
      <input ref={ref} />
    </div>
  );
});
import "./App.css";
import React, { useRef } from "react";
import MyInput from "./components/MyInput";

function App() {
  const myInputRef = useRef();
  const click = () => {
    // 하위컴포넌트의 ref를 가져와 활용
    console.log(myInputRef.current.value);
  };
  return (
    <div>
      {/* component에 ref를 지정하여, 상위 컴포넌트에서 하위 컴포넌트의 ref를 연결 할 수 있음*/}
      <MyInput ref={myInputRef} />
      <button onClick={click}>send</button>
    </div>
  );
}

export default App;



Deploy React App

SPA 프로젝트 배포 이해하기

학습 예제 환경 설정

  • 강의 repo gitClone
  • node 10.16.3 버전 필요
  • nvm install 10.16.3, nvm list, nvm use 10.16.3
  • npm ci : npm install 과 같이 package.json의 dependencies를 참조하여 node_modules에 해당 package를 설치함
    • node_modules가 전체 다 비워져 있으면 npm ci가 빠르고, 어느정도 채워져 있다면 npm install 이 빠르다고 한다.
  • npm run build (CRA 프로젝트 에서)
    • production 모드로 빌드되어, build 폴더에 파일 생성 -> 웹서버를 통해 사용자가 접근할 수 있도록 처리
    • build/static 폴더 안에 JS, CSS 파일들이 생성
      • long term caching techniques : 파일 이름에 hash 값이 붙어 이름이 달라져서 캐싱이 해제되서 새로운 파일인 경우에만 사용자들이 해당 파일을 사용할 수 있도록 함
  • static 서버로 build 폴더 제시
    • static 서버 : 파일 서버처럼 파일 하나 하나를 웹서버로 제공하는 서버
    • 단, 서버에서 database 등의 추가적인 작업을 한다면 파일서버로만 활용하는 것은 한계가 있음

SPA Deploy의 특징


  • 모든 요청을 서버에 하고 받아오는 형태가 아님
  • 라우팅 경로에 상관없이 리액트 앱을 받아 실행 (즉, 어떤 경로라도 해당 앱을 다운을 받아지게 해야함(초기화 과정))
  • 라우팅은 받아온 리액트 앱을 실행 후, 적용 (초기화 과정 후 앱에서 제공하는 경로 처리를 함)
  • static 파일을 제외한 모든 요청을 index.html로 응답해 주도록 작업해야 다른 경로라고 하더라도 404 같은 error 가 안뜸

파일이 없는 경우에 index.html을 내려 줄수 있도록 하는 방법


  • serve -s build
  • AWS S3 에 배포
  • node.js express



Serve 패키지로 React Wep App 배포하기


  • npm install serve -g
    • serve 패키지 전역으로 설치
  • serve -s build
    • serve 명령어를 -s 옵션으로 build 폴더를 지정하여 실행
    • -s (SPA의 약자) 옵션은 어떤 라우팅을 요청해도 index.html 을 응답하도록 함
      • -s 옵션이 없으면, server에서 NotFound 404 페이지를 처리함
      • 그런데, 앱을 만들때 앱 자체에서 404를 처리하는 것을 의도하고 있기 때문에 일단은 index.html로 응답하도록 처리해야함
    • docker안에서 실행해서 docker로 container화 시켜서 실행하는 경우가 많음



AWS S3에 React Wep App 배포하기


  • Amazon simple storage service
  • 버킷 만들기
    • 버킷 이름은 고유하기 때문에 기존의 다른 사람들까지 포함해서 겹치면 안됨
    • 리전은 서울
    • 해당 버킷에서 파일 업로드 하기
      • 파일 추가하기 : build 파일안에 있는 모든 파일
      • 폴더 추가하기 : static 폴더
      • 업로드
    • 정적 웹사이트로 변경하기
      • 지정된 주소에 업로드된 파일명을 작성하면, 해당 파일이 브라우저에서 열림
      • 아래쪽 정적 웹사이트 호스팅 탭에서 편집 선택
      • 호스팅 유형을 정적 웹사이트 호스팅으로 변경
      • 인덱스 문서(/ 경로시 페이지))와 오류 문서(없는 경로시 페이지) 모두 index.html로 작성 (react app이니까)
    • AWS에서는 보안 때문에 기본적으로 파일 업로드 하면, public이 아닌 private로 설정 되어 있음
      • 권한 풀어주기 : 버킷의 권한 탭에서 퍼블릭 엑세스 차단 편집에서 체크 해제 설정 -> 객체를 퍼블릭으로 설정할 수 있는 권한이 있음 (퍼블릭 상태 아직 아님)
      • 퍼블릭 정책 만들기 : 버킷 정책 편집
// Public 정책
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3::react-camp/*"]
    }
  ]
}



Node.js express로 React Wep App 배포하기

  • npm i express
  • server.js 파일 생성하기
// express 가져오기
const express = require("express");
// path 객체 가져오기
const path = require("path");
// 앱 생성하기
const app = express();

// static 서버 경로 제공 -> build 파일을 가르킴 (__dirname 해당 파일의 위치에 build 경로를 join 하여 build path를 만듦)
// build 폴더를 app에서 사용하도록 함
app.use(express.static(path.join(__dirname, "build")));

// 항상 어떤 path로든 request시 index.html을 response 시키기
app.get("*", (req, res) => {
  res.sendFile(path.join(__dirname, "build", "index.html"));
});
// 포트번호 지정하기
app.listen(9000);
  • node server.js 서버 실행하기



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


  • REACT : ReactDOMServer
  • 서버에서 react component를 렌더링 하고 그것을 문자열로 바꾸어서 내려줄 수 있음
  • 실무에서 까다로운 작업

SSR (Server Side Rendering)


  • 서버에서 응답을 가져올때, 기존 처럼 static file 만을 가져오는 것이 아니고, 먼저 서버에서 응답 값을 만들어서 내려주고, 그 후에 static file을 내려줌
  • static file을 다 내려받고, 리액트 앱을 브라우저에서 실행한 뒤에는 SPA 처럼 동작함

React Server Side Rendering 기초


  • React Component를 브라우저가 아니라 Node.js에서 사용
  • ReactDOMServer.renderToString(<App />)
    • 결과가 문자열
    • 이것을 응답으로 내려줌
  • 라우팅, 리덕스와 같은 처리를 서버에서 진행하고 내려주어 복잡하고 어려움
  • JSX가 포함된 react 코드를 서버에서 읽을 수 있도록 Babel 설정을 해야 함

기본적인 방법

const express = require("express");
const path = require("path");

// SSR로 rendering 하기 위해서 CSR에서 구현하고 있는 Component를 String으로 만듦
const ReactDOMServer = require("react-dom/server");

// Component 및 react Element를 만들기 위해서 React를 가져옴
const React = require("react");
// 단, 사용시 JSX 표현문은 사용하지 못하고 객체, 함수 등을 사용하여
// component 및 react Element를 구성해야하는 문제가 있어 나중에는 Babel 설정을 필요로 함

// SSR을 위해서는 html을 조작 해줘야함 그래서 일단 html을 읽어을 수 있게 File System API를 가져옴
const fs = require("fs");

const app = express();
app.use(express.static(path.join(__dirname, "build")));

// SSR
// '/test' 경로로 접속시 가공한 html 파일을 제공
app.get("/test", (req, res) => {
  // SSR로 먼저 표현할 Element 생성하여 가공하기 쉽게 string으로 만듦
  const ssr = ReactDOMServer.renderToString(
    React.createElement("div", null, "hello")
    // 만든 react Element : <div data-reactroot="">hello</div>
  );
  // SSR을 구현시킬 해당 page html(index.html)을 가져와서 string으로 만듦
  // index.html에서 바꿀 부분을 replace로 채워 넣음
  const indexHtml = fs
    .readFileSync(path.join(__dirname, "build", "index.html"))
    .toString()
    .replace('<div id="root"></div>', `<div id="root">${ssr}</div>`);

  // SSR 작업이 완료 된 index.html을 보냄
  res.send(indexHtml);
});

// CSR
app.get("*", (req, res) => {
  res.sendFile(path.join(__dirname, "build", "index.html"));
});

app.listen(9000);