Next.js と TypeScript で、アロー関数を学びなおす

TwitterFacebookHatena

TL;DR

このページでは、アロー関数の実装方法について解説しますね。一言でいうとアロー関数とは、JavaScript(とその派生言語である TypeScript)における新しい関数の定義方法の一つで、従来の関数よりもコードがスッキリし、this の扱いが容易になる特性があります。

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

アロー関数とは?

アロー関数は、JavaScript ES6 から導入された新しい関数の定義方法で、特にコードがシンプルに書ける点と、thisの振る舞いが従来の関数とは異なる点が特徴として挙げられます。this については後述します。

基本構文

従来の関数定義は以下のように記述されます。

function square(n) {
  return n * n
}

一方、アロー関数では次のように表記できます。

const square = (n) => {
  return n * n
}

これは更に省略して以下のように書くこともできます。

const square = (n) => n * n

それぞれの場合について、簡単なアロー関数の例を以下に示します。

引数がない場合

アロー関数に引数がない場合、引数リストを空の丸括弧 () で表現します。以下は、"Hello, World!"をコンソールに出力するアロー関数の例です。

const greet = () => {
  console.log('Hello, World!')
}

greet() // "Hello, World!" を出力

引数が一つの場合

アロー関数がただ一つの引数を取る場合、その引数を囲む丸括弧を省略することが可能です。以下は、与えられた数値を 2 倍にして返すアロー関数の例です。

const double = (num) => {
  return num * 2
}

console.log(double(3)) // 6 を出力

本体が一文である場合

アロー関数の本体が一つの文だけで構成されている場合、その文を波括弧 {} で囲む必要はありません。さらに、その一文が表現式である場合、その結果は自動的にリターンされます。以下は、与えられた数値を 2 倍にして返すアロー関数の例ですが、この場合は本体が一文であるため波括弧と return を省略しています。

const double = (num) => num * 2

console.log(double(3)) // 6 を出力

これらの例からわかるように、アロー関数はコードを簡潔に記述するための有用なツールです。ただし、その振る舞いは従来の関数とは異なるため、使用の際には注意が必要です。

アロー関数の様々な使い方

アロー関数の様々な使い方をご紹介します。

デフォルト値を持つパラメータ

JavaScript の関数では、パラメータにデフォルト値を設定することができます。アロー関数でも同じように設定することが可能です。次の例では、greet 関数の引数 name にデフォルト値として 'World' を設定しています。

const greet = (name = 'World') => `Hello, ${name}!`

console.log(greet()) // Hello, World!
console.log(greet('User')) // Hello, User!

可変長引数

JavaScript では、関数のパラメータに ... を前置することで、任意の数の引数を配列として受け取ることができます。これを「可変長引数」と呼びます。アロー関数でも同様に可変長引数を使用することができます。

const sum = (...numbers: number[]) => numbers.reduce((acc, num) => acc + num, 0)

console.log(sum(1, 2, 3, 4, 5)) // 15

オブジェクトリテラルを返す

関数の戻り値としてオブジェクトリテラルを返す際、括弧()で囲むことで正しくオブジェクトとして解釈させることができます。

const createPerson = (name: string, age: number) => ({ name, age })

console.log(createPerson('John', 30)) // { name: 'John', age: 30 }

高階関数(関数を返す関数)

関数の戻り値として別の関数を返すことも可能です。これは高階関数と呼ばれるパターンで、関数型プログラミングの一部としてよく利用されます。

const addPrefix = (prefix: string) => (name: string) => `${prefix} ${name}`

const greetMr = addPrefix('Mr.')

console.log(greetMr('John')) // Mr. John

即時実行関数

JavaScript では関数を定義と同時に実行することができます。これを「即時実行関数」と呼びます。アロー関数も即時実行することが可能です。

const result = ((num1: number, num2: number) => num1 * num2)(3, 4)

console.log(result) // 12

これらの特性も合わせて理解することで、アロー関数をより深く活用することができます。

this の振る舞い

このように、アロー関数はコードの見通しが良くなるだけでなく、thisの振る舞いが改善されるという特性も持っています。

従来の関数ではthisは実行コンテクストに依存しますが、アロー関数では定義した時点のスコープのthisを引き継ぎます。これにより、thisの振る舞いに頭を悩ますことが少なくなりました。

thisとは、JavaScript(および TypeScript)で使用されるキーワードで、オブジェクト自身を参照します。thisの値は、それが使用されるコンテクストにより異なります。例えば、以下のコードでは、thisはオブジェクトmyObjectを指します。

const myObject = {
  property: 'Hello',
  method: function () {
    console.log(this.property)
  },
}

myObject.method() // "Hello"を出力

この場合、thismyObjectを参照し、そのプロパティpropertyの値を出力します。しかし、関数やメソッドの中でthisを使用すると、その振る舞いは関数がどのように呼び出されるかによって変わります。これは、特にコールバック関数やイベントハンドラなどで問題となることがあります。

この問題を解決するのがアロー関数です。アロー関数は、自身のthisがないため、thisは周囲のスコープから引き継がれます。これは、それ自体が定義されたスコープのthisを「記憶」するという特性を持ちます。この特性により、コールバック関数やイベントハンドラなどで問題となるthisの振る舞いを簡単に制御することが可能になります。

TypeScript でのアロー関数

以下に TypeScript を用いたアロー関数の基本的な例をいくつかお見せします。

Interface を用いた例

TypeScript では、関数の引数や戻り値の型を定義するために Interface を用いることができます。以下の例では、Personという Interface を定義し、Person型の引数を取るアロー関数を作成しています。

interface Person {
  name: string
  age: number
}

const greet: (person: Person) => void = (person) => {
  console.log(`Hello, my name is ${person.name} and I'm ${person.age} years old.`)
}

