🐣

Next.jsでReactをimportするのとしないのって何か変わるの?

2023/03/28に公開

株式会社IVRy (アイブリー)のエンジニアのkinashiです。
IVRyでは主にフロントエンドを担当しています。

先日zennを見ていたら下記の記事があり、楽しく読ませていただきました。
https://zenn.dev/rgbkids/articles/d7691b6c852b42

この記事内ではReactのコンポーネントの書き方で主に次のことを考察していました。[1]

  • 戻り値の型(FC or VFC or 使わない)
  • 宣言は関数かアロー関数か

この記事を読んだときに、Next.jsでFCなどの型を使う場合、importの書き方でビルド結果が変わったりするのだろうかとふと思ったので、今回はビルドしたコードを見てみようと思います。

検証対象

以下のものを検証します

import { FC } from 'react'

export const SomeComponent: FC = () => <div>Hello World!</div>
// 型だけを使う場合でも React が暗黙的に import される?
// import React from 'react'

export const SomeComponent: React.FC = () => <div>Hello World!</div>
// 明示的に React を importする
import React from 'react'

export const SomeComponent: React.FC = () => <div>Hello World!</div>

検証環境

$ yarn create next-app --typescript

を実行した環境で検証

 "dependencies": {
    "@types/node": "18.15.10",
    "@types/react": "18.0.29",
    "@types/react-dom": "18.0.11",
    "eslint": "8.36.0",
    "eslint-config-next": "13.2.4",
    "next": "13.2.4",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "typescript": "5.0.2"
  }

準備

ビルド結果が分かりやすいようにminimizeの設定をfalseにします

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack: (config) => {
    config.optimization.minimize = false
    return config
  }
}

module.exports = nextConfig

Home(トップページから検証用のコンポーネントを呼び出すようにしました)

src/pages/index.tsx
import { SomeComponent } from '@/components/SomeComponent'

export default function Home() {
  return (
    <main>
      <SomeComponent />
    </main>
  )
}

importの書き方でビルド結果は変わるのか

import { FC } from 'react' と書いた場合

import { FC } from 'react'

export const SomeComponent: FC = () => <div>Hello World!</div>
出力
$ next build
info  - Linting and checking validity of types
warn  - Production code optimization has been disabled in your project. Read more: https://nextjs.org/docs/messages/minification-disabled
info  - Compiled successfully
info  - Collecting page data
info  - Generating static pages (3/3)
info  - Finalizing page optimization

Route (pages)                              Size     First Load JS
┌ ○ /                                      621 B           103 kB
├   /_app                                  0 B             102 kB
└ ○ /404                                   338 B           103 kB
+ First Load JS shared by all              103 kB
  ├ chunks/framework-66a63f2873fdf2b3.js   46.8 kB
  ├ chunks/main-f97290b10d9e1503.js        53.2 kB
  ├ chunks/pages/_app-0c3c66b91afc0668.js  635 B
  ├ chunks/webpack-c4d4ccd65a37d296.js     1.77 kB
  └ css/96e8dac7b6e5d511.css               797 B

○  (Static)  automatically rendered as static HTML (uses no initial props)

✨  Done in 3.17s.
.next/static/chunks/pages/index-1c48a2820d9f84e3.js
(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[405],{

/***/ 8312:
/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {


    (window.__NEXT_P = window.__NEXT_P || []).push([
      "/",
      function () {
        return __webpack_require__(134);
      }
    ]);
    if(false) {}
  

/***/ }),

/***/ 134:
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);

// EXPORTS
__webpack_require__.d(__webpack_exports__, {
  "default": function() { return /* binding */ Home; }
});

// EXTERNAL MODULE: ./node_modules/react/jsx-runtime.js
var jsx_runtime = __webpack_require__(5893);
;// CONCATENATED MODULE: ./src/components/SomeComponent.tsx

const SomeComponent = ()=>/*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
        children: "Hello World!"
    });

;// CONCATENATED MODULE: ./src/pages/index.tsx


function Home() {
    return /*#__PURE__*/ (0,jsx_runtime.jsx)("main", {
        children: /*#__PURE__*/ (0,jsx_runtime.jsx)(SomeComponent, {})
    });
}


