Utility Types
提供一些實用工具涵式,處理複雜的型別操作

$Keys<T>

Flow 可以定義 union types 來達到類似 enum 的效果。
1
// @flow
2
type Country = 'US' | 'IT' | 'FR';
3
4
const us: Country = 'US';
5
const jp: Country = 'JP'; // 錯誤! 因為 Countries 沒有定義 JP
Copied!
這是一個很簡單的國別碼範例,但是當面對到需求擴增的時候可能會顯得功能有點不足。 假設我們的需求從簡單的國別碼擴增到使用國別碼取得國家全名。
那我們的做法會變成這樣:
1
// @flow
2
type Country = 'US' | 'IT' | 'FR';
3
4
const countries = {
5
US: '美國',
6
IT: '義大利',
7
FR: '法國',
8
};
9
10
function printCountryName(code: Country): void {
11
console.log(countries[code]);
12
}
13
14
printCountryName('IT'); // 義大利
15
printCountryName('JP'); // 錯誤! 因為 Countries 沒有定義 JP
16
Copied!
這樣會動是沒錯,但是不夠好 ( doesn’t feel very DRY ),因為我們定義了兩次國別碼。
在這種情況下,$Keys<T> 會是我們的好朋友。 我們用原本的範例,使用 $Keys<T> 來改寫。
1
// @flow
2
const countries = {
3
US: '美國',
4
IT: '義大利',
5
FR: '法國',
6
};
7
8
type Country = $Keys<typeof countries>;
9
10
function printCountryName(code: Country): void {
11
console.log(countries[code]);
12
}
13
14
printCountryName('IT'); // 義大利
15
printCountryName('JP'); // 錯誤! 因為 Countries 沒有定義 JP
Copied!
範例中的 type Country = $Keys<typeof countries> 就相等於前面用的 type Country = 'US' | 'IT' | 'FR' 差別在於後者是使用 $Keys<T> 解析 countries 物件所產生的。
有了 $Keys<T>,我們不用再自己定義 Unions 類別,取而代之的是解析物件本身的 keys 來取得類別。

$Values<T>

$Values<T> 會從物件中提取值的屬性別,最終以 union types 回傳。 ( 注意! 不是從 key 哦! 他是去解析 value 的 )
1
// flow
2
type Person = {
3
name: string,
4
age: number,
5
height: number,
6
alive: boolean,
7
};
8
9
// 這兩種型態是一樣的
10
type PersonValue = string | number | boolean;
11
type Person$Value = $Values<Person>;
12
13
const name: Person$Value = 'Wayne';
14
const age: Person$Value = 26;
15
// 錯誤! 因為從 Person 型別中解析出來的 union types 中不含有 function
16
const swim: Person$Value = () => {};
Copied!
$Values<T> 可以幫我們快速從已存在的型態中,取出其 union types。
( 但是老實說,我也還沒有找到具體使用的情境 )

小陷阱

假設我們定義了一個 children 型態是 Array<String>
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
children: Array<string>, // 新定義 Array
6
}
7
8
type Person$Value = $Values<Person>;
9
10
const children: Person$Value = ['Willy', 'Lee']; // ok
Copied!
但是當我們現在有多一個 account 型態是 Array<number>。( 先不要管為什麼要這樣,我就演示演示而已~ )
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
children: Array<string>, // 新定義 Array<string>
6
account: Array<number>, // 多一個 Array<number>
7
};
8
9
type Person$Value = $Values<Person>;
10
11
const children: Person$Value = ['Willy', 'Lee']; // 錯誤!
12
const account: Person$Value = [1, 12, 123]; // 錯誤!
Copied!
我們預期兩種都接受的型態應該是 Array<string | number> 但是它幫我們解析成 Array<string> | Array<number> 所以就不行囉~~ 要小心。

$ReadOnly<T>

