자바스크립트/러닝 리엑트

리액트와 서버

막이86 2023. 11. 13. 13:09
728x90

러닝 리엑트을 요약한 내용입니다.

  • 리액트는 뷰 계층이므로 브라우저에서 모든 것을 처리하는 것이 타당하다.
    • 리액트는 UI 랜터링을 위한 라이브러리다.
  • 대부분의 서비스는 백엔드로부터 데이터를 가져올 필요가 있다.
    • HTTP 요청과 관련한 지연 시간 문제를 해결하기 위한 라이브러리들이 존재한다.
  • 리액트를 아이소모픽하게 랜더링할수 있다.
    • 아이소모픽하다는 말은 브라우저가 아닌 플랫폼에서 리액트를 랜더링한다는 뜻이다.
    • UI를 서버에서 렌더링 한 결과를 브라우저에 보내서 표시한다.
    • 서버 렌더링의 강점은 성능, 이식성, 보안을 향상시킬 수 있다.

12.1 아이소모피즘과 유니버설리즘 비교

  • 아이소모픽과 유니버설이라는 말은 클라이언트와 서버 양쪽에서 작동하는 애플리케이션을 의미한다.
  • 아이소모픽 애플리케이션은 여러 플랫폼에서 렌더링 되는 애플리케션을 말한다.
  • 유니버설은 완전히 같은 코드를 여러 환경에서 실행할 수 있다는 뜻이다.</aside>
  • <aside> ⛔ 말이 모호하네... 이해가 렌더링과 실행의 차이??? 또는 예외 처리 코드(모듈)로 실행 차이인가?
  • 노드를 사용하면 브라우저에서 작성한 코드를 서버나 CLI등의 다른 플랫폼에서 재사용할 수 있다.
  • 네이티브 애플리케이션에서도 자바스크립트 코드를 재사용할 수 있다.
  • 유니버설 자바스크립트 코드 예제
    • userDetails 함수는 유니버설 하다.
    • 같은 코드를 브라우저나 서버에서 모두 사용할 수 있다.
    const userDetails = response => {
    	const login = response.login;
    	console.log(login);
    }
    
  • 서버를 노드를 사용해 구축하면 브라우저와 서버 환경에서 상당한 양의 코드를 재사용할 수 있는 여지가 있다.
  • 유니버설 자바스크립트는 오류 없이 서버와 브라우저에서 실행 될 수 있는 자바스크립트 코드를 말한다.

