Help us understand the problem. What is going on with this article?

『実践TypeScript』第10章をNext.js 9で実習する

書籍『実践TypeScript』ではNext.js 8系を使った手順が解説されていますが、出版後すぐにNext.js 9系がリリースされ、これによってTypeScriptサポートが強化されました。そこで、本記事では、Next.js 9を使って『実践TypeScript』を実習していきます。

環境構築

next.js 9、React. 16.9を使います。

package.json
{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "^9.0.6",
    "react": "^16.9.0",
    "react-dom": "^16.9.0"
  }
}
pages/index.jsx
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.jsxpages/index.tsx にリネームします。

ここで yarn dev を実行すると、TypeScriptコンパイラの設定ファイル(tsconfig.json)など、必要なファイルが自動生成されます。

なお、バージョン違いによって、書籍で next モジュールからimportしている型が利用できないことがあります。

たとえば、 Next.NextFC という型はありません。pages/index.tsx では、かわりに NextPage 型をつかいましょう。

pages/index.tsx
import React from 'react'
import { NextPage } from 'next'
const Page: NextPage = props => (
  <div>Welcome to next.js!</div>
)
export default Page

Custom Components

書籍と同じ。

layouts/index.tsx
import React from "react"
import { Head, Main, NextScript } from "next/document"

export default () => (
  <html>
    <Head />
    <body>
      <Main />
      <NextScript />
    </body>
  </html>
)

Next.NextFC 型はないので、 NextComponentType 型を使う。

components/index.tsx
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)。

pages/_document.tsx
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
pages/_app.tsx
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

pages/_error.tsx
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
pages/index.tsx
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 で代用。

components/index.tsx
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プロパティをもつとみなされます。

pages/_app.tsx
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サポートが強化されたという話でしたが、実際その通りで、セットアップはより簡単に、型はより柔軟で使いやすく、といった感じですね。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away