ざきの学習帳(旧 zackey推し )

日々の学びを書きます

【JavaScript】URLSearchParams でクエリパラメータを操作する

フロントで、クエリパラメータをいい感じにkey-valueのObjectに変換したい...っていうことをやりたくて、探したところ、Web標準APIとして提供されていました。

developer.mozilla.org

正規表現やVueRouterのようなライブラリを用いる手段のみと思っていたので、忘れないよう書き留めときます。

使い方

URLパラメータの構築

typescript-sandbox/fb-login-sample at master · zakizaki-ri9/typescript-sandbox · GitHub

上記リポジトリで、FacebookログインおよびSDKの素振りをおこなっています。

長期ユーザートークンを発行するため、以下のようにURLSearchParamsを利用し、クエリパラメータを構築しています。

// https://github.com/zakizaki-ri9/typescript-sandbox/blob/03b4e49fb64dfbe116620827a666946922780a72/fb-login-sample/src/components/FacebookLoginForm.vue#L74-L92
// から抜粋
    // https://developers.facebook.com/docs/instagram-api/getting-started Step3
    const onGetLongAccessToken = async () => {
      const params = new URLSearchParams({
        grant_type: "fb_exchange_token",
        client_id: appId.value,
        client_secret: appSecrets.value,
        fb_exchange_token: getAccessToken() // Long Access Tokenでもいける
      });
      const response = await fetch(
        "https://graph.facebook.com/oauth/access_token?" + params
      );
      // 〜省略〜
    };

axiosを使わず、Web標準fetchを用いる場面だと、有効活用できそうです。

github.com

developer.mozilla.org

クエリパラメータをkey-valueなObjectに変換

クエリパラメータを抜き出したいURLをURLインスタンス化 -> searchParamsURLSearchParamsを取得 -> key-valueなObjectを取得できます。

以下、コード例です。

TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript

// "クエリパラメータ"でGoogle検索したときのURL
const url = "https://www.google.com/search?q=%E3%82%AF%E3%82%A8%E3%83%AA%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF&rlz=1C5CHFA_enJP848JP848&oq=%E3%82%AF%E3%82%A8%E3%83%AA%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF&aqs=chrome..69i57j0l7.2154j0j7&sourceid=chrome&ie=UTF-8";

console.log({
  params: new Array(...new URL(url).searchParams.entries())
});
/*
output↓
{
  "params": [
    [
      "q",
      "クエリパラメータ"
    ],
    [
      "rlz",
      "1C5CHFA_enJP848JP848"
    ],
    [
      "oq",
      "クエリパラメータ"
    ],
    [
      "aqs",
      "chrome..69i57j0l7.2154j0j7"
    ],
    [
      "sourceid",
      "chrome"
    ],
    [
      "ie",
      "UTF-8"
    ]
  ]
} 
*/

おわり

以上です。

Web標準でこういうのあると、ライブラリやフレームワークに依存しなくて良いすね。(もっと早く知りたかった...)

じぶん Release Notes ver 0.31.0

f:id:kic-yuuki:20201101230920p:plain

先月で31歳となりました。

先月分はこちら -> じぶん Release Notes ver 0.30.11 - ざきの学習帳(旧 zackey推し )

  • 学んだこと
      • 読書中
      • 完了
        • やめてみたシリーズから、まずやめてみる
  • ブログ
    • PV数
  • Challenge Every Month
    • 2020/10の結果
      • 輪読会についていく
    • 2020/11の目標
      • 輪読会についていく
      • チャリの冬支度を済ませる
      • viteseeを参考にVue3 x viteの素振りをする
  • おわりと所感
続きを読む

【Vue.js】 vue-facebook-login-component を参考に Facebookログイン を実装してみた

とある要件でInstagram Graph APIを使う事になったのですが、
使用するにはFacebookログインを行なっている動画撮影と
アプリ申請が必要となることがわかりました。

Facebookログインするやり方を素振りしましたので、
参考にしたものや調査・実装したなかでの注目点を
本記事にまとめておきたいと思います。

参考にしたFacebookログイン用のVueコンポーネント