12.1.1 클라이언트와 서버 도메인

  • 자바스크립트 코드를 자동으로 서버와 클라이언트 영역에서 실행할 수 있는 것은 아니다.
  • 브라우저에서 AJAX 요청을 보내는 경우를 살펴보자.
    • 브라우저에서 실행하면 정상이지만 노드에서 실행하면 오류가 발생한다.
    fetch("<https://api.github.com/users/moonhighway>")
    	.then(res => res.json())
    	.then(console.log)
    
  • 노드는 브라우저와 달리 fetch를 기본 제공하지 않으므로 오류가 발생
    • npm에 있는 isomrphic-fetch 모듈을 사용하거나 https 모듈을 사용해야한다.
     npm install isomorphic-fetch
    
  • isomorphic-fetch를 임포트하면 코드를 변경하지 않아도 된다.
  • const fetch = require("isomorphic-fetch"); const userDetails = response => { const login = response.login; console.log(login); } fetch("<https://api.github.com/users/moonhighway>") .then(res => res.json()) .then(userDetails)
  • 아무런 의존 관계없이 노드에서 API를 통해 데이터를 가져오려면 코어 모듈을 사용해야 한다.
    • 브라우저상에서 실행할 코드와 다른 코드가 필요하면 유니버설 코드가 아니다.
    • printNames는 양 환경에서 수정없이 작동할 수 있는 유니버설 함수다
  • isomorphic-fetch를 임포트하면 fetch를 사용하는 코드가 아이소모픽해진다.
  • Star 컴포넌트는 유니버설 컴포넌트일까?
    • JSX는 자바스크립트로 컴파일된다는 점을 기억하자
    • Star 컴포넌트는 단순함 함수에 불과하다.
    function Star({
    	selected = false,
    	onclick = f => f
    }) {
    	return (
    		<div
    			className={ selected ? "start selected" : "star"}
    			onClick={onClick}
    		>	
    		</div>
    	)
    }
    
  • 컴포넌트를 브라우저에서 직접 렌더링할 수도 있고 다른 환경에서 렌더링해서 HTML 출력 문자열을 만들 수 있다.
  • function Star({ selected = false, onclick = f => f }) { return React.createElement("div", { className: slected ? "start selected" : "star", onClick: onClick } }
  • ReactDOM의 renderToString 메서드를 사용하면 UI를 HTML 문자열로 렌더링할 수 있다.
  • // 브라우저에서 직접 HTML로 렌더링한다. ReactDOM.render(<Star />); // HTML 문자열로 렌더링한다. var html = ReactDOM.renderToString(<Star />);
  • 어러 플랫폼에서 컴포넌트를 렌더링하는 아이소모픽 애플리케이션을 만들 수 있다.
  • 아이소모픽 애플리케이션을 만들때 여러 환경에서 변경 없이 실행될 수 있는 유니버설 자바스크립트 코드를 재활용하는 방향으로 설계할 수 있다.
  • Go, Python과 같은 언어를 사용해도 아이소모픽 애플리케이션을 만들 수 있다
    • 꼭 노드를 사용할 필요는 없다.

12.2 서버 렌더링 리액트

  • ReactDOM.renderToString 메서드를 사용하면 UI를 서버에서 렌더링 할 수 있다.
  • 서버에서는 브라우저에서 사용할 수 없는 모든 자원을 활용할 수 있다.
  • 서버는 브라우저보다 더 안전하고 여러 보안 정보에 접근할 수도 있다.
  • 5장 조리법 앱 예제
    • 클라이언트에서 컴포넌트를 렌더링 했다.
    • create-react-app의 build 폴더를 사용해 서버를 열고, 브라우저가 HTML을 실행하고 script.js를 호출해서 로딩한다.
    • 이런 방식은 시간이 오래 걸린다.
      • 네트워크 속도에 따라...
    • index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import { Menu } from './Menu'
    
    const data = [
    	{
    	    "name": "Baken Salmon",
    	    "ingredients": [
    	        { "name": "연어", "amount": 500, "measurement": "그램"},
    	        { "name": "잣", "amount": 1, "measurement": "컵"},
    	        { "name": "버터 상추", "amount": 2, "measurement": "컵"},
    	        { "name": "옐로 스쿼시(Yello Squash, 호박의 한 종류)", "amount": 1, "measurement": "개"},
    	        { "name": "올리브 오일", "amount": 0.5, "measurement": "컵"},
    	        { "name": "마늘", "amount": 3, "measurement": "쪽"}
    	        
    	    ],
    	    "steps": [
    	        "오븐을 180도로 예열한다.",
    	        "유리 베이킹 그릇에 올리브 오일을 두른다.",
    	        "연어, 마늘, 잣을 그릇에 담는다.",
    	        "오븐에서 15분간 익힌다.",
    	        "옐로 스쿼시를 추가하고 다시 30분간 오븐에서 익힌다.",
    	        "오븐에서 그릇을 꺼내서 15분간 식힌 다음에 상추를 곁들여서 내놓는다."
    	    ]
    	},
    	{
    	    "name": "생성 타코",
    	    "ingredients": [
    	        { "name": "흰살생성", "amount": 500, "measurement": "그램"},
    	        { "name": "치즈", "amount": 1, "measurement": "컵"},
    	        { "name": "아이스버그 상추", "amount": 2, "measurement": "컵"},
    	        { "name": "토마토", "amount": 2, "measurement": "개(큰것)"},
    	        { "name": "또띠야", "amount": 3, "measurement": "개"}
    	    ],
    	    "steps": [
    	        "생선을 그릴에 익힌다.",
    	        "또띠야 3장 위에 생선을 얹는다.",
    	        "또띠야에 얹은 생성 위에 상추, 토마토, 치즈를 얹는다."
    	    ]
    	}
    ];
    
    ReactDOM.render(
      <Menu
        recipes={data} title="맛있는 조리법"
      />,
      document.getElementById('root')
    );
    
    • Menu.js
    function Recipe({ name, ingredients, steps }) {
        return (
            <section
                id={name.toLowerCase().replace(/ /g, "-")}
            >
                <h1>{name}</h1>
                <ul className="ingredients">
                    {ingredients.map((ingredient, i) => (
                        <li key={i}>{ingredient.name}</li>
                    ))}
                </ul>
                <section className="instrucctions">
                    <h2>조리 절차</h2>
                    {steps.map((step, i) => (
                        <p key={i}>{step}</p>
                    ))}
                </section>
            </section>
        )
    }
    
    export function Menu({ title, recipes }) {
        return (
            <article>
                <header>
                    <h1>{title}</h1>
                </header>
                <div className="recipes">
                    {recipes.map((recipe, i) => (
                        <Recipe key={i} {...recipe} />
                    ))}
                </div>
            </article>
        )
    }
    
  • 조리법 예제를 익스프레스 서버와 함께 사용하면 클라인트와 서버쪽 렌더링을 함께 사용하는 하이브리드 경험을 제공할 수 있다.
    • ReactDOM.render → ReactDOM.hydrate를 사용
    • hydrate가 ReactDOMServer에 의해 렌더링된 컨테이너에 콘텐츠를 추가하기 위해 사용되는 점이 차이
    1. 앱의 정적인 버전을 렌더링한다.
      1. 이를 통해 페이지가 완전히 로딩 되기 전에 무언가를 사용자에게 보여줄 수 있다.
    2. 동적 자바스크립트를 요청한다.
    3. 정적 콘텐츠를 동적 콘텐츠로 교체한다.
    4. 사용자가 콘텐츠 각 부분을 클릭하거나 키보드로 입력을 하면 제대로 작동한다.
  • 익스프레스를 사용해서 변경해보자.
  • npm install express
  • server/index.js<aside> ❓ 4000번이 아닌 3000번으로 실행 ㅡㅡ?
  • </aside>
  • import express from "express" import React from "react"; import { flushSync } from "react-dom"; import ReactDOMServer from "react-dom/server"; import { Menu } from "../src/Menu"; const app = express(); app.use(express.static("./build")); const PORT = process.env.PORT || 4000; app.get("/*", (request, res) => { const app = ReactDOMServer.renderToString( <Menu /> ); const indexFile = path.resolve("./build/index.html"); flushSync.readFile(indexFile, "utf8", (err, data) => { return res.send( data.replace('<div id="root"></div>', `<div id="root">${app}</div>`) ) }) }); app.listen(PORT, () => { console.log(`Server is listening on port ${PORT}`); });
  • 바벨 및 몇가지 의존 관계 추가
  • npm install @babel/core @babel/preset-env babel-loader nodemon npm-run-all webpack webpack-cli webpack-node-externals
  • 바벨에 설치된 상태에서 몇가지 preset 설정이 들어 있는 .babelrc 파일
  • { "presets": ["@babel/preset-env", "react-app"] }
  • webpack.server.js 웹팩 설정 추가
    • babel-loader를 사용해서 자바스크립트 파일을 변환
    • nodeExternals는 node_modules 폴더를 스캔해서 모든 node-modules 이름을 찾는다
    • 하위 모듈을 번들하지 않도록 설정해주는 외부 함수 생성
    const path = require("path");
    const nodeExternals = require("webpack-node-externals");
    
    module.exports = {
        entry: "./server/index.js",
        target: "node",
        externals: [nodeExternals()],
        output: {
            path: path.resolve("build-server"),
            filename: "index.js"
        },
        module: {
            rules: [
                {
                    test: /\\.js$/,
                    use: "babel-loader"
                }
            ]
        }
    }
    
  • .env 파일을 추가
  • SKIP_PREFLIGHT_CHECK=true
  • dev 명령에 몇가지 npm 스크립트를 추가
    1. dev:build-server: developenent를 환경 변수로 전달하고, 웹팩이 새 서버 설정을 사용해 실행
    2. dev:start : nodemon으로 서버 파일을 실행
    3. dev : dev에 속한 모든 명령을 병렬로 실행(build, start 실행)
    <aside> ❓ "webpack": "^4.44.0" → 5버전에서 오류가 발생하여 수정
    {
      "name": "server-render",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "@babel/core": "^7.16.0",
        "@babel/preset-env": "^7.16.0",
        "@testing-library/jest-dom": "^5.15.0",
        "@testing-library/react": "^11.2.7",
        "@testing-library/user-event": "^12.8.3",
        "babel-loader": "^8.2.3",
        "express": "^4.17.1",
        "nodemon": "^2.0.14",
        "npm-run-all": "^4.1.5",
        "react": "^17.0.2",
        "react-dom": "^17.0.2",
        "react-scripts": "4.0.3",
        "web-vitals": "^1.1.2",
        "webpack": "^4.44.0",
        "webpack-cli": "^4.9.1",
        "webpack-node-externals": "^3.0.0"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject",
        "dev:build-server": "NODE_ENV=development webpack --config webpack.server.js --mode=development -w",
        "dev:start": "nodemon ./server-build/index.js",
        "dev": "npm-run-all --parallel build dev:*"
      },
      "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"
        ]
      }
    }
    
  • </aside>
  • 실행
  • npm run dev
  • 실행 파일
  • server-render.zip

