Next.js で型安全に Facebook SDK を利用する

本記事は、 [Uzabase Advent Calendar 2021](https://qiita.com/advent-calendar/2021/uzabase) 16日目の記事です。

Next.js + TypeScript 環境で、Facebook SDK for JavaScript を使用して、シェアダイアログ（ref. [シェアダイアログ - Meta for Developers](https://developers.facebook.com/docs/sharing/reference/share-dialog/)）を表示できるように実装したので紹介します。

# 実装にあたって直面した課題

Facebook SDK の npm ライブラリは提供されておらず、 js ファイルを読み込むしかありませんでした。それに伴い、以下の2点をクリアする必要がありました。
    
- TypeScript 環境のプロジェクトなので、 any 型でお茶を濁さず、型安全に SDK を扱う
- `<script>` タグを埋め込んで、ロードされたタイミングで初期化する処理（`FB.init()`）を実行するようにハンドリングする

※ [facebook-js-sdk](https://www.npmjs.com/package/facebook-js-sdk) という npm ライブラリが存在するのですが、公式のライブラリはないので、間違って import してしまわないように注意してください。

# 今回やったこと

## 型定義の追加

`DefinitelyTyped` に Facebook SDK for JavaScript 用の型定義が追加されていた（ref. [DefinitelyTyped/types/facebook-js-sdk](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/facebook-js-sdk/index.d.ts)）ので、こちらを使用することで型安全な実装が可能になりました。

※ こちらの型定義ファイルは、前述した [facebook-js-sdk](https://www.npmjs.com/package/facebook-js-sdk) という npm ライブラリの型定義ファイルではないことは確認できました。紛らわしい。。

導入は、自プロジェクトに `@types/facebook-js-sdk` をインストールして、`tsconfig.json` の `compilerOptions.types` にインストールした型情報を読み込む設定を追加して完了です。

```shell
$ npm install --save-dev @types/facebook-js-sdk
```

```tsconfig.json
{
  "compilerOptions": {
     (...中略),
     "types": ["facebook-js-sdk"]
  }
}
```

## next/script で SDK ファイルを読み込む

### 前提

Facebook SDK の導入の際のドキュメント（ref. [Quickstart: Facebook SDK for JavaScript](https://developers.facebook.com/docs/javascript/quickstart)）には、 `<body>` タグの直後に `<script>` タグを挿入し、非同期ロードでスクリプトを読み込み、読み込みが完了したら `FB.init()` を実行するようにと書かれています。

```html
<script>
  window.fbAsyncInit = function() {
    FB.init({
      appId            : 'your-app-id',
      autoLogAppEvents : true,
      xfbml            : true,
      version          : 'v13.0'
    });
  };
</script>
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/ja_JP/sdk.js"></script>
    
```

上記の動作を `next/script` を使用して実装します。

### next/script とは（ざっくり）

- Next.js 11 で追加された新機能の一つ
- `strategy` props でスクリプトのロード戦略をハンドリングできる
- `onLoad` メソッドを使用して、スクリプトの読み込みが完了した後に実行する必要のある処理を記述できる

### next/script を使用した実装

以下のようなコンポーネントを作成し、前提部分で記載したコードを再現します。

```FacebookSdk.tsx
import Script from 'next/script';
import React from 'react';

export const FacebookSdk: React.FC = () => {
  const handleLoadedSdk = useCallback(() => {
    window.fbAsyncInit = () => {
      FB.init({
        appId: 'your-app-id',
        autoLogAppEvents: true,
        xfbml: true,
        version: 'v13.0'
      })
    }
  }, []);

  return (
    <Script
      strategy="afterInteractive"
      crossOrigin="anonymous"
      src="https://connect.facebook.net/ja_JP/sdk.js"
      onLoad={handleLoadFacebookSdk}
    />
  );
}
```

このコンポーネントを `_app.tsx` で呼び出します。

```_app.tsx
import type { AppProps } from 'next/app';
import { NextPage } from 'next';
import React from 'react';

const App: NextPage<AppProps> = ({ Component, pageProps }) => {
  return (
    <FacebookSdk />
    <Component {...pageProps} />
  );
};

export default App;
```

以上で Facebook SDK を使用する準備が完了したので、あとはシェアダイアログを表示する実装を追加します。

```シェアダイアログを呼びたいコンポーネント.tsx
export const Hoge: React.FC = () => {
  const handleClick = () => {
    // @see https://developers.facebook.com/docs/sharing/reference/share-dialog/
    FB.ui({
      method: 'share',
      href: 'https://developers.facebook.com/docs/',
    })
  };
  
  return (
    <button onClick={handleClick}>シェアする</button>
  )
};
```

# 最後に

Next.js + TypeScript 環境で、Facebook SDK for JavaScript を使用する方法を紹介しました。今回の実装を通して、`next/script` のありがたみを深く実感することができました。これまで `<script>` タグでしか利用ができないものなどに出会い、読み込みの完了が保証された状態でその後の処理を書くことが少なからずあったので、 `onLoad` メソッドを見たときは感激しました。

今回の技術構成での導入記事が見当たらなかったので、本記事が同じ状況で困っている方の一助になれたら幸いです。