TS泛型進(jìn)階

拿下泛型财松,TS 還有什么難的嗎梆掸?

大家好扬卷,我是沐華,本文將剖析 TS 開發(fā)中常見工具類型的源碼實現(xiàn)及使用方式酸钦,并且搭配與內(nèi)容結(jié)合的練習(xí)怪得,方便大家更好的理解和掌握。本文目標(biāo):

  • 更加深入的理解和掌握泛型
  • 更加熟練這些內(nèi)置工具類型在項目中的運用

Exclude

Exclude<T, U>:作用簡單說就是把 T 里面的 U 去掉卑硫,再返回 T 里還剩下的徒恋。TU 必須是同種類型(具體類型/字面量類型)。如下

type T1 = Exclude<string | number, string>;
// type T1  = number; 

// 上面這個肯定一看就懂欢伏,那下面這樣呢

type T2 = Exclude<'a' | 'b' | 'c', 'b' | 'd'>;
// type T2  = 'a' | 'c';

怎么就剩個 a | c 了入挣?這怎么執(zhí)行的?

先看一張圖

三元表達(dá)式大家都知道硝拧,不是返回 a 就是返回 b径筏,這么算的話,這個 some 的類型應(yīng)該是 b 才對呀障陶,可這個結(jié)果是 a | b 又是怎么回事呢滋恬,這都是由于 TS 中的拆分或者說叫分發(fā)機制導(dǎo)致的

簡單說就是聯(lián)合類型并且是裸類型就會產(chǎn)生分發(fā),分發(fā)就會把聯(lián)合類型中的每一個類型單獨拿去判斷抱究,最后返回結(jié)果組成的聯(lián)合類型恢氯,a | b 就是這么來的,這個特性在本文后面會提到多次所以鋪墊一下鼓寺,這也是為什么反 Exclude 放在開頭的原因

結(jié)合 Exclude 的實現(xiàn)和例子來理解下

// 源碼定義
type Exclude<T, U> = T extends U ? never : T;

// 例子
type T2 = Exclude<'a' | 'b' | 'c', 'b' | 'd'>;
// type T2  = 'a' | 'c';

上面例子中的執(zhí)行邏輯:

  • 由于分發(fā)會把聯(lián)合類型中的每一個類型單獨拿去判斷的原因勋拟,會先把 T ,也就是前面 a | b | c 給拆分再單獨放入 T extends U ? never : T 判斷
  • 第一次判斷 a(T就是a) 妈候,U 就是 b | d 指黎,T 并沒有繼承自 U,判斷為假州丹,返回 T 也就是 a
  • 第二次判斷放入 b 判斷為真醋安,返回 neverts 中的 never 我們知道就是不存在值的意思墓毒,連 undefined 都沒有吓揪,所以 never 會被忽略崎弃,不會產(chǎn)生任何效果
  • 第三次判斷放入 c膳叨,判斷為假,和 a 同理
  • 最后將每一個單獨判斷的結(jié)果組成聯(lián)合類型返回走触,never 會忽略主胧,所以就剩下 a | c

總之就是:如果 T extends U 滿足分發(fā)的條件叭首,就會把所有單個類型依次放入判斷习勤,最后返回記錄的結(jié)果組合的聯(lián)合類型

Extract

Extract<T, U>:作用是取出 T 里面的 U ,返回焙格。作用和 Exclude 剛好相反图毕,傳參也是一樣的

看例子理解 Extract

type T1 = Extract<'a' | 'b' | 'c', 'a' | 'd'>;
// type T1  = 'a';

// 源碼定義
type Extract<T, U> = T extends U ? T : never

Exclude 源碼對比也只是三元表達(dá)式返回的 never : T 對調(diào)了一下,執(zhí)行原理也是一樣一樣兒的眷唉,就不重復(fù)了

Omit

Omit<T, K>:作用是把 T(對象類型) 里邊的 K 去掉予颤,返回 T 里還剩下的

Omit 的作用和 Exclude 是一樣的,都能做類型過濾并得到新類型冬阳。

不同的是 Exclude 主要是處理聯(lián)合類型蛤虐,且會觸發(fā)分發(fā),而 Omit 主要是處理對象類型肝陪,所以自然的這倆參數(shù)也不一樣驳庭。

用法如下

// 這種場景 type 和 interface 是一樣的,后面就不重復(fù)說明了
type User = {
    name: string
    age: number
}
type T1 = Omit<User, 'age'>
// type T1 = { name: string }