12.3 넥스트.js를 사용한 서버 렌더링

  • 서버 렌더링 생태계에서 널리 쓰이는 강력한 도구로는 Next.js를 들수 있다.
  • 넥스트는 Zeit가 서버 렌더링된 앱을 더 쉽게 작성하는데 도움이 되도록 배포한 오픈 소스 기술이다.
  • 넥스트에는 직관적인 라우팅, 정적인 최적화, 자동화된 분리 등의 기능이 들어 있다.
  • 프로젝스 생성
  • mkdir project-next cd project-next npm init -y npm install --save react react-dom next mkdir pages
  • 쉽게 사용할수 있도로 npm 스크립트를 추가하자
  • { "name": "project-next", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \\"Error: no test specified\\" && exit 1", "dev": "next", "build": "next build", "start": "next start" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "next": "^12.0.2", "react-dom": "^17.0.2" } }
  • pages 폴더에 index.js를 만들고 컴포넌트를 작성하자
  • export default function Index() { return ( <div> <p>Hello everyone!</p> </div> ) }
  • 실행
  • npm run dev
  • Prerenderd Page 버튼을 클릭해보자.
    • 정적 최적화란 정적 HTML로 미리 렌더링을 한다는 뜻이다.
    <aside> ❓ 어떤것을 말하는지 잘 모르겠음...
  • </aside>
  • 페이지에 데이터 요구사항이 존재하면 어떨까? 페이지를 미리 렌더링할 수 없다면 어떻게 해야할까?
  • API로부터 원격 데이터를 불러오게 하자.
    • Pets.js 파일 추가
    • export default function Pets() { return <h1>Pets!</h1> }
    • http://localhost:3000/Pets 페이지 방문<aside> 💡 대소문자 구분함
    • </aside>
  • 두 페이지가 공유하는 헤더를 추가하자.
    • Header.js
    import Link from "next/link"
    
    const linkStyle = {
        marginRight: 15,
        color: "salmon"
    }
    
    export default function Header() {
        return (
            <div>
                <Link href="/">
                    <a style={linkStyle}>Home</a>
                </Link>
                <Link href="/Pets">
                    <a style={linkStyle}>Pets</a>
                </Link>
            </div>
        )
    }
    
    • Header 컴포넌트를 Layout.js라는 새 파일에 넣자.
    import Header from "./Header"
    
    export default function Layout(props) {
        return (
            <div>
                <Header />
                {props.children}
            </div>
        )
    }
    
    • index.js 파일 수정
    import Layout from "./Layout"
    
    export default function Index() {
        return (
            <Layout>
                <div>
                    <p>Hello everyone!</p>
                </div>
            </Layout>
        )
    }
    
    • Pets.js 파일 수정
    import Layout from "./Layout"
    
    export default function Pets() {
        return (
            <Layout>
                <h1>Pets!</h1>
            </Layout>
        )
    }
    
    • 실행 화면
  • API 호출을 위한 fetch 라이브러리를 추가하자.
    • 반려동물 라이브러리 API를 호출하는 함수를 추가 하자.
    • Pages.js
    • import fetch from "isomorphic-unfetch" import Pets from "./Pets" Pets.getInitialProps = async function() { const res = await fetch(`http://pet-library.moonhighway.com/api/pets`) const data = await res.json() return { pets: data } }
    • Pets.js<aside> ❓ 실행이 안되는데...?project-next.zip
    • </aside>
    • import Layout from "./Layout" export default function Pets(props) { return ( <Layout> <h1>Pets!</h1> <ul> {props.pets.map(pet => ( <li key={pet.id}>{pet.name}</li> ))} </ul> </Layout> ) }
    • getInitalProps 컴포넌트 안에 있으면 넥스트가 각 요청에 맞춰 이 페이지를 렌더링해준다.
    • 이 페이지가 정적으로 미리 렌더링되는 대신 서버쪽에서 렌더링된다는 뜻이다.
    • npm run build를 실행하면 각 파일이 몇 킬로바이트인지 보여준다.
  • npm install isomrphic-unfetch
  • 넥스트는 Zeit의 오픈 소스 제품이기 때문에 Zeit를 통해 디플로이하면 편하다.

CRS(클라인트쪽 렌더링)

  • 앱을 브라우저에서 렌더링한다.
  • 보통은 DOM을 사용
  • create-react-app으로 만든 앱을 따로 변경하지 않으면 이 방식을 사용

SSR(서버쪽 렌더링)

  • 서버쪽에서 클라이언트 앱이나 유니버설 앱을 HTML로 렌더링한다.

재수화

  • 서버에서 렌더링한 HTML의 DOM 트리와 데이터를 재사용하면서 자바스크립트 뷰를 클라이언트에서 로딩한다.

미리 렌더링

  • 빌드 시점에 클라이언트쪽 애플리케이션을 실행해서 초기 상태를 잡아내어 정적 HTML로 만든다.

12.4 개츠비

  • 리액트를 기반으로 하는 유명한 사이트 생성기로 개츠비가 있다.
  • 개츠비는 콘텐츠 기반의 웹사이트를 직접적으로 만드는 방법으로 세계를 휩쓸고 있다.
  • 개츠비의 목적은 성능, 접근성, 이미지 처리 등의 관리와 관련된 사항에 대한 현명한 디폴트 값을 제공하는 것이다.
  • 블로그나 정적 콘텐츠를 서비스해야 하는 경우 개츠비가 훌륭한 선택이며, 리액트를 아는 경우에는 더 좋은 선택이다.
  • API에서 데이터를 로딩하는 동적 콘텐츠를 처리할수도 있고, 다른 프레임워크와 통합될 수도 있으며, 더 많은 기능을 제공한다.
  • 개츠비 설치 및 프로젝트 생성
  • npm install gatsby-cli npx gatsby new pets cd pets npx gatsby develop
  • 프로젝트 구조
    • src 폴더에 components, images, pages라는 하위 폴더가 생성되어 있다.
    • Layout.js
      • useStaticQuery 훅을 사용해 사이트로부터 그래프QL로 데이터를 쿼리해 받는다.
    • seo.js
      • 검색 엔진 최적화를 위해 페이지 메타데이터에 접근할수 있게 해준다.
  • pages 폴더에 파일을 더 추가하면 사이트에 다른 페이지를 추가할 수 있다.
  • page-3.js 파일을 추가 해보자.
  • import * as React from "react" import { Link } from "gatsby" import Layout from "../components/layout" import Seo from "../components/seo" const ThirdPage = () => ( <Layout> <Seo title="Page three"/> <h1>Hi from third page</h1> <Link to="/">Go back to the homepage</Link> </Layout> ) export default ThirdPage
  • 개츠비가 제공하는 다른 기능을 몇가지 알아보자.

정적 콘텐츠

  • 사이트를 정적 파일로 구축할 수 있다.
  • 서버가 없어도 사이트를 디플로이 할 수 있다. (????)

CDN 지원

  • 전 세계의 CDN에 캐시해서 성능과 가용성을 향상 시킨다.

반응형 및 진행형 이미지

  • 위지지정자를 표시해서 이미지를 로딩한다.
  • 이미 로딩이 끝나면 흐릿한 위치지정자가 점차 사라진다.
  • 모든 자원이 로딩되기 전에도 사용자들이 렌더링 되는 모습을 지켜볼 수 있다.

연결된 페이지 미리 불러오기

  • 다음 페이지를 로딩하기 위해 필요한 콘텐츠들을 클릭하기 전에 백그라운드 작업으로 로딩해 둔다.

12.5 리액트의 미래

  • 리액트가 자바스크립트 앱을 만들 때 가장 널리 쓰이고 가장 많은 영향을 끼치는 라이브러리이다.
  • 넥스트나, 개프비와 같은 프로젝트를 보면 알수 있는 것처럼 더 많은 자바스크립트 커뮤니티 구성원들이 이랙트를 도구로 선택해서 사용 중이다.
  • 그외 리액트 네이티브나 그래프 QL을 살펴보자.
  • 콘텐츠 기반의 웹사이트를 만들고 싶다면 넥스트와 개츠비를 자세히 살펴보자.
728x90

'자바스크립트 > 러닝 리엑트' 카테고리의 다른 글

리액트 라우터  (0) 2023.11.13
리액트 테스트  (1) 2023.11.13
Suspense  (0) 2023.11.13
데이터 포함시키기  (0) 2023.11.13
훅스 컴포넌트 개선하기  (1) 2023.11.13