22.compositionApi中的ref,reactive,readonly讲解

reactive

如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive函数

那么这是什么原因呢?为什么就可以变成响应式的呢?
  • 这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;
  • 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
  • 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
<template>
  <div>
    <div>数量:{{ state.count }}</div>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  setup() {
    //reactive函数把对象参数处理过后,返回一个响应式对象,响应式对象内部的key的值都是响应式的
    const state = reactive({
      count: 100,
    });

    const increment = () => {
      state.count++;
      console.log(state.count);
    };

    return {
      state,
      increment,
    };
  },
};
</script>

<style lang="scss" scoped></style>

reactive函数把对象参数处理过后,返回一个响应式对象,
响应式对象内部的key的值都是响应式的
缺陷:响应式变量被存放到对象中,不管在template还是在setup中使用都需要从对象中获取

reactive函数的参数类型

reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:

  • 如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;
    image.png

ref API

ref函数 会返回一个可变的响应式对象ref对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;

  • 它内部的值是在ref对象的 value属性中被维护的;
这里有两个注意事项:
  • 在template模板中使用ref对象的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式使用,直接使用ref对象就行
  • 但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;
template模板中的对ref对象的解包是一个浅层的解包
  • 如果包裹着ref对象的是普通对象,vue不会自动帮我们把ref对象解包
  • 如果包裹着ref对象的是reactive函数返回的可响应式对象,vue依然会自动帮我们把ref对象解包
<template>
  <div>
    <!-- 在template模板中使用ref对象,vue会自动帮我们解包
    不需要再去value属性中获取值 -->
    <div>数量:{{ count }}</div>

    <!-- template中的解包是浅层的解包,
    如果包裹响应式变量的对象是普通对象,vue不会自动帮我们解包 -->
    <div>数量:{{ info.count.value }}</div>

    <!-- 如果包裹响应式变量的对象是reactive函数返回的对象,
    vue会自动帮我们解包,不需要通过state.count.value获取 -->
    <div>数量:{{ state.count }}</div>

    <button @click="increment">+1</button>
  </div>
</template>

<script>
import { ref, reactive } from "vue";
export default {
  setup() {
    //ref函数返回一个ref对象,参数存储在ref对象的value属性中
    //ref对象是一个可响应式对象
    const count = ref(100);
    const info = { count }; //info是一个普通对象,此对象的count属性的值是count这个ref对象
    const state = reactive({
      //state是一个响应式对象,此对象的属性count的值count这个ref对象
      count,
    });

    const increment = () => {
      count.value++; //ref对象的值存储在value属性中
      console.log(count.value);
    };

    return {
      count,
      info,
      state,
      increment,
    };
  },
};
</script>

<style lang="scss" scoped></style>

readonly

我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?

Vue3为我们提供了readonly的方法;
  • readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不
    能对其进行修改);
在开发中常见的readonly方法会传入三个类型的参数:
  • 类型一:普通对象;
  • 类型二:reactive返回的对象;
  • 类型三:ref的对象;

readonly的使用

在readonly的使用过程中,有如下规则:

  • readonly返回的对象都是不允许修改的;
  • 但是经过readonly处理的原来的对象是允许被修改的;
    • 比如 const info = readonly(obj),info对象是不允许被修改的;
    • 当obj被修改时,readonly返回的info对象也会被修改;
    • 但是我们不能去修改readonly返回的对象info;
  • 其实本质上就是readonly返回的对象的setter方法被劫持了而已;

readonly的使用场景

在我们传递给其他组件数据时,往往希望其他组件使用我们传递的内容,但是不允许它们修改时,就可以使用readonly了;
需求:父组件App.vue传递给子组件Home.vue的响应式变量,在父组件内部修改了响应式变量的值,子组件中的数据要会响应式更新,但是子组件不能修改通过属性从父组件接收到的响应式变量的值
App.vue

<template>
  <div>
    <button @click="update">app:修改</button>
    <home :info="readonlyInfo2" :name="readonlyInfo3" />
  </div>
