React: Next
インストール
TypeScript と Sass も使えるようにする。
yarn add next react react-dom @zeit/next-typescript @zeit/next-sass node-sass
yarn add -D typescript @types/next @types/react @types/react-dom
Redux とか rxjs を使うときはこれもいれる。
yarn add next-redux-wrapper react-redux redux redux-observable redux-logger rxjs
yarn add -D @types/next-redux-wrapper @types/react @types/react-redux @types/redux @types/redux-logger babel-plugin-module-resolver
next.config.js
を作る。
const withTypescript = require("@zeit/next-typescript");
module.exports = withTypescript();
VSCode の設定
ext install esbenp.prettier-vscode
ルール
pages
ディレクトリの中にあるコンポーネントは自動で URL と紐づけられる
pages
- Index.jsx
- About.jsx
- contents
- ContentA.jsx
この場合、以下のページが存在することになる。
localhost:3000/index
localhost:3000/about
localhost:3000/contents/contentA
共通コンポーネントや util 的なコンポーネントはpages
以外に置く。
基本
共通レイアウト
ヘッダ、サイドバーなどの共通レイアウト用にコンポーネントを作成できる。
components/Layout.jsx
import Header from "./Header";
const Layout = props => (
<div>
<Header />
{props.children}
</div>
);
export default Layout;
pages/index.jsx
import Layout from "../components/MyLayout.js";
export default () => (
<Layout>
<p>Hello Next.js</p>
</Layout>
);
このようにpages
配下の各コンポーネントから利用できる。
ページ遷移
Link
コンポーネントを使う。
import Link from "next/link";
<Link href="/about">
<a>About Page</a>
</Link>;
これはpages/about.jsx
へ遷移するリンク。
イベントハンドラなどで遷移したい場合はRouter.push()
を使う。
import Router from "next/router";
const Login = () => {
const handleClick = (e: any) => Router.push("/");
return <p onClick={handleClick}>Login...</p>;
};
export default Login;
動的ページ生成
href
にクエリパラメータを書いて遷移もできる。
<Link href="/post?title=hoge">
<a>Go to post</a>
</Link>
pages/post.jsx
import { withRouter } from "next/router";
const Post = withRouter(props => <h1>{props.router.query.title}</h1>);
export default Post;
withRouter()
を使ってコンポーネントを作るとprops.router.query
でクエリパラメータを参照できるようになる。
クリーン URL
Link
コンポーネントのas
プロパティに URL の別名を指定できる。
const Link = props => (
<Link as={`/p/${props.id}`} href={`/post?title=${props.title}`}>
<a>{props.title}</a>
</Link>
);
pages/post.jsx
に遷移するが、URL は/p/${props.id}
となる。
サーバーサイドレンダリング
クリーン URL を使うとF5
更新で 404 になってしまう。
カスタムサーバーを使うとこの問題を回避できる。
express
を利用する。
yarn add express
server.js
const express = require("express");
const next = require("next");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
// ここでカスタムルートを定義する
// この場合、`post.jsx`ではクエリパラメータの`title`(=id)をもとにサーバーからデータを取得する形になる
server.get("/p/:id", (req, res) => {
const actualPage = "/post";
const queryParams = { title: req.params.id };
app.render(req, res, actualPage, queryParams);
});
server.get("*", (req, res) => {
return handle(req, res);
});
server.listen(3000, err => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
package.json
を書き換える。
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
}
yarn dev
し、クリーン URL へ遷移してからF5
更新が正常に行われることがわかる。
サーバーからデータを取得
データ取得ライブラリにはisomorphic-unfetch
を使う。
クライアント環境もサーバーサイド環境も対応しているらしい。
yarn add isomorphic-unfetch
初期データ取得用にgetInitialProps
が使える。
import Link from "next/link";
import fetch from "isomorphic-unfetch";
const Index = props => (
<div>
<ul>
{props.shows.map(({ show }) => (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}>
<a>{show.name}</a>
</Link>
</li>
))}
</ul>
</div>
);
// 引数のpropsからクエリパラメータを受け取れる
// ex) props.query.title
Index.getInitialProps = async function(props) {
const res = await fetch("https://api.tvmaze.com/search/shows?q=batman");
const data = await res.json();
console.log(`Show data fetched. Count: ${data.length}`);
return {
shows: data
};
};
export default Index;
getInitialProps()
の中で参照する
クエリパラメータをcontext
を引数にとると、context.query
で参照できる。
Index.getInitialProps = async function(context) {
const { id } = context.query;
const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
const show = await res.json();
console.log(`Fetched show: ${show.name}`);
return { show };
};
Typescript と使う
FAQ
reactWEBPACK_IMPORTED_MODULE_0.useEffect is not a function
webpack で React.use***()を使ったときに、React のバージョンが正しくない場合におこる。
https://stackoverflow.com/questions/53024307/typeerror-dispatcher-usestate-is-not-a-function-when-using-react-hooks
package.json
を以下のように書き換えて解決。
"devDependencies": {
"react": "16.7.0-alpha.2",
"react-dom": "16.7.0-alpha.2",
}
Hooks can only be called inside the body of a function component
React のコンポーネントライブラリを読み込んだときなどに、React のインスタンスを複数生成してしまった場合に起こる。
バージョン違いの React を使用していると起こるらしい。
import * as React from "@hako1912/my-lib";
// import * as React from 'react' の代わりに
react-hot-loader
を入れる
yarn add react-hot-loader
スニペット
ローディング中は別のコンポーネントを表示する
const component = () => {
const [loading, setLoading] = useState(true);
React.useEffect(() => {
Router.push("/login");
});
return <>{loading ? <p>loading...</p> : <Main />}</>;
};
環境変数を使う
dotenv-webpack
を使う。
yarn add dotenv-webpack
next.config.js
を修正する。
// 必ず先頭で実行
require("dotenv").config();
const Dotenv = require("dotenv-webpack");
const path = require("path");
module.exports = webpack(config) {
config.plugins = config.plugins || [];
config.plugins = [
...config.plugins,
// Read the .env file
new Dotenv({
path: path.join(__dirname, ".env"),
systemvars: true
})
];
return config;
}
})
)
);
.env
ファイルをプロジェクトルートに作成する。
TEST=hogehoge
上記の設定をすると、process.env.<環境変数名>
で環境変数を参照できるようになる。
process.env の値がwebpack build
時に環境変数で置き換わる。
console.log(process.env.TEST);
TypeScript の補完をprocess.env
に対して適用したいときは、以下のようにProcessEnv
の定義を追加する。
/// <reference types="node" />
declare namespace NodeJS {
interface ProcessEnv {
readonly HOGE: string;
readonly FUGA: string;
}
}
ただし、d.ts
で既存の interface の型を減らすことはできないので、どんな値を参照してもエラーにはならない。
// ProcessEnvのもともとの定義
interface ProcessEnv {
[key: string]: string | undefined;
}
Ref:
https://github.com/zeit/next.js/tree/master/examples/with-dotenv