stin's Blog

Twitter シェアボタン React 作り方


Twitter のシェアボタンをブログに設置しました

記事ページの下部に Twitter のシェアボタンと Buy me a coffee のリンクを設置しました!

Twitter のシェアボタンを作るに当たって考えたことを書いていきます。

コード

実体はただの a 要素

Twitter のシェアボタンはクリックしてもらうと Twitter のサイト(ネイティブアプリをインストールしているスマホならアプリ)が開き、投稿画面にシェアするテキストやサイトの URL が入力された状態になります。

シェアボタンの実体はただの a 要素になっていて、 href に Twitter のシェア用 URL を指定しておくと、遷移先で Twitter がよしなにやってくれます。

「よしなに」の内容は、 href に付いているクエリパラメータを参照してツイートのテキストやハッシュタグ、URL などを投稿画面にセットしてくれることですね。

どの URL にリンクすべきかや、どんなクエリパラメータを受け付けてくれるのかは、次のドキュメントページに記載があります。

例えば https://twitter.com/intent/tweet?text=hoge を開くと、「hoge」 と入力された Twitter の投稿画面が開くと言った具合です。簡単ですね。

他には hashtags なんかは , 区切りで複数指定できるようです。 https://twitter.com/intent/tweet?hashtags=foo,bar,baz を開いてみると、 「#foo #bar #baz」と入力された投稿画面が開きます。

ということで、 https://twitter.com/intent/tweet?text=hoge のような URL を href に指定した a タグをおいておくだけでいいということがわかりました。

React コンポーネントに落とし込む

ここから React コンポーネントに落とし込むことを考えます。コンポーネント名は、リンクの URL を参考に TwitterIntentTweet としました。

Props 型定義

前節で説明したように、実体はただの a 要素なので、コンポーネントからは areturn されます。なので a が受け取れる props は同様にすべて受け取れるようにしておきたいですね。しかし、 href には決まった URL が渡されるため、 href は含まないようにしたいです。それと、これは好みですが(ドメイン違いのリンクは新規タブで開いてほしい) target="_blank", rel="noopener noreferrer" も強制したいので渡されないようにします。これを実現するのが、 React.ComponentPropsOmit の組み合わせです。 (ちょうど先日 Takepepe さんも記事を書いていましたね)

type TwitterIntentTweetProps = Omit<ComponentProps<"a">, "href" | "target" | "rel">;

ComponentProps<"a">a タグに渡すことができる props の型を取得します。そこから Omit を使って "href" | "target" | "rel" のプロパティだけを除外します。

また、URL のクエリパラメータに渡す値も props 経由で受け取りたいですね。上で決まった Props とクエリパラメータの分の Props をマージします。ドキュメントを参考に、名前と型を指定していきます。

type TwitterIntentTweetProps = {
  text?: string;
  url?: string;
  hashtags?: string[];
  via?: string;
  related?: string[];
  in_reply_to?: string;
} & Omit<ComponentProps<"a">, "href" | "target" | "rel">;

クエリパラメータに渡る予定の値たちはすべてオプショナルになっています。これは意図通りで、実際にクエリパラメータなしでもツイート投稿画面は開きます(もちろん何も初期値が入らないので意味はありません)。

これが TwitterIntentTweetProps の完成形になります。

コンポーネントの内部実装

Props の型定義が決まったので続いて内部実装に入ります。

まず前提として、 a 要素が返されることを意図していましたね。つまり href 等が渡せないこと以外は通常の a 要素と同様の振る舞いをしてほしいです。 ref オブジェクトに関しても同様です。

Function Component は何もしないと ref を渡せないようになっています。明示的に渡せるようにするためには forwardRef 関数でコンポーネントを定義します。

export const TwitterIntentTweet = forwardRef<HTMLAnchorElement, TwitterIntentTweetProps>(...);

これで実 DOM である HTMLAnchorElement の参照を掴むための ref を受け取れるようになります。

props は分割代入によってクエリパラメータに渡すものと a タグに直接渡すものを分割しておきます。