vue-facebook-login-component という
Facebookログイン用のVueコンポーネントが公開されています。

github.com

vue-facebook-login-component/packages/vue-facebook-login-component/src/Sdk.js
Facebook JavaScript SDKをラッパーした実装が記述されていました。

Vue3 x TypeScriptでFacebookログインまで実装してみた

vue-facebook-login-componentの実装を参考に、
Facebookログインまでを実装し直してみたのが以下になります。

window.FBの定義

Facebook JavaScript SDKscriptタグ等でダウンロード・実行すると、
window.FBに様々なメソッド等が定義されます。

これらをコード補完させたい(かつ型定義の練習のため)、
公式ドキュメントの仕様を元に一部API・レスポンス等を
以下tsファイルへ定義しました。

/* ~省略~ */
interface Facebook {
  // https://developers.facebook.com/docs/javascript/reference/FB.init/v8.0
  init(param: FacebookInitParameter): void;

  // https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus
  getLoginStatus(callback: (response: FacebookCallbackResponse) => void): void;

  // https://developers.facebook.com/docs/reference/javascript/FB.login/v8.0
  login(callback: (response: FacebookCallbackResponse) => void): void;

  // https://developers.facebook.com/docs/reference/javascript/FB.logout/v8.0
  logout(callback: (response: FacebookCallbackResponse) => void): void;
}

interface Window {
  FB: Facebook;
}

余談

@types/facebook-js-sdkがあるので、
自分で型定義したくない方はこちらをどうぞ。

www.npmjs.com

Facebook JavaScript SDKのダウンロード

以下tsファイルのloadメソッドに実装しました。

// ~省略~
  private load(): void {
    if (document.getElementById(this.FACEBOOK_SCRIPT_ID)) {
      // facebookのsdkが読み込まれていたら終了
      return;
    }

    // sdkを読み込むためのscriptタグを生成
    const scriptElement = document.createElement("script");
    scriptElement.id = this.FACEBOOK_SCRIPT_ID;
    scriptElement.src = this.srcUrl;
    scriptElement.onerror = error => {
      console.error(`${this.srcUrl} load Fail.`);
      new Error(error.toString());
    };

    // headに仕込む&読み込みさせる
    document.querySelector("head")?.appendChild(scriptElement);
  }
// ~省略~

コメント文通りですが、
headタグにscriptタグを追加することで
SDKをダウンロードさせています。

おわり・所感

あとは

  1. 画面上に入力されたappIdFB.init -> 初期化
  2. FB.loginでログイン

するだけです。
yarn install && yarn serveすれば実行できます。

同じようなことをやりたい方の参考になれば幸いです。

そのた参考リソース

レガシーコードからの脱却 5章のメモ - プロダクトオーナーって誰がなるの?

5章が印象的だったので、
メモとして思ったことを書き残しておきたいと思います。

5章は、タイトルに記載してある「9つのプラクティス」のうち、
1つめ「やり方より先に目的、理由、誰のためか伝える」が紹介されています。

実現方法を伝えるべきではない、目的を話すべき

受託案件ではクライアント、プロダクト開発ではユーザーから「こうしたい、こうしてほしい」という要件をもとに開発すると思います。

要件を出す側は「こうしたい、こうしてほしい」のような具体的の方法ではなく、その目的を話すべき。具体的な方法を伝えると、それを正として開発しがちとなり、目的に沿ったものが出来上がらないことが多い。また、方法をそのまま実装すると、システムに最適な設計プロセスが損なわれ、レガシーになるのを止められない。

と、本書から解釈しました。

実際、ユーザー側に「こうしたい、こうしてほしいを伝えないで、何をしたいか目的を伝えて」と言える機会は少ないと思っています。(言える機会があるのは、社内システム開発のようなパターンと考えています)

本書で記載されている通り、目的を知らないまま開発すると、
目的や効果がでないものが出来上がる可能性を拭えません。

そのため、以下のような手法等を使い、
目的を割り出す必要があると感じています。

medium.com

www.catapultsuplex.com