會將已經定義好的型別轉換成 ReadOnly修飾 ( 只可讀 ),詳細請參考 ReadOnly 章節。
這意味這這兩個是相等的:
1
// @flow
2
type ReadOnlyObj = {
3
+key: any,
4
};
Copied!
1
// @flow
2
type ReadOnlyObj = $ReadOnly<{
3
key: any
4
}>;
Copied!
這兩個同樣具有不可被寫入只可讀取值的特性。
$ReadOnly<T> 在需要將一個既存物件轉為一個 ReadOnly版本的資料型態是很方便的,不需要重新定義一個同樣結構、卻只是在每個 key上加個 ReadOnly修飾的物件。
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
};
6
7
type ReadOnlyPerson = $ReadOnly<Person>;
8
9
function fn(person: ReadOnlyPerson) {
10
// 皆會有錯誤
11
// ... 略過 ... property `name` is not writable.Flow(cannot-write)
12
person.name = 'Wayne';
13
// ... 略過 ... property `age` is not writable.Flow(cannot-write)
14
person.age = 26;
15
}
Copied!

$Exact<T>

可以從原先定義的型別轉換為精確匹配型別,參考 Exact object types
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
};
6
7
function fn(person: Person) {
8
// ... 跑跑跑 ...
9
}
10
11
// 成功,因為預設的型別判斷是只要定義的都有就好
12
fn({
13
name: 'Wayne',
14
age: 26,
15
children: [],
16
});
17
Copied!
現在改用 $Exact<T> 來做精確限制。
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
};
6
7
type ExactPerson = $Exact<Person>;
8
9
function exactFn(person: ExactPerson) {
10
// ... Exact 跑跑跑 ...
11
}
12
13
// 錯誤! 因為改成了精確匹配
14
exactFn({
15
name: 'Wayne',
16
age: 26,
17
children: [], // 多定義了 :(
18
});
19
Copied!
補充說明:
1
type ExactUser = $Exact<{name: string}>;
2
type ExactUserShorthand = { | name: string | };
Copied!
這兩個是一樣的哦~~~

$Diff<A, B>

顧名思義,他會比較 AB 的差別,我的了解是:
以 A 為主,去除 B 的型別,取出剩餘的型別。
借用官方範例,實在精妙呀!
1
// @flow
2
type Props = {
3
name: string,
4
age: number,
5
};
6
type DefaultProps = { age: number };
7
type RequiredProps = $Diff<Props, DefaultProps>;
8
9
function setProps(props: RequiredProps) {
10
// ...
11
}
12
13
setProps({ name: 'foo'}); // ok
14
setProps({ name: 'foo', age: 42, baz: false }); // 可以傳入額外的屬性,沒問題的
15
setProps({ age: 42 }); // 錯誤! 一定要有 name
Copied!
眼尖的朋友或許注意到了,這個跟我們在 React 定義 Props 型別與預設 Props 是不是很像呢? 哈哈沒錯,官方也這麼說道了:
$Diff is exactly what the React definition file uses to define the type of the props accepted by a React Component.
就是用在 React Component 中的囉~ 給有發現的自己拍拍手 ^^ ( 我自己要不是整理這份筆記,我也沒發現 )
拉回來,繼續講 $Diff<A, B>,其實個人認為在理解上,可以把 $Diff<A, B> 方法以 A - B 去理解,會比較好理解。
我們的 Aname, age 兩個屬性,B 有一個 age 屬性,這樣 Diff<A, B>比較下去,就會只留下 Aname 屬性。
1
// @flow
2
type Props = {
3
name: string,
4
age: number,
5
};
6
type DefaultProps = {
7
age: number
8
};
9
type RequiredProps = $Diff<Props, DefaultProps>; // 只剩下 name 一個屬性
Copied!
為什麼我前面要不厭其煩的不斷寫道 $Diff<A, B> ? 因為我要讓各位記住, AB放置的位置是有不同意義的! 就像減法中 10 - 55 - 10 得出來的結果不會相等一樣。而 $Diff<A, B> 位置放錯是會有錯誤的哦~
$Diff<A, B> 的要求是,B 之中的屬性一定要是 A 所存在的。
1
// @flow
2
type Props = {
3
name: string,
4
age: number,
5
};
6
type DefaultProps = {
7
age: number,
8
other: string,
9
};
10
11
// 錯誤!
12
// Cannot instantiate `$Diff` because undefined property `other` [1] ...
13
type RequiredProps = $Diff<Props, DefaultProps>;
Copied!
Bother屬性 A 沒有,所以就會報出「 B 含有 A 所沒有的屬性」,這種錯誤了。

