よく使いそうなtemplate-literal-typesを使った型定義
いつもTSのPlaygroundで作ってるなぁと思うやつをメモする
下に書いてあるやつすべてplaygroundで実行しているやつ
splitの型定義
// 文字列を受け取って受け取ってその文字列をDelimiterでsplitする型 type Split<T, U extends string> = T extends string ? T extends "" ? [] : T extends `${infer Head}${U}${infer Tail}` ? [Head, ...Split<Tail, U>] : [T] : [] type UseSplit1 = Split<"aaaaaa", ""> // 1文字づつ区切る type UseSplit2 = Split<"abababab", "b"> // 特定文字で区切る type UseSplit3 = Split<"a,b,c", ","> // csv的なやつ
nestしたobjectのkeyを列挙する型定義
// nestしたオブジェクトのkeyをすべて列挙する型 // json to csv的なやつに使えそうかも type NestObjectKeys<T, D extends string = "."> = { [K in keyof T]: K extends string ? T[K] extends Record<string, unknown> ? `${K}${D}${NestObjectKeys<T[K], D>}` : K : never }[keyof T] const obj1 = { a: 1, b: null, c: "c", d: { a: 1, b: undefined, c: "dc", d: { a: 2, b: null, c: "ddc" } } } as const type Keys1 = NestObjectKeys<typeof obj1> // objectのkeyを.でつなげる type Keys2 = NestObjectKeys<typeof obj1, "-"> // 特定の文字でつなげる
よくあるrouterライブラリのpath文字列を型に落とし込む型定義
// あるあるのrouterライブラリのpath文字列を型に落とし込む型 type AnalyzePath<T> = T extends string ? T extends `/${infer Head}/${infer Tail}` ? Head extends `:${infer U}` ? { [K in U]: string } & AnalyzePath<`/${Tail}`> : AnalyzePath<`/${Tail}`> : T extends `/:${infer U}` ? { [K in U]: string } : {} : {} type AnalizedPath1 = AnalyzePath<"/users/:user_id/"> type AnalizedPath2 = AnalyzePath<"/users/:user_id/tweets/:tweet_id">
Nextjsのpath文字列を型に落とし込む型
これ型に落とし込めてもnextjsはfsでroutingされているからあまり意味ないんだよなぁと言うのが最近の悩み。ずっと考えてるけどいい方法思いつかないなぁ.....next.config.js
にwithXXX
でコンパイラをいじったりしていい感じにするとかかなぁ....わかりません
// Nextjsのpath文字列を型に落とし込む型 type AnalizeNextJSPath<T> = T extends string ? T extends `${infer Head}[${infer U}]${infer Tail}` ? U extends `...${infer U1}` ? { [K in U1]: string[] } & AnalizeNextJSPath<Tail> : { [K in U]: string } & AnalizeNextJSPath<Tail> : {} : {} type AnalizedNextJSPath1 = AnalizeNextJSPath<"/users/[id]/"> type AnalizedNextJSPath2 = AnalizeNextJSPath<"/users/[id]/directory/[...directories]/">
snake caseをcamel caseにする型
// snake case to camel case type SnakeToCamel<T> = T extends string ? T extends `${infer Head}_${infer Tail}` ? `${Lowercase<Head>}${Capitalize<Tail>}` : never : never type UseSnakeToCamel = SnakeToCamel<"user_id" | "tweet_id">
camel caseをsnake caseにする型
// camel case to snake case type CamelToSnake<T> = T extends string ? T extends `${infer Head}${infer Tail}` ? Head extends Uppercase<Head> ? `_${Lowercase<Head>}${CamelToSnake<Tail>}` : `${Head}${CamelToSnake<Tail>}` : T : never type UsecCamelToSnake = CamelToSnake<"userId" | "tweetId">
まとめ
便利ですね。template-literal-types。一生遊べる
ここ1年で買ってよかったものシリーズ
買ってよかったものシリーズ2020
1. ルンバi7
最近買ったけどこれが最高すぎる。起動1、2回目までは30分くらい掃除してるんですが3回目くらいからは15分くらいで掃除してくれます。音は結構大きい。ルンバに掃除させながらAPEXをやると何も聞こえなくて普通に背後に寄られて死ぬのが容易になる。まぁ普通に掃除機なのでそれはそうだね。
地形を覚えてくれて仕切りを自分で設定して仕切りごとに掃除できる。これが便利〜〜〜いいね
2. hue
正社員になってからコロナの影響でフルリモートだったので気持ちの切り替えが大変だったんですが、これで割と僕は解決しました。始業時間と就業時間になったらライトの色を変えるように設定していたのでスパッとモード切替できてよかった〜。これもおすすめ。Amazon Echoとの相性もとてもいい。最高
3. ウォーターサーバー
ウォーターサーバー自体はどこでもいいじゃないかと思ってるけど僕はこれ使ってます。温水と冷水が常に出てくるのはめちゃくちゃいいですね〜。東京は想像以上に水道水が飲めたものじゃないのでこれがあると家がペットボトルまみれにならないのが最高です。まじでいい。ごみ捨てが楽になるので。
4. webカメラ
別にストリーマーってわけじゃないけど無駄にいいカメラを買った。カメラは最高すぎる。自分の顔を移したいわけじゃないが高画質のwebカメラはいろいろな遊び道具になるからね。いいもの!
5. マイク・オーディオインターフェース(これは前から持ってた)
フルリモートだったのでMTGすべてがオンラインだったので聞こえづらいとか相手の耳障りにならないようにコンデンサーマイクを買いました。オーディオインターフェースがあるとハードウェアミュートがあるので色々便利ですよ。ミュートマークが出ずにミュートできるので〜
さいごに
今年も色々買った。AirPods ProとかMacBookProもかったけどフルリモートという関係上あまり効力を発揮せずランクインならずって感じです。ほと〜〜〜〜んど家にいたからね。来年は椅子とディスプレイを買いたいです。あとキーボードかな〜キーボード。ゴミ箱からひろってきたHHKBを漂白して使ってるのでそろそろ新品をね......
退職しました
from: SOELU株式会社
to: ??? <-- そのうち開放されます
しばらくニート生活です。年明けまで
伏線
いい雰囲気のお店に行ってきました。とても良かったです。 pic.twitter.com/ucLN4PugcZ
— Naporitan🍆🍆 (@naporin24690) 2020年11月26日
欲しい物リストです www.amazon.jp
デカ枕を買ったら腰痛くなくなったんだがwwwwwwwwww
腰が痛ぇ!
起きたら腰痛いし、常に椅子に座って作業してるのでどう考えても運動不足で常に腰に負担をかけ続けている生活を送ってたし、健康診断に行ったら基本的には健康ですが背骨が曲がってますねぇ......って言われるくらい腰が終わってました。
あまりに痛いので整体にここ1ヶ月くらい通ってたのですが、そこでおすすめされた枕を購入してみるとまじで劇的な変化があったのでおすすめしておきます。全腰痛民族に捧ぐ.....
僕は枕が終わってたために腰を痛めていたようでした。前買った枕3年くらい使ってたものだったのでそれはそうかも知れない.....
これ高さ調整ができる(中のクッションが層になっている)のである程度自分用に調整できます。
2週間くらいつかってみた結果ですがまじで調子がいい。ありえないくらい調子がいい。無限に作業ができるし座ってられる。あと睡眠欲がめっちゃ増えた(睡眠による回復がとてつもないため)
さいごに
枕は1年、2年おきくらいに変えたほうがええ
実際に使っている様子を写真で取ろうと思ったけど掃除してないのでやめた。部屋が汚ねぇ
reselectのメモ化について
reselectのメモ化がどうなってるのか知りたい
ので調べました。コード自体は100行くらいで1ファイルだけだったのでとても読みやすかったです。
reselectはreduxのためのselectorライブラリです。どうやらメモ化してくれるらしいが.....?
reselectは受け取るオブジェクトを整形する関数と結果を計算する関数を受け取って、selectorを作る関数です。
以下の例だと、state => state.items
とstate => state.selectId
がオブジェクトを整形する関数、(items, id) => items.find(item => item.id === id)
が結果を計算する関数です。
stateが変更したときどこが再計算されて、どこがメモ化されるのかを知ることでreselectの力を最大限使おうというのが本記事の意図です。
import { createSelector } from "reselect"; const state = { items: [ { id: 1, name: "hoge" }, { id: 2, name: "huga" }, { id: 3, name: "nyan" } ], selectId: 1, }; type State = typeof state; type Item = State["items"] extends Array<infer U> ? U : never; const findUser = createSelector<State, State["items"], State["selectId"], Item | undefined>( state => state.items, state => state.selectId, (items, id) => items.find(item => item.id === id) )
どこがメモ化されるのか先に言うと、最後の関数(items, id) => items.find(item => item.id === id)
です。この子だけstateが変更されたときに計算がスキップされることがあります。それ以外の関数state => state.items
, state => state.selectId
に関しては毎回計算されます。では実際にどのようなコードで実現されているのかを見ていきます。
reselectのコードを見ていく
createSelector
まずはcreateSelector。いつもimportして使う関数から見ていきます。これは単にcreateSelectorCreator
にdefaultMemoize
を食わせているだけのようです。何もわかりませんね。
reselect/index.js at master · reduxjs/reselect · GitHub
export const createSelector = /* #__PURE__ */ createSelectorCreator(defaultMemoize)
defaultMemoize
どうやら関数と比較関数を受け取る関数みたいです。比較関数defaultEqualityCheck
は単にa === b
をするだけのコードでした。気になる方は見てみてもいいかもしれませんが、ほんとにこれだけです。
areArgumentsShallowlyEqual
に比較関数(equalityCheck)、前回入力された引数(lastArgs)、今回入力された引数(arguments)を食わせます。前回と今回入力された引数を比較してshallowEqualがfalseならfunc
を再計算してlastResult
に再代入するという感じでしょう。
arguments
に関してはjavascript組み込みなのでMDNを見るといいと思います。
reselect/index.js at master · reduxjs/reselect · GitHub
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) { let lastArgs = null let lastResult = null // we reference arguments instead of spreading them for performance reasons return function () { if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) { // apply arguments instead of spreading for performance. lastResult = func.apply(null, arguments) } lastArgs = arguments return lastResult } }
areArgumentsShallowlyEqual
equalityCheck
は比較関数、prev, nextは配列でやってくるようですね。配列の要素を総ナメして同じ値ならtrueそうでないならfalseを返すだけの関数です。
reselect/index.js at master · reduxjs/reselect · GitHub
function areArgumentsShallowlyEqual(equalityCheck, prev, next) { if (prev === null || next === null || prev.length !== next.length) { return false } // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible. const length = prev.length for (let i = 0; i < length; i++) { if (!equalityCheck(prev[i], next[i])) { return false } } return true }
ここまででわかったことをまとめます。
- defaultMemoizeは複数の引数を受け取る関数と比較関数(optional)を受け取る
- defaultMemoizeは複数の引数を受け取る関数を返す
こんな感じでしょうか。次に行きましょう。
createSelectorCreator
ここでのmemoize
はdefaultではdefaultMemoize
です。...memoizeOpptions
はユーザがmemoize
関数を定義したときoptionを渡したときに使うものです。今回はdefaultMemoize
が使われる前提なのでいらない子として扱います。
この関数では受け取ったデータ整形用の関数と結果生成用の関数からメモ化された関数を返します。
変数名で言うとresultFunc
が結果生成用の関数。dependencies
がデータ整形用の関数です。
memoizedResultFunc
は結果出力関数をdefualtMemoize
でラップしただけのものなのでselector
だけを見ていきましょう。
reselect/index.js at master · reduxjs/reselect · GitHub
export function createSelectorCreator(memoize, ...memoizeOptions) { return (...funcs) => { let recomputations = 0 const resultFunc = funcs.pop() const dependencies = getDependencies(funcs) const memoizedResultFunc = memoize( function () { recomputations++ // apply arguments instead of spreading for performance. return resultFunc.apply(null, arguments) }, ...memoizeOptions ) // If a selector is called with the exact same arguments we don't need to traverse our dependencies again. const selector = memoize(function () { const params = [] const length = dependencies.length for (let i = 0; i < length; i++) { // apply arguments instead of spreading and mutate a local list of params for performance. params.push(dependencies[i].apply(null, arguments)) } // apply arguments instead of spreading for performance. return memoizedResultFunc.apply(null, params) }) selector.resultFunc = resultFunc selector.dependencies = dependencies selector.recomputations = () => recomputations selector.resetRecomputations = () => recomputations = 0 return selector } }
selector
このselector
はcreateSelector()
の返り値そのものです。なのでこの子が実データ(一番はじめの例でいうとstate)を受け取ります。
与えられた実データをargumetns
経由で取り出します。それをデータ整形用の関数(dependencies
)に食わせます。このときdependencies
の関数たちに引数を適用する計算はselector
実行時に毎回呼ばれるのでデータ整形用の関数はコストの小さい関数を置いておくのがいいでしょう。
こうして得られた配列params
をmemoizedResultFunc
に与えます。params
の結果が前回と同じであれば計算をスキップし、前回の結果を返します。reselect
においてここだけが計算が省略される部分というわけでした。
reselect/index.js at master · reduxjs/reselect · GitHub
const selector = memoize(function () { const params = [] const length = dependencies.length for (let i = 0; i < length; i++) { // apply arguments instead of spreading and mutate a local list of params for performance. params.push(dependencies[i].apply(null, arguments)) } // apply arguments instead of spreading for performance. return memoizedResultFunc.apply(null, params) })
まとめ
- 計算がスキップされるのは
createSelector
で渡す引数のうち最後の関数だけ! - 最後の関数以外は毎回計算される
この2つだけわかれば十分すぎるくらいです。reselect
完全に理解した!ですね!
reselect
メモ化してくれることは知っていたのですが、どこメモ化されるのか全くわからずuseSelector
に食わせる関数を作ってくれる君程度にしか思ってなかったのでちゃんと理解できてよかったです。
あとarguments
。存在を知らなかったので突然定義してないやつが出てきた!?!?!?と凄い驚きました。
おわりです。ありがとうございました。