TypeScript

概述

  • 类型就是一组值的集合

类型擦除

TS 转化成 JS

方法

  1. npm i -g esbuild => esbuild 1.ts > 1.js => 快(不检查 TS 语法)
  2. npm i -g @swc/cli @swc/core => swc 1.ts -o 1.js => 快(不检查 TS 语法)
  3. npm i -g typescript => tsc 1.ts

数据类型

  • null/undefined
  • string/number/boolean
  • bigint/symbol
  • object
  • void
  • any/unknow
  • never
  • enum
  • 自定义类型 => type(类型别名)/interface

TS 描述对象

  1. 使用 class/constructor 描述
  2. 使用 type/interface 描述
type A = {
    // 索引签名
    [key: string]: number
}

// Record 泛型
type B = Record<string, number>;

const n: BigInt = 100n;
console.log(n); // 100n
const symbol: Symbol = Symbol("s");
console.log(symbol); // Symbol(s)

const date: Date = new Date()
const regexp: RegExp = /ab+c/;
const regexp1: RegExp = new RegExp("/\d+/");
const map: Map<string, number> = new Map();
map.set("a", 1);
const set: Set<number> = new Set();
set.add(1);

TS 描述数组

  1. type A = string[] == type A = Array<string>
  2. 元组 => type A = [string, string, number] => length == 3
  3. type A = [1, 2, 3 | 4] => const a: A = [1, 2, 3]

TS 描述函数

  1. type FnA = (a: number, b: number) => string => const a: FnA = () => "hello world";
type FnReturnVoid = () => void;
type FnReturnUndefined = () => undefined;

const f1: FnReturnVoid = () => {
    console.log("hi");
}

const f2: FnReturnUndefined = () => {
    console.log("hi");

    // 此处需要显式声明
    return undefined;
}

type Person = {
    name: string,
    age: number,
    sayHi: FnWithThis
}
type FnWithThis = (this: Person, name: string) => void;

// 此处如果使用箭头函数,则 this 指向有问题
const sayHi: FnWithThis = function () {
    console.log("hi " + this.name)
}

const ming: Person = {
    name: "ming",
    age: 18,
    sayHi: sayHi
}

ming.sayHi("Jack");
sayHi.call(ming, "Jack");

any vs unknown

  • any => 全集 => 不做任何类型检查
  • any 不等于所有类型的联合 => 反证法:联合类型如果没有区分类型,只能使用交集的属性|方法
  • TS 绝大部分规则对 any 不生效 => const fn = (a: any) => {const b: never = a;} => 报错
  • unknown => 未知集 => 使用时需要先收窄类型 => 所有类型的联合(联合类型)

never

  • 空集
  • 不应该出现的类型

enum

  1. 使用一个有意义的名字代表一个状态
enum A {
    TODO = 0,
    DONE,
    ARCHIVED,
    DELETED
}

// 类型擦除时 A.DONE 会变成 1
let status: A = A.DONE;

  1. 通过使用位运算可以很方便的将权限进行组合和对比
enum Permission {
    NONE = 0, // 0000
    READ = 1 << 0, // 0001
    WRITE = 1 << 1, // 0010
    DELETE = 1 << 2, // 0100
    MANAGE = READ | WRITE | DELETE // 0111
}

type User = {
    permission: Permission
}

const user: User = {
    permission: 0b0011
}

if ((user.permission & Permission.WRITE) === Permission.WRITE) {
    console.log("拥有写权限")
}
  1. 使用 enum 做字符串映射字符串时可以使用类型别名代替
// enum Status {
//     done = 'done',
//     process = 'process',
//     start = 'start'
// }

type Status = 'done' | 'process' | 'start';

type

  • 类型别名
  • 声明一个别名,并不是一个新的类型
  • type 的值都是类型 => type 的值里面不能有任何值 => type nan = NaN // 这是错误的 => 'NaN' refers to a value, but is being used as a type here
  • type A = {name: string} => 表示 name 为 string 的对象
