vue3

1.vue3与vue2的对比

① 响应式原理不同

Vue2.x实现双向数据绑定原理,是通过es5的Object.defineProperty,根据具体的key去读取和修改。其中的setter方法来实现数据劫持的,getter实现数据的修改。但是必须先知道想要拦截和修改的key是什么,所以vue2对于新增的属性无能为力,比如无法监听属性的添加和删除、数组索引和长度的变更,vue2的解决方法是使用Vue.set(object,propertyName, value) 等方法向嵌套对象添加响应式。

它的缺点主要是:

(一)不能监听数组的变化

(二)必须遍历对象的每个属性

(三)必须深层遍历嵌套的对象

Vue3.x使用了更快的proxy 替代 Object.defineProperty。Proxy可以理解成,在对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy可以直接监听对象而非属性,并返回一个新对象,具有更好的响应式支持。

②生命周期

image.png

Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子

生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。

Vue2的生命周期

创建阶段:beforeCreatecreated

挂载阶段:beforeMountmounted

更新阶段:beforeUpdateupdated

更新阶段:beforeUpdateupdated

Vue3的生命周期

创建阶段:setup

挂载阶段:onBeforeMountonMounted

更新阶段:onBeforeUpdateonUpdated

卸载阶段:onBeforeUnmountonUnmounted

如果要想在页面中使用生命周期函数,以往vue2的操作是直接在页面中写入生命周期,而vue3是需要去引用的,这就是为什么3能够将代码压缩到更低的原因。

import { onMounted,onActivated...} from 'vue'

③代码风格

vue2采用的是选项式API(Options API )

Options类型的API,数据、方法、计算属性等,都是分散在:data、methods、computed中的

v2-1.gif
v2-2.gif

vue3采用的是组合式API,(Composition API)

使用函数方式,更加凝聚的组织代码,让相关的代码更加有序的组织在一起


v1.gif
v2.gif

④webpack与 vite 对比

webpack构建的流程图

image.png

Vite 构建的流程图

image.png

2.setup概叙

setupVue3中一个新的配置项,值是一个函数,组件中所用到的:数据、方法、计算属性、监视......等等,均配置在setup中。

特点如下:

  • setup函数返回的对象中的内容,可直接在模板中使用。

  • setup中访问thisundefined

  • setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。

<script lang="ts">
  export default {
    name:'Person',
    setup(){
      // 数据,原来写在data中(注意:此时的name、age、tel数据都不是响应式数据)
      let name = '张三'
      let age = 18
      let tel = '13888888888'

      // 方法,原来写在methods中
      function changeName(){
        name = 'zhang-san'
        console.log(name)
      }
      function changeAge(){
        age += 1 //注意:此时这么修改age页面是不变化的
        console.log(age)
      }
      function showTel(){
        alert(tel)
      }
      return {name,age,tel,changeName,changeAge,showTel}
    }
  }
</script>

setup 的返回值

  • 若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用(重点关注)。

  • 若返回一个函数:则可以自定义渲染内容,此时template中的html代码无任何作用,页面只会渲染return返回的渲染内容

setup 与 Options API 的关系(可同时存在)

  • Vue2 的配置(datamethos......)中可以访问到 setup中的属性、方法。

  • 但在setup不能访问到Vue2的配置(datamethos......)。

  • 如果与Vue2冲突,则setup优先。

setup 语法糖

setup函数有一个语法糖,这个语法糖,可以让我们把setup独立出去,代码如下:

<!-- 下面的写法是setup语法糖 -->
<script setup lang="ts">
  let name = '张三'
  let age = 18
  let tel = '13888888888'

  function changName(){
    name = '李四'
  }
  function changAge(){
    age += 1
  }
  function showTel(){
    alert(tel)
  }
</script>