$Rest<A, B>

要瞭解 $Rest<A, B> 的話一定要先瞭解 JavaScript 的 解構賦值 ( Destructuring assignment )
1
const person = {
2
name: 'Wayne',
3
age: 26,
4
children: ['Andy', 'Tom', 'Jayson'],
5
foo: 'Test',
6
bar: 123,
7
baz: false,
8
}
9
10
const { name, children, ...other } = person;
11
12
console.log(name); // Wayne
13
console.log(children); // [ 'Andy', 'Tom', 'Jayson' ]
14
console.log(other); // { age: 26, foo: 'test', bar: 123, baz: false }
Copied!
大致演示一下它的概念。 我們有一個 person 物件,含有多個屬性,當我們使用解構方法提取出 namechildren 後,再將剩餘的屬性放入 other 物件中,所以 other 物件就會有除了剛才被提去出去的 namechildren 以外的全部屬性。
那 $Rest<A, B> 的概念也就類似這樣,它會保留 A 擁有的且 B 沒有的屬性,flow 判定擁有屬性是以精確指定 ( Exact Object Types )為擁有依據哦!。因此是這種定義方法 {| name: string, age: number |} 而不是 { name: string, age: number } 哦!
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
children: Array<string>,
6
foo: string,
7
}
8
9
type RestPerson = $Rest<Person, {| age: number, foo: string |}>
10
11
function fn(person: RestPerson) {
12
person.name // ok!
13
person.age // 錯誤!
14
person.children // ok!
15
person.foo // 錯誤!
16
}
17
Copied!
可以看到因為我們送給 $Rest<A, B>B 當中含有 agefoo 兩個屬性,因此在下面呼叫 person.ageperson.foo 的時候都會報錯。

與 $Diff<A, B> 的差別

這樣乍看下來... 跟 $Diff<A, B> 是不是很像? 確實是滿像的,但還是有差別的! 他們最主要的差別就展現在 必須有 與 optional 上面。 當我們使用 $Rest<{| n: number |}, {}>$Diff<{| n: number |}, {}> 時,答案就呼之欲出了。
1
// @flow
2
type RestType = $Rest<{| n: number |}, {}> // {| n?: number |}
3
type DiffType = $Diff<{| n: number |}, {}> // {| n: number |}
4
Copied!
差別就在 optional 囉!!

$PropertyType<T, k>

傳入 T ( Type ) 與 k ( Key ),回傳指定 k 的型態, T 必須是類型, 而 k 呢,必須是一個字串。
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
parent: Person
6
};
7
8
function nameFn(name: $PropertyType<Person, 'name'>) {
9
// ...
10
}
11
12
nameFn('Wayne'); // ok!
13
nameFn(123); // 錯誤,必須是 string
14
15
Copied!
可以看到若是指定 name 這個 key 進去,它會提取出 Personname 的型別,也就是 string
剛才上面的是我個人的理解方式,接下來用官方的範例吧!
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
parent: Person
6
};
7
8
// ok
9
const newName: $PropertyType<Person, 'name'> = 'Toni Braxton';
10
// ok
11
const newAge: $PropertyType<Person, 'age'> = 51;
12
// 錯誤,因為要是 Person 形態
13
const newParent: $PropertyType<Person, 'parent'> = 'Evelyn Braxton';
14
// 如果要正確的話要這樣哦!
15
const newParent2: $PropertyType<Person, 'parent'> = {
16
name: 'Wayne',
17
age: 123,
18
parent: {}
19
};
20
21
// 錯誤! children 不存在
22
const newChildren: $PropertyType<Person, 'children'> = {};
Copied!
$PropertyType<T, K> 在 React 的用例場景上尤其有用。
1
// @flow
2
import React from 'react';
3
4
// 定義 Props
5
type Props = {
6
text: string,
7
onMouseOver: ({x: number, y: number}) => void
8
}
9
10
// 指定這個 Component 會有 text 跟 onMouseOver 兩個 Prop 傳入
11
class Tooltip extends React.Component<Props> {
12
props: Props;
13
}
14
15
// 存 Tooltip 中取得 props 屬性的型別,剛好就是剛才定義的 Props
16
const someProps: $PropertyType<Tooltip, 'props'> = {
17
text: 'foo',
18
onMouseOver: (data: {x: number, y: number}) => undefined
19
};
20
21
// 錯誤
22
const otherProps: $PropertyType<Tooltip, 'props'> = {
23
text: 'foo'
24
// 缺少 `onMouseOver` 定義
25
};
26
Copied!
延續剛才的範例,我們也可以使用巢狀的定義 $PropertyType 像是這樣
1
// @flow
2
// 指定這個 Component 會有 text 跟 onMouseOver 兩個 prop 傳入
3
type Props = {
4
text: string,
5
onMouseOver: ({x: number, y: number}) => void
6
}
7
8
class Tooltip extends React.Component<Props> {
9
props: Props;
10
}
11
12
type PositionHandler = $PropertyType<$PropertyType<Tooltip, 'props'>, 'onMouseOver'>;
13
14
const handler: PositionHandler = (data: {x: number, y: number}) => undefined; // ok
15
const handler2: PositionHandler = (data: string) => undefined; // 錯誤!傳入參數錯誤
Copied!
稍微來解析一下,先從 $PropertyType<Tooltip, 'props'> 中由 props 字串轉出 Props 類別,轉出來後,再由 $PropertyType<$PropertyType<Tooltip, 'props'>, 'onMouseOver'> 中解析出 onMouseOver 這個 function 類型。

