Next.js 주요 개념 스터디 (useCallback, useMemo, useEffect 등 22개)

우디·2024년 3월 4일
0
post-thumbnail

안녕하세요:) 개발자 우디입니다! 아래 내용 관련하여 작업 중이신 분들께 도움이되길 바라며 글을 공유하니 참고 부탁드립니다😊
(이번에 벨로그로 이사오면서 예전 글을 옮겨적었습니다. 이 점 양해 부탁드립니다!)

작업 시점: 2021년 12월

배경

  • 다음 프로젝트 개발에 바로 착수할 수 있도록 Next.js 사전 스터디 진행.

기본 개념

  • next.js는 React로 만드는 서버사이드 렌더링 프레임워크임
  • 왜 서버 사이드 렌더링을 하는가?
    • 리액트의 경우, 클라이언트 사이드 렌더링을 기반.
    • 클라이언트 사이드 렌더링의 문제
      1. 클라이언트 렌더링의 경우 모든 js 파일을 로드하고 사용자는 웹을 보게됨 → 이때까지 사용자는 많은 시간을 대기해야 함.
      2. seo 문제
        1. 클라이언트 사이드 렌더링의 경우 자바스크립트가 로드 되지 않으면 아무런 정보를 보이지 않음.
        2. 구글의 검색엔진이 페이지를 스캔할 때, 자바스크립트가 로드되지 않은 페이지를 스캔하게되어 결론적으로 검색에 아무 페이지도 걸리지 않게 됨.
          ⇒ 이런 문제들을 해결하는 것이 서버 사이드 렌더링임.
  • 즉, 서버사이드 렌더링을 활용하면
    • 서버에서 자바스크립트를 로딩함으로써 클라이언트 측에서는 자바스크립트를 로딩하는 시간이 줄어들게 됨.
    • 서버 측에서 자바스크립트, html, css를 만들어 컨텐츠를 직접 업로드 함으로 검색엔진에 게시글이 걸리게 됨.
    • 추가적으로, meta 태그를 자유롭게 추가하여 seo를 용이하게 할 수도 있음.