greet({ name: 'John', age: 25 }) // "Hello, my name is John and I'm 25 years old."

この例では、greet関数の引数personPerson型であることを確実にするために TypeScript の Interface を使用しています。

ジェネリクスを用いた例

TypeScript のジェネリクスは、型の再利用や抽象化を可能にします。以下の例では、ジェネリクスを用いてアロー関数を定義しています。

const identity: <T>(arg: T) => T = (arg) => arg

let output = identity<string>('myString') // type of output will be 'string'

この例では、identity関数は任意の型Tを引数として受け取り、同じ型Tを返す関数として定義されています。このようにジェネリクスを用いることで、より汎用的なコードを書くことが可能になります。

アロー関数のメリット

アロー関数の他の特徴やメリットには以下のようなものがあります。

  1. 短縮構文: アロー関数は関数を宣言するための短縮構文を提供します。これにより、コードの冗長さが減り、可読性が向上します。

  2. 明示的なリターン: アロー関数の本体が波括弧({})で囲まれていない場合、その結果は自動的にリターンされます。これは、シンプルな関数やラムダ式を記述する際に便利です。

  3. 引数の省略: アロー関数がただ一つの引数を取る場合、その引数を囲む丸括弧を省略することが可能です。これにより、さらにコードのシンプルさが増します。

ただし、アロー関数はargumentsオブジェクトや、新たなインスタンスを生成するためのnewキーワードとの互換性がありません。そのため、すべての場面で従来の関数を置き換えるものではないことを理解しておくことが重要です。

アロー関数を使うべきではないケース

アロー関数はその特性から、多くの場面で活用されますが、次のようなケースでは従来の関数を使うべきです。

メソッドの定義

オブジェクトのメソッドとして関数を定義する際には、アロー関数を使うとthisが期待するオブジェクトを指さないため、従来の関数を使います。

const person = {
  name: 'John',
  greet: function () {
    return `Hello, my name is ${this.name}`
  },
}

console.log(person.greet()) // "Hello, my name is John"

この例では、greetメソッド内のthispersonオブジェクトを指します。もしアロー関数を使用した場合、thispersonオブジェクトではなく、外部のスコープを指してしまうため、意図した動作になりません。

イベントハンドラー

ブラウザのイベントハンドラーでは、アロー関数を使うとthisが期待する要素を指さないため、従来の関数を使います。

document.querySelector('button').addEventListener('click', function () {
  this.classList.toggle('active')
})

この例では、addEventListenerのコールバック関数でthisを使っていて、thisはクリックされたボタン要素を指します。アロー関数を使用すると、thisはグローバルオブジェクトまたはundefinedを指すため、クリックされた要素にアクセスできません。

コンストラクター関数

newキーワードと一緒に呼び出されるコンストラクター関数では、アロー関数を使うことはできません。アロー関数は自身のthisを持たないため、新しいインスタンスを作成することができません。

function Person(name: string) {
  this.name = name
}

const john = new Person('John')

この例では、Person関数をnewキーワードと共に呼び出して新しいオブジェクトを作成します。このとき、Person関数内のthisは新しく作成されたオブジェクトを指します。アロー関数を使うと、thisが新しいオブジェクトを指さないため、このようなパターンは不適切です。

アロー関数は便利であり、コードの簡潔性や可読性を向上させますが、上記のようなケースでは従来の関数が適切です。関数の選択は、それぞれの特性と利用するコンテキストによります。

Next.js で、アロー関数を実装

それでは Next.js と TypeScript のコードにおけるアロー関数の使用例を見てみましょう。この例では、Next.js のページコンポーネントの中でアロー関数を使っています。

/pages/index.tsx の内容を示します。

import { NextPage } from 'next'
import React from 'react'

type Props = {
  message: string
}

const IndexPage: NextPage<Props> = ({ message }) => {
  const showMessage = () => {
    alert(message)
  }

  return (
    <div>
      <button onClick={showMessage}>メッセージを表示</button>
    </div>
  )
}

IndexPage.getInitialProps = async () => {
  const message = 'こんにちは、Next.js!'
  return { message }
}

export default IndexPage

このコードでは、アロー関数を 2 つ使用しています。

1 つ目はconst IndexPage: NextPage<Props> = ({ message }) => {...}の部分で、これはIndexPageという名前の React コンポーネントをアロー関数を使って定義しています。このコンポーネントは、messageという名前の props を受け取ります。

2 つ目は、const showMessage = () => {...}の部分です。この内部でアロー関数を定義し、その中でalertを使ってメッセージを表示する処理を行っています。ここでもアロー関数を使っているため、シンプルで直感的な記述を実現しています。

高等技術としてのアロー関数の実装

次に、アロー関数を使ったより高度な実装例をご紹介します。

type ComplexProps = {
  numbers: number[]
  handleSum: (sum: number) => void
}

const ComplexComponent: React.FC<ComplexProps> = ({ numbers, handleSum }) => {
  React.useEffect(() => {
    const sum = numbers.reduce((a, b) => a + b, 0)
    handleSum(sum)
  }, [numbers, handleSum])

  return <div>...</div>
}

この例では、アロー関数を 2 つ使用しています。1 つ目はconst ComplexComponent: React.FC<ComplexProps> = ({ numbers, handleSum }) => {...}という部分で、ComplexComponentという React コンポーネントをアロー関数を使って定義しています。

2 つ目はReact.useEffectの中のconst sum = numbers.reduce((a, b) => a + b, 0);という部分です。配列のreduceメソッドを使い、各要素を加算しています。ここでもアロー関数が使われており、コールバック関数を簡潔に記述できています。

このように、アロー関数はコードをシンプルにし、可読性と保守性を高めるために重要なツールと言えます。

Next.js と TypeScript で、アロー関数を学びなおす