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를 사용해야 함
- 그래서 특정 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.3
npm 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);