Vue 3.0 學習筆記(李南江老師Vue3視頻筆記)

01-Vue 3.0開篇-理解

一胧辽、為什么現(xiàn)在才講Vue3.0?

  • 因為昨天才發(fā)布正式版本
  • 正式版之前API不穩(wěn)定(白學)
  • 正式版之前企業(yè)開發(fā)用不上(不穩(wěn)定)

二促煮、為什么現(xiàn)在要講Vue3.0?

  • 正式版已經(jīng)發(fā)布,已經(jīng)基本穩(wěn)定
  • 預計2021年企業(yè)開發(fā)用得上
    • 學習是一個漸進的過程

三颜矿、如何學習Vue3.0?

  • 不用全力以赴
    • 因為上線項目暫時還不會用
    • 因為相關生態(tài)還有待完善
  • 先學習vue2.x
    • Vue3.0并不是推到重來,很多2.x內(nèi)容依然被保留
  • 先學習TypeScript
    • Vue3.0采用TS重寫,想知其然知其所以然必須學習TS

02-Vue3.0-diff算法-理解&&03-Vue3.0-靜態(tài)提升和監(jiān)聽緩存-理解

一召衔、Vue3.0六大亮點

  • Performance: 性能比Vue 2.x快1.2~2倍
  • Tree shaking support: 按需編譯,體積比vue2.x更小
  • Composition API: 組合API(類似React Hooks)
  • Better TypeScript support: 更好的Ts支持
  • Custom Renderer API: 暴露了自定義渲染API
  • Fragment, Teleport(Protal), Suspense: 更先進的組件

二祭陷、Vue3.0是如何變快的

1. diff方法優(yōu)化
  • Vue2中的虛擬dom是進行全量比對
  • Vue3新增了靜態(tài)標記(PatchFlag)
    在與上次虛擬節(jié)點進行比對時候苍凛,只對比帶有patch flag的節(jié)點
    并且可以通過flag的信息 得知當前節(jié)點要比對的具體內(nèi)容
(1) Vue2 diff算法
image.png

(2) Vue3 diff算法

image.png
(3) Vue編譯demo
<div>
  <p>我是段落</p>
  <p>{{msg}}</p>
</div>
import {
  createVNode as _createVNode,
  toDisplayString as _toDisplayString,
  openBlock as _openBlock,
  createBlock as _createBlock
} from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "我是段落"),
    _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}
(4) 附錄 PatchFlags
export const enum PatchFlags {
  TEXT = 1, // 動態(tài)文本節(jié)點
  CLASS = 1 << 1, // 2 動態(tài)class
  STYLE = 1 << 2, // 4 動態(tài)style
  PROPS = 1 << 3, // 8 動態(tài)屬性,但不包含類名和樣式
  FULL_PROPS = 1 << 4, // 16 具有動態(tài) key 屬性兵志,當 key 改變時醇蝴,需要進行完整的 diff 比較
  HYDRATE_EVENTS = 1 << 5, // 32 帶有監(jiān)聽事件的節(jié)點
  STABLE_FRAGMENT = 1 << 6, // 64 一個不會改變子節(jié)點順序的 fragment
  KEYED_FRAGMENT = 1 << 7, // 128 帶有key屬性的 fragment 或部分帶有 key
  UNKEYED_FRAGMENT = 1 << 8, // 256 子節(jié)點沒有 key 的 fragment
  NEED_PATCH = 1 << 9, // 512 一個節(jié)點只會進行非 props 比較
  DYNAMIC_SLOTS = 1 << 10, // 1024
  HOISTED = -1,
  BAIL = -2
}
2. 靜態(tài)提升(hoistStatic)
  • Vue2中無論元素是否參與更新,每次都會重新創(chuàng)建想罕,然后再渲染
  • Vue3中對于不參與更新的元素悠栓,會做靜態(tài)提升,只會被創(chuàng)建一次,在渲染時直接復用即可
(1) Vue編譯demo
<div>
  <p>我是段落</p>
  <p>{{msg}}</p>
</div>
import {
  createVNode as _createVNode,
  toDisplayString as _toDisplayString,
  openBlock as _openBlock,
  createBlock as _createBlock
} from "vue"

const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "我是段落", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _hoisted_1,
    _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}
3. 事件偵聽器緩存(cacheHandlers)
  • 默認情況下onClick會被視為動態(tài)綁定闸迷,所以每次都會去追蹤它的變化
    但是因為是同一個函數(shù)嵌纲,所以沒有追蹤變化,直接緩存起來復用即可
(1) Vue編譯demo
<div>
  <button @click="onClick">按鈕</button>
</div>

關閉事件偵聽器緩存

import {
  createVNode as _createVNode,
  openBlock as _openBlock,
  createBlock as _createBlock
} from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", { onClick: _ctx.onClick }, "按鈕", 8 /* PROPS */, ["onClick"])
  ]))
}

開啟事件偵聽器緩存

import {
  createVNode as _createVNode,
  openBlock as _openBlock,
  createBlock as _createBlock
} from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
    }, "按鈕")
  ]))
}

注意點:轉換之后的代碼腥沽,大家可能還看不懂逮走,但是不要緊,我們只需要觀察有沒有靜態(tài)標記即可今阳,因為我們知道在Vue3的diff算法中师溅,只有有靜態(tài)標記的才會進行比較宴霸,才會進行追蹤

