Next.js と TypeScript で、void 型を理解する

TwitterFacebookHatena

TL;DR

この記事では、いつも使っているような、stringnumber などとは少し違った、特殊な存在である void 型についてお話しします。なんの役に立つのか、いつ使うのか、どう使うのか、すべてを明らかにしますね。

開発環境 バージョン
Next.js 13.4.4
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

void 型とは?

「void」は英語で「空虚」「何もない」という意味があります。そう、プログラミングの世界でも void 型は「何も返さない」という意味を持っています。他のデータ型が値を返すのに対して、void 型は値を返さないんですね。「何も返さないというのがなんで必要?」と思うかもしれませんが、それぞれの役割があるんです。

たとえば、ある関数が特定の操作を行うだけで、その結果を他の部分で使わない場合、その関数の返り値は void 型になります。それが以下のような TypeScript のコードです。

function logMessage(message: string): void {
  console.log(message)
}

この関数 logMessage は、メッセージを受け取り、それをコンソールに出力します。ですが、この関数は何も返しません。そのため、この関数の返り値の型は void となるんですね。

void 型の使いどころ

void型は、一体どのようなシーンで使うのでしょうか?

void型は一般的に、関数が値を返す必要がない場合に使用されます。具体的な例をいくつか挙げてみましょう。

クリックイベントで特定の状態を更新する場合

例えば、ボタンがクリックされたときにカウンターの値を増加させるような関数は、何も返す必要がありません。その操作自体が状態を直接変更するからです。

const handleClick = (): void => {
  // 何かしらの状態を更新する
}

ログ出力のための関数

ログをコンソールに表示するだけの関数も、何も返す必要がありません。

const logMessage = (message: string): void => {
  console.log(message)
}

ユーザーへのアラート表示

ユーザーへメッセージを表示するだけの関数も、何も返す必要がありません。

const showAlert = (alertText: string): void => {
  alert(alertText)
}

このように、関数が何らかの副作用(状態の変更、コンソールへの出力、アラートの表示など)を引き起こすだけで、特定の値を返す必要がない場合、その関数の戻り値の型として void を使用します。

イベントハンドラ以外でも、void型はよく使用されます。主に関数が値を返す必要がない場合、または関数が明示的に何も返さないことを示したい場合に使用します。具体的な使用例として以下のシーンが挙げられます。

副作用を伴う関数

この種の関数は何らかのアクションを実行しますが、値を返す必要はありません。例えば、データベースへのデータ保存や、ファイルへの書き込み、ログの出力などが該当します。

const saveToDatabase = (data: Data): void => {
  // データベースへ data を保存する処理
}

状態の変更

React の useState フックのセッター関数(setState)は典型的な例です。この関数は新しい状態を受け取り、状態を更新しますが、何も返しません。

const [count, setCount] = React.useState(0)
// setCount の型は (value: number) => void

コールバック関数

ある関数を別の関数に渡して、特定のタイミングで呼び出すための関数です。これらの関数は結果を直接返すのではなく、何らかの副作用(状態の変更、ログ出力など)を引き起こすことが多いです。

array.forEach((element) => {
  console.log(element) // 値を返す必要はありません
})

このような場面でvoid型は活躍します。

Next.js で、void 型を実装

それでは、Next.js と TypeScript を使って void 型を使った具体的なソースコードを見てみましょう。以下に示すコードは /pages/index.tsx として配置します。

import React from 'react'

type Props = {
  message: string
}

const IndexPage = ({ message }: Props): JSX.Element => {
  const showMessage = (msg: string): void => {
    alert(msg)
  }

  React.useEffect(() => {
    showMessage(message)
  }, [message])

  return <div>Welcome to Next.js!</div>
}

export default IndexPage

この IndexPage コンポーネントは、message プロパティを受け取り、React.useEffect フック内で showMessage 関数を呼び出します。showMessage 関数は受け取ったメッセージを alert ダイアログで表示しますが、何も返さないため、その戻り値の型は void になります。

void 型を活用