주요 기능들

  • 1) useCallback 을 사용하여 함수 재사용하기

    • useCallback 은 useMemo 와 비슷한 Hook임
    • useMemo 는 특정 결과값을 재사용 할 때 사용하고, useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용함
    • 함수들은 컴포넌트가 리렌더링 될 때 마다 새로 만들어짐. 함수를 선언하는 것 자체는 사실 메모리도, CPU 도 리소스를 많이 차지 하는 작업은 아니기 때문에 함수를 새로 선언한다고 해서 그 자체 만으로 큰 부하가 생길일은 없지만, 한번 만든 함수를 필요할때만 새로 만들고 재사용하는 것은 여전히 중요함.
    • 리액트 컴포넌트에서는 props 가 바뀌지 않았으면 Virtual DOM 에 새로 렌더링하는 것 조차 하지 않고 컴포넌트의 결과물을 재사용 하는 방식으로 최적화 작업이 이루어지는데, 이 과정이 제대로 이루어지려면 함수를 재사용 하는 과정이 필수적임.
    • 주의!) 함수 안에서 사용하는 상태 혹은 props 가 있다면 꼭, deps 배열안에 포함시켜야 함. 만약에 deps 배열 안에 함수에서 사용하는 값을 넣지 않게 된다면, 함수 내에서 해당 값들을 참조할때 가장 최신 값을 참조 할 것이라고 보장 할 수 없음. props 로 받아온 함수가 있다면, 이 또한 deps 에 넣어주어야 함.
    • 사실 useCallback 은 useMemo 를 기반으로 만들어졌음. 다만 함수를 위해서 사용 할 때 더욱 편하게 해준 것 뿐임
      • 이렇게도 사용 가능함
        const example = useMemo(
          () => () => {
            /* ... */
          },
          [exam]
        );
  • 2) useMemo를 통해 특정 결과값 재사용하기

    • 리액트 코드를 작성하다 보면 관련 없는 함수인데 호출되는 경우가 있음.
      • 컴포넌트가 리렌더링 되면서 불필요하게 함수가 호출되고 자원이 낭비되는 것임.
    • useMemo의 Memo 는 "memoized" 를 의미하는데, 이는, 이전에 계산 한 값을 재사용한다는 의미를 가짐
    • useMemo 의 첫번째 파라미터에는 어떻게 연산할지 정의하는 함수를 넣어주면 되고, 두번째 파라미터에는 deps 배열을 넣어주면 됨.
      • 이 배열 안에 넣은 내용이 바뀌면, 우리가 등록한 함수를 호출해서 값을 연산해주고, 만약에 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용하게 됨.
  • 3) useEffect 를 통해 컴포넌트가 마운트 됐을 때 (처음 나타났을 때), 언마운트 됐을 때 (사라질 때), 그리고 업데이트 될 때 (특정 props가 바뀔 때) 특정 작업을 처리하기

    • useEffect 를 사용 할 때에는 첫번째 파라미터에는 함수, 두번째 파라미터에는 의존값이 들어있는 배열 (deps)을 넣음. 만약에 deps 배열을 비우게 된다면, 컴포넌트가 처음 나타날때에만 useEffect 에 등록한 함수가 호출됨.
    • useEffect 에서는 함수를 반환 할 수 있는데 이를 cleanup 함수라고 부름. cleanup 함수는 useEffect 에 대한 뒷정리를 해주는 역할, deps 가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출됨.
    • 마운트 시에 주로 하는 작업들은 다음과 같음
      • props 로 받은 값을 컴포넌트의 로컬 상태로 설정
      • 외부 API 요청 (REST API 등)
      • 라이브러리 사용 (D3, Video.js 등...)
      • setInterval 을 통한 반복작업 혹은 setTimeout 을 통한 작업 예약
    • 언마운트 시에 하는 작업들은 다음과 같음
      • setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)
      • 라이브러리 인스턴스 제거
    • deps 파라미터를 생략한다면, 컴포넌트가 리렌더링 될 때마다 호출 됨.
  • 4) hot reloading

    • 개발 과정에서 저장되는 코드는 자동으로 새로고침되어 빠르게 확인 가능함.
  • 5) automatic routing

    • pages 폴더에 있는 파일은 해당 파일 이름으로 라우팅되어 따로 설정할 필요 없음.
      • pages/sample.tsx -> localhost:3000/sample
    • public 폴더도 pages의 폴더와 동일하게 라우팅 할 수 있지만, 모든 사람이 페이지에 접근할 수 있으므로 주의.
  • 6) single file components

    • style-jsx를 사용함으로 컴포넌트 내부에 해당 컴포넌트만 스코프를 가지는 css를 만들수 있음.
    • style-jsx-global을 사용하면 글로벌로 스타일 정의 가능.
      • _app.tsx 에만 정의 가능 → 다른 컴포넌트에 정의한 경우 다른 클래스와 겹쳐 오류를 발생할 수 있음.
  • 7) 서버 사이드 렌더링

    • 서버에서 렌더링 진행.
  • 8) Code Splitting

    • dynamic import를 이용하면 손쉽게 코드 스플리팅이 가능.
    • Code Splitting이란
      • 내가 원하는 페이지에서 원하는 자바스크립트와 라이브러리를 렌더링 하는 것.
      • 모든 번들(chunk.js)이 하나로 묶이지 않고, 각각 나뉘어 좀 더 효율적으로 자바스크립트 로딩 시간을 개선할 수 있음.
  • 9) Typescript

    • 타입스크립트 활용을 위해 웹팩을 만지거나 바벨을 만질 필요 없음.
    • 타입스크립트를 설치하고 (yarn add typescript @types/node @types/react) → 명령어 (yarn run dev)만 입력하면 → 자동으로 tsconfig, next-end.d.ts가 생성되며 타입스크립트로 코딩이 가능
  • 10) _document.tsx 파일

    • meta 태그를 정의하거나, 전체 페이지에 관려하는 컴포넌트.
    • 여기에서의 console은 서버에서만 보이고 클라이언트에서는 보이지 않음.
    • render 요소는 반영하지만 페이지 구성 요소만 반영되고 js는 반영 하지 않기 때문에 console은 보이지 않음.
      • componentDidMount 같은 훅도 실행 되지 않음.
  • 11) _app.tsx

    function MyApp({ Component, pageProps }) {
      return <Component {...pageProps} />;
    }
    
    export default MyApp;
    • 이곳에서 렌더링 하는 값은 모든 페이지에 영향을 줌.
    • 최초로 실행되는 파일임.
    • 공통 레이아웃임으로 최초 실행되며 내부에 컴포넌트들을 실행함
    • Component, pageProps를 받음.
      • pageProps는 페이지 getInitialProps를 통해 내려 받은 props들임.
    • 그 다음 _document.tsx가 실행.
    • console.log 실행시 client, server 둘다 확인 가능.
  • 12) SASS(Syntactically Awesome StyleSheets) 사용

    • 따로 config 파일을 정의 하지 않고, css 파일을 scss로 바꾸고 yarn add sass --dev 명령어를 입력하면 알아서 설정해 줌.
    • SASS 관하여
      • CSS는 웹 콘텐츠를 위한 스타일시트 형식 언어
      • 치명적인 단점이 존재함.
        • CSS는 규모가 커질수록 코드가 복잡
        • 유지보수가 불편
        • CSS 코드 내에서 동일한 코드를 재사용하기 위해서 할 수 있는 유일한 조치는 '복사 & 붙여넣기' 뿐임
        • 위의 이유들로 인해 사용하는 과정에서 무척 귀찮고, 짜증나고, 또 많은 실수를 유발함.
      • Sass는 앞서 언급한 CSS의 치명적 단점을 보완할 수 있는 몇 가지 방법 중 단연 최선의 방법임
        • 별도의 컴파일 과정을 통해 CSS 파일을 생성해 주는 CSS의 확장 언어이자 전처리기(preprocessor)임.
        • CSS에는 존재하지 않는 다양한 기능들을 가지고 있음.
        • 보유한 기능들이 코드 작성에 드는 시간을 줄여주고, 코드를 유지 관리하는 데 도움을 줌.
      • Sass 관련 주의 사항
        • Sass는 CSS의 대체 언어가 아니라는 점.
        • 그냥 CSS의 확장 언어이고, 이는 결국 CSS 코드를 생산해내기 위해 사용하는 일종의 도구임.
        • 스타일시트 생산 절차: Sass가 제공하는 문법을 기반으로 작성된 코드를 별도의 컴파일 과정을 통해 CSS 파일로 빌드함.
      • Sass의 특징
        • 호환성: 모든 버전의 CSS와 완벽하게 호환된다.
        • 기능성: 다양한 기능을 제공하고, 거의 모든 면에서 뛰어나다.
        • 안정성: 오랜 기간 적극적인 지원 아래 관리되어 왔다.
        • 인지도: 업계에서 인정받고 있으며, 많이 사용되고 있다.
        • 신뢰도: 거대 커뮤니티의 지원 아래 개발되고 있다.
        • 확장성: Sass 기반의 프레임워크가 다수 존재한다.
      • 결국 Sass는 생산성을 위한 것임. 코드 작성을 빠른 시간 안에 간결하게 할 수 있도록 해 주고, 코드 수정 시에 신경 써야 할 부분을 최소화해주는 효과 있음.
      • Sass와 SCSS
        • SASS 는 SASS 표기법(.sass)과 SCSS(.scss) 표기법이 존재하는데, Sass 3.0 부터 기본 표기법으로 채택된 SCSS (Sassy CSS) 사용을 권장하고 있음.
  • 13) Link 사용하기

    import Link from "next/link";
    
    const Index = () => (
      <div>
        <Link href="/blog">
          <a>Sample</a>
        </Link>
        // 동적 link시에는 []를 사용함.
        <Link href="/blog/[address]">
          <a>Sample</a>
        </Link>
      </div>
    );
    • 보통의 경우 페이지간 이동은 a 태그를 사용하나 next에서는 사용하지 않음.
      • a 태그를 사용할 경우, 처음 페이지에 진입시 번들 파일을 받고 a 태그에 의해 라우팅 되면 다시 번들 파일을 받기 때문.
      • 또한 자바스크립트 상태 관리 라이브러리인 redux을 쓰시는 경우 store의 state 값이 증발되는 현상도 발생.
      • 그렇기 때문에 a 태그는 전혀 다른 사이트로 페이지를 이동시켜 다시 돌아오지 않는 경우만 사용 → 그 이외에는 모두 Link 태그를 사용.
  • 14) 동적 URL (dynamic route)

    // pages/[id].tsx
    
    import { useRouter } from "next/router";
    
    export default () => {
      const router = useRouter();
    
      return (
        <>
          <h1>post</h1>
          <p>postid: {router.query.id}</p>
        </>
      );
    };
    • 가변적으로 변하는 url에 대해 동적 url을 지원.
    • [] 문법으로 동적 페이지를 생성하는 동적 url을 만들 수 있음.
    • 위 예시 코드에서 pages/[id].tsx 구조의 id 값은 router.query.id 값과 동일함.
    • dynamic route를 사용하고 싶지 않을 경우, dynamic page를 optional하게 주는 문법도 사용 가능 (Optional catch all routes)
  • 15) prefetching

    • 백그라운드 단에서 페이지를 미리 가져옴.
    • 기본값은 true임.
    • 뷰포트에있는 모든 항목들이 미리 로드됨.
    • 정적 생성을 사용하는 JSON페이지는 더 빠른 페이지 전환을 위해 데이터가 포함 된 파일을 미리 로드함.
    • 사실 이건 Link 컴포넌트를 사용해서 이뤄지는 것임
      • 링크 컴포넌트를 렌더링할때 형식으로 prefetch 값을 전달해주면 데이터를 먼저 불러온다음에 라우팅을 시작함.
    • 프로덕션 레벨에서만 이루어지는 점 개발 과정에서 주의!
  • 16) next/router 사용법

    import { useEffect } from "react";
    import { useRouter } from "next/router";
    import posts from "../posts.json";
    
    export default () => {
      const router = useRouter();
    
      const post = posts[router.query.id as string];
      if (!post) return <p>nothing to post!</p>;
    
      useEffect(() => {
        router.prefetch("/test");
      }, []);
    
      return (
        <>
          <h1>{post.title}</h1>
          <h1>{post.content}</h1>
          <button onClick={() => router.push("test")}>go to Test page!</button>
        </>
      );
    };
    • React로 생성된 SPA 내부에서 페이지 이동이 가능하도록 만들어주는 라이브러리인 react-router-dom과 사용 방법은 거의 유사함.
    • link에 있는 preferch 기능도 사용 가능.
  • 17) 컴포넌트에 데이터 보내주는 getInitialProps() (+ getStaticProps, getStaticPaths, getServerSideProps)

    • 서버사이드 렌더링을 하는 nextJs에서 컴포넌트는 각 페이지마다 사전에 불러와야할 데이터가 있음

    • react, vue같은 Client Side Rendering (CSR)의 경우는 data fetching 과정이 useEffect 등을 통해 이루어짐.

    • 서버 사이드에서 실행하는 next에서는 getInitialProps를 이용하여 data fetching 작업이 이루어짐.

    • 단, next v9 이상에서는 getInitialProps 대신 getStaticProps, getStaticPaths, getServerSideProps을 사용하도록 가이드 함!

      • getStaticProps

        // Fetch data at build time, pre-render for Static Generation
        // getStaticPaths only runs at build time on server-side.
        
        function Blog({ posts }) {
          return (
            <ul>
              {posts.map(post => (
                <li>{post.title}</li>
              ))}
            </ul>
          );
        }
        
        export async function getStaticProps() {
          const res = await fetch("https://.../posts");
          const posts = await res.json();
        
          // By returning { props: posts }, the Blog component
          // will receive `posts` as a prop at build time
          return {
            props: {
              posts
            }
          };
        }
        
        export default Blog;
        • docs 예제에서는 fetch를 통해 게시물을 가져오고 그 게시물의 title을 보여줌.
        • 빌스시 고정되는 값들임 → 빌드 이후 값 변경 불가능.
      • getStaticPaths

        // This function gets called at build time
        export async function getStaticPaths() {
          return {
            // 빌드 타임 때 아래 정의한  /dyna/1,  /dyna/2, ... /dyna/동적인값 경로만 pre렌더링.
            paths: [
              { params: { dynamic: 1 } },
              { params: { dynmic: 2 } }
              ......
              { params: { dynmic: 동적인값 } }
            ],
            // 만들어지지 않은 것도 추후 요청이 들어오면 만들어 줄 지 여부.
            fallback: true,
          }
        }
        • 빌드 시 사전에 렌더링할 경로를 미리 설정함.
        • 원래는 이곳에 정의하지 않은 하위 경로는 접근해도 페이지가 뜨지 않음.
        • 빌드 타임 때 사전에 정의한 /dyna/1, /dyna/2, ... /dyna/동적인값 경로만 미리 렌더링 됨.
        • 만들어지지 않은 경로도 추후 요청이 들어오면 만들어 줄 지 여부를 fallback으로 설정.
      • getServerSideProps

        // Fetch data on each request. pre-render for Server-side Rendering
        
        function Page({ data }) {
          // Render data...
        }
        
        // This gets called on every request
        export const getServerSideProps: GetServerSideProps = async context => {
          // Fetch data from external API
          const res = await fetch(`https://.../data`);
          const data = await res.json();
        
          // Pass data to the page via props
          return { props: { data } };
        };
        
        export default Page;
        • 각 요청에 따라 서버로부터 데이터를 가져옴.
        • 클라이언트 측에서 데이터를 가져오는 경우에 활용
          • 페이지에 자주 업데이트되는 데이터가 포함되어 있고 데이터를 미리 렌더링 할 필요가없는 경우 클라이언트 측에서 데이터를 가져올 수 있음.
          • 사용자별 데이터가 이에 해당할 것임.
            • 작동 방식: 먼저 데이터가 없는 페이지를 즉시 표시→ 페이지의 일부는 정적 생성을 사용하여 미리 렌더링 할 수 있음 → 누락 된 데이터에 대한 로드 상태를 표시함. → 클라이언트 측에서 데이터를 가져와 준비가 되면 표시
          • 이러한 접근 방식은 사용자 대시 보드 페이지에 적합함. 대시 보드는 비공개 사용자 별 페이지이기 때문에 SEO는 관련이 없으며 페이지를 미리 렌더링 할 필요가 없음. 데이터는 자주 업데이트되므로 요청 시간 데이터 가져 오기가 필요함.
  • 18) server side lifeCycle

    • nextJs 서버가 GET 요청을 받는다.
    • GET 요청에 맞는 pages/Component를 찾는다.
    • _app.tsx의 getInitialProps가 있다면 실행한다.
      • route에 맞는 페이지의 Component의 getInitialProps가 있다면 실행한다. pageProps들을 받아온다.
    • _document.tsx의 getInitialProps가 있다면 실행한다. pageProps들을 받아온다.
    • 모든 props들을 구성하고, _app.tsx -> page Component 순서로 렌더링
    • 모든 Content를 구성하고 _document.tsx를 실행하여 html 형태로 출력한다
  • 19) custom 태그

    import Head from "next/head";
    
    export default () => (
      <div>
        <Head>
          <title>new title!</title>
        </Head>
        <div>...</div>
      </div>
    );
    • 페이지 제목이나 메타 태그를 커스텀 및 변경하고 싶을때 커스텀 태그 활용.
    • next/head로 부터 Head 컴포넌트를 받아 모든 컴포넌트에서 사용할 수 있음.
    • next.js가 해당 컴포넌트를 mount 할때, Head내 태그들을 페이지의 HTML의 Head에 포함 시킴. 마찬가지로 unMount 시에는 해당 태그를 제거함.
  • 20) dynamic component import

    import React, { useState } from "react";
    import dynamic from "next/dynamic";
    
    const DynamicComponent = dynamic<{ nowTab: number }>(() =>
      import("./DynamicComponent")
    );
    
    const Index = () => {
      const [nowTab, setNowTab] = useState(0);
    
      return (
        <>
          {nowTab === 0 && <div>0 tab</div>}
          {nowTab === 1 && <DynamicComponent nowTab={nowTab} />}
        </>
      );
    };
    • dynamic component import는 react에서 lazy하게(지연시켰다가) component를 import 해오는 방식과 유사함.
    • 처음에 보여주지 않아도 되는 컴포넌트들을 dynamic하게 처리하여 맨 처음에 import 하지 않게 됨 → 초기 화면 렌더링 속도를 상승시킴.
    • 주의) 페이지에 진입할때 맨처음 보이는 컴포넌트라면 dynamic 메소드 사용 금지!
  • 21) production 배포

    • npm run build
    • npm run start
  • 22) material-ui 또한 어렵지 않게 사용 가능.

토이 프로젝트

  • 스터디 내용들을 직접 사용해보기 위해 토이 프로젝트 진행.
  • 실제 개발에 들어가기 전에 활용될 것 같은 기능들을 대략적으로 구현해보는 연습 위함.
  • 디자인 등은 신경쓰지 않고 진행함.
  • 동적 라우팅, 무한 스크롤, 버튼, 트위치 비디오 삽입 등의 작업을 미리 해봄.
  • 만들어진 결과물

배우고 느낀 점

  • 스터디에서 끝나는게 아니라 실제 개발을 하고 적용해보면서 확실하게 이해하자
  • 관련 개념들을 한 번 보고 넘어가지 말고 계속 숙지하면서 확실하게 이해하자
profile
넓고 깊은 지식을 보유한 개발자를 꿈꾸고 있습니다:) 기억 혹은 공유하고 싶은 내용들을 기록하는 공간입니다

0개의 댓글