Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Conditional Types I/O
Search
Takepepe
August 23, 2018
Technology
1.1k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Conditional Types I/O
Roppongi.js #5 発表資料。redux-aggregate の 型合成テクニック
https://roppongi-js.connpass.com/event/95936/
Takepepe
August 23, 2018
More Decks by Takepepe
See All by Takepepe
聞き手の目線で考えるプロポーザル
takefumiyoshii
0
890
どの様にAIエージェントと 協業すべきだったのか?
takefumiyoshii
2
1.2k
ServerAction で Progressive Enhancement はどこまで頑張れるか? / progressive-enhancement-with-server-action
takefumiyoshii
7
1.4k
App Router への移行は「改善」となり得るのか?/ Can migration to App Router be an improvement
takefumiyoshii
8
3.9k
フロントエンドの書くべきだったテスト、書かなくてよかったテスト
takefumiyoshii
40
18k
Webフロントエンドのための実践「テスト」手法 CodeZine Night #1
takefumiyoshii
24
11k
Next.js でリアーキテクトした話 / story-of-re-architect-with-nextjs
takefumiyoshii
12
9.3k
より速い WEB を目指す Next.js / nextjs-make-the-web-faster
takefumiyoshii
54
22k
フロントエンドの複雑さに耐えるため実践したこと / readyfor-nextjs-first
takefumiyoshii
25
11k
Other Decks in Technology
See All in Technology
「重なり」は迎える側がつくる ― 人もAIエージェントも歓迎するプロダクトエンジニアリング ―
go0517go
PRO
0
270
薬剤師(ドメインエキスパート)と一緒に育てる薬局向けAIアシスタント
kakehashi
PRO
2
130
Screen Lens - 今見てる画面を翻訳する
komagata
0
190
GoにおけるFFIのこれまでとこれから
goccy
4
1.6k
AI活用の現在地、 ちゃんと見えてますか?/XPfest-2026
visional_engineering_and_design
0
170
作り直せるコードは迅速に 作り直せないDBは慎重に - AI時代のプロダクトエンジニアが「判断の不可逆性」で開発速度を変える話
kinosuke01
0
280
Webとヘルスデータ
yukukotani
1
200
ペアプロの価値はコードを書くことだけじゃない
codmoninc
PRO
0
150
Adaptive Warehouse を今すぐ導入すべき理由と迷ったときの判断基準
__allllllllez__
0
140
幾何アルゴリズムで なめらかなピン操作を / iOSDC Japan 2026 / smoothpin
kazumanagano
0
240
2026/09/10 Spring_Bootから_Jakarta_EE_MicroProfileへの移行
megascus
0
280
あるけみー式LTスライド作成術
alchemy1115
1
140
Featured
See All Featured
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
1
400
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
230
Scaling GitHub
holman
464
140k
Practical Orchestrator
shlominoach
191
12k
30 Presentation Tips
portentint
PRO
1
390
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
3
1.2k
Into the Great Unknown - MozCon
thekraken
41
2.7k
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
510
GraphQLとの向き合い方2022年版
quramy
50
15k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
45k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
370
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 !