export const TwitterIntentTweet = forwardRef<HTMLAnchorElement, TwitterIntentTweetProps>(
  (
    { text, url, hashtags, via, related, in_reply_to, ...intrinsicProps },
    forwardedRef,
  ) => {},
);

受け取ったクエリパラメータ用の props からシェア用のリンクを組み立てます。ここで使えるのが URL クラスです。

const _url = new URL("https://twitter.com/intent/tweet");

if (text !== undefined) _url.searchParams.set("text", text);
if (url !== undefined) _url.searchParams.set("url", url);
if (hashtags !== undefined) _url.searchParams.set("hashtags", hashtags.join(","));
if (via !== undefined) _url.searchParams.set("via", via);
if (related !== undefined) _url.searchParams.set("related", related.join(","));
if (in_reply_to !== undefined) _url.searchParams.set("in_reply_to", in_reply_to);

各クエリパラメータはオプショナルなので、 undefined の可能性を排除した上で _url.searchParams に set していきます。 undefined を排除しないと ?text=undefined のような URL ができてしまいます。マジでクソ仕様なんだが

そして最後に a 要素を return します。

export const TwitterIntentTweet = forwardRef<HTMLAnchorElement, TwitterIntentTweetProps>(
  (
    { text, url, hashtags, via, related, in_reply_to, ...intrinsicProps },
    forwardedRef,
  ) => {
    const _url = new URL("https://twitter.com/intent/tweet");

    if (text !== undefined) _url.searchParams.set("text", text);
    if (url !== undefined) _url.searchParams.set("url", url);
    if (hashtags !== undefined) _url.searchParams.set("hashtags", hashtags.join(","));
    if (via !== undefined) _url.searchParams.set("via", via);
    if (related !== undefined) _url.searchParams.set("related", related.join(","));
    if (in_reply_to !== undefined) _url.searchParams.set("in_reply_to", in_reply_to);

    return (
      <a
        ref={forwardedRef}
        href={_url.toString()}
        target="_blank"
        rel="noopener noreferrer"
        {...intrinsicProps}
      />
    );
  },
);

URL のインスタンスで toString() を呼び出せば目的の URL が得られます。それを href に渡します。 forwardedReftarget, rel も忘れず指定しましょう。

分割代入で aprops 用に避けておいた intrinsicProps はスプレッド構文によって一括で渡すことができます。

以上で Twitter のシェアボタンを生成するための TwitterIntentTweet コンポーネント完成です!

TwitterIntentTweet を使う

TwitterIntentTweet が(レンダリング結果、渡せる props 含め)ただの a 要素にこだわってきたのには理由があります。それは移植性の高さです。

簡単に言えばコピペで他のプロジェクトで使い回せるということですね。責務がシェア用のリンクを生成することだけに絞られているので、脳死で使い回すことができます。

また、このブログサイトは ChakraUI によってスタイリングされています。 ChakraUI と TwitterIntentTweet を組み合わせるには下記のようなコードで書きます。

<Button
  as={TwitterIntentTweet}
  text="記事を読んだ!"
  url="https://blog.stin.ink"
  hashtags={["react", "nextjs"]}
  colorScheme="twitter"
>
  記事をシェアする!
</Button>

ChakraUI の as を使うと、 TwitterIntentTweet で生成した a 要素に ChakraUI の Button がスタイリングする、といったコンポーネントの融合のような挙動が実現できます。

ChakraUI を使っていなくても、 a 要素と全く同様に使えるため、 className を与える使い方もできるでしょう。

<TwitterIntentTweet className="share-button">記事をシェア</TwitterIntentTweet>

このブログサイトで実際に使っている箇所はこちらです ↓

まとめ

Twitter のシェアボタンを React で作る方法を紹介しました。

href を渡しただけの a 要素を生成するだけに限ることで、使い回しの効くコードになります。

FAQ

  • Buy me a coffee のリンク貼ってるけどあなたの記事に価値はありますか?

    すみませんでした。

  • コンポーネントにしなくても getTweetShareURL(params):string のような純粋関数でいいのでは?

    それな。(書き終えてから気づきました)