type A = { name: string };
const a1 = { name: 'ming', age: 18 };

// 此时 TS 不会做严格检查
const a2: A = a1;

// Error => TS 第一次声明的时候会进行严格检查
const a: A = { name: 'ming', age: 18 }

// 其中的 0、null、undefined、''、false 都是类型
type Falsy = 0 | null | undefined | '' | false;

// 类型A是一个集合,集合中只有一个0
type A = 0;

type FnWithProp = {
    (a: number, b: number): number,
    props1: number;
}

const fn: FnWithProp = (x, y) => x + y;
fn.props1 = 101;

console.log(fn);
console.log(fn.props1)

interface

  • 声明接口
  • 描述对象的属性 => declare the shapes of object

type vs interface

  1. interface 只描述对象,type 则描述所有类型
  2. interface 是类型声明,type 是别名
    type A = string;
    type B = A; // B is string
    
    interface C extends Date {
    }
    
    type D = C; // D is C
    
  3. interface 可以进行 merging(扩展),type 不可以重新赋值 => 对外 API 尽量使用 interface,方便扩展。对内 API 尽量使用 type(case: props),防止代码分散
  4. interface extends 时如果属性冲突,直接报错,type 会联合类型到 never

Object vs object

  • Object 包含 Number/String/Boolean 包装类型
  • object 不包含包装类型

void vs null vs undefined

  • void 不可以赋值给 null 和 undefined
  • undefined 可以赋值给 void
  • 其余需要看配置 => strictNullChecks
let a: void

// 此时 void = null | undefined
if (typeof a === 'object') {
    // a is null
    console.log(a);
} else if (typeof a === 'undefined') {
    // a is undefined 
    console.log(a);
} else {
    // a is never 
    console.log(a);
}

// 此时 void = undefined
if (typeof a === 'undefined') {
    // a is undefined
    console.log(a);
} else if (typeof a === 'object') {
    // a is never 
    console.log(a);
} else {
    // a is never 
    console.log(a);
}

Assignment

Assignment

TS 类型系统运算

联合类型

  • union types => 并集

JS 语言特性区分类型

  • 使用 JS 的语言特性 typeof + instanceof + in 进行类型收窄
const fn = (a: number | string) => {
    // 类型收窄 => Narrowing
    if (typeof a === 'number') {
        a.toFixed(2);
    } else if (typeof a === 'string') {
        a.split(',');
    } else {
        // a === never
        console.log(a);
    }
}

type Person = {
    name: string;
}

type Animal = {
    x: string;
}

const f1 = (param: Person | Animal) => {
    // 使用 in 进行类型收窄
    if ('name' in param) {
        console.log(param.name);
    } else {
        console.log(param.x);
    }
}

类型谓词 is

type Rect = {
    height: number,
    width: number
}

type Circle = {
    center: [ number, number ],
    radius: number
}

const fn = (a: Rect | Circle) => {
    if (isRect(a)) {
        // a is Rect
        console.log(a.height);
    } else if (isCircle(a)) {
        // a is Circle
        console.log(a.radius);
    } else {
        // a is never
        console.log(a);
    }
}

function isRect(x: Rect | Circle): x is Rect {
    return 'height' in x && 'width' in x;
}

// 此处 x is Circle 不能写在左侧 => const isCircle: (x: Rect | Circle) => x is Circle = x => 'radius' in x && 'center' in x;
const isCircle = (x: Rect | Circle): x is Circle => {
    return 'radius' in x && 'center' in x;
}

可辨别联合 Discriminated Unions

  • 让复杂类型的收窄变成简单类型的对比
type Circle = { kind: "Circle", center: [ number, number ] };
type Square = { kind: "Square", sideLength: number };
type Shape = Circle | Square;

const f1 = (a: string | Shape) => {
    if (typeof a === 'string') {
        // a is string
        a.split(',');
    } else if (a.kind === 'Circle') {
        // a is Circle
        console.log(a.center);
    } else {
        // a is Square
        console.log(a.sideLength);
    }
}