$ElementType<T, K>

之後會用比較多 Type Casting Expressions,不熟的話再去複習一下吧!
$ElementType<T, K>$PropertyType<T, k> 一樣 都是傳入 Tk,回傳指定 k 的型態。
在特定場景下,它的用法會跟 $PropertyType<T, k> 一樣
1
// @flow
2
type Person = {
3
name: string,
4
age: number,
5
}
6
7
(123: $ElementType<Person, 'age'>); // ok
8
(123: $PropertyType<Person, 'age'>); // ok
9
10
// children 不存在
11
(123: $ElementType<Person, 'children'>); // 錯誤!
12
(123: $PropertyType<Person, 'children'>); // 錯誤!
Copied!
在這種情況下確實是會有一樣的回傳結果,只是 ElementType<T, K> 的 T 不一定要接收物件型別,而是可以接收一個物件、陣列、甚至是 Tuple 的型別。接收到後再根據 K 傳入的值去解析出相對應的欄位型別。
1
// @flow
2
type Tuple = [boolean, string];
3
4
(true: $ElementType<Tuple, 0>); // ok!
5
('123': $ElementType<Tuple, 1>); // ok!
6
('123': $ElementType<Tuple, 2>); // 錯誤! 長度並沒有到 2
7
8
(true: $PropertyType<Tuple, 0>); // 錯誤! $PropertyType<T, k> 中,k 必須是字串
Copied!
可以看到我們這次用了 Tuple 類別,$ElementType<T, K> 可以使用位置解析出型別,但是 $PropertyType<T, k> 不行,因為 $PropertyType<T, k> k 是只可以傳入字串的,不接受其他型別的傳入, 而 $ElementType<T, K> 並沒有這個限制,只要傳入的 KT 存在的屬性就好,這也是 $ElementType<T, K> $PropertyType<T, k> 最大的差別囉!
1
// @flow
2
3
// 物件解析
4
type Obj = {
5
[key: string]: number,
6
};
7
8
(42: $ElementType<Obj, string>); // ok!
9
(42: $ElementType<Obj, boolean>); // 錯誤! Obj 內並沒有 boolean 型別
10
(true: $ElementType<Obj, string>); // 錯誤! 回傳值是 number 型別
11
12
// 解析陣列,由於一個陣列我們通常不知道它確切的長度,因此我們直接使用 number 作為 key 就好了
13
type Arr = Array<boolean>;
14
15
(true: $ElementType<Arr, number>);
16
(true: $ElementType<Arr, boolean>); // 錯誤! 陣列中並沒有 boolean 型別作為 key
17
('foo': $ElementType<Arr, number>); // 錯誤! 回傳值是 boolean 型別
Copied!
也可以使用巢狀的使用 $ElementType ,在需要取得型別中的型別時會特別有用。
像這樣
1
// @flow
2
type NumberObj = {
3
nums: Array<number>,
4
};
5
6
(42: $ElementType<$ElementType<NumberObj, 'nums'>, number>);
Copied!
稍微來解析一下,先從 $ElementType<NumberObj, 'nums'> 中由 nums 字串轉出 NumberObj 陣列類別,轉出來後,再由 $ElementType<$ElementType<NumberObj, 'nums'>, number> 透過 array key ( number ) 解析出 number 類型。
巢狀有點複雜,可以多看幾次!

