Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

モダン・フロントエンドに慣れるとWPFクソだるい

2014〜2015年頃はWPFのMVVMを面白がっていたのだが、2022年の今となってはだるい。もっと言うとオワコン。

宣言的な書き方ができないし、バインディングの補完が効かない(できるのかな?)

XAML側でバインディングを書く時に、脳内でこのパラメーターだよねって補完するのだるい。実行時にエラーが発生して気づくことになる。BindingしているSourceに対するコンパイルエラーが入ればいいんだけど、多分無理だよね。Bindingってdynamicのようだし。サクサク書けない。Commandインターフェイスですら、もうだるい。ロジックの書けないXML形式でビューを表現するフォーマットのせいでボイラーテンプレートワークフローからの凡ミスが出てしまう。かったるい。

Recoilのような、グローバルなデータストアを保持できる機構が原則ない。ViewとViewModelが1対1なので。シングルトンなクラスを1個作ってWindow.Resoureで共有すると出来るっぽいけども力技。

stackoverflow.com

また、環境の切り分けもIDEに詳しくないとできないっぽい。Visual Studioの技術的情報ってかなり少ないので、わからないことが多い...Debug/Releaseというビルドスキームを増やして対応するしか無いのかな?

テスティングについても情報が... FriendlyでE2Eを書けば良いんだけども、いわゆるCI/CDに乗せることが難しそう...と思ったらやれば出来るようだ。

zenn.dev

まとまった情報が本当に少ない。WPFを続けられる気力がないので、TypeScriptに切り替えるぞ。

ドットインパクトプリンタに印刷するためだけに10年近く前にWPFを選んだ(正確にはPrintDocumentで座標指定する印字方法しかわからなかった)が、Windowsのドライバを入れてユーザ定義で用紙サイズの設定をし、ブラウザから印刷する時にその用紙サイズでPDFを印字かければ、理論的には行けそうなんだけど。

とりとめのないメモ。

StatefulWidgetを一切使わずRiverpodだけで頑張りたいお気持ち

2年近くFlutterをやっていて、データストアにRiverpodを使っている場合 StatefulWidgetはまじで要らない子なんじゃないかと思い始めている。disposeする対象の管理が面倒でメモリリークする可能性があり、良いことがない。コードも色々増える。

StatefulWidgetを使いたい=initStateを使いたいだと思うけど、画面が表示される度にイベントを発火したい的なことであれば、操作するデータストアのあるProviderをrefreshするとか、autoDisposeで破棄するとか、listenでリスナーを作るなどを組み合わせれば、同等のことが出来る。

個人的にはautoDisposeを使うケースがほとんどなくて、子画面に遷移する時に常に最新化したい場合と、Streamを取り扱う時。StreamによってUIを構築する場合、そのUIがスクリーンから消えた時にStreamを自動的に閉じたいのと、ref.onDisposeでやりたいことがある時ぐらいか。

今までやった中でStatefulWidgetの扱いが難しく感じたのは、アプリ内課金(in_app_purchase)の所。公式サンプルコード、はっきり言ってグチャグチャ。これでちゃんと動くんかいって思う。

github.com

これをConsumerWidgetで一本化するのが、Riverpod中級者になるための壁だなと思っている。今はConsumerStatefulWidgetでベタベタにやっている。サブスクとアプリ内アイテムで画面が分かれているので、IAPのゴニョゴニョを解決するだけを作りたみ。

アプリ内課金はサーバーサイドのコードが複雑なので(レシート検証やサブスクのイベント通知に伴うハンドリングなど)予算が許すのあればRevenueCatのようなアプリ内課金管理サービスに抱かれる方が断然楽です。月間収入が$1000未満なら無料。それ以上は、$1000単位で$8です。1万ドルだと80$。148円換算で、月間148万の売上に対して利用料が11840円。0.8%ですか。かなり安い原価率のように感じました。

だいたいのことはFlutterで出来るようになった。やっていきだ。

Reactの学習を辞めてSvelteで開発しようかなというお気持ちが

lealog.hateblo.jp

こちらの記述がピンズドだった。