断言 as

// a is string
let a = 'hi';

// b is hi
const b = 'hi';

// c is hi
let c = 'hi' as const;

// array is (number | string)[] => 对于引用类型在 TS 中 const 相当于 let
const array = [ 1, 'hi' ];

// array1 is readonly [1, 'hi']
const array1 = [ 1, 'hi' ];

// Error: Property 'push' does not exist on type 'readonly [1, "hi"]'.
array1.push(2);

交叉类型 intersection types

  • 交叉类型常用于有交集的类型 A B
  • 如果 A、B 无交集,可能得到 never,也可能属性为 never

type

type Left = {
    left: string;
}

type Right = {
    right: string;
}

type C = Left | Right;
type D = Left & Right;
const c: C = {
    left: "left"
}
const d: D = {
    left: 'left',
    right: 'right'
}

type Person = {
    name: string;
    age: number;
    id: string
}

// 此处 id 属性的类型是 string & number => id is never
type User = Person & {
    id: number;
    email: string;
}

const user: User = {
    id: 1 as never,
    name: 'ming',
    age: 18,
    email: 'email'
}

// userIsNever is never
type UserIsNever = { id: 'A' } & { id: 'B' };

interface

interface Colorful {
    color: string;
}

interface Circle {
    radius: number
}

type ColorfulCircle = Colorful & Circle;

interface Person {
    id: string;
    name: string;
}

interface User extends Person {
    // Error: type 'number' is not assignable to type 'string'.
    // id: number;
    email: string;
}

神奇

type A = {
    method: (n: number) => void;
}

type B = {
    method: (n: string) => void;
} & A;
const b: B = {
    // n is any
    method: (n) => {
        console.log(n);
    }
}

type F1 = (n: number) => void;
type F2 = (n: string) => void;
type X = F1 & F2;
const x: X = (n) => {
    // n is any
    console.log(n);
    n.split(',')
    n.toFixed(2);
}

类型兼容 & 赋值

基本类型

type A = string | number;
const a: A = 'hi';

普通对象

type Person = {
    name: string;
    age: number;
}

let user = { name: 'ming', age: 18, id: 1 };
let p: Person;
p = user;
// type User = {name: string; age: number; id: nubmer}
type User = typeof user;

接口

interface Parent {
    x: string;
}

interface Sub extends Parent {
    y: string;
}

let sub: Sub = {
    x: '1',
    y: '2'
};

let p: Parent;
p = sub;

函数

  • 函数包括参数返回值
  • 参数少的函数可以赋值给参数多的函数
  • 对参数要求少函数可以赋值给对参数要求多的函数

深入对象语法

索引签名 Index Signature

type Hash = {
    [k: string]: unknown;
    length: number;
}

映射类型 Mapped Type

  • 多用于泛型
type Hash = {
    // 不能再写其他属性
    [key in string]: unknown;

    // length: number; => error
}

只读属性 readonly

interface User {
    readonly id: number;
    age?: number;
}

const user: User = {
    id: 1,
    age: 18,
}
// Error: Cannot assign to 'id' because it is a read-only property
user.id = 2;

深入函数语法

对象语法全部适用于函数

type F = {
    (a: number, b: number): number;
    readonly count?: number;
}

type F1 = (a: number, b: number) => number;

const f: F = (x, y) => x + y;

声明函数及其类型

// First
type F1 = (a: number, b: number) => number;
const f1: F1 = (a, b) => a + b;

// Second
const f2 = (a: number, b: number): number => a + b;
type F2 = typeof f2;

// Third
function f3(this: unknown, a: number, b: number): number {
    return a + b;
}

type F3 = typeof f3;

// Fourth
const f4 = function (this: unknown, a: number, b: number): number {
    return a + b;
}
type F4 = typeof f4;

类型谓词

可选参数

参数默认值

function addEventListener(eventType: string, fn: (this: HTMLElement, e: Event) => void, useCapture = false) {
    const element = {} as HTMLElement;
    const event = {} as Event;
    fn.call(element, event);
}

