書籍『実践TypeScript』ではNext.js 8系を使った手順が解説されていますが、出版後すぐにNext.js 9系がリリースされ、これによってTypeScriptサポートが強化されました。そこで、本記事では、Next.js 9を使って『実践TypeScript』を実習していきます。
環境構築
next.js 9、React. 16.9を使います。
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^9.0.6",
"react": "^16.9.0",
"react-dom": "^16.9.0"
}
}
import React from 'react'
const Page = props => (
<div>Welcome to next.js!</div>
)
export default Page
yarn install
でパッケージインストール後、 yarn dev
でnext.jsサーバを起動して、動作確認しておきましょう。
TypeScript導入
TypeScriptのコンパイラと、Node.jsおよびReact.js用の型定義ファイルをインストールします。
yarn add -D typescript @types/node @types/react
なお、書籍のように @zeit/next-typescript
をインストールする必要はありません。TypeScriptサポート機能はNext.jsに内蔵されるようになっています。
TypeScriptサポートを有効化するには、 pages/index.jsx
を pages/index.tsx
にリネームします。
ここで yarn dev
を実行すると、TypeScriptコンパイラの設定ファイル(tsconfig.json)など、必要なファイルが自動生成されます。
なお、バージョン違いによって、書籍で next
モジュールからimportしている型が利用できないことがあります。
たとえば、 Next.NextFC
という型はありません。pages/index.tsx では、かわりに NextPage
型をつかいましょう。
import React from 'react'
import { NextPage } from 'next'
const Page: NextPage = props => (
<div>Welcome to next.js!</div>
)
export default Page
Custom Components
書籍と同じ。
import React from "react"
import { Head, Main, NextScript } from "next/document"
export default () => (
<html>
<Head />
<body>
<Main />
<NextScript />
</body>
</html>
)
Next.NextFC
型はないので、 NextComponentType
型を使う。
import React from "react"
import { NextComponentType } from "next"
const Component : NextComponentType = () => (
<div>Welcome to next.js!</div>
)
export default Component
next/document
からexportされている型も微妙に違うので注意(NextDocumentContextではなくDocumentContext)。
import React from "react"
import Document, { DocumentContext } from "next/document"
import DefaultLayout from "../layouts/index"
export default class extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return <DefaultLayout />
}
}
変更点は2つ。
- ContainerはDeprecatedなので削除
- NextAppContext => AppContext
import React from "react"
import App, { AppContext } from "next/app"
export default class extends App {
static async getInitialProps({ Component, ctx }: AppContext) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
render() {
const { Component, pageProps } = this.props
return (
<Component {...pageProps} />
)
}
}
NextContext => NextPageContext
import React from "react"
import { NextPageContext } from "next"
import Head from "next/head"
type Props = {
title: string
errorCode: number
}
class Error extends React.Component<Props> {
static async getInitialProps({res}: NextPageContext): Promise<Props> {
return {
title: `Error: ${res!.statusCode}`,
errorCode: res!.statusCode,
}
}
render() {
return (
<>
<Head>
<title>{this.props.title}</title>
</Head>
{this.props.errorCode}
</>
)
}
}
export default Error
import React from "react"
import Head from "next/head"
import Component from "../components/index";
type Props = {
title: string
}
class App extends React.Component<Props> {
static async getInitialProps(): Promise<Props> {
return { title: "Hello world" }
}
render() {
return (
<>
<Head>
<title>{this.props.title}</title>
</Head>
<Component />
</>
)
}
}
export default App
styled-components
書籍の通りの手順でOK。 Next.NextFC
型はないので NextComponentType
で代用。
import React from "react"
import { NextComponentType, NextPageContext } from "next"
import styled from "styled-components"
type Props = {
className?: string
}
const Component : NextComponentType<NextPageContext, {}, Props> = props => (
<div className={props.className}>Welcome to next.js!</div>
)
const StyledComponent = styled(Component)`
color: #f00;
`
export default StyledComponent
Redux
Reduxの導入のところは書籍の通りでOK。next.jsとの結合の部分では、そのままでは動かないところがあります。
書籍ではshims-next.d.ts でnext組み込みのinterfaceを拡張しています。しかし、next 9では拡張は必要ありません。
かわりに、storeを持ったPropsを定義して、これをAppの型引数に渡します。これでApp.propsがstoreプロパティをもつとみなされます。
import React from "react"
import { Provider } from "react-redux"
import App, { AppContext } from "next/app"
import withRedux from "next-redux-wrapper"
import { initStore, ReduxStoreInstance } from "../store"
type Props = {
store: ReduxStoreInstance
}
export default withRedux(initStore)(
class extends App<Props> {
static async getInitialProps({ Component, ctx }: AppContext) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps}
}
render() {
const { Component, pageProps, store } = this.props
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
}
)
Express
ほぼ書籍通りでOK。例によって、NextContext型は無くなってるので、NextPageContext型を拡張します。
type ExNextPageContext = NextPageContext & {
req?: Express.Request,
res?: Express.Response,
}
感想
Next 9はTypeScriptサポートが強化されたという話でしたが、実際その通りで、セットアップはより簡単に、型はより柔軟で使いやすく、といった感じですね。