/***/ })

},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
/******/ __webpack_require__.O(0, [774,888,179], function() { return __webpack_exec__(8312); });
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]);
抜粋
// EXTERNAL MODULE: ./node_modules/react/jsx-runtime.js
var jsx_runtime = __webpack_require__(5893);
;// CONCATENATED MODULE: ./src/components/SomeComponent.tsx

const SomeComponent = ()=>/*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
        children: "Hello World!"
    });

;// CONCATENATED MODULE: ./src/pages/index.tsx

React17からトランスフォーム時に React をインポートせず JSX を使用できるようになりましたが、React.createElement を使わなくなってるいることが分かります。
https://ja.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html

React を import せずに、 React.FC と書いた場合

export const SomeComponent: React.FC = () => <div>Hello World!</div>
出力
$ next build
info  - Linting and checking validity of types
warn  - Production code optimization has been disabled in your project. Read more: https://nextjs.org/docs/messages/minification-disabled
info  - Compiled successfully
info  - Collecting page data
info  - Generating static pages (3/3)
info  - Finalizing page optimization

Route (pages)                              Size     First Load JS
┌ ○ /                                      621 B           103 kB
├   /_app                                  0 B             102 kB
└ ○ /404                                   338 B           103 kB
+ First Load JS shared by all              103 kB
  ├ chunks/framework-66a63f2873fdf2b3.js   46.8 kB
  ├ chunks/main-f97290b10d9e1503.js        53.2 kB
  ├ chunks/pages/_app-0c3c66b91afc0668.js  635 B
  ├ chunks/webpack-c4d4ccd65a37d296.js     1.77 kB
  └ css/96e8dac7b6e5d511.css               797 B

○  (Static)  automatically rendered as static HTML (uses no initial props)

✨  Done in 5.35s.
.next/static/chunks/pages/index-1c48a2820d9f84e3.js
(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[405],{

/***/ 8312:
/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {


    (window.__NEXT_P = window.__NEXT_P || []).push([
      "/",
      function () {
        return __webpack_require__(134);
      }
    ]);
    if(false) {}
  

/***/ }),

/***/ 134:
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);

// EXPORTS
__webpack_require__.d(__webpack_exports__, {
  "default": function() { return /* binding */ Home; }
});

// EXTERNAL MODULE: ./node_modules/react/jsx-runtime.js
var jsx_runtime = __webpack_require__(5893);
;// CONCATENATED MODULE: ./src/components/SomeComponent.tsx

const SomeComponent = ()=>/*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
        children: "Hello World!"
    });

;// CONCATENATED MODULE: ./src/pages/index.tsx


function Home() {
    return /*#__PURE__*/ (0,jsx_runtime.jsx)("main", {
        children: /*#__PURE__*/ (0,jsx_runtime.jsx)(SomeComponent, {})
    });
}


/***/ })

},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
/******/ __webpack_require__.O(0, [774,888,179], function() { return __webpack_exec__(8312); });
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]);
抜粋
// EXTERNAL MODULE: ./node_modules/react/jsx-runtime.js
var jsx_runtime = __webpack_require__(5893);
;// CONCATENATED MODULE: ./src/components/SomeComponent.tsx

const SomeComponent = ()=>/*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
        children: "Hello World!"
    });

;// CONCATENATED MODULE: ./src/pages/index.tsx

なにも変わっていないことが分かります。
暗黙的に React が読み込まれることはなさそうでした。

明示的に React を import した場合

では、明示的に React を import とするとどうなるでしょうか

import React from 'react'

export const SomeComponent: React.FC = () => <div>{text}</div>
出力
$ next build
info  - Linting and checking validity of types
warn  - Production code optimization has been disabled in your project. Read more: https://nextjs.org/docs/messages/minification-disabled
info  - Compiled successfully
info  - Collecting page data
info  - Generating static pages (3/3)
info  - Finalizing page optimization

Route (pages)                              Size     First Load JS
┌ ○ /                                      638 B           103 kB
├   /_app                                  0 B             102 kB
└ ○ /404                                   338 B           103 kB
+ First Load JS shared by all              103 kB
  ├ chunks/framework-66a63f2873fdf2b3.js   46.8 kB
  ├ chunks/main-f97290b10d9e1503.js        53.2 kB
  ├ chunks/pages/_app-0c3c66b91afc0668.js  635 B
  ├ chunks/webpack-c4d4ccd65a37d296.js     1.77 kB
  └ css/96e8dac7b6e5d511.css               797 B

○  (Static)  automatically rendered as static HTML (uses no initial props)

✨  Done in 3.98s.
.next/static/chunks/pages/index-250853d30b20a921.js
(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[405],{

/***/ 8312:
/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {


    (window.__NEXT_P = window.__NEXT_P || []).push([
      "/",
      function () {
        return __webpack_require__(134);
      }
    ]);
    if(false) {}
  

/***/ }),