addEventListener('click', () => console.log(1));

重载 overload

  • overload => 同名函数参数不同
  • 参数类型不同 => 联合类型
  • 参数个数不同 => 考虑是用 overload
  • 参数类型和个数都不同
function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(a: number, b?: number, c?: number): Date {
    if (a !== undefined && b !== undefined && c !== undefined) {
        return new Date(a, b, c);
    } else if (a !== undefined && b === undefined && c === undefined) {
        return new Date(a);
    } else {
        throw new Error('Params is invalid!');
    }
}

// Error: No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
createDate(2022, 10);

展开参数

function sum(...array: number[]) {
    // Error: Argument of type 'number[]' is not assignable to parameter of type 'number'.
    // f(array)
    f(...array);
    f.apply(null, array);
}

function f(...array: number[]) {
    console.log(array);
}

as const

function add(a: number, b: number): number {
    return a + b;
}

// arr is number[]
const arr = [ 1, 2 ];

// Error: A spread argument must either have a tuple type or be passed to a rest parameter.
add(...arr);

// arr1 is readonly [1, 2]
const arr1 = [ 1, 2 ] as const;
add(...arr1);

解构

type Config = {
    url: string;
    method: "GET" | "POST" | "PUT" | "DELETE";
    body?: unknown;
    headers?: unknown
}

// rest is {body?: unknown; headers?: unknown}
function ajax({ url, method, ...rest }: Config = { url: '', method: "GET" }) {
}

function ajax1({ url, method, ...rest } = { url: '', method: "GET" } as Config) {
}

泛型 Generic Type

  • 推后执行的、部分待定的类型
type F<A, B> = A | B;

// Result is number | string
type Result = F<number, string>;

function echo(msg: number | boolean | string) {
    return msg;
}

// F is (msg: number | boolean | string) => number | boolean | string;
type F = typeof echo;

类型参数默认值

interface Hash<V = string> {
    [k in string]: V;
}

条件类型 Conditional Type

  • 以下两条仅对泛型有效
  • 如果 T 是 never,则表达式的值为 never
  • 如果 T 是联合类型,则分开计算
type LikeString<T> = T extends string ? true : false;

type R1 = LikeString<'hi'>; // true
type R2 = LikeString<true>; // false
type X = LikeString<never>; // never
type Y = LikeString<string | number>; // Y is boolean

type ToArray<T> = T extends unknown ? T[] : never;
type Result = ToArray<number | string>; // Result is number[] | string[]
type Result1 = ToArray<never>; // Result1 is never

type Z = never extends unknown ? 1 : 2; // Z is 1

// 此处不能对应 string -> string[]  number -> number[] 
type U = string | number extends unknown ? string[] | number[] : 1;

keyof

type Person = { name: string, age: number };
type GetKeys<T> = keyof T;
type Result = GetKeys<Person>; // Result is name | age

泛型约束

type GetKeyType<T, K extends keyof T> = T[K];
type Result1 = GetKeyType<Person, "name">; // string
type Test = Person['name']; // Test is string

内置

Readonly

type Person = { name: string, age: number };
// type Result = {
//   readonly name: string;
//   readonly age: number;
// }
type Result = Readonly<Person>;
// type Readonly<T> = {
//     readonly [k in keyof T]: T[k];
// }

Partial

type Person = { name: string, age: number };
// type Result = {
//     name?: string | undefined;
//     age?: number | undefined;
// }
type Result = Partial<Person>;
// type Partial<T> = {
//   [k in keyof T]?: T[k];
// }

Required

type Person = { name: string, age: number, email?: string };
// type Result = {
//   name: string;
//   age: number;
//   email: string;
// }
type Result = Required<Person>;
// type Required<T> = {
//   [k in keyof T]-?: T[k];
// }

Record

type Result = Record<string, number>;
// type Record<K extends string | number | symbol, T> = {
//   [k in K]: T;
// }