$NonMaybeType<T>

顧名思義,$NonMaybeType<T> 會將收到的 T 轉換為 non-mayby type,簡單一點瞭解就是將允許 nullundefined 的定義轉為不允許啦~
1
// @fow
2
type MaybeName = ?string;
3
type Name = $NonMaybeType<MaybeName>;
4
5
('Wayne': MaybeName); // ok
6
(null: MaybeName); // ok
7
8
('Wayne': Name); // ok
9
(null: Name); // 錯誤
Copied!

$ObjMap<T, F>

這段比較不好懂,會用到比較多的泛型 ( Generic ),與 js 方法,如果一時半刻看不懂,不要怪自己,是很正常的。只要花點時間細看一下,我相信一定能看懂!功力也會有些許進步的。 也不要放棄唷!因為接下來的很多都要在這個的基礎上做延伸嘿!
這個不太好懂,如果一時半刻看不懂,不要怪自己,是很正常的!
首先我們來看一個純 js function
1
function run(o) {
2
return Object.keys(o).reduce(
3
(acc, k) => Object.assign(acc, { [k]: o[k]() }),
4
{}
5
);
6
}
7
8
const obj = run({
9
foo: () => 2,
10
bar: () => true,
11
baz: () => 'test'
12
})
Copied!
這很簡單,就是一個 function 接收一個物件,而這個 function 會把物件中每個屬性都當成 function 去跑,再將回傳值放入一個空物件中,用 reduce() 方法做出新的物件回傳。 ( 不熟的話麻煩自己再去補一下 reduce()Object.assign() 囉! )
但是這樣的回傳我們要怎麼知道 obj 他的回傳型別呢? 我怎麽知道 foonumberbarboolean 呢?
這就是 $ObjMap<T, F> 的使用時機了!
$ObjMap<T, F> 收取 物件形態 T function 形態 F
會從 F 中提取回傳形態作為該屬性最終的形態依據,換句話說,$ObjMap<T, F> 會解析 T 的所有屬性,並且一一執行 F 取得回傳值。
再多說下去就要茫了~直接來看範例吧!
1
// @flow
2
type ExtractReturnType = <V>(() => V) => V;
3
4
function run<O: {[key: string]: Function}>(o: O): $ObjMap<O, ExtractReturnType> {
5
return Object.keys(o).reduce(
6
(acc, k) => Object.assign(acc, { [k]: o[k]() }),
7
{}
8
);
9
}
10
11
const obj = run({
12
foo: () => 2, // ok!
13
bar: () => true, // ok!
14
baz: () => 'test' // ok!
15
})
16
17
(obj.foo: number);
18
(obj.bar: bool);
19
(obj.baz: string);
20
(obj.baz: bool); // 錯誤!
21
Copied!
我們在前面先定義了一個 function 形態 ExtractReturnType,他的內容就是簡單地將收去到的形態照原本回傳回去而已。
run() 做的事情跟原先一樣,只是多加了些型別的判斷、使用, 首先 run<O: {[key: string]: Function}> 是限制傳入的參數必須為物件、且物件的 key 必須是字串型式、value 必須是個 Function
再來run<O: {[key: string]: Function}>(o: O): $ObjMap<O, ExtractReturnType> 的後半段,$ObjMap<O, ExtractReturnType> 就是將收到的物件 O 與剛才定義 ExtractReturnType F 傳入,$ObjMap<T, F> 就會執行它的任務了。