</template>

<script>
import { ref, reactive, readonly } from "vue";
import Home from "./Home.vue";
export default {
  name: "App",
  components: {
    Home,
  },
  setup() {
    // 1.info1为一个普通对象
    const info = { name: "why" };
    const readonlyInfo1 = readonly(info);

    // 2.info2为一个reactive返回的响应式对象
    //reactive对象被readonly函数转换后的返回值的用法和之前的reactive对象一样,只是不能被修改
    const info2 = reactive({ name: "why" });
    const readonlyInfo2 = readonly(info2);

    // 3.info3是一个ref对象
    //ref对象被readonly函数转换后的返回值的用法和之前的ref对象一样,只是不能被修改
    //依然可以在template中自动解包
    const info3 = ref("why");
    const readonlyInfo3 = readonly(info3);

    const update = () => {
      info.name = "lily";
      info2.name = "kobe";
      info3.value = "curry";
    };

    // 所以传给子组件时可以把readonlyInfo2或readonlyInfo3传给子组件
    //在子组件内部只能使用,但不能修改readonlyInfo2或readonlyInfo3中的属性的值
    //但是在当前组件可以通过info2或info3修改属性的值,子组件中也会响应式更新

    return {
      readonlyInfo2,
      readonlyInfo3,
      info2,
      info3,
      update,
    };
  },
};
</script>

<style></style>

Home.vue

<template>
  <div>
    <div>名字: {{ name }}</div>
    <div>信息: {{ info.name }}</div>
    <button @click="updateName">修改名字</button>
    <button @click="updateInfo">修改信息</button>
  </div>
</template>

<script>
export default {
  props: {
    info: {
      type: Object,
      required: true,
    },
    name: {
      type: String,
      required: true,
    },
  },
  setup(props) {
    const updateName = () => {
      props.name = "coder"; //报错
    };
    const updateInfo = () => {
      props.info.name = "coder"; //报错
    };

    return {
      updateName,
      updateInfo,
    };
  },
};
</script>

<style lang="scss" scoped></style>

image.png

image.png

所以传给子组件时可以把readonlyInfo2或readonlyInfo3只读变量传给子组件
在子组件内部只能使用,但不能修改readonlyInfo2中的属性的值或readonlyInfo3的值
但是在当前组件可以通过info2或info3修改属性的值,子组件中也会响应式更新

Reactive判断的API

isProxy
  • 检查对象是否是由 reactive 或 readonly创建的 proxy。
isReactive
  • 检查对象是否是由 reactive创建的响应式代理:
  • 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
isReadonly
  • 检查对象是否是由 readonly 创建的只读代理。
toRaw
  • 返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
shallowReactive
  • 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
shallowReadonly
  • 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
案例
<template>
  <div>
    <div>reactive: {{ info.friend.name }}</div>
    <div>shallowReactive: {{ info1.friend.name }}</div>
    <button @click="updateInfo">修改info</button>
    <button @click="updateInfo1">修改info1</button>
  </div>
</template>

<script>
import {
  reactive,
  readonly,
  shallowReadonly,
  isProxy,
  isReactive,
  isReadonly,
  shallowReactive,
} from "vue";
export default {
  name: "App",
  setup() {
    //reactive创建的reactive对象,不管内嵌多少层都是响应式的,即inof.friend.name值改变,页面也会响应式更新
    const info = reactive({ name: "why", age: 18, friend: { name: "kobe" } });
    //shallowReactive创建的reactive对象,只有其自身的属性是响应式的,内嵌对象的属性不是响应式的,
    //即inof.friend.name值改变,页面不会响应式更新
    const info1 = shallowReactive({
      name: "why",
      age: 18,
      friend: { name: "kobe" },
    });
    const updateInfo = () => {
      info.friend.name = "curry";
      console.log("info", info.friend.name);
    };
    const updateInfo1 = () => {
      info1.friend.name = "curry";
      console.log("info1", info1.friend.name);
    };

    //readonlyInfo.name和readonlyInfo.friend.name都不可以被修改
    const readonlyInfo = readonly(info);
    //shallowReadonlyInfo.friend.name可以被修改,shallowReadonlyInfo.name不能被修改
    const shallowReadonlyInfo = shallowReadonly(info);

    const message = readonly({ name: "苹果", price: 18 });
    console.log(isProxy(message)); //true
    console.log(isProxy(info)); //true
    console.log(isProxy(readonlyInfo)); //true

    console.log(isReactive(info)); //true
    console.log(isReactive(readonlyInfo)); //true
    console.log(isReactive(info.friend)); //true
    console.log(isReactive(info1.friend)); //false

    console.log(isReadonly(readonlyInfo)); //true

    return {
      info,
      info1,
      updateInfo,
      updateInfo1,
    };
  },
};
</script>