原初の時代からReactな案件をそれなりにこなしてきたけど、今でもReact-wayですべてを考えるのはやっぱり小難しいな〜って思うし、このEasyではなくSimpleに極振りしたAPIセットを使いこなすのまじムズいな〜って思う。

Reactって全然簡単じゃない。これほど習得するのに苦戦したフレームワークは初めてで、独学で進めることに限界を感じた。入門書も全く参考にならない。Simpleではあるけど、Easyではないから、ということだろう。

責務を小さく単純化するために関数型を駆使したり、コンポーネントに外からHooksで状態を与えて管理したりと、SoCを保ってシンプルにしていくAPIセットがたくさんあるけど、メンターがいないと使いこなせる気がしない。

その時のユースケースにフィットするようなAPIセットが有るけど、色んな書き方が世の中にあふれているので「React-Way」が見えてくるのにどれだけの時間を費やせばよいのだろうか。まわりにReactに長けている人がいない自分は不安に思う。

CSS in JSを使う意味もよくわからないし、Reduxも重たく感じる。Contextだけでやっていける気がするけど、そうでもないとしたらどこに地雷があるのかな...とか。

言葉を変えると「とりま、こういう書き方で戦える」という実感がなかなか出てこない。

FlutterはSimpleとEasyが絶妙なバランスで設計されていたので、すぐ戦えた。その香りを、Svelteにも強く感じている。まずはSvelteをやってみようかな。

ReactでUIコンポーネント作る粒度の温度差について

Checkboxのコンポーネントを作るになんでこれだけのコード量が必要になるのだろうか... Hooksもふんだんに使われている。

github.com

import React, { useCallback, useEffect, useRef, useState } from "react";
import styled from "styled-components";

import {
  CheckBoxIcon,
  CheckBoxOutlineBlankIcon,
} from "components/atoms/IconButton";

import Text from "components/atoms/Text";
import Flex from "components/layout/Flex";

export interface CheckBoxProps
  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "defaultValue"> {
  /**
   * 表示ラベル
   */
  label?: string;
}

const CheckBoxElement = styled.input`
  display: none;
`;

const Label = styled.label`
  cursor: pointer;
  margin-left: 6px;
  user-select: none;
`;

/**
 * チェックボックス
 */
const CheckBox = (props: CheckBoxProps) => {
  const { id, label, onChange, checked, ...rest } = props;
  const [isChecked, setIsChecked] = useState(checked);
  const ref = useRef<HTMLInputElement>(null);
  const onClick = useCallback(
    (e: React.MouseEvent) => {
      e.preventDefault();
      // チェックボックスを強制的にクリック
      ref.current?.click();
      setIsChecked((isChecked) => !isChecked);
    },
    [ref, setIsChecked]
  );

  useEffect(() => {
    // パラメータからの変更を受け付ける
    setIsChecked(checked ?? false);
  }, [checked]);

  return (
    <>
      <CheckBoxElement
        {...rest}
        ref={ref}
        type="checkbox"
        checked={isChecked}
        readOnly={!onChange}
        onChange={onChange}
      />
      <Flex alignItems="center">
        {/* チェックボックスのON/OFFの描画 */}
        {checked ?? isChecked ? (
          <CheckBoxIcon size={20} onClick={onClick} />
        ) : (
          <CheckBoxOutlineBlankIcon size={20} onClick={onClick} />
        )}
        {/* チェックボックスのラベル */}
        {label && label.length > 0 && (
          <Label htmlFor={id} onClick={onClick}>
            <Text>{label}</Text>
          </Label>
        )}
      </Flex>
    </>
  );
};

export default CheckBox;

個人的にはこれぐらいでよいのではと感じる。propでUIコンポーネントが受け取るパラメータをtypeで定義して代入する。場合によっては、useEffectやuseStateをonChangeに仕込むなりしたらいい。

weev.media

type Props = {
  //idを追加
  id: string;
  value: boolean;
  text: string;
  onChange: () => void;
};