4. ssr渲染
  • 當有大量靜態(tài)內(nèi)容時候柄沮,這些內(nèi)容會被當做純字符串推進一個buffer里面涛碑,
    即使存在動態(tài)綁定窒典,會通過模板插值嵌入進去。這樣會比通過虛擬dom來渲染的快上很多很多合陵。
  • 當靜態(tài)內(nèi)容大到一定量級時候氮唯,會用_createStaticVNode方法在客戶端生成一個static node轧简,這些靜態(tài)node膝舅,會被直接innerHTML嗡载,就不需要創(chuàng)建對象,然后根據(jù)對象渲染仍稀。

04-Vue3.0-項目創(chuàng)建-理解

一洼滚、創(chuàng)建Vue3的三種方式

1. Vue-CLI
npm install -g @vue/cli
vue create projectName
cd projectName
npm run serve
2. Webpack
git clone https://github.com/vuejs/vue-next-webpack-preview.git projectName
cd projectName
npm install
npm run dev
3. Vite

二、 什么是Vite?

  • Vite是Vue作者開發(fā)的一款意圖取代webpack的工具
  • 其實現(xiàn)原理是利用ES6的import會發(fā)送請求去加載文件的特性技潘,攔截這些請求遥巴,做一些預編譯,省去webpack冗長的打包時間

三享幽、 利用Vite創(chuàng)建Vue3項目

1. 安裝Vite
npm install -g create-vite-app
2. 創(chuàng)建Vue3項目
create-vite-app projectName
3. 安裝依賴運行項目
cd projectName
npm install
npm run dev

四铲掐、Vue3.0 demo

  • 修改App.vue文件
<template>
  <div>
    <p>{{msg}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      msg: '智播漁',
    }
  },
  methods: {
    myFn() {
      alert('www.it666.com')
    },
  },
}
</script>
image.png
image.png

05-Vue2.x-存在的問題-理解

一、todolist demo

<template>
  <div>
    <form>
      <input
        type="text"
        v-model="stu.id"
      />
      <input
        type="text"
        v-model="stu.name"
      />
      <input
        type="text"
        v-model="stu.age"
      />
      <input
        type="submit"
        @click="addStu"
      />
    </form>
    <ul>
      <li
        v-for="(stu,index) in stus"
        :key="stu.id"
        @click="remStu(index)"
      >
        {{stu.name}} -- {{stu.age}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      stus: [
        { id: 1, name: 'zs', age: 10 },
        { id: 2, name: 'ls', age: 20 },
        { id: 3, name: 'ww', age: 30 },
      ],
      stu: {
        id: '',
        name: '',
        age: '',
      },
    }
    // 新增功能1的數(shù)據(jù)
    // 新增功能2的數(shù)據(jù)
  },
  methods: {
    remStu(index) {
      this.stus = this.stus.filter((stu, idx) => idx != index)
    },
    addStu(e) {
      e.preventDefault()
      const stu = Object.assign({}, this.stu)
      this.stus.push(stu)
      this.stu = {
        id: '',
        name: '',
        age: '',
      }
    },
    // 新增功能1的業(yè)務邏輯
    // 新增功能2的業(yè)務邏輯
  },
  computed: {
    // 新增功能1的業(yè)務邏輯
    // 新增功能2的業(yè)務邏輯
  },
  watch: {
    // 新增功能1的業(yè)務邏輯
    // 新增功能2的業(yè)務邏輯
  },
}
</script>

二琉闪、 問題

數(shù)據(jù)和業(yè)務邏輯分散迹炼,不利于管理維護

06-Vue3.0-組合API上-理解

一砸彬、組合API初體驗 demo

<template>
  <div>
    <p>{{count}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'App',
  // setup函數(shù)是組合API的入口函數(shù)
  setup() {
    // 定義了一個名稱叫做count的變量颠毙,這個變量的初始值是0
    // 這個變量發(fā)生改變之后,Vue會自動更新UI
    let count = ref(0)

    // 在組合API中砂碉,如果想定義方法蛀蜜,不用定義到methods中,直接定義即可
    function myFn() {
      count.value += 1
    }

    // 注意點
    // 在組合API中定義的變量/方法增蹭,要想在外界使用滴某,必須通過return {xxx, xxx}暴露出去
    return {
      count,
      myFn,
    }
  },
}
</script>

二、注意點

  • setup函數(shù)是組合API的入口函數(shù)
  • 使用ref定義一個變量并設置初始值,這個變量發(fā)生改變之后霎奢,Vue會自動更新UI
  • ref函數(shù)只能監(jiān)聽簡單類型的變化户誓,不能監(jiān)聽復雜類型的變化(對象/數(shù)組)
  • 在組合API中,如果想定義方法幕侠,不用定義到methods中帝美,直接定義即可
  • 在組合API中定義的變量/方法,要想在外界使用晤硕,必須通過return {xxx, xxx}暴露出去

07-Vue3.0-組合API中-理解

一悼潭、業(yè)務抽離demo

<template>
  <div>
    <ul>
      <li
        v-for="(stu,index) in state.stus"
        :key="stu.id"
        @click="remStu(index)"
      >
        {{stu.name}} -- {{stu.age}}
      </li>
    </ul>
  </div>
</template>