$ObjMapi<T, F>

$ObjMapi<T, F>$ObjMap<T, F> 看起來很類似,差別在於 $ObjMapi<T, F>Function F 會呼叫 Tkeyvalue,而不是只針對 value 做處理。
1
// @flow
2
const person = {
3
name: () => 'Wayne',
4
age: () => 26
5
};
6
7
type ExtractReturnObjectType = <K, V>(K, () => V) => { k: K, v: V };
8
9
declare function run<O: Object>(o: O): $ObjMapi<O, ExtractReturnObjectType>;
10
11
(run(person).name: { k: 'name', v: string}); // ok!
12
(run(person).age: { k: 'age', v: number}); // ok!
13
14
(run(person).name: { k: 'age', v: string}); // 錯誤! name 的 k 應該是 name
15
(run(person).age: { k: 'age', v: string}); // 錯誤! age 的 v 應該是 number
Copied!

$TupleMap<T, F>

$TupleMap<T, F> 接收一個可迭代的型別 T ( 像是 Tuple、陣列... ),另外再接收一個 function 形態的 F。 最終回傳另一個可迭代的物件,物件中每個元素都經過 F 的回傳,有點像是 JavaScript 的 map() 方法。
此功能的用例上也是比較特殊一點的,假設我們有一個陣列裡面每個元素都是 function,我們需要取得每個元素的回傳值作為最終的形態。
1
// @flow
2
type ExtractReturnType = <V>(() => V) => V;
3
4
function run<A, I: Array<() => A>>(iter: I): $TupleMap<I, ExtractReturnType> {
5
return iter.map(fn => fn());
6
}
7
8
const arr = [
9
() => 'foo',
10
() => 'bar',
11
() => 'baz',
12
];
13
14
(run(arr)[0]: string); // ok
15
(run(arr)[1]: string); // ok
16
(run(arr)[2]: string); // ok
17
(run(arr)[2]: number); // 錯誤! 應該是 string 才對
18
Copied!

$Call<F, T...>

$Call<F, T...> 接收一個 F 參數與 0 個或多個 T 參數,並且已 F 的回傳做作為最終的形態。
注意到了!不一定要有 T 參數也是可以的哦!
1
// @flow
2
3
// 一個固定會回傳 number 型態的 function
4
type ExtractNumberType = () => number;
5
6
type NumberType = $Call<ExtractNumberType>;
7
8
9
(26: NumberType); // ok
10
('Wayne': NumberType); // 錯誤! 必須要是 number 形態
11
Copied!
上例雖然沒有什麼意義,就是演示沒有接收 T 參數,$Call<F, T...> 也是可以正常運作的~
再來我們看一下接收一個參數的幾個範例
1
// @flow
2
// 解析接收到的物件中 prop 屬性的的型態
3
type ExtractPropType = <T>({prop: T}) => T;
4
5
type PropObj = { prop: number };
6
type NopeObj = { nope: number };
7
8
type PropType = $Call<ExtractPropType, PropObj>;
9
type NopeType = $Call<ExtractPropType, NopeObj>; // 錯誤
10
11
12
(26: PropType); // ok!
13
('Wayne': PropType); // ok!
14
(26: NopeType); // 錯誤
15
Copied!
1
// @flow
2
type ExtractReturnType = <R>(() => R) => R;
3
type Fn = () => number;
4
type Return = $Call<ExtractReturnType, Fn>;
5
6
(26: Return); // ok!
7
('Wayne': Return); // 錯誤! 必須是 number 型態
8
Copied!
再來些進階的用法
1
// @flow
2
type NestedObj = {
3
status: number,
4
data: $ReadOnlyArray<{
5
foo: {
6
bar: number,
7
},
8
}>,
9
};
10
11
type Fn = <T>({
12
data: $ReadOnlyArray<{
13
foo: {
14
bar: T,
15
},
16
}>
17
}) => T
18
19
// $Call<F, T...> 也是可以往下解析型態的哦!
20
type BarType = $Call<Fn, NestedObj>;
21
22
(26: BarType); // ok!
23
('Wayne': BarType); // 錯誤! 必須是 number 型態
24
Copied!
$Call<F, T...> 還可以解析 Map 等等型態,就等你們再去挖掘囉~

