Next.jsでReactをimportするのとしないのって何か変わるの?
株式会社IVRy (アイブリー)のエンジニアのkinashiです。
IVRyでは主にフロントエンドを担当しています。
先日zennを見ていたら下記の記事があり、楽しく読ませていただきました。
この記事内では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にします
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: (config) => {
config.optimization.minimize = false
return config
}
}
module.exports = nextConfig
Home(トップページから検証用のコンポーネントを呼び出すようにしました)
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.
(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 を使わなくなってるいることが分かります。
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.
(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.
(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では一緒に働いてくれるエンジニアを絶賛募集中です!
話だけ聞いてみたいといった方はカジュアル面談もできるので、お気軽にご応募ください!
-
VFCはReact18で非推奨になったのと宣言はFCを使うならアロー関数で定義することになるので、個人的には表題のままでも良さそうに見えました ↩︎
Discussion