export const Checkbox = ({ id, value, text, onChange }: Props) => {
  return (
    <div>
      <label htmlFor={id}>
        <div>
          <input
            type="checkbox"
            id={id}
            checked={value}
            onChange={() => {
              onChange();
            }}
          />
        </div>
        <div>
          {text}
        </div>
      </label>
    </div>
  );
};

CloudSQL にローカルから接続するメモ

GCP SDKのインストール(任意)

$ brew install --cask google-cloud-sdk
$ source /opt/homebrew/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.zsh.inc
$ source /opt/homebrew/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/completion.zsh.inc
$  source ~/.zshrc
$ gcloud --version
Google Cloud SDK 404.0.0
bq 2.0.78
core 2022.09.23
gcloud-crc32c 1.0.0
gsutil 5.14

Cloud SQLに接続可能なユーザーを作成

$ gcloud iam service-accounts create sample-user --display-name "sample-user" --project <PROJECT_ID>
$ gcloud projects add-iam-policy-binding <PROJECT_ID> --member serviceAccount:sample-user@<PROJECT_ID>.iam.gserviceaccount.com --role roles/cloudsql.client
$ gcloud iam service-accounts keys create key.json --iam-account sample-user@<PROJECT_ID>.iam.gserviceaccount.com

Cloud SQL Proxyを入れて、立ち上げる。SSHトンネルのようなものか。

# M1 はこれ
$ curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.arm64
$ chmod +x cloud_sql_proxy
$ ./cloud_sql_proxy -instances=<インスタンス接続名>=tcp:13306 -credential_file=key.json

あとはSQLクライアントソフトでつなぐだけ。ローカルホストで13306番に。お疲れさまでした。

Google 認証のしょーもないエラー

Unhandled Exception: PlatformException(network_error, com.google.android.gms.common.api.ApiException: 7:

このエラーが出て、なんだろうと思ったら、なんてことはない、検証機AndroidWifiが切れていて、インターネットにつながっていなかった...

ちなみに、ApiException:10の場合は、AndroidのフィンガープリントがUPされていないか、UPされたフィンガープリントの署名が違うか。

SQLのINとEXISTSの違い

ユーザーテーブルと、そのユーザーが好きなプログラミング言語というテーブルがあるとします。

ユーザーテーブル(UserTable)
id name
1 山田
2 村上
3 中村
好きなプログラミング言語テーブル(FavLangTable)
id user_id lang
1 1 Python
2 1 PHP
3 2 TypeScript
4 2 C#
5 2 Java
6 3 TypeScript

INとEXISTSの使い分け

結合先のレコードを全て欲しい場合は、EXISTS。そうでない場合は、INを使う。
TypeScriptが好きなユーザーを引いてくるSQLをINで書くとこうなる。

SELECT
	ut.name,
         f.lang
FROM
	user_table AS ut
	JOIN fav_lang_table AS f
	ON f.user_id = ut.id
WHERE
	f.lang IN("TypeScript")

結合先をINで絞っているので、そのレコードのみが返却されるため、こうなる。

name lang
村上 TypeScript
中村 TypeScript

TypeScriptが好きなユーザーを抽出するならこれでいい。だが、TypeScriptが好きなユーザーの「他の全てのプログラミング言語」を同時に取得したい場合、うまくいかない。結合先をINで絞り込んでしまうと、結合先のレコードしか取得しないので。

こーゆー時は、EXISTSの出番。EXISTSはそのレコードの存在有無だけを見てくれるので、JOINした結合先のレコードを丸々取ってくる。

SELECT
	ut.username,
         f.lang
FROM
	user_table AS ut
	JOIN fav_lang_table AS f
	ON f.user_id = ut.id
WHERE
	EXISTS (
		SELECT 
			* 
		FROM 
			fav_lang_table AS f1 
        WHERE 
        	f1.lang IN("TypeScript")
        AND
        	f1.user_id = ut.id
)

こうなる。

name lang
村上 TypeScript
村上 C#
村上 Java
中村 TypeScript

EXISTSはINに比べると可読性が下がる(ぱっと見て内容がわかりにくい)が、使いこなせると楽しいよ。