Class<T>

接收一個 T 參數回傳一個實體 Class 型態 CC 型態是 Class 本身而不是被 new 出來的 Class 實體。
1
// @flow
2
class Store {}
3
4
// 接收 Store 型態
5
function getStore(newStore: Store) {
6
return newStore;
7
}
8
9
// 接收 Class<Store> 型態
10
function makeStore(storeClass: Class<Store>) {
11
return new storeClass();
12
}
13
14
(getStore(Store): Store); // 錯誤! 必須接收 Class 實體
15
(getStore(new Store()): Store); // ok
16
17
(makeStore(Store): Store); // ok
18
(makeStore(new Store()): Store); // 錯誤! 必須接收 Class 本身
Copied!
Class 的建構子需要接收參數的話,我們也需要提供參數的型別
1
// @flow
2
class ParamStore<T> {
3
constructor(data: T) {
4
}
5
}
6
7
function makeStore<T>(storeClass: Class<ParamStore<T>>, data: T): ParamStore<T> {
8
return new storeClass(data);
9
}
10
11
(makeStore(ParamStore, 26): ParamStore<number>); // ok
12
(makeStore(ParamStore, 'Wayne'): ParamStore<string>); // ok
13
(makeStore(ParamStore, 'Wayne'): ParamStore<bool>); // 錯誤!
Copied!

$Shape<T>

接收一個物件型態的 T,他會去解析 T 的所有屬性,並回傳一個只要符合一個屬性就通過的型別。
1
// @flow
2
type Person = {
3
age: number,
4
name: string,
5
}
6
type PersonDetails = $Shape<Person>;
7
8
const person001: Person = { age: 26, name: 'Wayne' }; // ok
9
const person002: PersonDetails = { age: 26, name: 'Wayne' }; // ok
10
11
const person003: Person = { age: 26 }; // 錯誤!
12
const person004: PersonDetails = { age: 26 }; // ok
13
const person005: PersonDetails = { name: 'Wayne' }; // ok
Copied!
注意了!$Shape<T> 跟 T 使用 Optional 型別定義是不一樣的哦!

$Exports<T>

官網給出來的解釋是這樣的
1
// @flow
2
import typeof * as T from 'my-module';
Copied!
1
// @flow
2
type T = $Exports<'my-module'>;
Copied!
恩... 當然是沒錯啦!但是我個人在看的時候為了搞懂這兩句可是花了好大的心思呀!
為什麼呢?因為這短短兩行,我們就必須要去補 flow module 的部分,看看看還會被帶到 flow-typedLibrary Definitions 等等的地方,是很容易迷路的!
這邊我給大家個範例!加速理解,有空大家再自己去補技術細節就好。
先在 /flow-typed 資料夾內建立一個叫做 'test-module.js' 的檔案 ( /flow-typed 資料夾是預設的全域 lib,詳細在該章節細細討論 )
1
// @flow
2
declare module "test-module" {
3
declare export function concatPath(dirA: number, dirB: string): string;
4
declare export var Name: string;
5
declare export var PI: number;
6
}
Copied!
接著在專案內引入
typeof * as T 方法引入
1
// @flow
2
import typeof * as T from 'test-module';
3
4
// ok!
5
const a: T = {
6
concatPath: (a, b) => b,
7
PI: 3.14,
8
Name: 'Wayne',
9
}
10
11
12
// 錯誤
13
const b: T = {
14
concatPath: (a, b) => b,
15
PI: 3.14,
16
Name: 'Wayne',
17
test: 'Hello',
18
}
Copied!
$Exports<T> 方法引入
1
// @flow
2
type T = $Exports<'test-module'>;
3
4
// ok!
5
const a: T = {
6
concatPath: (a, b) => b,
7
PI: 3.14,
8
Name: 'Wayne',
9
}
10
11
// 錯誤
12
const b: T = {
13
concatPath: (a, b) => b,
14
PI: 3.14,
15
Name: 'Wayne',
16
test: 'Hello',
17
}
Copied!
Last modified 10mo ago