源碼定義

// keyof any 就是 string | number | symbol
type Omit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P]; }
  • 首先第一個參數(shù) T 要傳對象類型氯窍, typeinterface 都可以
  • 第二個參數(shù) K 限制了類型只能是 string | number | symbol饲常,這一點跟 js 里的對象是一個意思,對象類型的屬性名只支持這三種類型
  • in 是映射類型荞驴,用來映射遍歷枚舉類型。大白話就是循環(huán)贯城、循環(huán)語法熊楼,需要配合聯(lián)合類型來對類型進(jìn)行遍歷。in 的右邊是可遍歷的枚舉類型能犯,左邊是遍歷出來的每一項
  • Exclude 去除掉傳入的屬性后鲫骗,再遍歷剩下的屬性,生成新的類型返回

示例解析:

type User = {
    name: string
    age: number
    gender: string
}
type Omit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P]; }
type T1 = Omit<User, 'age'>
// type T1 = { name: string, gender: string }

我們調(diào)用 Omit 傳入的參數(shù)是正確的踩晶,所以就分析一下后面的執(zhí)行邏輯:

  • Exclude<keyof T, K> 等于 Exclude<'name'|'age'|'gender', 'age'>执泰,返回的結(jié)果就是 'name'|'gender

  • 然后遍歷 'name'|'gender',第一次循環(huán) P 就是 name渡蜻,返回 T[P] 就是 User['name']

  • 第二次循環(huán) P 就是 gender术吝,返回 T[P] 就是 User['gender'],然后循環(huán)結(jié)束

  • 結(jié)果就是 { name: string, gender: string }

Pick

Pick<T, K> :作用是取出 T(對象類型) 里邊兒的 K茸苇,返回排苍。

好像和 Omit 剛好相反,Omit 是不要 K 学密,Pick 是只要 K

傳參方式和 Omit 是一樣的淘衙,就不贅述了,用法示例:

type User = {
    name: string
    age: number
    gender: string
}
type T1 = Pick<User, 'name' | 'gender'>
// type T1 = { name: string, gender: string }

源碼定義

type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
  • 可以看到等號左邊做了泛型約束腻暮,限制了第二個參數(shù) K 必須是第一個參數(shù) T 里的屬性彤守。
  • 如果第二個參數(shù)傳入聯(lián)合類型毯侦,會觸發(fā)分發(fā),以此來確保準(zhǔn)確性具垫,聯(lián)合類型中的每一個單獨類型都必須是第一個對象類型中的屬性(不限制的話右邊就要出錯了)
  • 參數(shù)都正確之后侈离,等號右邊的邏輯其實就是和 Omit 一模一樣的了,直接遍歷 K做修,取出返回就完事兒了

練習(xí)一

請利用本文上述內(nèi)容完成:基于如下類型霍狰,實現(xiàn)一個去掉了 gender 的新類型,實現(xiàn)方法越多越好

type User = {
    name: string
    age: number
    gender: string
}

這個饰及?

type T1 = { name: string, age: number }

蔗坯??燎含?

我寫了幾個宾濒,歡迎補充:

type T1 = Omit<User, 'gender'>
type T2 = Pick<User, 'name' | 'age'>
type T3 = Pick<User, Exclude<keyof User, 'gender'>>
type T4 = { [P in 'name' | 'age'] : User[P] }
type T5 = { [P in Exclude<keyof User, 'gender'>] : User[P] }

Record

Record<K, T>:作用是自定義一個對象。K 為對象的 keykey 的類型屏箍,Tvaluevalue 的類型绘梦。

你有沒有這樣用過 ↓

const obj:any = {}

反正我有,其實用 Record 定義對象赴魁,在工作中還是很好用的卸奉,而且非常靈活,不同的對象定義上也會有一點區(qū)別颖御,如下

空對象

// never榄棵,會限制為空對象
// any 指的是 string | number | symbol 這幾個類型都行
type T1 = Record<any, never>
let obj1:T1 = {}    // ok
// let obj1:T1 = {a:1} 這樣不行,只能是空對象

任意對象

// 任意對象潘拱,unknown 或 {} 表示對象內(nèi)容不限疹鳄,空對象也行
type T1 = Record<any, unknown>
// 或
type T1 = Record<any, {}>
let obj2:T1 = {}    // ok
let obj3:T1 = {a:1}  // ok

