最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

利用TypeScript從字符串字面量類型提取參數(shù)類型

 更新時間:2022年09月13日 11:03:19   作者:前端科代表張繼科???????  
這篇文章主要介紹了利用TypeScript從字符串字面量類型提取參數(shù)類型,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下

正文

挑戰(zhàn)

我們先來做一個ts的挑戰(zhàn)。

你知道如何為下面的app.get方法定義TypeScript類型嗎?

req.params是從傳入的第一個參數(shù)字符串中提取出來的。

當(dāng)你想對一個類似路由的函數(shù)定義一個類型時,這顯得很有用,你可以傳入一個帶路徑模式的路由,你可以使用自定義語法格式去定義動態(tài)參數(shù)片段(例如:[shopid]、:shopid),以及一個回調(diào)函數(shù),它的參數(shù)類型來源于你剛剛傳入的路由。

所以,如果你嘗試訪問沒有定義的參數(shù),將會報錯!

舉一個真實案例,如果你對React Router很熟悉,應(yīng)該知道render函數(shù)中的RouteProps的類型是從path參數(shù)派生出來的。

本文將探討如何定義這樣一個類型,通過各種ts技術(shù),從字符串字面量類型中提取類型。

需要掌握的內(nèi)容

首先,在我們探討之前,需要先講下一些基本的知識要求。

字符串字面量類型

ts的字符串類型是一個可以有任何值的字符串

let str: string = 'abc';
str = 'def'; // no errors, string type can have any value

而字符串字面量類型是一個具有特定值的字符串類型。

let str: 'abc' = 'abc';
str = 'def'; // Type '"def"' is not assignable to type '"abc"'.

通常情況下,我們將它與聯(lián)合類型一起使用,用來確定你可以傳遞給函數(shù)、數(shù)組、對象的字符串取值的列表。

function eatSomething(food: 'sushi' | 'ramen') {}
eatSomething('sushi');
eatSomething('ramen');
eatSomething('pencil'); // Argument of type '"pencil"' is not assignable to parameter of type '"sushi" | "ramen"'.

let food: Array<'sushi' | 'ramen'> = ['sushi'];
food.push('pencil'); // Argument of type '"pencil"' is not assignable to parameter of type '"sushi" | "ramen"'.

let object: { food: 'sushi' | 'ramen' };
object = { food: 'sushi' };
object = { food: 'pencil' }; // Type '"pencil"' is not assignable to type '"sushi" | "ramen"'.

你是如何創(chuàng)建字符串字面量類型的呢?

當(dāng)你使用const定義一個字符串變量時,它就是一個字符串字面量類型。然而,如果你用let去定義它,ts識別出變量的值可能會改變,所以它把變量分配給一個更通用的類型:

同樣的情況對對象和數(shù)組也一樣,你可以在以后去修改對象、數(shù)組的值,因此ts分配給了一個更通用的類型。

不過,你可以通過使用const斷言向ts提示,你將只從對象、數(shù)組中讀取值,而不會去改變它。

模板字面量類型和字符串字面量類型

從ts4.1開始,ts支持一種新的方式來定義新的字符串字面量類型,就是大家熟悉的字符串模板的語法:

const a = 'a';
const b = 'b';

// In JavaScript, you can build a new string
// with template literals
const c = `${a} $`; // 'a b'

type A = 'a';
type B = 'b';

// In TypeScript, you can build a new string literal type
// with template literals too!

type C = `${A} ${B}`; // 'a b'

條件類型

條件類型允許你基于另一個類型來定義一個類型。在這個例子中,Collection<X>可以是number[]或者Set<number>,這取決于X的類型:

type Collection<X> = X extends 'arr' ? number[] : Set<number>;

type A = Collection<'arr'>; // number[]

// If you pass in something other than 'arr'
type B = Collection<'foo'>; // Set<number>

你使用extends關(guān)鍵字用來測試X的類型是否可以被分配給arr類型,并使用條件運算符(condition ? a : b)來確定測試成立的類型。

如果你想測試一個更復(fù)雜的類型,你可以使用infer關(guān)鍵字來推斷該類型的一部分,并根據(jù)推斷的部分定義一個新類型。

// Here you are testing whether X extends `() => ???`
// and let TypeScript to infer the `???` part
// TypeScript will define a new type called
// `Value` for the inferred type
type GetReturnValue<X> = X extends () => infer Value ? Value : never;