/***/ 134:
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);

// EXPORTS
__webpack_require__.d(__webpack_exports__, {
  "default": function() { return /* binding */ Home; }
});

// EXTERNAL MODULE: ./node_modules/react/jsx-runtime.js
var jsx_runtime = __webpack_require__(5893);
// EXTERNAL MODULE: ./node_modules/react/index.js
var react = __webpack_require__(7294);
;// CONCATENATED MODULE: ./src/components/SomeComponent.tsx


const SomeComponent = ()=>/*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
        children: "Hello World!"
    });

;// CONCATENATED MODULE: ./src/pages/index.tsx


function Home() {
    return /*#__PURE__*/ (0,jsx_runtime.jsx)("main", {
        children: /*#__PURE__*/ (0,jsx_runtime.jsx)(SomeComponent, {})
    });
}


/***/ })

},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
/******/ __webpack_require__.O(0, [774,888,179], function() { return __webpack_exec__(8312); });
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]);
抜粋
// EXTERNAL MODULE: ./node_modules/react/jsx-runtime.js
var jsx_runtime = __webpack_require__(5893);
// EXTERNAL MODULE: ./node_modules/react/index.js
var react = __webpack_require__(7294);
;// CONCATENATED MODULE: ./src/components/SomeComponent.tsx


const SomeComponent = ()=>/*#__PURE__*/ (0,jsx_runtime.jsx)("div", {
        children: "Hello World!"
    });

;// CONCATENATED MODULE: ./src/pages/index.tsx

Reactが読み込まれています。
FCを使うところは同じでも、 import を書くか書かないかで React が読み込まれるかどうかが変わることが分かりました!

まとめ

同じように const SomeComponent: React.FC = ... と書いた場合でも、 import React from 'react' と書くかどうかでビルド結果が変わることが分かりました。

しかしながら、buildの標準出力を見ると、 React を import するしないに関わらず、 First Load JS の部分で framework のコードがすべて Load されています。

Route (pages)                              Size     First Load JS
┌ ○ /                                      638 B           103 kB
├   /_app                                  0 B             102 kB
└ ○ /404                                   338 B           103 kB
+ First Load JS shared by all              103 kB
  ├ chunks/framework-66a63f2873fdf2b3.js   46.8 kB
  ├ chunks/main-f97290b10d9e1503.js        53.2 kB
  ├ chunks/pages/_app-0c3c66b91afc0668.js  635 B
  ├ chunks/webpack-c4d4ccd65a37d296.js     1.77 kB
  └ css/96e8dac7b6e5d511.css               797 B

結論

import React from 'react' と書くとページの JS で React が読み込まれるが、どちらにせよ最初から React は読み込まれてしまうので初回読み込みの時の通信量が減らせる訳ではない。

なので今回検証した3パターンであればどの書き方でも良さそうです。

最後に

IVRyでは一緒に働いてくれるエンジニアを絶賛募集中です!
話だけ聞いてみたいといった方はカジュアル面談もできるので、お気軽にご応募ください!

https://ivry-jp.notion.site/IVRy-e1d47e4a79ba4f9d8a891fc938e02271

脚注
  1. VFCはReact18で非推奨になったのと宣言はFCを使うならアロー関数で定義することになるので、個人的には表題のままでも良さそうに見えました ↩︎

IVRyテックブログ

Discussion