<script>
import { reactive } from 'vue'

function useRemoveStudent() {
  let state = reactive({
    stus: [
      { id: 1, name: 'zs', age: 10 },
      { id: 2, name: 'ls', age: 20 },
      { id: 3, name: 'ww', age: 30 },
    ],
  })

  function remStu(index) {
    state.stus = state.stus.filter((stu, idx) => idx != index)
  }

  return { state, remStu }
}

export default {
  name: 'App',
  setup() {

    let { state, remStu } = useRemoveStudent()

    return {
      state,
      remStu,
    }
  },
}
</script>

二、 理解

刪除用戶的業(yè)務代碼被抽離到了useRemoveStudent中舞箍,利于之后的管理和維護

08-Vue3.0-組合API下-理解

一舰褪、多文件demo

<template>
  <div>
    <form>
      <input
        type="text"
        v-model="state2.stu.id"
      />
      <input
        type="text"
        v-model="state2.stu.name"
      />
      <input
        type="text"
        v-model="state2.stu.age"
      />
      <input
        type="submit"
        @click="addStu"
      />
    </form>
    <ul>
      <li
        v-for="(stu,index) in state.stus"
        :key="stu.id"
        @click="remStu(index)"
      >
        {{stu.name}} -- {{stu.age}}
      </li>
    </ul>
  </div>
</template>

<script>
import { reactive } from 'vue'
import useAddStudent from './js/add'
import useRemoveStudent from './js/remove'
export default {
  name: 'App',
  setup() {
    let { state, remStu } = useRemoveStudent()
    let { state2, addStu } = useAddStudent(state)

    return {
      state,
      remStu,
      state2,
      addStu,
    }
  },
}
</script>
import { reactive } from 'vue'

function useRemoveStudent() {
  let state = reactive({
    stus: [
      { id: 1, name: 'zs', age: 10 },
      { id: 2, name: 'ls', age: 20 },
      { id: 3, name: 'ww', age: 30 },
    ],
  })

  function remStu(index) {
    state.stus = state.stus.filter((stu, idx) => idx != index)
  }

  return { state, remStu }
}

export default useRemoveStudent
import { reactive } from 'vue'

function useAddStudent (state) {
  let state2 = reactive({
    stu: {
      id: '',
      name: '',
      age: '',
    },
  })

  function addStu (e) {
    e.preventDefault()
    const stu = Object.assign({}, state2.stu)
    state.stus.push(stu)
    state2.stu = {
      id: '',
      name: '',
      age: '',
    }
  }

  return { state2, addStu }
}

export default useAddStudent

二、理解

  • 所有的功能都可以放到獨立的模塊中去管理

09-Vue3.0-來點動力-理解

Vue2.0 按照操作劃分代碼塊疏橄,Vue3.0 按業(yè)務劃分代碼塊


image

10-Vue3.0-組合API本質(zhì)-理解&&11-Vue3.0-setup執(zhí)行時機和注意點-理解

一占拍、 Composition API 和 Option API 混合使用

Composition API 和 Option API可以混合使用

1. 混合使用demo

<template>
  <div>
    <p>{{name}}</p>
    <button @click="myFn1">按鈕</button>
    <p>{{age}}</p>
    <button @click="myFn2">按鈕</button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'App',
  data() {
    return {
      name: 'lnj',
    }
  },
  methods: {
    myFn1() {
      alert('abc')
    },
  },
  setup() {
    let age = ref(18)
    function myFn2() {
      alert('sxx')
    }
    return {
      age,
      myFn2,
    }
  },
}
</script>

二、 Composition API本質(zhì)(組合API/注入API)

Composition API的本質(zhì)就是在運行的時候將暴露出去的數(shù)據(jù)注入到option api中捎迫,如將數(shù)據(jù)注入到data中刷喜,將方法注入到methods中。

三立砸、 setup執(zhí)行時機

setup在beforeCreate和created兩個生命周期之間執(zhí)行

  • beforeCreate: 表示組件剛剛被創(chuàng)建出來掖疮,組件的data和methods還沒有初始化好
  • setup
  • created: data和methods已經(jīng)初始化好

四、 setup注意點

  • 由于在執(zhí)行setup函數(shù)的時候颗祝,還沒有執(zhí)行created生命周期方法浊闪,所以在setup函數(shù)中,是無法使用data和methods
  • 由于我們不能在setup函數(shù)中無法使用data和methods螺戳,所以Vue為了避免我們錯誤的使用搁宾,它直接將函數(shù)中的this修改成了undefined
  • setup函數(shù)只能是同步的,不能是異步的

12-Vue3.0-reactive-理解

一倔幼、什么是reactive

  • reactive是Vue3中提供的實現(xiàn)響應式數(shù)據(jù)的方法
  • 在Vue2中響應式數(shù)據(jù)是通過defineProperty來實現(xiàn)的盖腿,而在Vue3中響應式數(shù)據(jù)是通過ES6的Proxy來實現(xiàn)的

二、reactiv注意點

  • reactive參數(shù)必須是對象(json/arr)
  • 如果給reactive傳遞了其他對象
    • 默認情況下修改對象损同,界面不會自動更新
    • 如果想更新翩腐,可以通過重新賦值的方式
1. 給reactive傳遞非對象無法實現(xiàn)響應式

點擊按鈕值發(fā)生變化但頁面將不會發(fā)生變化

