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=
            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를 사용해야 함
 
 - 그래서 특정 prop을 받을 경우엔, 
 
// 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.3npm ci: npm install 과 같이 package.json의 dependencies를 참조하여 node_modules에 해당 package를 설치함- node_modules가 전체 다 비워져 있으면 
npm ci가 빠르고, 어느정도 채워져 있다면npm install이 빠르다고 한다. 
- node_modules가 전체 다 비워져 있으면 
 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);