<style></style>

image.png

toRefs

如果我们使用ES6的解构语法,对reactive返回的对象state进行解构获取值,解构出来的变量name, age,只是普通的值,和原reactive对象state没有关系,

  • 修改reactive返回的state对象的属性name,age的值,不会影响之前已经解构出的name, age变量,
  • 修改变量name和age的值,也不会影响原reactive返回的state对象
  • 在template模板中使用变量name, age,当变量name, age值改变时,页面中数据不会响应式更新
const info = reactive({ name: "why", age: 18 });
let { name, age } = info;
那么有没有办法让我们解构出来的属性是响应式的呢?
  • Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;
  • 那么我们再次进行解构出来的 name 和 age 本身都是 ref对象;
  • 这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化;
    • 也就是修改name.value值,state.name也会发生改变,
    • 修改state.name的值,name.value也会发生改变
    • 如果在template模板中使用了变量name, age,name.value和age.value的值改变,页面中的数据也会响应式更新
    • toRefs函数的参数必须是reactive对象
const info = reactive({ name: "why", age: 18 });
let { name, age } = toRefs(info);
案例1:使用es6解构出的变量为普通的值:

解构出的变量的值是普通的值,不是响应式变量,值修改,页面中的数据不会响应式更新,与原reactive也没有关系

<template>
  <div>
    <h1>名字:{{ name }},年龄:{{ age }}</h1>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  name: "App",
  setup() {
    const info = reactive({ name: "why", age: 18 });
    //通过此种对象解构的方式,获取的name,age变量的值,只是普通的值,因为不是ref对象或reactive对象,所以不具备响应式 ,
    //修改其值,页面也不会重新渲染
    //相当于下面写法:name = 'why'   age = 18
    let { name, age } = info;

    const changeName = () => {
      info.name = "kobe"; //修改info.name,name不会发生改变
      console.log(info.name, name); //kobe  why
    };
    const changeAge = () => {
      age++; //修改age,页面中的数据也不会响应式更新, info.age不会发生改变,
      console.log(info.age, age); //18  19
    };

    return {
      name,
      age,
      changeAge,
      changeName,
    };
  },
};
</script>

<style></style>

image.png
案例2:使用ref创建ref对象, 参数为reactive对象的某个属性的值:

ref函数只是获取了reactive对象的某个属性的值,ref函数返回的ref对象和原reactive对象没有其他关系,互不影响

<template>
  <div>
    <h1>名字:{{ name }},年龄:{{ age }}</h1>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>

<script>
import { reactive, ref, toRefs } from "vue";
export default {
  name: "App",
  setup() {
    const info = reactive({ name: "why", age: 18 });
    //下面这种方式,变量age和name获取的只是info对应属性的值,
    //修改变量age和name的value,或者修改info的name,age属性的值,都不会影响对方
    const name = ref(info.name);
    const age = ref(info.age);

    const changeName = () => {
      info.name = "kobe"; //修改info.name,name.value不会发生改变
      console.log(info.name, name.value); //kobe  why
    };
    const changeAge = () => {
      age.value++; //修改age,页面中的数据会响应式更新, info.age不发生改变,
      console.log(info.age, age.value); //18  19
    };

    return {
      name,
      age,
      changeAge,
      changeName,
    };
  },
};
</script>