// Here we inferred that `Value` is type `string`
type A = GetReturnValue<() => string>;

// Here we inferred that `Value` is type `number`
type B = GetReturnValue<() => number>;

函數(shù)重載和通用函數(shù)

當(dāng)你想在ts中定義一個參數(shù)類型和返回值類型相互依賴的函數(shù)類型時,可以使用函數(shù)重載或者通用函數(shù)。

function firstElement(arr) {
    return arr[0];
}
const string = firstElement(['a', 'b', 'c']);
const number = firstElement([1, 2, 3]);
// return string when passed string[]
function firstElement(arr: string[]): string;
// return number when passed number[]
function firstElement(arr: number[]): number;
// then the actual implementation
function firstElement(arr) {
    return arr[0];
}

const string = firstElement(['a', 'b', 'c']);
// Define type parameter `Item` and describe argument and return type in terms of `Item`
function firstElement<Item>(arr: Item[]): Item | undefined {
    return arr[0];
}

// `Item` can only be of `string` or `number`
function firstElement<Item extends string | number>(arr: Item[]): Item | undefined {
    return arr[0];
}

const number = firstElement([1, 3, 5]);
const obj = firstElement([{ a: 1 }]); // Type '{ a: number; }' is not assignable to type 'string | number'.

著手解決問題

了解了以上知識,我們對于問題的解決方案可能可以采取這樣的形式:

function get<Path extends string>(path: Path, callback: CallbackFn<Path>): void {
	// impplementation
}

get('/docs/[chapter]/[section]/args/[...args]', (req) => {
	const { params } = req;
});

我們使用了一個類型參數(shù)Path(必須是一個字符串)。path參數(shù)的類型是Path,回調(diào)函數(shù)的類型是CallbackFn<Path>,而挑戰(zhàn)的關(guān)鍵之處就是要弄清楚CallbackFn<Path>。

我們計劃是這樣子的:

  • 給出path的類型是Path,是一個字符串字面量類型。
type Path = '/purchase/[shopid]/[itemid]/args/[...args]';
  • 我們派生出一個新的類型,這個類型將字符串分解成它的各個部分。
type Parts<Path> = 'purchase' | '[shopid]' | '[itemid]' | 'args' | '[...args]';
  • 篩選出只包含參數(shù)的部分
type FilteredParts<Path> = '[shopid]' | '[itemid]' | '[...args]';
  • 刪除不需要的括號
type FilteredParts<Path> = 'shopid' | 'itemid' | '...args';
  • 將參數(shù)映射到一個對象類型中
type Params<Path> = {
	shopid: any;
	itemid: any;
	'...args': any;
};
  • 使用條件類型來定義map的值部分
type Params<Path> = {
	shopid: number;
	itemid: number;
	'...args': string[];
};
  • 重置鍵名,刪除...args中的...
type Params<Path> = {
	shopid: number;
	itemid: number;
	args: string[];
};

最后

type CallbackFn<Path> = (req: { params: Params<Path> }) => void;

分割字符串字面量類型

為了分割一個字符串字面量類型,我們可以使用條件類型來檢查字符串字面量的取值:

type Parts<Path> = Path extends `a/b` ? 'a' | 'b' : never;
type AB = Parts<'a/b'>; // type AB = "a" | "b"

但是要接收任意字符串字面量,我們無法提前知道是什么值

type CD = Parts<'c/d'>;
type EF = Parts<'e/f'>;

我們必須在條件測試中推斷出數(shù)值,并使用推斷出來的數(shù)值類型:

type Parts<Path> = Path extends `${infer PartA}/${infer PartB}` ? PartA | PartB : never;
type AB = Parts<'a/b'>; // type AB = "a" | "b"
type CD = Parts<'c/d'>; // type CD = "c" | "d"
type EFGH = Parts<'ef/gh'>; // type EFGH = "ef" | "gh"

而如果你傳入一個不匹配模式的字符串字面量,我們希望直接返回:

type Parts<Path> = Path extends `${infer PartA}/${infer PartB}` ? PartA | PartB : Path;

type A = Parts<'a'>; // type A = "a"

有一點需要注意,PartA的推斷是'non-greedily'的,即:它將盡可能地進(jìn)行推斷,但不包含一個/字符串。