<template>
  <div>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    // 創(chuàng)建一個響應式數(shù)據(jù)
    // 本質(zhì):就是將傳入的數(shù)據(jù)包裝成一個Proxy對象
    let state = reactive(123)

    function myFn() {
      state = 666 // 由于在創(chuàng)建響應式數(shù)據(jù)的時候傳遞的不是一個對象,所以無法實現(xiàn)響應式
      console.log(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>
2. 需要傳遞一個對象才可以實現(xiàn)響應式

點擊按鈕頁面將發(fā)生變化

<template>
  <div>
    <p>{{state.age}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = reactive({
      age: 123,
    })

    function myFn() {
      state.age = 666
      console.log(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>
3. 數(shù)組也可以監(jiān)聽

點擊按鈕修改數(shù)組的值將發(fā)現(xiàn)頁面發(fā)生變化

<template>
  <div>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = reactive([1, 3, 5])

    function myFn() {
      state[0] = 666
      console.log(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>
4. 其他對象不能實現(xiàn)響應式膏燃,需要重新賦值

調(diào)用Date自帶的方法不能實現(xiàn)響應式茂卦,需要使用注釋的方法實現(xiàn)

<template>
  <div>
    <p>{{state.time}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = reactive({
      time: new Date(),
    })

    function myFn() {
      // const newTime = new Date(state.time.getTime())
      // newTime.setDate(newTime.getDate() + 1)
      // state.time = newTime
      state.time.setDate(state.time.getDate() + 1)
      console.log(state.time)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

13-Vue3.0-ref-理解

一、什么是ref

  • ref和reactive一樣组哩,也是用來實現(xiàn)響應式數(shù)據(jù)的方法
  • 由于reactive必須傳遞一個對象等龙,所以導致在企業(yè)開發(fā)中如果我們只想讓某個變量實現(xiàn)響應式的時候會非常麻煩处渣,所以Vue3就給我們提供了ref方法,實現(xiàn)對簡單值的監(jiān)聽

二蛛砰、ref本質(zhì)

  • ref底層的本質(zhì)其實還是reactive罐栈,系統(tǒng)會自動根據(jù)我們給ref傳入的值將它轉換成ref(xx) -> reactive({value: xx})

三、ref注意點

  • 在template中使用ref的值不用通過value獲取
  • 在js中使用ref的值必須通過value獲取
  1. js中加value泥畅,template中不需要加value
<template>
  <div>
    <p>{{age}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'App',
  setup() {
    let age = ref(18)

    function myFn() {
      age.value = 666
    }

    return {
      age,
      myFn,
    }
  },
}
</script>

14-Vue3.0-ref和reactive區(qū)別-理解

一悠瞬、Vue在處理的時候會先判斷數(shù)據(jù)是什么類型的

  • 如果在template里面使用的是ref類型的數(shù)據(jù),那么Vue會自動幫我們添加.value
  • 如果template里使用的是reactive類型的數(shù)據(jù)涯捻,那么Vue不會自動幫我們添加.value

二浅妆、Vue是如何判斷數(shù)據(jù)類型的呢

1. 打印ref數(shù)據(jù)的結果
RefImpl {_rawValue: 18, _shallow: false, __v_isRef: true, _value: 18}
__v_isRef: true
_rawValue: 18
_shallow: false
_value: 18
value: 18
2. 解釋
  • Vue在解析數(shù)據(jù)之前,會自動判斷這個數(shù)據(jù)是否是 ref 類型的障癌,如果是就自動添加 .value 凌外,如果不是就不自動添加 .value
  • 通過當前數(shù)據(jù)的 __v_isRef 來判斷,如果有這個私有屬性涛浙,并且取值為true康辑,那么就代表是一個ref類型的數(shù)據(jù)

三、isRef和isReactive

通過 isRef 和 isReactive 可以判斷數(shù)據(jù)是 ref 還是 reactive

<template>
  <div>
    <p>{{age}}</p>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { ref, isRef, isReactive, reactive } from 'vue'

export default {
  name: 'App',
  setup() {
    let age = ref(18)
    let state = reactive({ age: 18 })

    function myFn() {
      console.log(isRef(age))
      console.log(isRef(state))
      console.log(isReactive(age))
      console.log(isReactive(state))
    }

    return {
      age,
      state,
      myFn,
    }
  },
}
</script>

15-Vue3.0-遞歸監(jiān)聽理解

一轿亮、遞歸監(jiān)聽

默認情況下疮薇,無論是通過ref還是通過reactive都是遞歸監(jiān)聽

1. reactive遞歸監(jiān)聽
<template>
  <div>
    <p>{{state.a}}</p>
    <p>{{state.gf.b}}</p>
    <p>{{state.gf.f.c}}</p>
    <p>{{state.gf.f.s.d}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = reactive({
      a: 'a',
      gf: {
        b: 'b',
        f: {
          c: 'c',
          s: {
            d: 'd',
          },
        },
      },
    })

    function myFn() {
      state.a = '1'
      state.gf.b = '2'
      state.gf.f.c = '3'
      state.gf.f.s.d = '4'
    }

    return {
      state,
      myFn,
    }
  },
}
</script>
2. ref遞歸監(jiān)聽
<template>
  <div>
    <p>{{state.a}}</p>
    <p>{{state.gf.b}}</p>
    <p>{{state.gf.f.c}}</p>
    <p>{{state.gf.f.s.d}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = ref({
      a: 'a',
      gf: {
        b: 'b',
        f: {
          c: 'c',
          s: {
            d: 'd',
          },
        },
      },
    })

    function myFn() {
      state.value.a = '1'
      state.value.gf.b = '2'
      state.value.gf.f.c = '3'
      state.value.gf.f.s.d = '4'
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

二、遞歸監(jiān)聽存在的問題

如果數(shù)據(jù)量比較大我注,非常消耗性能
因為遞歸監(jiān)聽將使每一層都被包裝成一個Proxy

1. 遞歸監(jiān)聽驗證
<template>
  <div>
    <p>{{state.a}}</p>
    <p>{{state.gf.b}}</p>
    <p>{{state.gf.f.c}}</p>
    <p>{{state.gf.f.s.d}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = reactive({
      a: 'a',
      gf: {
        b: 'b',
        f: {
          c: 'c',
          s: {
            d: 'd',
          },
        },
      },
    })

    function myFn() {
      console.log(state)
      console.log(state.gf)
      console.log(state.gf.f)
      console.log(state.gf.f.s)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕看到控制臺的輸出


image.png

16-Vue3.0-非遞歸監(jiān)聽-掌握

一按咒、非遞歸監(jiān)聽

1. shallowReactive
<template>
  <div>
    <p>{{state.a}}</p>
    <p>{{state.gf.b}}</p>
    <p>{{state.gf.f.c}}</p>
    <p>{{state.gf.f.s.d}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { shallowReactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = shallowReactive({
      a: 'a',
      gf: {
        b: 'b',
        f: {
          c: 'c',
          s: {
            d: 'd',
          },
        },
      },
    })

    function myFn() {
      // state.a = '1'
      state.gf.b = '2'
      state.gf.f.c = '3'
      state.gf.f.s.d = '4'

      console.log(state)
      console.log(state.gf)
      console.log(state.gf.f)
      console.log(state.gf.f.s)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕頁面將不會發(fā)生變化,查看控制臺將看到以下打印結果


image.png

發(fā)現(xiàn)除了第一層之外其它層沒有被包裝成Proxy

2. shallowRef
<template>
  <div>
    <p>{{state.a}}</p>
    <p>{{state.gf.b}}</p>
    <p>{{state.gf.f.c}}</p>
    <p>{{state.gf.f.s.d}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { shallowRef } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = shallowRef({
      a: 'a',
      gf: {
        b: 'b',
        f: {
          c: 'c',
          s: {
            d: 'd',
          },
        },
      },
    })

    function myFn() {
      state.value.a = '1'
      state.value.gf.b = '2'
      state.value.gf.f.c = '3'
      state.value.gf.f.s.d = '4'

      console.log(state)
      console.log(state.value)
      console.log(state.value.gf)
      console.log(state.value.gf.f)
      console.log(state.value.gf.f.s)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕頁面將不會發(fā)生變化但骨,查看控制臺將看到以下打印結果

image.png

發(fā)現(xiàn)除了第一層之外所有層均沒有被包裝
注意: 如果是通過shallowRef創(chuàng)建數(shù)據(jù)励七,nameVue監(jiān)聽的是.value的變化,并不是第一層的變化

<template>
  <div>
    <p>{{state.a}}</p>
    <p>{{state.gf.b}}</p>
    <p>{{state.gf.f.c}}</p>
    <p>{{state.gf.f.s.d}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { shallowRef } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = shallowRef({
      a: 'a',
      gf: {
        b: 'b',
        f: {
          c: 'c',
          s: {
            d: 'd',
          },
        },
      },
    })

    function myFn() {
      state.value = {
        a: '1',
        gf: {
          b: '2',
          f: {
            c: '3',
            s: {
              d: '4',
            },
          },
        },
      }
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕發(fā)現(xiàn)頁面發(fā)生變化了

3. triggerRef

采用非遞歸監(jiān)聽如果想監(jiān)聽第四層的數(shù)據(jù)奔缠,可以使用triggerRef根據(jù)傳入的數(shù)據(jù)主動更新界面

<template>
  <div>
    <p>{{state.a}}</p>
    <p>{{state.gf.b}}</p>
    <p>{{state.gf.f.c}}</p>
    <p>{{state.gf.f.s.d}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { shallowRef, triggerRef } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = shallowRef({
      a: 'a',
      gf: {
        b: 'b',
        f: {
          c: 'c',
          s: {
            d: 'd',
          },
        },
      },
    })

    function myFn() {
      state.value.gf.f.s.d = '4'
      triggerRef(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕發(fā)現(xiàn)第四層的數(shù)據(jù)發(fā)生了變化
注意: Vue3只提供了triggerRef方法掠抬,沒有提供triggerReactive方法,所以如果是reactive類型的數(shù)據(jù)校哎,那么是無法主動觸發(fā)界面更新的

17-Vue3.0-shallowRef本質(zhì)

ref->reactive
ref(10)->reactive({value: 10})
shallowRef->shallowReactive
shallowRef(10)->shallowReactive({value: 10})
所以如果是通過shallowRef創(chuàng)建的數(shù)據(jù)两波,它監(jiān)聽的是.value的變化,因為底層本質(zhì)上value才是第一層

18-Vue3.0-toRaw && 19-Vue3.0-toRaw

<template>
  <div>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {
      name: 'lnj',
      age: 18,
    }
    let state = reactive(obj)

    console.log(obj === state) // false

    // state和obj的關系
    // 引用關系闷哆,state的本質(zhì)是一個Proxy對象腰奋,在這個Proxy對象引用了obj

    function myFn() {
      // 如果直接修改obj,那么是無法觸發(fā)界面更新的阳准,只有通過包裝之后的對象來修改氛堕,才會觸發(fā)界面的更新
      obj.name = 'zs'
      console.log(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

一馏臭、toRaw

從reactive或ref中得到原始數(shù)據(jù)

<template>
  <div>
    <p>{{state}}</p>
  </div>
</template>

<script>
import { reactive, toRaw } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {
      name: 'lnj',
      age: 18,
    }
    let state = reactive(obj)
    let obj2 = toRaw(state)

    console.log(obj === obj2)

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

控制臺打印true

二野蝇、toRaw作用

做一些不想被監(jiān)聽的事情(提升性能)

ref/reactive數(shù)據(jù)類型的特點:每次修改都會被追蹤讼稚,都會更新UI界面,但是這樣其實是非常消耗性能的绕沈,所以如果我們有一些操作不需要追蹤锐想,不需要更新UI界面,那么這個時候乍狐,我們就可以通過toRaw方法拿到它的原始數(shù)據(jù)赠摇,對原始數(shù)據(jù)進行修改,這樣就不會被追蹤浅蚪,這樣就不會更新UI界面藕帜,這樣性能就好了

<template>
  <div>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { reactive, toRaw } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {
      name: 'lnj',
      age: 18,
    }
    let state = reactive(obj)
    let obj2 = toRaw(state)

    console.log(obj === obj2)

    function myFn() {
      obj2.name = 'zs'
      console.log(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕發(fā)現(xiàn)數(shù)據(jù)發(fā)生了改變,但界面沒有發(fā)生改變

三惜傲、ref的toRaw

如果想通過toRaw拿到ref類型的原始數(shù)據(jù)(創(chuàng)建時傳入的那個數(shù)據(jù))洽故,那么就必須明確告訴toRaw方法,要獲取的是.value的值盗誊,因為經(jīng)過Vue處理之后.value中保存的才是當初創(chuàng)建時傳入的那個原始數(shù)據(jù)

<template>
  <div>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { ref, toRaw } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {
      name: 'lnj',
      age: 18,
    }
    // ref本質(zhì):reactive
    // ref(obj) -> reactive({value: obj})
    
    let state = ref(obj)
    let obj2 = toRaw(state.value)

    console.log(obj === obj2)

    function myFn() {
      obj2.name = 'zs'
      console.log(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

20-Vue3.0-markRaw

markRaw標記某個數(shù)據(jù)永遠不會被追蹤

<template>
  <div>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { markRaw, reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {
      name: 'lnj',
      age: 18,
    }

    obj = markRaw(obj)

    let state = reactive(obj)

    function myFn() {
      state.name = 'zs'
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕數(shù)據(jù)不會發(fā)生變化

21-Vue3.0-toRef-理解

一时甚、toRef的理解

  • 如果利用ref將某一個對象中的屬性變成響應式的數(shù)據(jù), 我們修改響應式數(shù)據(jù)是不會影響到原始數(shù)據(jù)的哈踱。
  • 如果利用toRef將某一個對象中的屬性變成響應式的數(shù)據(jù)荒适,我們修改響應式數(shù)據(jù)是會影響到原始數(shù)據(jù)的
  • 但是如果響應式數(shù)據(jù)是通過toRef創(chuàng)建的,那么修改了數(shù)據(jù)并不會觸發(fā)UI界面的更新
<template>
  <div>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { toRef } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {
      name: 'lnj',
    }

    let state = toRef(obj, 'name')

    function myFn() {
      state.value = 'zs'

      console.log(state)
      console.log(obj)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕發(fā)現(xiàn)數(shù)據(jù)與原始數(shù)據(jù)都發(fā)生了變化

二开镣、ref和toRef區(qū)別

  • ref->復制刀诬,修改響應式數(shù)據(jù)不會影響以前的數(shù)據(jù)
  • toRef->引用,修改響應式數(shù)據(jù)會影響以前的數(shù)據(jù)
  • ref->數(shù)據(jù)發(fā)生改變邪财,界面就會自動更新
  • toRef->數(shù)據(jù)發(fā)生改變舅列,界面也不會自動更新

三、toRef引用場景

如果想讓響應式數(shù)據(jù)和以前的數(shù)據(jù)關聯(lián)起來卧蜓,并且更新響應式數(shù)據(jù)之后還不想更新UI帐要,name就可以使用toRef。

22-Vue3.0-toRefs-理解

將對象中所有的屬性全部追蹤

<template>
  <div>
    <p>{{state}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { toRefs } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = {
      name: 'lnj',
      age:18
    }

    let state = toRefs(obj)

    function myFn() {
      state.name.value = 'zs'
      state.age.value = 666

      console.log(state)
      console.log(obj)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

點擊按鈕發(fā)現(xiàn)數(shù)據(jù)和原始數(shù)據(jù)都發(fā)生了變化

23-Vue3.0-customRef 上-理解 && 24-Vue3.0-customRef 下-理解

一弥奸、customRef

返回一個ref對象榨惠,可以顯式地控制依賴追蹤和觸發(fā)響應

<template>
  <div>
    <p>{{age}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { customRef } from 'vue'

function myRef(value) {
  return customRef((track, trigger) => {
    return {
      get() {
        track() // 告訴Vue這個數(shù)據(jù)需要追蹤變化
        console.log('get', value)
        return value
      },
      set(newValue) {
        console.log('set', newValue)
        value = newValue
        trigger() // 告訴Vue觸發(fā)界面更新
      },
    }
  })
}

export default {
  name: 'App',
  setup() {
    let age = myRef(18)

    function myFn() {
      age.value += 1
    }

    return {
      age,
      myFn,
    }
  },
}
</script>

二、為什么要使用customRef

一個使用customRef的場景盛霎,根據(jù)數(shù)據(jù)的請求路徑進行追蹤

<template>
  <ul>
    <li
      v-for="item in state"
      :key="item.id"
    >
      {{item.name}}
    </li>
  </ul>
</template>

<script>
import { customRef } from 'vue'

function myRef(path, initValue) {
  let value = initValue
  return customRef((track, trigger) => {
    fetch(path)
      .then((res) => res.json())
      .then((data) => {
        value = data
        trigger()
      })
      .catch((err) => {
        console.log(err)
      })
    return {
      get() {
        // 這個數(shù)據(jù)是需要追中變化的
        track()
        console.log('get', value)
        return value
      },
      set(newValue) {
        console.log('set', newValue)
        value = newValue
        // 告訴Vue觸發(fā)界面更新
        trigger()
      },
    }
  })
}

export default {
  name: 'App',
  setup() {
    let state = myRef('/data.json', [])

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

25-Vue3.0-ref-獲取元素-理解

在vue3.x中我們也可以通過ref來獲取元素

<template>
  <div ref="box">
    我是div
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
export default {
  name: 'App',
  setup() {
    let box = ref(null) // reactive({value: null})

    onMounted(() => {
      console.log('onMounted', box.value)
    })

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

26-Vue3.0-readonly家族-理解

用于創(chuàng)建一個只讀的數(shù)據(jù)赠橙,并且是遞歸只讀

一、readonly

<template>
  <div>
    <p>{{state.name}}</p>
    <p>{{state.attr.age}}</p>
    <p>{{state.attr.height}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { readonly, isReadonly, shallowReadonly } from 'vue'
export default {
  name: 'App',
  setup() {
    // 用于創(chuàng)建一個只讀的數(shù)據(jù)愤炸,并且是遞歸只讀
    let state = readonly({
      name: 'lnj',
      attr: {
        age: 18,
        height: 1.88,
      },
    })

    function myFn() {
      state.name = '知播漁'
      state.attr.age = 666
      state.attr.height = 1.66
      console.log(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

二期揪、shallowReadonly

用于創(chuàng)建一個只讀的數(shù)據(jù),但不是遞歸只讀的

<template>
  <div>
    <p>{{state.name}}</p>
    <p>{{state.attr.age}}</p>
    <p>{{state.attr.height}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { readonly, isReadonly, shallowReadonly } from 'vue'
export default {
  name: 'App',
  setup() {
    // 用于創(chuàng)建一個只讀的數(shù)據(jù)规个,并且是遞歸只讀
    let state = shallowReadonly({
      name: 'lnj',
      attr: {
        age: 18,
        height: 1.88,
      },
    })

    function myFn() {
      state.name = '知播漁'
      state.attr.age = 666
      state.attr.height = 1.66
      console.log(state)
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

三凤薛、isReadonly

判斷一個數(shù)據(jù)是否是只讀的

<template>
  <div>
    <p>{{state.name}}</p>
    <p>{{state.attr.age}}</p>
    <p>{{state.attr.height}}</p>
    <button @click="myFn">按鈕</button>
  </div>
</template>

<script>
import { readonly, isReadonly, shallowReadonly } from 'vue'
export default {
  name: 'App',
  setup() {
    // 用于創(chuàng)建一個只讀的數(shù)據(jù)姓建,并且是遞歸只讀
    let state = shallowReadonly({
      name: 'lnj',
      attr: {
        age: 18,
        height: 1.88,
      },
    })

    function myFn() {
      state.name = '知播漁'
      state.attr.age = 666
      state.attr.height = 1.66
      console.log(state)
      console.log(isReadonly(state))
    }

    return {
      state,
      myFn,
    }
  },
}
</script>

四、readonly 和 const 的區(qū)別

const:賦值保護缤苫,不能給變量重新賦值
readonly:屬性保護速兔,不能給屬性重新賦值

27-Vue3.0-V3響應式數(shù)據(jù)本質(zhì)上-理解 && 28-Vue3.0-V3響應式數(shù)據(jù)本質(zhì)下-理解

一、Vue3.0響應式數(shù)據(jù)本質(zhì)

  • 在Vue2.x中是通過defineProperty來實現(xiàn)響應式數(shù)據(jù)的
    詳見:手寫Vue全家桶
  • 在Vue3.x中是通過Proxy來實現(xiàn)響應式數(shù)據(jù)的
let obj = { name: 'lng', age: 18 }

let state = new Proxy(obj, {
  get (obj, key) {
    console.log(obj, key)
    return obj[key]
  },
  set (obj, key, value) {
    obj[key] = value
    console.log('更新界面', obj, key, value)
  }
})

console.log(state.name) // lng
state.name = '直播'
console.log(state.name)

二活玲、 Proxy注意點

  • set方法必須通過返回值告訴Proxy此次操作是否成功
let arr = [1, 3, 5]

let state = new Proxy(arr, {
  get (obj, key) {
    console.log(obj, key)
    return obj[key]
  },
  set (obj, key, value) {
    obj[key] = value
    console.log('更新界面', obj, key, value)
    // 沒有會報錯
    return true
  }
})

console.log(state[1])
state.push(7)

29-Vue3.0-手寫 shallowReactive-shallowRef-理解

function shallowReactive (obj) {
  return new Proxy(obj, {
    get (obj, key) {
      return obj[key]
    },
    set (obj, key, value) {
      obj[key] = value
      console.log('更新界面', obj, key, value)
      return true
    }
  })
}

function shallowRef (val) {
  return shallowReactive({
    value: val
  })
}

30-Vue3.0-手寫reactive-ref-理解

function reactive (obj) {
  if (typeof obj === 'object') {
    if (obj instanceof Array) {
      // 如果是一個數(shù)組涣狗,那么取出數(shù)組中的某一個元素
      // 判斷每一個元素是否又是一個對象,如果又是一個對象舒憾,如果又是一個對象镀钓,那么也需要包裝成一個Proxy
      obj.forEach((item, index) => {
        if (typeof item === 'object') {
          obj[index] = reactive(item)
        }
      })
    } else {
      // 如果是一個對象,那么取出對象屬性的取值
      // 判斷每一個元素是否又是一個對象镀迂,如果又是一個對象掸宛,如果又是一個對象,那么也需要包裝成一個Proxy
      for (let key in obj) {
        let item = obj[key]
        if (typeof item === 'object') {
          obj[key] = reactive(item)
        }
      }
    }
    return new Proxy(obj, {
      get (obj, key) {
        return obj[key]
      },
      set (obj, key, value) {
        obj[key] = value
        console.log('更新界面', JSON.stringify(obj), key, value)
        return true
      }
    })
  } else {
    console.warn('不是一個對象')
  }
}

function ref (val) {
  return reactive({
    value: val
  })
}

31-Vue3.0-手寫readonly-shallowReadonly-理解

function shallowReadonly (obj) {
  return new Proxy(obj, {
    get (obj, key) {
      return obj[key]
    },
    set (obj, key, value) {
      console.log('只讀')
    }
  })
}

function readonly (obj) {
  if (typeof obj === 'object') {
    if (obj instanceof Array) {
      // 如果是一個數(shù)組招拙,那么取出數(shù)組中的某一個元素
      // 判斷每一個元素是否又是一個對象唧瘾,如果又是一個對象,如果又是一個對象别凤,那么也需要包裝成一個Proxy
      obj.forEach((item, index) => {
        if (typeof item === 'object') {
          obj[index] = readonly(item)
        }
      })
    } else {
      // 如果是一個對象饰序,那么取出對象屬性的取值
      // 判斷每一個元素是否又是一個對象,如果又是一個對象规哪,如果又是一個對象求豫,那么也需要包裝成一個Proxy
      for (let key in obj) {
        let item = obj[key]
        if (typeof item === 'object') {
          obj[key] = readonly(item)
        }
      }
    }
    return new Proxy(obj, {
      get (obj, key) {
        return obj[key]
      },
      set (obj, key, value) {
        console.log('只讀')
      }
    })
  } else {
    console.warn('不是一個對象')
  }
}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诉稍,隨后出現(xiàn)的幾起案子蝠嘉,更是在濱河造成了極大的恐慌,老刑警劉巖杯巨,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚤告,死亡現(xiàn)場離奇詭異,居然都是意外死亡服爷,警方通過查閱死者的電腦和手機杜恰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仍源,“玉大人心褐,你說我怎么就攤上這事×龋” “怎么了逗爹?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嚎于。 經(jīng)常有香客問我掘而,道長挟冠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任镣屹,我火速辦了婚禮圃郊,結果婚禮上价涝,老公的妹妹穿的比我還像新娘女蜈。我一直安慰自己,他們只是感情好色瘩,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布伪窖。 她就那樣靜靜地躺著,像睡著了一般居兆。 火紅的嫁衣襯著肌膚如雪覆山。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天泥栖,我揣著相機與錄音簇宽,去河邊找鬼。 笑死吧享,一個胖子當著我的面吹牛魏割,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钢颂,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼钞它,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了殊鞭?” 一聲冷哼從身側響起遭垛,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎操灿,沒想到半個月后锯仪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡趾盐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年卵酪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谤碳。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡溃卡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜒简,到底是詐尸還是另有隱情瘸羡,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布搓茬,位于F島的核電站犹赖,受9級特大地震影響队他,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜峻村,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一麸折、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粘昨,春花似錦垢啼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吞瞪,卻和暖如春馁启,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芍秆。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工惯疙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人妖啥。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓霉颠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迹栓。 傳聞我的和親對象是個殘疾皇子掉分,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345