Exclude

type Result = Exclude<1 | 2 | 3, 1 | 2>; // Result is 3
// type Exclude<T, K> = T extends K ? never : T;

Extract

type Result = Extract<1 | 2 | 4, 2 | 3>; // Result is 3
// type Extract<T, K> = T extends K ? T : never;

Pick

type Person = { name: string, age: number, email?: string };
// type Result = {
//   name: string | number;
//   age: string | number;
// }
type Result = Pick<Person, 'name' | 'age'>;
// type Pick<T, K extends keyof T> = {
//   [key in K]: T[K];
// }

Omit

type Person = { name: string, age: number, email?: string };
// type Result = {
//     email?: string | undefined;
// }
type Result = Omit<Person, 'name' | 'age'>;
// type Omit<T, K> = {
//     [k in keyof T as (k extends K ? never : k)]: T;
// }

// 使用 Pick
// type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

-readonly

type Person = { name: string, age: number, email?: string };
// type Result = {
//   name: Readonly<Person>;
//   age: Readonly<Person>;
//   email?: Readonly<Person> | undefined;
// }
type Result = Mutable<Readonly<Person>>;
type Mutable<T> = {
    -readonly [key in keyof T]: T;
}

class

class Point {
    x: number;
    y: number;

    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
}

// 等价于
class Point1 {
    constructor(public x = 0, public y = 0) {

    }
}

class Hash {
    [key: string]: unknown;

    set(key: string, value: unknown) {
        this[key] = value;
    }

    get(key: string) {
        return this[key];
    }
}

constructor reload

class Point {
    x!: number;
    y!: number;

    constructor(x: number, y: number);
    constructor(str: string);
    constructor(xs: number | string, y?: number) {
        if (typeof xs === 'number' && typeof y == 'number') {
            this.x = xs;
            this.y = y;
        } else if (typeof xs === 'string') {
            this.x = parseFloat(xs.split(',')[0]);
            this.x = parseFloat(xs.split(',')[1]);
        }
    }
}

implements

  • implements 仅仅会做类型检查,其余不会进行任何操作
interface Person {
    name: string;
    sayHi: (target: Person) => void;
}

interface Taggable {
    tags: string[];
    addTag: (tag: string) => void;
    removeTag: (tag: string) => void;
}

class User implements Person, Taggable {
    constructor(public name: string, public tags: string[] = []) {
    }

    sayHi(target: Person) {
        console.log('Hi ' + target.name);
    }

    addTag(tag: string) {
        this.tags.push(tag);
    }

    removeTag(tag: string) {
        this.tags.splice(this.tags.indexOf(tag), 1);
    }
}

const ming = new User('ming');
ming.sayHi(ming);

declare

  • 重写属性
class Person {
    friend?: Person;

    constructor(public name: string, friend?: Person) {
        this.friend = friend;
    }
}

class User extends Person {
    // Error: Property 'friend' will overwrite the base property in 'Person'. If this is intentional, add an initializer. Otherwise, add a 'declare' modifier or remove the redundant declaration.
    // friend?: User;
    declare friend?: User;

    constructor(public id: string, name: string, friend?: User) {
        super(name, friend);
    }
}

const u1 = new User("1", "jack");

const u2 = new User("2", "george", u1);

// 如果没有 declare => Friend is Person | undefined
// 如果有 declare => Friend is User | undefined
type Friend = typeof u2.friend;

public & private & protected

  • public => 类外可见
  • private => 类内可见 => 类型擦除之后就没有 private 了,在 js 中仍然可以使用该属性
  • protected => 子类可见
  • #<var> => 真私有属性 => 类型擦除之后会保留,在 js 中不可使用该属性

static

  • 通过类型访问
  • 类属性/静态属性
  • 不能有 static name => 类是使用函数模拟的,函数本身有 name 属性
  • static block
class Foo {
    static #count = 0;
    static {
        const count = loadFromLocalStorage() || 0;
        Foo.#count += count;
    }