type ABCD = Parts<'a/b/c/d'>; // type ABCD = "a" | "b/c/d"

因此,為了遞歸地分割Path字符串字面量,我們可以返回Parts<PathB>類型替代原有的PathB類型:

type Parts<Path> = Path extends `${infer PartA}/${infer PartB}` ? PartA | Parts<PartB> : Path;
type ABCD = Parts<'a/b/c/d'>; // type ABCD = "a" | "b" | "c" | "d"

以下是所發(fā)生的詳細(xì)復(fù)盤:

type Parts<'a/b/c/d'> = 'a' | Parts<'b/c/d'>;
type Parts<'a/b/c/d'> = 'a' | 'b' | Parts<'c/d'>;
type Parts<'a/b/c/d'> = 'a' | 'b' | 'c' | Parts<'d'>;
type Parts<'a/b/c/d'> = 'a' | 'b' | 'c' | 'd';

參數(shù)語法部分的過濾

這一步的關(guān)鍵是觀察到,任何類型與never類型聯(lián)合都不會產(chǎn)生類型

type A = 'a' | never; // type A = "a"

type Obj = { a: 1 } | never; // type Obj = { a: 1; }

如果我們可以轉(zhuǎn)換

'purchase' | '[shopid]' | '[itemid]' | 'args' | '[...args]'

never | '[shopid]' | '[itemid]' | never | '[...args]'

那我們就可以得到:

'[shopid]' | '[itemid]' | '[...args]'

所以,要怎么實現(xiàn)呢?

我們得再次向條件類型尋求幫助,我們可以有一個條件類型,如果它以[開始,以]結(jié)尾,則返回字符串字面量本身,如果不是,則返回never

type IsParameter<Part> = Part extends `[${infer Anything}]` ? Part : never;
type Purchase = IsParameter<'purchase'>; // type Purchase = never
type ShopId = IsParameter<'[shopid]'>; // type ShopId = "[shopid]"
type IsParameter<Part> = Part extends `[${infer Anything}]` ? Part : never;
type FilteredParts<Path> = Path extends `${infer PartA}/${infer PartB}` ? IsParameter<PartA> | FilteredParts<PartB> : IsParameter<Path>;
type Params = FilteredParts<'/purchase/[shopid]/[itemid]/args/[...args]'>; // type Params = "[shopid]" | "[itemid]" | "[...args]"

刪除括號:

type IsParameter<Part> = Part extends `[${infer ParamName}]` ? ParamName : never;
type FilteredParts<Path> = Path extends `${infer PartA}/${infer PartB}` ? IsParameter<PartA> | FilteredParts<PartB> : IsParameter<Path>;
type ParamsWithoutBracket = FilteredParts<'/purchase/[shopid]/[itemid]/args/[...args]'>;

在對象類型里做一個映射

在這一步中,我們將使用上一步的結(jié)果作為鍵名來創(chuàng)建一個對象類型。

type Params<Keys extends string> = {
    [Key in Keys]: any;
};

const params: Params<'shopid' | 'itemid' | '...args'> = {
    shopid: 2,
    itemid: 3,
    '...args': 4,
};
type IsParameter<Part> = Part extends `[${infer ParamName}]` ? ParamName : never;
type FilteredParts<Path> = Path extends `${infer PartA}/${infer PartB}` ? IsParameter<PartA> | FilteredParts<PartB> : IsParameter<Path>;
type Params<Path> = {
    [Key in FilteredParts<Path>]: any;
};
type ParamObject = Params<'/purchase/[shopid]/[itemid]/args/[...args]'>;

最終版:

type IsParameter<Part> = Part extends `[${infer ParamName}]` ? ParamName : never;
type FilteredParts<Path> = Path extends `${infer PartA}/${infer PartB}` ? IsParameter<PartA> | FilteredParts<PartB> : IsParameter<Path>;
type ParamValue<Key> = Key extends `...${infer Anything}` ? string[] : number;
type RemovePrefixDots<Key> = Key extends `...${infer Name}` ? Name : Key;
type Params<Path> = {
    [Key in FilteredParts<Path> as RemovePrefixDots<Key>]: ParamValue<Key>;
};
type CallbackFn<Path> = (req: { params: Params<Path> }) => void;
function get<Path extends string>(path: Path, callback: CallbackFn<Path>) {
    // TODO: implement
}