<style></style>

案例3:使用toRefs解构出的变量为ref对象:

使用toRefs会将reactive返回的对象中的属性都转成ref,
从info解构出的变量都是ref对象,且跟原reactive对象的对应属性建立了链接,一个变量,另一个也会改变

<template>
  <div>
    <h1>名字:{{ name }},年龄:{{ age }}</h1>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>

<script>
import { reactive, ref, toRefs } from "vue";
export default {
  name: "App",
  setup() {
    const info = reactive({ name: "why", age: 18 });
    // 使用toRefs会将reactive返回的对象中的属性都转成ref,
    //从info解构出的变量都是ref对象,且根原reactive对象的对应属性建立了链接,一个变量,另一个也会改变
    let { name, age } = toRefs(info);

    const changeName = () => {
      info.name = "kobe"; //修改info.name,name.value会发生改变
      console.log(info.name, name.value); //kobe  kobe
    };
    const changeAge = () => {
      age.value++; //修改age,页面中的数据会响应式更新, info.age也会发生改变,
      console.log(info.age, age.value); //19  19
    };

    return {
      name,
      age,
      changeAge,
      changeName,
    };
  },
};
</script>

<style></style>

image.png

toRef

如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
使用toRef函数返回一个ref对象,和原reactive对象中对应属性建立了链接,一个改变,另一个也改变

<template>
  <div>
    <h1>名字:{{ name }},年龄:{{ age }}</h1>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>

<script>
import { reactive, toRef } from "vue";
export default {
  name: "App",
  setup() {
    const info = reactive({ name: "why", age: 18 });
    let { age } = info; //变量age的值为普通的值
    // 从info解构name变量转换为ref对象,
    //ref对象name和原reactive对象info的属性name建立了链接,一个改变,另一个也改变
    let name = toRef(info, "name");

    const changeName = () => {
      info.name = "kobe"; //修改info.name,name.value会发生改变,页面中的数据会响应式更新
      console.log(info.name, name.value); //kobe  kobe
    };
    const changeAge = () => {
      age++; //age值改变,页面中的数据不会响应式更新,不会引起info.age值的改变
      console.log(age, info.age); //19 18
    };

    return {
      name,
      age,
      changeAge,
      changeName,
    };
  },
};
</script>

<style></style>

ref其他的API

unref
  • 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
  • 如果参数是一个 ref,则返回内部值,否则返回参数本身;
  • 这是 val = isRef(val) ? val.value : val 的语法糖函数;
isRef
  • 判断值是否是一个ref对象。
shallowRef
  • 创建一个浅层的ref对象;
triggerRef
  • 手动触发和 shallowRef 相关联的副作用:
案例:ref函数创建的深层响应式对象和shallowRef创建的浅层响应式对象的区别

ref函数返回的ref对象,是深层响应的,其内嵌的对象的属性发生改变,页面中的数据也会响应式
shallowRef创建ref对象为浅层响应,

  • 即只有其ref.value的值发生改变,页面才会响应式更新,
  • ref.value.name发生改变,页面不会响应式更新更新
<template>
  <div>
    <h1>info:{{ info.name }}</h1>
    <h1>info1:{{ info1.name }}</h1>
    <button @click="changeInfoName">修改info名字</button>
    <button @click="changeInfo1Name">修改info1名字</button>
  </div>
</template>

<script>
import { ref, shallowRef, triggerRef } from "vue";
export default {
  name: "App",
  setup() {
    //ref函数返回的ref对象,是深层响应的,
    //其内嵌的对象的属性发生改变,页面中的数据也会响应式更新
    const info = ref({
      name: "why",
      age: 18,
    });

    //shallowRef创建ref对象为浅层响应,
    //即只有其ref.value的值发生改变,页面才会响应式更新,
    //ref.value.name发生改变,页面不会响应式更新
    const info1 = shallowRef({
      name: "why",
      age: 18,
    });

    const changeInfoName = () => {
      info.value.name = "curry";
      console.log(info.value.name);
    };
    const changeInfo1Name = () => {
      info.value.name = "curry";
      console.log(info.value.name);
    };

    return {
      info,
      info1,
      changeInfoName,
      changeInfo1Name,
    };
  },
};
</script>