基本类型的响应式数据------ref

  • 作用:定义响应式变量。

  • 语法:let xxx = ref(初始值)

  • 返回值:一个RefImpl的实例对象,简称ref对象refref对象的value属性是响应式的

  • 注意点:

    • JS中操作数据需要:xxx.value,但模板中不需要.value,直接使用即可。

    • 对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的。

<script setup lang="ts">
  import {ref} from 'vue'
  let name = ref('张三')
   function changeName(){
    // JS中操作ref对象时候需要.value
    name.value = '李四'
    console.log(name.value)
  }

</script>

只能定义对象类型的响应式数据-----reactive

  • 作用:******定义一个******响应式对象(基本类型不要用它,要用ref,否则报错)

  • 语法:let 响应式对象= reactive(源对象)

  • 返回值:一个Proxy的实例对象,简称:响应式对象。

  • 注意点:reactive定义的响应式数据是“深层次”的。

<script lang="ts" setup>
import { reactive } from 'vue'
let car = reactive({ brand: 'ES6', price: 100 })
function changeCarPrice() {
  car.price += 10
}
</script>

对象类型的响应式数据------ref

  • 其实ref接收的数据可以是:基本类型对象类型

  • ref接收的是对象类型,内部其实也是调用了reactive函数。

<script lang="ts" setup name="Person">
import { ref } from 'vue'

// 数据
let car = ref({ brand: 'es6', price: 100 })
let games = ref([
  { id: 'ahsgdyfa01', name: '英雄联盟' },
  { id: 'ahsgdyfa02', name: '王者荣耀' },
  { id: 'ahsgdyfa03', name: '原神' }
])

function changeCarPrice() {
  car.value.price += 10
}
function changeFirstGame() {
  games.value[0].name = '三国志'
}
let  {  a,b,c } = form
let a = 'xxx'
</script>

ref 对比 reactive

  1. ref用来定义:基本类型数据对象类型数据

  2. reactive用来定义:对象类型数据

区别:

  1. ref创建的变量必须使用.value

  2. reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。

toRefs 与 toRef

  • 作用:将一个响应式对象中的每一个属性,转换为ref对象。

  • 备注:toRefstoRef功能一致,但toRefs可以批量转换。

watch

  • 作用:监视数据的变化(和Vue2中的watch作用一致)

  • 特点:Vue3中的watch只能监视以下四种数据

  1. ref定义的数据。

  2. reactive定义的数据。

  3. 函数返回一个值(getter函数)。

  4. 一个包含上述内容的数组。

我们在Vue3中使用watch的时候,通常会遇到以下几种情况:

* 情况一

监视ref定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变。

<script lang="ts" setup>
  import {ref,watch} from 'vue'
  // 数据
  let sum = ref(0)
  // 方法
  function changeSum(){
    sum.value += 1
  }
  // 监视,情况一:监视【ref】定义的【基本类型】数据
  const stopWatch = watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
    if(newValue >= 10){
      stopWatch()
    }
  })

  watch: {
     sum(newVal,oldVal){

     }

  }
</script>

* 情况二

监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。

注意:

  • 若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。

  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

<script lang="ts" setup>
  import {ref,watch} from 'vue'
  // 数据
  let person = ref({
    name:'张三',
    age:18
  })
  // 方法
  function changeName(){
    person.value.name += '~'
  }
  function changeAge(){
    person.value.age += 1
  }
  function changePerson(){
    person.value = {name:'李四',age:90}
  }
  a:{
      b:{
          c:{
              d:{

              }
          }
      }
  }
  /* 
    监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
    watch的第一个参数是:被监视的数据
    watch的第二个参数是:监视的回调
    watch的第三个参数是:配置对象(deep、immediate等等.....) 
  */
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  },{deep:true})

</script>

* 情况三

监视reactive定义的【对象类型】数据,且默认开启了深度监视,如果手动修改无效。

* 情况四

监视refreactive定义的【对象类型】数据中的某个属性,注意点如下:

  1. 若该属性值不是【对象类型】,需要写成函数形式。

  2. 若该属性值是依然是【对象类型】,可直接写,也可写成函数,建议写成函数。