自定義對象 key

type keys = 'name' | 'age'
type T1 = Record<keys, string>
let obj1:T1 = {
    name: '沐華',
    age: '18'
    // age: 18  報錯,第二個參數(shù) string 表示 value 值都只能是 string 類型
}

// 如果需要 value 是任意類型芦岂,下面兩個都行
type T2 = Record<keys, unknown>
type T3 = Record<keys, {}>

自定義對象 value

type keys = 'a' | 'b'
// type 或 interface 都一樣
type values<T> = {
    name?: T,
    age?: T,
    gender?: string
}

// 自定義 value 類型
type T1 = Record<keys, values<number | string>>
let obj:T1 = {
    a: { name: '沐華' },
    b: { age: 18 }
}

// 固定 value 值
type T2 = Record<keys, 111>
let obj1:T2 = {
    a: 111,
    b: 111
}

源碼定義

type Record<K extends any, T> = { [P in K]: T; }

左邊限制了第一個參數(shù) K 只能是 string | number | symbol 類型瘪弓,可以是聯(lián)合類型,因為右邊遍歷 K 了禽最,然后遍歷出來的每個屬性的值腺怯,直接賦值為傳入的第二個參數(shù)

Partial

Partial<T>:作用生成一個將 T(對象類型) 里所有屬性都變成可選的之后的新類型

示例如下:

type User = {
    name: string
    age: number
}
type T1 = Partial<User>
// 簡單說 T1 和 T2 是一模一樣的
type T2 = {
    name?: string
    age?: number
}

源碼定義

type Partial<T> = { [P in keyof T]?: T[P]; }

這下看源碼定義的是不是特別簡單,就是循環(huán)傳進(jìn)來的對象類型川无,給每個屬性加個 ? 變成可選屬生

Required

Required<T>:作用和 Partial<T> 剛好相反瓢喉,Partial 是返回所有屬性都是非必填的對象類型,而 Required 則是返回所有屬性都是必填項的對象類型舀透。參數(shù) T 也是一個對象類型栓票。

示例:

type User = {
    name?: string
    age?: number
}
type T1 = Required<User>
// 簡單說 T1 和 T2 是一模一樣的
type T2 = {
    name: string
    age: number
}

源碼定義

type Required<T> = { [P in keyof T]-?: T[P]; }

Partial 的源碼定義相比基本一樣的,只是這里多了個減號 -,沒錯走贪,就是減去的意思佛猛,-? 就是去掉 ?,然后就變成必填項了坠狡,這樣解釋是不是很好理解

Readonly

Readonly<T> :作用是返回一個所有屬性都是只讀不可修改的對象類型继找,與 PartialRequired 是非常相似的。參數(shù) T 也是一個對象類型逃沿。

示例:

type User = {
    name: string
    age?: number
}
type T1 = Readonly<User>
// 簡單說 T1 和 T2 是一模一樣的
type T2 = {
    readonly name: string
    readonly age?: number
}
type Readonly<T> = { readonly [P in keyof T]: T[P]; }

怎么樣婴渡?看到這是不是越發(fā)覺得源碼的類型定義越看越簡單了

我:那是不是說把所有只讀類型,全都變成非只讀就只需要 -readonly 就行了凯亮?

你:是的边臼,說得很對,就是這樣的

練習(xí)二

從上面幾個工具類型的源碼定義中我們可以發(fā)現(xiàn)假消,都只是簡單的一層遍歷柠并,就好像 js 中的淺拷貝,比如有下面這樣一個對象

type User = {
    name: string
    age: number
    children: {
        boy: number
        girl: number
    }
}

要把這樣一個對象所有屬性都改成可選屬性富拗,用 Partial 就行不通了臼予,它只能改變第一層,children 里的所有屬性都改不了啃沪,所以請寫一個可以實現(xiàn)的類型粘拾,功能類似深拷貝的意思

先稍微想想再往下看答案喲

寫出來一個的話,Partial 创千、Required 缰雇、 Readonly 的 “深拷貝” 類型是不是就都有了呢

想一下

// Partial 源碼定義
type Partial<T> = { [P in keyof T]?: T[P]; }

// 遞歸 Partial
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> }:T;

外層再加了一個三元表達(dá)式,如果不是對象類型直接返回签餐,如果是就遍歷寓涨;然后屬性值改成遞歸調(diào)用就可以了