<style></style>


triggerRef的用法

如果希望浅层响应ref对象的内嵌对象的值改变时,页面上的数据响应式更新,

  • 需要使用triggerRef函数手动触发浅层ref对象相关的副作用,
  • 参数为要触发副作用浅层ref对象
<template>
  <div>
    <h1>info:{{ info.name }}</h1>
    <button @click="changeInfoName">修改info名字</button>
  </div>
</template>

<script>
import { shallowRef, triggerRef } from "vue";
export default {
  name: "App",
  setup() {
    //shallowRef创建ref对象为浅层响应,
    //即只有其ref.value的值发生改变,页面才会响应式更新,
    //ref.value.name发生改变,页面不会响应式更新
    const info = shallowRef({
      name: "why",
      age: 18,
    });

    const changeInfoName = () => {
      info.value.name = "curry";
      console.log(info.value.name);
      //如果希望info.value.name值改变时,页面上的数据响应式更新,
      //需要使用triggerRef函数手动触发浅层ref对象相关的副作用,
      //参数为要触发副作用浅层ref对象
      triggerRef(info);
    };

    return {
      info,
      changeInfoName,
    };
  },
};
</script>

<style></style>

案例:unref和isRef的用法
<template>
  <div></div>
</template>

<script>
import { ref, isRef, unref } from "vue";
export default {
  name: "App",
  setup() {
    const info = ref({
      name: "why",
      age: 18,
    });

    const value = isRef(info) ? info.value : info;
    //unref是上面用法的语法糖
    const value1 = unref(info);
    console.log(value, value1);

    return {};
  },
};
</script>

<style></style>

image.png

customRef

创建一个自定义的ref,并对其依赖项跟踪更新触发进行显示控制
  • customRef函数的参数为一个工厂函数,此工厂函数有个参数,每个参数都是一个函数
    • 第一个参数是追踪函数track, 你可以通过调用track函数,来决定什么时候收集依赖
    • 第二个参数是触发函数trigger,你可以通过调用trigger函数,来决定什么时候触发所有的依赖更新数据
  • 通过上面两个函数,你可以决定什么时候收集依赖,什么时候触发更新
  • 工厂函数需要返回一个有get和set的对象
这里我们使用一个的案例:
  • 对双向绑定的属性进行debounce(节流)的操作;

./hooks/useDebounceRef.js

import {
  customRef
} from 'vue'

export default function (value) {
  let timer = null
  //customRef函数的参数为一个工厂函数,此工厂函数有两个参数,每个参数都是一个函数
  //第一个参数是追踪函数track, 你可以通过调用track函数,来决定什么时候收集依赖
  //第二个参数是触发函数trigger,你可以通过调用trigger函数,来决定什么时候触发所有的依赖去更新数据
  //通过上面两个函数,你可以决定什么时候收集依赖,什么时候触发更新
  //工厂函数需要返回一个有get和set的对象
  return customRef((track, trigger) => {
    return {
      get() {
        track() //在获取值的时候收集依赖
        return value
      },
      set(newVal) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          value = newVal
          trigger() //在更新值后的1s后触发更新
        }, 1000)
      }
    }
  })
}

App.vue

<template>
  <div>
    <input v-model="message" />
    <h1>{{message}}</h1>
  </div>
</template>

<script>
  import debounceRef from './hook/useDebounceRef.js'
  export default {
    setup() {
      let message = debounceRef('hello')

      return {
        message
      }
    }
  }
</script>

<style lang="scss" scoped>

</style>

此文档主要内容来源于王红元老师的vue3+ts视频教程

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

推荐阅读更多精彩内容