结论:监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'

  // 数据
  let person = reactive({
    name:'张三',
    car:{
      c1:'奔驰',
      c2:'宝马'
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeC1(){
    person.car.c1 = '奥迪'
  }
  function changeC2(){
    person.car.c2 = '大众'
  }
  function changeCar(){
    person.car = {c1:'雅迪',c2:'爱玛'}
  }

  // 监视,情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
  watch(()=> person.name,(newValue,oldValue)=>{
    console.log('person.name变化了',newValue,oldValue)
  }) 

  // 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
  watch(()=>person.car,(newValue,oldValue)=>{
    console.log('person.car变化了',newValue,oldValue)
  })
</script>

* 情况五

监视上述的多个数据

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'

  // 数据
  let person = reactive({
    name:'张三',
    car:{
      c1:'奔驰',
      c2:'宝马'
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeC1(){
    person.car.c1 = '奥迪'
  }
  function changeC2(){
    person.car.c2 = '大众'
  }
  function changeCar(){
    person.car = {c1:'雅迪',c2:'爱玛'}
  }

  // 监视,情况五:监视上述的多个数据,返回值为数组
  watch([()=>person.name,person.car],(newValue,oldValue)=>{
    console.log('person.car变化了',newValue,oldValue)
  })

</script>

watchEffect

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数

watch对比watchEffect

  1. 都能监听响应式数据的变化,不同的是监听数据变化的方式不同

  2. watch:要明确指出监视的数据

  3. watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。

  <script lang="ts" setup name="Person">
    import {ref,watch,watchEffect} from 'vue'
    // 数据
    let temp = ref(0)
    let height = ref(0)

    // 方法
    function changePrice(){
      temp.value += 10
    }
    function changeSum(){
      height.value += 1
    }

    // 用watch实现,需要明确的指出要监视:temp、height
    watch([temp,height],(value)=>{
      // 从value中获取最新的temp值、height值
      const [newTemp,newHeight] = value
      // 室温达到50℃,或水位达到20cm,立刻联系服务器
      if(newTemp >= 50 || newHeight >= 20){
        console.log('联系服务器')
      }
    })

    // 用watchEffect实现,不用
    const stopWtach = watchEffect(()=>{

      // 室温达到50℃,或水位达到20cm,立刻联系服务器
      if(temp.value >= 50 || height.value >= 20){
        console.log('联系服务器')
      }
      // 水温达到100,或水位达到50,取消监视
      if(temp.value === 100 || height.value === 50){
        console.log('清除监听')
        stopWtach()
      }
    })
  </script>

标签的 ref 属性

作用:用于注册模板引用。

  • 用在普通DOM标签上,获取的是DOM节点。

  • 用在组件标签上,获取的是组件实例对象。

<template>
  <div class="person">
    <h2 ref="title">前端</h2>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref} from 'vue'

  let title = ref();
  console.log(title.value)
</script>

用在组件标签上:

<!-- 父组件 -->
<template>
  <Person ref="ren"/>
  <button @click="test" ref="btn">测试</button>
</template>

<script lang="ts" setup name="App">
  import Person from './components/Person.vue'
  import {ref} from 'vue'

  let ren = ref()
  let btn =ref()
  function test(){
    console.log(ren.value.name)
    console.log(ren.value.age)
  }
</script>

<!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
<script lang="ts" setup name="Person">
  import {ref,defineExpose} from 'vue'
        // 数据
  let name = ref('张三')
  let age = ref(18)
  /****************************/
  /****************************/
  // 使用defineExpose将组件中的数据交给外部
  defineExpose({name,age})
</script>

3.自定义hook

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装,类似于vue2.x中的mixin

  • 自定义hook的优势:复用代码, 让setup中的逻辑更清楚易懂。

4.路由

vue3与vue2路由基本一致,只是在一些写法上不怎么一样

history模式

优点:URL更加美观,不带有#,更接近传统的网站URL

优点:URL更加美观,不带有#,更接近传统的网站URL

vue3:history:createWebHistory(), //history模式

vue2: mode: 'history'

hash模式

优点:兼容性更好,因为不需要服务器端处理路径。

缺点:URL带有#不太美观,且在SEO优化方面相对较差。

vue3:history:createWebHashHistory(), //hash模式

vue2: mode:'hash' //默认

参数接收与发送

import {useRoute,useRouter} from 'vue-router'

/ / 接收参数

const route = useRoute()

console.log(route.query)

console.log(route.parmas)

//路由跳转

const router = useRouter()

console.log(router.push)

console.log(router.replace)

replace属性

作用:控制路由跳转时操作浏览器历史记录的模式。

浏览器的历史记录有两种写入方式:分别为pushreplace

push是追加历史记录(默认值)。

replace是替换当前记录。

开启replace模式:

<RouterLink replace .......>Replace</RouterLink>

路由的props配置

让路由组件更方便的收到参数(可以将路由参数作为props传给组件)

{
    name:'xiang',
    path:'detail',
    component:Detail,

      // props的对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
      // props:{a:1,b:2,c:3}, 

      // props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
      // props:true

      // props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
    props(route){
        return route.query
    }
}

5.pinia -------- 状态管理工具

官网:https://pinia.vuejs.org/

Vuex和Pinia都是Vue.js的状态管理工具,它们的区别:

设计和使用:

Vuex采用全局单例模式,通过一个store对象来管理所有的状态,组件通过store对象来获取和修改状态。而Pinia则采用了分离模式,每个组件都拥有自己的store实例,通过在组件中创建store实例来管理状态。

数据修改:

Pinia没有mutations,它只有stategetterslaction,这与Vuex不同,Vuex有State同步GettesMutations模块化。Pinia没有modules配置,每一个独立的仓库都是definStore生成出来的,Pinia通过设计提供扁平结构,就是说每个store都是互相独立的,谁也不属于谁,也就是扁平化了更好的代码分割且没有命名空间。

语法和使用:

Pinia语法上比vuex更容易理解和使用灵活,Pinia提供了更好的TypeScript支持,Vue版本支持。Vuex当前最新版是4.x,Vuex4用于Vue3,Vuex3用于Vue2,而Pinia当前最新版是2.x,既支持Vue2也支持Vue3。

体积:Pinia的体积约1KB,相对较小。

目的:

Pinia是一个轻量级的状态管理库,专注于提供一个简单的API来管理应用程序的状态。Vuex是一个更完整的状态管理库,提供了更多的功能,比如模块化、插件和严格模式等。

社区支持:

Pinia是一个较新的框架,社区支持相对较弱;而Vuex是Vue.js官方出品,社区支持较强,拥有丰富的文档和示例。

适用场景:Pinia更适合初学者和快速开发项目,Vuex更适合复杂的项目和对状态管理有更高要求的开发者。

API设计:vuex使用严格单一的store模式,而pinia允许使用多个store实例。

性能:pinia比vuex具有更好的性能,因为它使用了新的ES6语法和新的数据处理方式。

易用性:pinia比vuex更易用,因为它不需要编写复杂的action、mutation和getter函数。

兼容性:vuex是为Vue 2.x设计的,而pinia是为Vue 3.x设计的。

6.组件通信

Vue3****组件通信和**Vue2**的区别:

  • 移出事件总线,使用mitt代替。

  • vuex换成了pinia

  • .sync优化到了v-model里面了。

  • $listeners所有的东西,合并到$attrs中了。

  • $children被砍掉了。

常见搭配形式:

image.png

props

概述:props是使用频率最高的一种通信方式,常用与 :父 ↔ 子

  • 父传子:属性值是非函数

  • 子传父:属性值是函数

父组件:

<template>
  <div class="father">
    <h3>父组件,</h3>
    <h4>我的车:{{ car }}</h4>
    <h4>儿子给的玩具:{{ toy }}</h4>
    <Child :car="car" :getToy="getToy" />
  </div>
</template>

<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
// 数据
const car = ref('奔驰')
const toy = ref()
// 方法
function getToy(value:string){
    toy.value = value
}
</script>

子组件

<template>
  <div class="child">
    <h3>子组件</h3>
    <h4>我的玩具:{{ toy }}</h4>
    <h4>父给我的车:{{ car }}</h4>
    <button @click="getToy(toy)">传值给父组件</button>
  </div>
</template>

<script setup lang="ts" name="Child">
import { ref } from "vue";
const toy = ref('奥特曼')
defineProps(['car','getToy'])
</script>

自定义事件

概述:自定义事件常用于:子 => 父。

和vue2唯一的区别就是子组件声明事件使用

defineEmits([''事件名]),其他完全一致

mitt

概述:与bus总线一致,可以实现任意组件间通信。

安装mitt

npm i mitt

新建文件:src\utils\emitter.ts

// 引入mitt 
import mitt from "mitt";

// 创建emitter
const emitter = mitt()

// 暴露emitter
export default emitter

//触发事件
emitter.emit('xxxx','')

//绑定事件
emitter.on()

//解绑事件
emitter.off()

//清理事件
emitter.all.clear()

v-model

  1. 概述:实现 父↔子 之间相互通信。

  2. 前序知识 —— v-model的本质

<!-- 使用v-model指令 -->
<input type="text" v-model="userName">

<!-- v-model的本质是下面这行代码 -->
<input 
  type="text" 
  :value="userName" 
  @input="userName =(<HTMLInputElement>$event.target).value"
>
  1. 组件标签上的v-model的本质::moldeValueupdate:modelValue事件。
<!-- 组件标签上使用v-model指令 -->
<mInput v-model="userName"/>

<!-- 组件标签上v-model的本质 -->
<mInput :modelValue="userName" @update:model-value="userName = $event"/>

mInput 组件中:

<template>
  <div class="box">
    <!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
                <!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
    <input 
       type="text" 
       :value="modelValue" 
       @input="emit('update:model-value',$event.target.value)"
    >
  </div>
</template>

<script setup lang="ts">
  // 接收props
  defineProps(['modelValue'])
  // 声明事件
  const emit = defineEmits(['update:model-value'])
</script>
  1. 也可以更换value,例如改成abc
<!-- 也可以更换value,例如改成abc-->
<mInput v-model:abc="userName" v-model:abc=""/>

<!-- 上面代码的本质如下 -->
<mInput :abc="userName" @update:abc="userName = $event"/>

mInput 组件中:

<template>
  <div class="box">
    <input 
       type="text" 
       :value="abc" 
       @input="emit('update:abc',$event.target.value)"
    >
  </div>
</template>

<script setup lang="ts" name="AtguiguInput">
  // 接收props
  defineProps(['abc'])
  // 声明事件
 let emit = inject('xxx')
  const emit = defineEmits(['update:abc'])
  provide('emit')
</script>

  1. 如果value可以更换,那么就可以在组件标签上多次使用v-model
<mInput v-model:abc="userName" v-model:xyz="password"/>

$attrs

  1. 概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。

  2. 具体说明:$attrs是一个对象,包含所有父组件传入的标签属性。

注意:$attrs会自动排除props中声明的属性(可以认为声明过的 props 被子组件自己“消费”了)

通过 v-bind="$atters"传递

$refs、$parent`

  • $refs用于 :父→子。

  • $parent用于:子→父。

原理如下:


微信图片_20240510175957.png

provide、inject

实现祖孙组件直接通信

具体使用:

  • 在祖先组件中通过provide配置向后代组件提供数据

  • 在后代组件中通过inject配置来声明接收数据

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