本当は、目的を直で聞けるのが一番だと思いますが、
話す方も「こうあるべき」という先入観にとらわれている可能性があるため、
整理するためにこういった方法や壁打ちして整理 -> 目的を明確にした方が良い。

と、思っています。

受け入れテストに明確な基準を設定する

目的をもとにユーザーストーリーを作り、POが受け入れ基準(何をするのか、いつ動くのか)を定義する。そうすると、開発者はストーリーと受け入れ基準を満たすため、最適な設計・開発に集中できる。

と、解釈しました。
内容は納得しました。

記載されている通り、POはいた方が良いというのは当然なのですが、
どの部門・立ち位置の人がなるべきなのか...というのが疑問です。

www.ryuzee.com

www.atmarkit.co.jp

もし、本書の通り代替パス・エッジケース等を含めた受け入れ基準を定義するには、
プログラミング的な観点がある開発側の人が最適かなと考えています。
(ただ、ユーザーの業務知識・システムを十分に知り尽くしているということが前提)

ビジネス側の人がPOになる際は、
開発側から受け入れ基準を明確にするためのヒヤリングを行い、
共通認識を得るプロセスが必要と感じました。

終わり

以上です。

本書の内容をそのまま適用するのは難しいですが、
目的と受け入れ基準を元に開発するということが重要であると認識しました。

普段、「目的は?」「受け入れ基準は?」を言語化せず、
自然とこなしているケースが多いため、再認識して開発に望もうと思います。

【Python】unittest.TestCase.subTest ブロックで、データをループするテストケースのエラーをすべて出力する

Effective PythonsubTestの存在を知ったため、
メモとして書き残しておこうと思います。

Effective Python 第2版 ―Pythonプログラムを改良する90項目

Effective Python 第2版 ―Pythonプログラムを改良する90項目

  • 作者:Brett Slatkin
  • 発売日: 2020/07/16
  • メディア: 単行本(ソフトカバー)

unittest.TestCase.subTest とは

Pythonに組み込まれているunittestにおいて、
Python3.4からsubTestという関数が実装されました。

subTest内で実行した検証(assert_hogehoge)は、失敗しても終了しないため、
1つのテストケース内で複数件のデータをまとめて検証するのに有効...と思いました。

サンプルコード

python-remote-container/src/effective/09/76_test.pyのコードから抜粋します。

class Logic:
    @classmethod
    def to_str(cls, data):
        if isinstance(data, str):
            return data
        elif isinstance(data, bytes):
            return data.decode("utf-8")
        else:
            raise TypeError(f"Must supply str or bytes, found: {data!r}")


from unittest import TestCase, main


class DataDrivenTestCase(TestCase):
    def test_good(self):
        good_cases = [
            (b"my bytes", "my bytes"),
            ("no error", b"no error"),  # 失敗ケース
            ("aaa", "bbb"),  # 失敗ケース
            ("other str", "other str"),
        ]
        for value, expected in good_cases:
            with self.subTest(value):
                self.assertEqual(expected, Logic.to_str(value))


if __name__ == "__main__":
    main()
# 実行結果
pwd
/code

python src/effective/09/76_test.py DataDrivenTestCase
======================================================================
FAIL: test_good (__main__.DataDrivenTestCase) [no error]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/code/src/effective/09/76_test.py", line 55, in test_good
    self.assertEqual(expected, Logic.to_str(value))
AssertionError: b'no error' != 'no error'

======================================================================
FAIL: test_good (__main__.DataDrivenTestCase) [aaa]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/code/src/effective/09/76_test.py", line 55, in test_good
    self.assertEqual(expected, Logic.to_str(value))
AssertionError: 'bbb' != 'aaa'
- bbb
+ aaa


----------------------------------------------------------------------
Ran 1 test in 0.003s

FAILED (failures=2)

通常では、
AssertionError: b'no error' != 'no error'でテストが中断され、
すべてのテストケースを確認することができません。

subTestを使うことで、
("no error", b"no error")以降(("aaa", "bbb"))のテストケースも実行可能です。

おわり

以上です。

Python3.4から使用可能なので、注意してください。