// 遞歸 Required
type DeepRequired<T> = T extends object ? { [P in keyof T]-?: DeepRequired<T[P]> }:T;

// 遞歸 Readonly
type DeepReadonly<T> = T extends object ? { readonly [P in keyof T]: DeepReadonly<T[P]> }:T;

NonNullable

NonNullable<T>:作用是去掉 T 中的 nullundefined盯串。T 為字面量/具體類型的聯(lián)合類型氯檐,如果是對象類型是沒有效果的。如下

type T1 = NonNullable<string | number | undefined>;
// type T1 = string | number

type T2 = NonNullable<string[] | null | undefined>;    
// type T2 = string[]

type T3 = {
    name: string
    age: undefined
}
type T4 = NonNullable<T3> // 對象是不行的

源碼定義

// 4.8版本之前的版本
type NonNullable<T> = T extends null | undefined ? never : T;
// 4.8
type NonNullable<T> = T & {}

TS 4.8版本 之前的就是用一個三元表達(dá)式來過濾 null | undefined体捏。而在 4.8 版本直接就是 T & {}冠摄,這是什么原理呢?其實是因為這個版本對 --strictNullChecks 做了增加几缭,這主要體現(xiàn)還是在聯(lián)合類型和交叉類型上河泳,為什么這么說?

js 中都知道萬物皆對象年栓,原型鏈的最終點的正常對象就是 Object 了(null 算不正常的)拆挥,數(shù)據(jù)類型都是在原型鏈中繼承于 Object 派生出來的。

ts 中也一樣,由于 {} 是一個空對象纸兔,所以除了 nullundefined 之外的基礎(chǔ)類型都可以視作繼承于 {} 派生出來的惰瓜。或者說如果一個值不是 nullundefined 就等于 這個值 & {} 的結(jié)果汉矿,如下

type T1 = 'a' & {};  // 'a'
type T2 = number & {};  // number
type T3 = object & {};  // object
type T4 = { a: string } & {};  // { a: string }
type T5 = null & {};  // never
type T6 = undefined & {};  // never

如果 T & {} 中的 T 不是 null/undefined 就可以認(rèn)為它肯定符合 {} 類型崎坊,就可以把 {} 從交叉類型中去掉了,如果是洲拇,則會被判為 never奈揍,而 never 是會被忽略的(上面 Exclude 源碼定義里有提到),所以在結(jié)果里自然就排除掉了 nullundefined赋续。

還有如果 T & {} 中的 T 是聯(lián)合類型男翰,是會觸發(fā)分發(fā)的,這個就不再解釋了

練習(xí)三

請實現(xiàn)一個能去掉對象類型中 nullundefined 的類型

// 需要把如下類型變成 { name: string }
type User = {
    name: string
    age: null,
    gender: undefined
}

// 實現(xiàn)如下
type ObjNonNullable<T> = { [P in keyof T as T[P] extends null | undefined ? never : P]: T[P] };

type T1 = ObjNonNullable<User>
// type T1 = { name: string }

這里出現(xiàn)了一個本文第一次出現(xiàn)的關(guān)鍵字 as蚕捉,我們知道它可以用來斷言奏篙,在 ts 4.1 版本可以在映射類型里用 as 實現(xiàn)鍵名重新映射,達(dá)到過濾或者修改屬性名的目的迫淹,如果指定的類型解析為 never 時秘通,會被忽略不會生成這個屬性

如上只能過濾對象第一層的 nullundefined

如何更進(jìn)一步改成可以遞歸的呢?

type User = {
    name: string
    age: undefined,
    children: {
        boy: number
        girl: number
        neutral: null
    }
}
// 遞歸處理對象類型的 DeepNonNullable
type DeepNonNullable<T> = T extends object ? { [P in keyof T as T[P] extends null | undefined ? never : P]: DeepNonNullable<T[P]> } : T;

type T1 = DeepNonNullable<User>
// type T1 = {
//    name: string;
//    children: {
//        boy: number;
//        girl: number;
//    };
//}

Awaited

Awaited<T>:作用是獲取 async/await 函數(shù)或 promisethen() 方法的返回值的類型敛熬。而且自帶遞歸效果肺稀,如果是這樣嵌套的異步方法,也能拿到最終的返回值類型

示例:

// Promise
type T1 = Awaited<Promise<string>>;
// type T1 = string

// 嵌套 Promise应民,會遞歸
type T2 = Awaited<Promise<Promise<number>>>;
// type T2 = number