到此這篇關(guān)于利用TypeScript從字符串字面量類型提取參數(shù)類型的文章就介紹到這了,更多相關(guān)TS取參數(shù)類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JS訪問SWF的函數(shù)用法實例

    JS訪問SWF的函數(shù)用法實例

    這篇文章主要介紹了JS訪問SWF的函數(shù)用法,實例分析了javascript訪問swf文件的方法及易錯點的處理技巧,需要的朋友可以參考下
    2015-07-07
  • ES6中async函數(shù)與await表達(dá)式的基本用法舉例

    ES6中async函數(shù)與await表達(dá)式的基本用法舉例

    async和await是我們進(jìn)行Promise時的一個語法糖,async/await為了讓我們書寫代碼時更加流暢,增強(qiáng)了代碼的可讀性,下面這篇文章主要給大家介紹了關(guān)于ES6中async函數(shù)與await表達(dá)式的基本用法,需要的朋友可以參考下
    2022-07-07
  • javascript下拉列表菜單的實現(xiàn)方法

    javascript下拉列表菜單的實現(xiàn)方法

    這篇文章主要介紹了javascript下拉列表菜單的實現(xiàn)方法,采用table來封裝,我們知道table的每一行寫滿了之后,下一行會自動添加,文章末尾附有完整的代碼,需要的朋友可以參考下
    2015-11-11
  • js處理包含中文的字符串實例

    js處理包含中文的字符串實例

    下面小編就為大家?guī)硪黄猨s處理包含中文的字符串實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • javascript中檢測變量的類型的代碼

    javascript中檢測變量的類型的代碼

    javascript對于變量的定義、類型的要求都比較松散,這樣既方便,但又容易犯錯。有時候進(jìn)行必要的類型檢查是必須的。
    2010-12-12
  • 原生js提示框并自動關(guān)閉(手工關(guān)閉)

    原生js提示框并自動關(guān)閉(手工關(guān)閉)

    今天在寫后臺交互的時候原來都是用alert太難看每次都需要點擊一下才可以,比較麻煩所以特整理了幾個比較好的js提示框代碼,方便提示一下
    2023-04-04
  • JS 排序輸出實現(xiàn)table行號自增前端動態(tài)生成的tr

    JS 排序輸出實現(xiàn)table行號自增前端動態(tài)生成的tr

    一個項目,需要對數(shù)據(jù)進(jìn)行排序輸出,要求有行號,依次遞增1.2.3.4.5,使用前端動態(tài)生成的tr
    2014-08-08
  • JS基于封裝函數(shù)實現(xiàn)的表格分頁完整示例

    JS基于封裝函數(shù)實現(xiàn)的表格分頁完整示例

    這篇文章主要介紹了JS基于封裝函數(shù)實現(xiàn)的表格分頁,結(jié)合完整實例形式分析了javascript針對table表格數(shù)據(jù)的遍歷、讀取以及模擬分頁顯示的相關(guān)操作技巧,需要的朋友可以參考下
    2018-06-06
  • JavaScript?中的作用域與閉包

    JavaScript?中的作用域與閉包

    這篇文章主要介紹了JavaScript中的作用域與閉包,JavaScript是一種具有函數(shù)優(yōu)先的輕量級,解釋型或即時編譯型的編程語言,下文是更多相關(guān)介紹需要的小伙伴可以參考一下
    2022-05-05
  • Jquery顏色選擇器ColorPicker實現(xiàn)代碼

    Jquery顏色選擇器ColorPicker實現(xiàn)代碼

    這里我要分享一個自己修改的顏色選擇器,有需要的朋友參考下
    2012-11-11

最新評論

客服| 呼伦贝尔市| 忻州市| 定日县| 同仁县| 丹巴县| 双城市| 安徽省| 浠水县| 天等县| 卢氏县| 农安县| 达尔| 奉化市| 浮梁县| 长治县| 西青区| 麦盖提县| 石楼县| 那曲县| 安吉县| 闽侯县| 靖江市| 内乡县| 新津县| 托克托县| 景洪市| 锡林浩特市| 桃园县| 山阴县| 朝阳县| 龙山县| 东莞市| 西充县| 邵阳县| 兴隆县| 鄂托克旗| 玛沁县| 铁力市| 景谷| 陇川县|