Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Conditional Types I/O
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Takepepe
August 23, 2018
Technology
1
1.1k
Conditional Types I/O
Roppongi.js #5 発表資料。redux-aggregate の 型合成テクニック
https://roppongi-js.connpass.com/event/95936/
Takepepe
August 23, 2018
Tweet
Share
More Decks by Takepepe
See All by Takepepe
どの様にAIエージェントと 協業すべきだったのか?
takefumiyoshii
2
1k
ServerAction で Progressive Enhancement はどこまで頑張れるか? / progressive-enhancement-with-server-action
takefumiyoshii
7
1.3k
App Router への移行は「改善」となり得るのか?/ Can migration to App Router be an improvement
takefumiyoshii
8
3.7k
フロントエンドの書くべきだったテスト、書かなくてよかったテスト
takefumiyoshii
40
17k
Webフロントエンドのための実践「テスト」手法 CodeZine Night #1
takefumiyoshii
24
10k
Next.js でリアーキテクトした話 / story-of-re-architect-with-nextjs
takefumiyoshii
12
9.1k
より速い WEB を目指す Next.js / nextjs-make-the-web-faster
takefumiyoshii
54
21k
フロントエンドの複雑さに耐えるため実践したこと / readyfor-nextjs-first
takefumiyoshii
25
11k
Redux の利点を振り返る
takefumiyoshii
26
9k
Other Decks in Technology
See All in Technology
8万デプロイ
iwamot
PRO
2
200
クラウド × シリコンの Mashup - AWS チップ開発で広がる AI 基盤の選択肢
htokoyo
2
130
タスク管理も1on1も、もう「管理」じゃない ― KiroとBedrock AgentCoreで変わった"判断の仕事"
yusukeshimizu
5
2.1k
Security Diaries of an Open Source IAM
ahus1
0
210
JAWS DAYS 2026 ExaWizards_20260307
exawizards
0
350
Ultra Ethernet (UEC) v1.0 仕様概説
markunet
3
240
Claude Cowork Plugins を読む - Skills駆動型業務エージェント設計の実像と構造
knishioka
0
310
自動テストが巻き起こした開発プロセス・チームの変化 / Impact of Automated Testing on Development Cycles and Team Dynamics
codmoninc
3
1.3k
Evolution of Claude Code & How to use features
oikon48
1
550
マルチアカウント環境でSecurity Hubの運用!導入の苦労とポイント / JAWS DAYS 2026
genda
0
190
Kaggleの経験が実務にどう活きているか / kaggle_findy
sansan_randd
7
1.3k
情シスのための生成AI実践ガイド2026 / Generative AI Practical Guide for Business Technology 2026
glidenote
0
150
Featured
See All Featured
Imperfection Machines: The Place of Print at Facebook
scottboms
269
14k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
100
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.1k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.4k
ラッコキーワード サービス紹介資料
rakko
1
2.6M
Deep Space Network (abreviated)
tonyrice
0
86
Information Architects: The Missing Link in Design Systems
soysaucechin
0
820
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.2k
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
210
Making the Leap to Tech Lead
cromwellryan
135
9.8k
Mind Mapping
helmedeiros
PRO
1
110
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Transcript
Conditional Types I/O Roppongi.js #5
自己紹介 - @Takepepe / Takefumi.Yoshii / DeNA DEG - 状態管理全般の話題が好き
- 最近は TypeScript に夢中
The Redux Action world const initialState = { count: 0
} const types = { INCREMENT: 'COUNTER_INCREMENT' } const increment = () => ({ type: types.INCREMENT }) function reducer(state = initialState, action) { switch (action.type) { case types.INCREMENT: return { ...state, count: state.count + 1 } default: return state } }
The Redux Action world const initialState = { count: 0
} const types = { INCREMENT: 'COUNTER_INCREMENT' } const increment = () => ({ type: types.INCREMENT }) function reducer(state = initialState, action) { switch (action.type) { case types.INCREMENT: return { ...state, count: state.count + 1 } default: return state } } Action を軸足に置くRedux。 Mutation は Action に順応する。
The Redux Action world const initialState = { count: 0
} const types = { INCREMENT: 'COUNTER_INCREMENT' } const increment = () => ({ type: types.INCREMENT }) function reducer(state = initialState, action) { switch (action.type) { case types.INCREMENT: return { ...state, count: state.count + 1 } default: return state } } Action を軸足に置くRedux。 Mutation は Action に順応する。
Doubt to Reducer ▪ Action 選定 / State 変更 の責務が混在している…?
▪ Action は最初から 複・Reducer の関心事なのか…? ▪ Action を除くとどうなる…?
The State world const initialState = { count: 0 }
function increment(state = initialState) { return { ...state, count: state.count + 1 } } State を軸足に置くRedux。
The State world const initialState = { count: 0 }
function increment(state = initialState) { return { ...state, count: state.count + 1 } } function setCount(state = initialState, { amount }) { return { ...state, count: amount } } State を軸足に置くRedux。 State 変更の責務のみが残る。
The State world const initialState = { count: 0 }
function increment(state = initialState) { return { ...state, count: state.count + 1 } } function setCount(state = initialState, { amount }) { return { ...state, count: amount } } export const Mutations = { increment, setCount } State を軸足に置くRedux。 Action は Mutation から「生成する」
redux-aggregate
createAggregate() import { createAggregate } from 'redux-aggregate' import { Mutations
} from './models/counter' const { types, // Generated ActionTypes creators, // Generated ActionCreators reducerFactory // Generated ReducerFactory } = createAggregate(Mutations, 'counter/') 状態変更関数 (Mutaion) MapObject から Redux の定型句を生成。 Action の初期コンテキストは狭い。Action / Reducer 密結合スタート。
createActions() import { createActions } from 'redux-aggregate' import { ActionSources
} from './actions/timer' const { types, // Generated ActionTypes creators // Generated ActionCreators } = createActions(ActionSources, 'timer/') 純関数(ActionSrc)MapObject から Actions を生成。 Aggregate が抱えるには不自然な Action は Actions に委譲。 Action / Reducer 分割定義の余地を。
redux-aggregate pros ▪ 開発速度の向上 ▪ Action 選定 / State 変更
の責務に境界が生まれる ▪ テストが容易・可読性の向上 ▪ many-to-many の余地もある 詳細は redux-aggregate.js.org で
The State world const initialState = { count: 0 }
function increment(state = initialState) { return { ...state, count: state.count + 1 } } 何はともあれ、これだけで3種の定型句が生成されるので 開発効率が良い。型との相性も良さそうに見えるが…
型も一緒に生成
createAggregate() function setCount(state: State, payload: { amount: number }) {
// Infer Src A return { ...state, count: payload.amount } }
createAggregate() function setCount(state: State, payload: { amount: number }) {
// Infer Src A return { ...state, count: payload.amount } } const { creators } = createAggregate({ setCount }, 'counter/')
createAggregate() function setCount(state: State, payload: { amount: number }) {
// Infer Src A return { ...state, count: payload.amount } } const { creators } = createAggregate({ setCount }, 'counter/') const { type, payload } = creators.setCount({ amount: 10 }) // Inferred Dist A
createAggregate() function setCount(state: State, payload: { amount: number }) {
// Infer Src A return { ...state, count: payload.amount } } const { creators } = createAggregate({ setCount }, 'counter/') const { type, payload } = creators.setCount({ amount: 10 }) // Inferred Dist A { type: "counter/setCount", payload: { amount: 10 } }: { type: string, payload: { amount: number } // Inferred Dist A }
型がマッピングされている
createActions() function tick({ message }: { message: string }) {
// Infer Src A return { date: new Date(), message } // Infer Src B }
createActions() function tick({ message }: { message: string }) {
// Infer Src A return { date: new Date(), message } // Infer Src B } const { creators } = createActions({ tick }, 'timer/')
createActions() function tick({ message }: { message: string }) {
// Infer Src A return { date: new Date(), message } // Infer Src B } const { creators } = createActions({ tick }, 'timer/') const { type, payload } = creators.tick({ message: 'hello' }) // Inferred Dist A
createActions() function tick({ message }: { message: string }) {
// Infer Src A return { date: new Date(), message } // Infer Src B } const { creators } = createActions({ tick }, 'timer/') const { type, payload } = creators.tick({ message: 'hello' }) // Inferred Dist A { type: "timer/tick", payload: { date: XXXX, message: "hello" } }: { type: string, payload: { date: Date, message: string } // Inferred Dist B }
開発時 の 型定義 は inline assertion のみ
※ これ以降の話はライブラリ利用時 意識する必要はないです。 裏側の話。
Conditional Types I/O
Bottom up Generics ▪ Generics による型定義も各々の責務を明瞭に ▪ ボトムアップで要件を定義していく ▪ 同じ定義でも使い所によって振る舞いが変わる
以降 <INPUT> は同一 mutation 関数を指す
Return type type R<INPUT> = INPUT extends (...arg:[]) => infer
OUTPUT ? OUTPUT : never // javascript 翻訳 function _R(input = () => {}) { return input() } 戻り型を抽出する型
Argument type type A1<INPUT> = INPUT extends (a1: infer OUTPUT)
=> any ? OUTPUT : never type A2<INPUT> = INPUT extends (a1: any, a2: infer OUTPUT) => any ? OUTPUT : never // javascript 翻訳 function _A1 (input = a1 => a1) { return input() } function _A2 (input = (a1, a2) => a2) { return input() } n番目引数型を抽出する型
Expected type type NoPayload<INPUT> = (state: A1<INPUT>) => A1<INPUT> type
WithPayload<INPUT> = (state: A1<INPUT>, payload: A2<INPUT>) => A1<INPUT> type ExpectedType<INPUT> = NoPayload<INPUT> | WithPayload<INPUT> // javascript 翻訳 function _ExpectedType(state, payload) { if (payload === undefined) return { ...state } return { ...state, payload } } 第1引数型 / 戻り型の同一性を要求。INPUT期待型
Cast type type NoPayload<INPUT> = () => { type: string
} type WithPayload<INPUT> = (payload: A2<INPUT>) => { type: string, paylod: A2<INPUT> } type CastType<INPUT> = NoPayload<INPUT> | WithPayload<INPUT> // javascript 翻訳 function _CastType(input) { const a2 = _A2(input) if (a2 === undefined) return () => ({ type: 'string' }) return (payload = a2) => ({ type: 'string', payload }) } 抽出した部分型を合成。OUTPUT振付型
Assert type type AssertType<INPUT> = INPUT extends ExpectedType<INPUT> ?
CastType<INPUT> : never // javascript 翻訳 function _AssertType(input) { if (input <= _ExpectedType(input)) { // 意訳 return _CastType(input) } } INPUT期待型 ならば OUTPUT振付型を返す。宣言型
Assert by Generics function foo<INPUT>(input) { return _AssertType(input) as AssertType<INPUT>
// Assertion } foo 関数に与えた input 関数が型制約を満たしている場合、 型が振付けられた関数が return される。 プログラマブルに型が導出できる。
Mapped types & readonly wrap type AssertMap<T> = { readonly
[K in keyof T]: AssertType<T[K]> } Mapped types & Lookup types で Assert type 仕上げ。 redux-aggregate の Mutations から生成される ActionCreators の型はこうして生まれる。
Conditional Types I/O 要約
Conditional Types を活用した Auto mixed subtyping by Generics (合成派生型の自動導出)テクニック
ライブラリ提供関数に hook しているので、 利用者は敷居高めの型定義を気にせず 開発に集中出来る。
Appendix
Optional wrap breaks KeysDiff ... type SubscribeActionsMap<T, M> = {
[K in keyof T & keyof M]?: SubscribeActions<T[K], M[K]> } 2つの MapObject を与えると、同名関数に呼応する機能がある。Optional wrap をすると、2者間の関数名 Diff が拾えない。
KeysDiff type alias type KeysDiff< T extends string | number
| symbol, U extends string | number | symbol > = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] 2者間の KeysDIff を抽出して、 Diff がなければ never に倒す型
Bool by KeysDiff type alias type HasKeysDiff<T, U> = KeysDiff<keyof
U, keyof T> extends never ? false : KeysDiff<keyof T, keyof U> extends never ? false : true KeysDiff の有無を真偽値で返す型
Optional wrap & Typo guard type SubscribeActionsMap<T, M> = HasKeysDiff<T,
M> extends false ? { [K in keyof T & keyof M]?: SubscribeActions<T[K], M[K]> } : never Optional wrap 適用前に KeysDiff でタイポチェックする。
String literal type as TypeError type SubscribeActionsMap<T, M> = HasKeysDiff<T,
M> extends false ? { [K in keyof T & keyof M]?: SubscribeActions<T[K], M[K]> } : 'SUBSCRIPTIONS_KEY_NOT_MATCH' 単純に never に倒すと undefined型になり、 どこが型違反しているのか分かりづらい。 String literal type な Error message を仕込むと利用者に優しい。
Finally, Don’t believe type... 型は簡単に嘘をつくのでね…
Thanks !