// 聯(lián)合類型话原,會觸發(fā)分發(fā)
type T3 = Awaited<boolean | Promise<number>>;
// type T3 = number | boolean

來看下源碼定義,看下到底是怎么執(zhí)行的诲锹,是怎么拿到結(jié)果的呢繁仁?

// 源碼定義
type Awaited<T> = T extends null | undefined
    ? T
    : T extends object & { then(onfulfilled: infer F): any }
        ? F extends (value: infer V, ...args: any) => any
            ? Awaited<V>
            : never
        : T

泛型條件有點多,就換了下行归园,方便看

  • 如果 Tnullundefined 就直接返回 T
  • 如果 T 是對象類型黄虱,并且里面有 then 方法,就用 infer 類型推斷出 then 方法的第一個參數(shù)onfulfilled 的類型賦值給 F庸诱,onfulfilled 其實就是我們熟悉的 resolve捻浦。所以這里可以看出或者準(zhǔn)確的說,Awaited 拿的不是 then() 的返回值類型桥爽,而是 resolve() 的返回值類型
    • 既然 F 是回調(diào)函數(shù) resolve 朱灿,就推斷出該函數(shù)第一個參數(shù)類型賦值給 Vresolve 的參數(shù)自然就是返回值
      • 傳入 V 遞歸調(diào)用
    • F 不是函數(shù)就返回 never
  • 如果 T 不是對象類型 或者 是對象但沒有 then 方法钠四,返回 T 盗扒,就是最后一行的 T

Parameters

Parameters<T>:作用是獲取函數(shù)所有參數(shù)的類型集合,返回的是元組。T 自然就是函數(shù)了

使用示例:

declare function f1(arg: { a: number; b: string }): void;

// 沒有參數(shù)的函數(shù)
type T1 = Parameters<() => string>;
// type T1 = []

// 一個參數(shù)的函數(shù)
type T2 = Parameters<(s: string) => void>;
// type T2 = [s: string]

// 泛型參數(shù)的函數(shù)
type T3 = Parameters<<T>(arg: T) => T>;
// type T3 = [arg: unknown]

// typeof f1 結(jié)果為 (arg: { a: number; b: string }) => void
type T4 = Parameters<typeof f1>;
// type T4 = [arg: {
//     a: number;
//     b: string;
// }]

// any 和 never
type T5 = Parameters<any>;
// type T5 = unknown[]
type T6 = Parameters<never>;
// type T6 = never

// 下面這樣傳參是會報錯的
type T7 = Parameters<string>;
type T8 = Parameters<Function>;
// 源碼定義
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

可以看到限制了函數(shù)類型侣灶,然后 ...args 取參數(shù)和 js 中的用法是一樣的习霹,infer 表示待推斷的類型變量,打斷出 ...args 取到的類型賦值給 P

ReturnType

ReturnType<T>:作用是獲取函數(shù)返回值的類型炫隶。T 為函數(shù)

示例:

declare function f1(): { a: number; b: string };

type T1 = ReturnType<() => string>;
// type T1 = string

type T2 = ReturnType<(s: string) => void>;
// type T2 = void

type T3 = ReturnType<<T>() => T>;
// type T3 = unknown

type T4 = ReturnType<<T extends U, U extends number[]>() => T>;
// type T4 = number[]

type T5 = ReturnType<typeof f1>;
// type T5 = {
//     a: number;
//     b: string;
// }

// any 和 never
type T6 = ReturnType<any>;
// type T6 = any
type T7 = ReturnType<never>;
// type T7 = never

// 下面這樣是不行的
type T8 = ReturnType<string>;
type T9 = ReturnType<Function>;
// 源碼定義
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

可以看到源碼定義上和 Parameters 是基本一樣的淋叶,只是把類型推斷的參數(shù)換成返回值了

ConstructorParameters/InstanceType

我們知道 ParametersReturnType 這一對是獲取普通/箭頭函數(shù)的參數(shù)類型集合以及返回值類型的了,還有一對組合ConstructorParametersInstanceType 是獲取構(gòu)造函數(shù)的參數(shù)類型集合以及返回值類型的伪阶,和上面的比較類似我就放到一起了

Uppercase/Lowercase

這倆兒的作用是轉(zhuǎn)換全部字母大小寫

type T1 = Uppercase<"abcd">
// type T1 = "ABCD"