さて、ここでさらにテクニカルな部分に踏み込んでみましょう。一見すると「何も返さない void 型、それって本当に役に立つの?」と思うかもしれませんが、次の例を見てみてください。

import React from 'react'

type Props = {
  onClick: () => void
}

const ButtonComponent = ({ onClick }: Props): JSX.Element => {
  return <button onClick={onClick}>Click me!</button>
}

export default ButtonComponent

この ButtonComponent コンポーネントは、onClick 関数をプロパティとして受け取り、それをボタンのクリックイベントハンドラとして使用します。ここで、onClick の型が () => void になっていることに注目です。つまり、何も返さない関数を期待しているわけです。void 型はこのように、ユーザーがクリックしたときなど、特定のアクションに対する反応を表現するのにとても役立つんです。

onClick: () => void の部分を詳しく解説していきましょう。

まず、全体を見てみましょう。onClick: () => void は TypeScript の関数型の表現方法で、onClickという名前のプロパティが関数を期待していることを示します。

  • onClickはプロパティ名です。これは React の onClick イベントハンドラに対応しています。
  • () は、関数が受け取る引数を示します。ここは空っぽなので、この関数は引数を受け取らないことを意味します。
  • => は、関数の型を定義するための特殊な記号です。これによって「この関数は何も受け取らず、何も返さない」という意味になります。
  • void は関数が何も返さないことを示します。つまり、この関数は実行すると何かしらの処理を行いますが、結果を返しません。

具体的には、この型定義に合う実際の関数は以下のような形になります。

const handleClick = () => {
  console.log('Button was clicked!')
}

この関数は引数を受け取らず、何も返さないので、onClick: () => void という型定義にマッチします。つまり、この関数をonClickの値として使用できます。

それに対し、次のような関数は引数を受け取りますので、onClick: () => void という型定義にはマッチしません。

const handleClick = (event: React.MouseEvent) => {
  console.log('Button was clicked!', event)
}

同様に、次のような関数は値を返すため、onClick: () => void という型定義にはマッチしません。

const handleClick = () => {
  console.log('Button was clicked!')
  return true
}

つまり、onClick: () => void という型は、「何も引数を受け取らず、何も返さない関数」を期待しているということを表しています。

void 型を使わないシーン

クリックイベントや他の関数が何かしらの値を返す必要がある場合は、その値の型を戻り値の型として指定します。void 型は、「何も返さない」ことを示すために使用されます。

例えば、あるボタンがクリックされたときに、そのボタンの ID を返すとします。その場合、その関数の型は次のようになります。

type Props = {
  onClick: () => string // ボタンの ID を返す
}

この例では、onClick 関数は何も返さないという void 型ではなく、文字列(string)を返すと宣言しています。したがって、この関数は必ず文字列を返さなければならないという制約がつけられています。このように、関数が何らかの値を返す場合は、その値の型を戻り値の型として指定します。

Emotion で実装

Emotion を用いた例を見てみましょう。ここでは、スタイリングされたボタンコンポーネントにクリックイベントハンドラを割り当てる例を見てみましょう。

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import React from 'react'

type Props = {
  onClick: () => void
}

const ButtonComponent = ({ onClick }: Props): JSX.Element => {
  const buttonStyle = css`
    padding: 10px;
    background-color: #0070f3;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
  `

  return (
    <button css={buttonStyle} onClick={onClick}>
      Click me!
    </button>
  )
}

export default ButtonComponent

この ButtonComponent コンポーネントは Emotion を使ってスタイリングされ、onClick プロパティとして何も返さない関数(void 型の関数)を受け取ります。これにより、クリックしたときの動作を親コンポーネントから自由に定義できるようになります。

これらの例から、TypeScript の void 型がどのように役立つかを理解してもらえたでしょうか。void 型は、「何も返さない」という単純な概念でありながら、実際の開発では多くの場面で使用され、JavaScript の柔軟性を保ちつつ、TypeScript の厳密な型チェックの恩恵を受けることができます。

Next.js と TypeScript で、void 型を理解する