    constructor() {
        console.log(Foo.#count);
    }
}

抽象类

  • 抽象类不能直接实例化
abstract class A {
    abstract name: string;
    age: string;

    constructor(age: string) {
        this.age = age;
    }

    abstract getName(): string;

    printName() {
        console.log("Hi, " + this.getName());
    }
}

类作为参数

class Person {
    constructor(public name: string) {
        this.name = name;
    }
}

function f(X: typeof Person) {
    const p = new X('George');
}

f(Person)

// new 表示传入的参数必须是 class
function f2(X: new (name: string) => Person) {
    const p = new X("George");
}

f2(Person);

类型体操

// if (A <= B) true else false
type A = 1;
type B = 1 | 2;

type Result = A extends B ? true : false;
// 空元组
type A = [];
type IsEmptyArray<Arr extends unknown[]> = Arr['length'] extends 0 ? true : false;

type Result = IsEmptyArray<[]>;

// 非空元组
type B = [ 1 ];
type NotEmpty<Arr extends unknown[]> = Arr extends [ ...unknown[], unknown ]
    ? true
    : false;
type NotEmpty1<Arr extends unknown[]> = Arr extends [ ...infer X, infer Last ]
    ? true
    : false;

type Result1 = NotEmpty<B>;

递归

  • TS 最多递归 48 层
type A = [ 1, 2, 3, 4 ];
type Reverse<Arr extends unknown[]> = Arr extends [ ...infer Rest, infer Last ] ? [ Last, ...Reverse<Rest> ] : Arr;

type Result = Reverse<A>;

模式匹配 + infer

  • infer => decoratively introduce a new generic type variable
type A = [ 1, 2, 3, 4 ];

// Result1 is 1
type Result1 = A extends [ infer First, ...infer Rest ] ? First : never;

// Result2 is [2, 3, 4]
type Result2 = A extends [ string, ...infer Rest ] ? Rest : never;

元组体操

// 将元组加长
type A = [ 1 ];
type B = [ ...A, number ];
type C = [ ...B, 'hi' ];
type D = [ ...B, ...C ];

// get Last type in Tuple
type Last<T extends unknown[]> = T extends [ ...unknown[], infer Last ] ? Last : never;
type Hi = Last<C>; // Hi is 'hi'

// Error => TS 没有减法
// type Last<T extends unknown[]> = T[T["length"] - 1];



字符串体操

Capitalize & Uncapitalize & Uppercase & Lowercase

  • TS 内部实现
  • Capitalize => 首字母大写
  • Uncapitalize => 首字母小写
  • Uppercase => 全部大写
  • Lowercase => 全部小写
type A = "george";
type B = Capitalize<A>; // B is George

type C = 'hi' | 'george';
type D = Capitalize<C>; // D is 'Hi' | 'George'

模板字符串

type A = "Hi";
type B = "George";

type Result = `${A} ${B}`;

模式匹配

type A = "Hi George";
type First<T extends string> = T extends `${infer F}${string}` ? F : never;
type A = "Hi George";

type LastOfTuple<T extends unknown[]> = T extends [ ...infer _, infer L ] ? L : never;

// String to Tuple
type StringToTuple<S extends string> = S extends `${infer F}${infer Rest}` ? [ F, ...StringToTuple<Rest> ] : [];
type Last<T extends string> = LastOfTuple<StringToTuple<T>>;

type Result = First<A>; // Result is H
type ResultOfLast = Last<A>; // Result is e

String to Union

  • 联合类型自动去重
type A = "Hi George";
type StringToUnion<S extends string> = S extends `${infer First}${infer Rest}` ? First | StringToUnion<Rest> : never;
type Result = StringToUnion<A>;

TSX

  • 在 TSX 中标签式断言会和标签冲突,此时使用 as 断言

知识点

  1. 在 TSX 中 input 的 onChange 实际上调用的是 oninput 事件
    • onchange => 触发时机是失去焦点时
    • oninput => 触发时机是输入时
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容