モダン・フロントエンドに慣れるとWPFクソだるい
2014〜2015年頃はWPFのMVVMを面白がっていたのだが、2022年の今となってはだるい。もっと言うとオワコン。
宣言的な書き方ができないし、バインディングの補完が効かない(できるのかな?)
XAML側でバインディングを書く時に、脳内でこのパラメーターだよねって補完するのだるい。実行時にエラーが発生して気づくことになる。BindingしているSourceに対するコンパイルエラーが入ればいいんだけど、多分無理だよね。Bindingってdynamicのようだし。サクサク書けない。Commandインターフェイスですら、もうだるい。ロジックの書けないXML形式でビューを表現するフォーマットのせいでボイラーテンプレートワークフローからの凡ミスが出てしまう。かったるい。
Recoilのような、グローバルなデータストアを保持できる機構が原則ない。ViewとViewModelが1対1なので。シングルトンなクラスを1個作ってWindow.Resoureで共有すると出来るっぽいけども力技。
また、環境の切り分けもIDEに詳しくないとできないっぽい。Visual Studioの技術的情報ってかなり少ないので、わからないことが多い...Debug/Releaseというビルドスキームを増やして対応するしか無いのかな?
テスティングについても情報が... FriendlyでE2Eを書けば良いんだけども、いわゆるCI/CDに乗せることが難しそう...と思ったらやれば出来るようだ。
まとまった情報が本当に少ない。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)の所。公式サンプルコード、はっきり言ってグチャグチャ。これでちゃんと動くんかいって思う。
これをConsumerWidget
で一本化するのが、Riverpod中級者になるための壁だなと思っている。今はConsumerStatefulWidget
でベタベタにやっている。サブスクとアプリ内アイテムで画面が分かれているので、IAPのゴニョゴニョを解決するだけを作りたみ。
- 各Storeからアイテムを取得するAsyncValue→ref.refreshを叩いて毎回ロード
- PurchaseStreamを監視するためのStateNotifierの用意
- 【Flutter Dart】Firebase Authenticationを使って匿名認証を実装!HooksとRiverpodを利用! に近い
アプリ内課金はサーバーサイドのコードが複雑なので(レシート検証やサブスクのイベント通知に伴うハンドリングなど)予算が許すのあればRevenueCatのようなアプリ内課金管理サービスに抱かれる方が断然楽です。月間収入が$1000未満なら無料。それ以上は、$1000単位で$8です。1万ドルだと80$。148円換算で、月間148万の売上に対して利用料が11840円。0.8%ですか。かなり安い原価率のように感じました。
だいたいのことはFlutterで出来るようになった。やっていきだ。
Reactの学習を辞めてSvelteで開発しようかなというお気持ちが
こちらの記述がピンズドだった。
原初の時代から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もふんだんに使われている。
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に仕込むなりしたらいい。
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番に。お疲れさまでした。
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に比べると可読性が下がる(ぱっと見て内容がわかりにくい)が、使いこなせると楽しいよ。