type T2 = Lowercase<"ABCD">
// type T2 = "abcd"

Capitalize/Uncapitalize

這倆兒的作用是轉(zhuǎn)換首字母大小寫

type T1 = Capitalize<"abcd efg">
// type T1 = "Abcd efg"

type T2 = Uncapitalize<"ABCD EFG">
// type T2 = "aBCD EFG"

練習(xí)四

請實現(xiàn)一個類型煞檩,把對象類型中的屬性名換成大寫,需要注意的是對象屬性名支持 string | number | symbol 三種類型

type User1 = {
    name: string
    age: number
    18: number
}

// 實現(xiàn)如下栅贴,只需調(diào)用現(xiàn)在的工具類型 Uppercase 就行了

// 先取出所有字符串屬性的出來斟湃,再處理返回 { NAME: string, AGE: number }
// type T1<T> = { [P in keyof T & string as Uppercase<P>]: T[P] }
// 只處理字符串屬性的,其他正常返回
type T1<T> = { [P in keyof T as P extends string ? Uppercase<P> : P]: T[P] }

type T2 = T1<User1>
// type T2 = {
//     NAME: string;
//     AGE: number;
//     18: number
// }

綜合練習(xí)

請實現(xiàn)一個類型檐薯,可以把下劃線屬性名的對象凝赛,換成駝峰屬性名的對象。這個就沒有現(xiàn)成的工具類型調(diào)用了坛缕,所以需要我們額外實現(xiàn)一個

這個練習(xí)用到了本文中的很多知識墓猎,先自己寫一下咯

type User1 = {
    my_name: string
    my_age_type: number // 多個下劃線
    my_children: {
        my_boy: number
        my_girl: number
    }
}

// 實現(xiàn)如下
type T1<T> = T extends string
    ? T extends `${infer A}_${infer B}`
        ? `${A}${T1<Capitalize<B>>}` // 這里有遞歸處理單個屬性名多個下劃線
        : T
    : T;
// 對象不遞歸
// type T2<T> = { [P in keyof T as T1<P>]: T[P] }
// 對象遞歸
type T2<T> = T extends object ? { [P in keyof T as T1<P>]: T2<T[P]> } : T

type T3 = T2<User1>
// type T3 = {
//     myName: string;
//     myAgeType: number;
//     myChildren: {
//         myBoy: number;
//         myGirl: number;
//     };
// }

這個練習(xí)用到了 extendsinfer赚楚、as毙沾、循環(huán)遞歸宠页,相信能更好地幫助我們理解和運用

結(jié)語

如果本文對你有一點點幫助左胞,點個贊支持一下吧,你的每一個【贊】都是我創(chuàng)作的最大動力 _

更多前端文章举户,或者加入前端交流群烤宙,歡迎關(guān)注公眾號【前端快樂多】,大家一起共同交流和進(jìn)步呀

參考資料

https://www.typescriptlang.org/docs/handbook/utility-types.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俭嘁,一起剝皮案震驚了整個濱河市躺枕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兄淫,老刑警劉巖屯远,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔓姚,死亡現(xiàn)場離奇詭異捕虽,居然都是意外死亡,警方通過查閱死者的電腦和手機坡脐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門泄私,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事晌端⊥北” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵咧纠,是天一觀的道長蓬痒。 經(jīng)常有香客問我,道長漆羔,這世上最難降的妖魔是什么梧奢? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮演痒,結(jié)果婚禮上亲轨,老公的妹妹穿的比我還像新娘。我一直安慰自己鸟顺,他們只是感情好惦蚊,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讯嫂,像睡著了一般蹦锋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上欧芽,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天晕粪,我揣著相機與錄音,去河邊找鬼渐裸。 笑死巫湘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昏鹃。 我是一名探鬼主播尚氛,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洞渤!你這毒婦竟也來了阅嘶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤载迄,失蹤者是張志新(化名)和其女友劉穎讯柔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體护昧,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡魂迄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了惋耙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捣炬。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡熊昌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出湿酸,到底是詐尸還是另有隱情婿屹,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布推溃,位于F島的核電站昂利,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铁坎。R本人自食惡果不足惜页眯,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厢呵。 院中可真熱鬧窝撵,春花似錦、人聲如沸襟铭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寒砖。三九已至赐劣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哩都,已是汗流浹背魁兼。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留漠嵌,地道東北人咐汞。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像儒鹿,于是被迫代替她去往敵國和親化撕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容