Vue3 快速上手

1. Vue3簡介

1.1. 【性能的提升】

  • 打包大小減少41%蕾久。

  • 初次渲染快55%, 更新渲染快133%

  • 內(nèi)存減少54%拌夏。

1.2.【 源碼的升級】

  • 使用Proxy代替defineProperty實(shí)現(xiàn)響應(yīng)式僧著。

  • 重寫虛擬DOM的實(shí)現(xiàn)和Tree-Shaking

1.3. 【擁抱TypeScript】

  • Vue3可以更好的支持TypeScript障簿。

1.4. 【新的特性】

  1. Composition API(組合API):

    • setup

    • refreactive

    • computedwatch

      ......

  2. 新的內(nèi)置組件:

    • Fragment

    • Teleport

    • Suspense

      ......

  3. 其他改變:

    • 新的生命周期鉤子

    • data 選項(xiàng)應(yīng)始終被聲明為一個函數(shù)

    • 移除keyCode支持作為v-on 的修飾符

      ......

2. 創(chuàng)建Vue3工程

2.1. 【基于 vue-cli 創(chuàng)建】

點(diǎn)擊查看官方文檔

備注:目前vue-cli已處于維護(hù)模式盹愚,官方推薦基于 Vite 創(chuàng)建項(xiàng)目。

## 查看@vue/cli版本站故,確保@vue/cli版本在4.5.0以上
vue --version

## 安裝或者升級你的@vue/cli 
npm install -g @vue/cli

## 執(zhí)行創(chuàng)建命令
vue create vue_test

##  隨后選擇3.x
##  Choose a version of Vue.js that you want to start the project with (Use arrow keys)
##  > 3.x
##    2.x

## 啟動
cd vue_test
npm run serve

2.2. 【基于 vite 創(chuàng)建】(推薦)

vite 是新一代前端構(gòu)建工具皆怕,官網(wǎng)地址:https://vitejs.cnvite的優(yōu)勢如下:

  • 輕量快速的熱重載(HMR)西篓,能實(shí)現(xiàn)極速的服務(wù)啟動愈腾。
  • TypeScriptJSX岂津、CSS 等支持開箱即用虱黄。
  • 真正的按需編譯,不再等待整個應(yīng)用編譯完成寸爆。
  • webpack構(gòu)建 與 vite構(gòu)建對比圖如下:
## 1.創(chuàng)建命令
npm create vue@latest

## 2.具體配置
## 配置項(xiàng)目名稱
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript?  Yes
## 是否添加JSX支持
√ Add JSX Support?  No
## 是否添加路由環(huán)境
√ Add Vue Router for Single Page Application development?  No
## 是否添加pinia環(huán)境
√ Add Pinia for state management?  No
## 是否添加單元測試
√ Add Vitest for Unit Testing?  No
## 是否添加端到端測試方案
√ Add an End-to-End Testing Solution? ? No
## 是否添加ESLint語法檢查
√ Add ESLint for code quality?  Yes
## 是否添加Prettiert代碼格式化
√ Add Prettier for code formatting?  No

自己動手編寫一個App組件

<template>
  <div class="app">
    <h1>你好敖嘎场盐欺!</h1>
  </div>
</template>

<script lang="ts">
  export default {
    name:'App' //組件名
  }
</script>

<style>
  .app {
    background-color: #ddd;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
  }
</style>

安裝官方推薦的vscode插件:

總結(jié):

  • Vite 項(xiàng)目中,index.html 是項(xiàng)目的入口文件仅醇,在項(xiàng)目最外層冗美。
  • 加載index.html后,Vite 解析 <script type="module" src="xxx"> 指向的JavaScript析二。
  • Vue3**中是通過 **createApp 函數(shù)創(chuàng)建一個應(yīng)用實(shí)例粉洼。

2.3. 【一個簡單的效果】

Vue3向下兼容Vue2語法,且Vue3中的模板中可以沒有根標(biāo)簽

<template>
  <div class="person">
    <h2>姓名:{{name}}</h2>
    <h2>年齡:{{age}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">年齡+1</button>
    <button @click="showTel">點(diǎn)我查看聯(lián)系方式</button>
  </div>
</template>

<script lang="ts">
  export default {
    name:'App',
    data() {
      return {
        name:'張三',
        age:18,
        tel:'13888888888'
      }
    },
    methods:{
      changeName(){
        this.name = 'zhang-san'
      },
      changeAge(){
        this.age += 1
      },
      showTel(){
        alert(this.tel)
      }
    },
  }
</script>

3. Vue3核心語法

3.1. 【OptionsAPI 與 CompositionAPI】

  • Vue2API設(shè)計是Options(配置)風(fēng)格的叶摄。
  • Vue3API設(shè)計是Composition(組合)風(fēng)格的属韧。

Options API 的弊端

Options類型的 API,數(shù)據(jù)蛤吓、方法宵喂、計算屬性等,是分散在:data会傲、methods锅棕、computed中的,若想新增或者修改一個需求淌山,就需要分別修改:data裸燎、methodscomputed泼疑,不便于維護(hù)和復(fù)用德绿。

Composition API 的優(yōu)勢

可以用函數(shù)的方式,更加優(yōu)雅的組織代碼退渗,讓相關(guān)功能的代碼更加有序的組織在一起移稳。



說明:以上四張動圖原創(chuàng)作者:大帥老猿

3.2. 【拉開序幕的 setup】

setup 概述

setupVue3中一個新的配置項(xiàng),值是一個函數(shù)氓辣,它是 Composition API “表演的舞臺秒裕,組件中所用到的:數(shù)據(jù)、方法钞啸、計算屬性几蜻、監(jiān)視......等等,均配置在setup中体斩。

特點(diǎn)如下:

  • setup函數(shù)返回的對象中的內(nèi)容梭稚,可直接在模板中使用。
  • setup中訪問thisundefined絮吵。
  • setup函數(shù)會在beforeCreate之前調(diào)用弧烤,它是“領(lǐng)先”所有鉤子執(zhí)行的。
<template>
  <div class="person">
    <h2>姓名:{{name}}</h2>
    <h2>年齡:{{age}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">年齡+1</button>
    <button @click="showTel">點(diǎn)我查看聯(lián)系方式</button>
  </div>
</template>

<script lang="ts">
  export default {
    name:'Person',
    setup(){
      // 數(shù)據(jù)蹬敲,原來寫在data中(注意:此時的name暇昂、age莺戒、tel數(shù)據(jù)都不是響應(yīng)式數(shù)據(jù))
      let name = '張三'
      let age = 18
      let tel = '13888888888'

      // 方法,原來寫在methods中
      function changeName(){
        name = 'zhang-san' //注意:此時這么修改name頁面是不變化的
        console.log(name)
      }
      function changeAge(){
        age += 1 //注意:此時這么修改age頁面是不變化的
        console.log(age)
      }
      function showTel(){
        alert(tel)
      }

      // 返回一個對象急波,對象中的內(nèi)容从铲,模板中可以直接使用
      return {name,age,tel,changeName,changeAge,showTel}
    }
  }
</script>

setup 的返回值

  • 若返回一個對象:則對象中的:屬性、方法等澄暮,在模板中均可以直接使用(重點(diǎn)關(guān)注)名段。
  • 若返回一個函數(shù):則可以自定義渲染內(nèi)容,代碼如下:
setup(){
  return ()=> '你好捌谩伸辟!'
}

setup 與 Options API 的關(guān)系

  • Vue2 的配置(datamethos......)中可以訪問到 setup中的屬性馍刮、方法信夫。
  • 但在setup不能訪問到Vue2的配置(datamethos......)渠退。
  • 如果與Vue2沖突忙迁,則setup優(yōu)先。

setup 語法糖

setup函數(shù)有一個語法糖碎乃,這個語法糖,可以讓我們把setup獨(dú)立出去惠奸,代碼如下:

<template>
  <div class="person">
    <h2>姓名:{{name}}</h2>
    <h2>年齡:{{age}}</h2>
    <button @click="changName">修改名字</button>
    <button @click="changAge">年齡+1</button>
    <button @click="showTel">點(diǎn)我查看聯(lián)系方式</button>
  </div>
</template>

<script lang="ts">
  export default {
    name:'Person',
  }
</script>

<!-- 下面的寫法是setup語法糖 -->
<script setup lang="ts">
  console.log(this) //undefined
  
  // 數(shù)據(jù)(注意:此時的name梅誓、age、tel都不是響應(yīng)式數(shù)據(jù))
  let name = '張三'
  let age = 18
  let tel = '13888888888'

  // 方法
  function changName(){
    name = '李四'//注意:此時這么修改name頁面是不變化的
  }
  function changAge(){
    console.log(age)
    age += 1 //注意:此時這么修改age頁面是不變化的
  }
  function showTel(){
    alert(tel)
  }
</script>

擴(kuò)展:上述代碼佛南,還需要編寫一個不寫setupscript標(biāo)簽梗掰,去指定組件名字,比較麻煩嗅回,我們可以借助vite中的插件簡化

  1. 第一步:npm i vite-plugin-vue-setup-extend -D
  2. 第二步:vite.config.ts
import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

export default defineConfig({
  plugins: [ VueSetupExtend() ]
})
  1. 第三步:<script setup lang="ts" name="Person">

3.3. 【ref 創(chuàng)建:基本類型的響應(yīng)式數(shù)據(jù)】

  • 作用:定義響應(yīng)式變量及穗。
  • 語法:let xxx = ref(初始值)
  • 返回值:一個RefImpl的實(shí)例對象绵载,簡稱ref對象ref埂陆,ref對象的value屬性是響應(yīng)式的
  • 注意點(diǎn):
    • JS中操作數(shù)據(jù)需要:xxx.value娃豹,但模板中不需要.value焚虱,直接使用即可。
    • 對于let name = ref('張三')來說懂版,name不是響應(yīng)式的鹃栽,name.value是響應(yīng)式的。
<template>
  <div class="person">
    <h2>姓名:{{name}}</h2>
    <h2>年齡:{{age}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">年齡+1</button>
    <button @click="showTel">點(diǎn)我查看聯(lián)系方式</button>
  </div>
</template>

<script setup lang="ts" name="Person">
  import {ref} from 'vue'
  // name和age是一個RefImpl的實(shí)例對象躯畴,簡稱ref對象薇芝,它們的value屬性是響應(yīng)式的。
  let name = ref('張三')
  let age = ref(18)
  // tel就是一個普通的字符串丰嘉,不是響應(yīng)式的
  let tel = '13888888888'

  function changeName(){
    // JS中操作ref對象時候需要.value
    name.value = '李四'
    console.log(name.value)

    // 注意:name不是響應(yīng)式的夯到,name.value是響應(yīng)式的,所以如下代碼并不會引起頁面的更新供嚎。
    // name = ref('zhang-san')
  }
  function changeAge(){
    // JS中操作ref對象時候需要.value
    age.value += 1 
    console.log(age.value)
  }
  function showTel(){
    alert(tel)
  }
</script>

3.4. 【reactive 創(chuàng)建:對象類型的響應(yīng)式數(shù)據(jù)】

  • 作用:定義一個響應(yīng)式對象(基本類型不要用它黄娘,要用ref,否則報錯)
  • 語法:let 響應(yīng)式對象= reactive(源對象)克滴。
  • 返回值:一個Proxy的實(shí)例對象逼争,簡稱:響應(yīng)式對象。
  • 注意點(diǎn):reactive定義的響應(yīng)式數(shù)據(jù)是“深層次”的劝赔。
<template>
  <div class="person">
    <h2>汽車信息:一臺{{ car.brand }}汽車誓焦,價值{{ car.price }}萬</h2>
    <h2>游戲列表:</h2>
    <ul>
      <li v-for="g in games" :key="g.id">{{ g.name }}</li>
    </ul>
    <h2>測試:{{obj.a.b.c.d}}</h2>
    <button @click="changeCarPrice">修改汽車價格</button>
    <button @click="changeFirstGame">修改第一游戲</button>
    <button @click="test">測試</button>
  </div>
</template>

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

// 數(shù)據(jù)
let car = reactive({ brand: '奔馳', price: 100 })
let games = reactive([
  { id: 'ahsgdyfa01', name: '英雄聯(lián)盟' },
  { id: 'ahsgdyfa02', name: '王者榮耀' },
  { id: 'ahsgdyfa03', name: '原神' }
])
let obj = reactive({
  a:{
    b:{
      c:{
        d:666
      }
    }
  }
})

function changeCarPrice() {
  car.price += 10
}
function changeFirstGame() {
  games[0].name = '流星蝴蝶劍'
}
function test(){
  obj.a.b.c.d = 999
}
</script>

3.5. 【ref 創(chuàng)建:對象類型的響應(yīng)式數(shù)據(jù)】

  • 其實(shí)ref接收的數(shù)據(jù)可以是:基本類型對象類型着帽。
  • ref接收的是對象類型杂伟,內(nèi)部其實(shí)也是調(diào)用了reactive函數(shù)。
<template>
  <div class="person">
    <h2>汽車信息:一臺{{ car.brand }}汽車仍翰,價值{{ car.price }}萬</h2>
    <h2>游戲列表:</h2>
    <ul>
      <li v-for="g in games" :key="g.id">{{ g.name }}</li>
    </ul>
    <h2>測試:{{obj.a.b.c.d}}</h2>
    <button @click="changeCarPrice">修改汽車價格</button>
    <button @click="changeFirstGame">修改第一游戲</button>
    <button @click="test">測試</button>
  </div>
</template>

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

// 數(shù)據(jù)
let car = ref({ brand: '奔馳', price: 100 })
let games = ref([
  { id: 'ahsgdyfa01', name: '英雄聯(lián)盟' },
  { id: 'ahsgdyfa02', name: '王者榮耀' },
  { id: 'ahsgdyfa03', name: '原神' }
])
let obj = ref({
  a:{
    b:{
      c:{
        d:666
      }
    }
  }
})

console.log(car)

function changeCarPrice() {
  car.value.price += 10
}
function changeFirstGame() {
  games.value[0].name = '流星蝴蝶劍'
}
function test(){
  obj.value.a.b.c.d = 999
}
</script>

3.6. 【ref 對比 reactive】

宏觀角度看:

  1. ref用來定義:基本類型數(shù)據(jù)赫粥、對象類型數(shù)據(jù)

  2. reactive用來定義:對象類型數(shù)據(jù)予借。

  • 區(qū)別:
  1. ref創(chuàng)建的變量必須使用.value(可以使用volar插件自動添加.value)友瘤。

  2. reactive重新分配一個新對象帕棉,會失去響應(yīng)式(可以使用Object.assign去整體替換)甜攀。

  • 使用原則:
  1. 若需要一個基本類型的響應(yīng)式數(shù)據(jù)蚕愤,必須使用ref
  2. 若需要一個響應(yīng)式對象瀑粥,層級不深挣跋,refreactive都可以狞换。
  3. 若需要一個響應(yīng)式對象避咆,且層級較深,推薦使用reactive哀澈。

3.7. 【toRefs 與 toRef】

  • 作用:將一個響應(yīng)式對象中的每一個屬性牌借,轉(zhuǎn)換為ref對象。
  • 備注:toRefstoRef功能一致割按,但toRefs可以批量轉(zhuǎn)換膨报。
  • 語法如下:
<template>
  <div class="person">
    <h2>姓名:{{person.name}}</h2>
    <h2>年齡:{{person.age}}</h2>
    <h2>性別:{{person.gender}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年齡</button>
    <button @click="changeGender">修改性別</button>
  </div>
</template>

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

  // 數(shù)據(jù)
  let person = reactive({name:'張三', age:18, gender:'男'})
    
  // 通過toRefs將person對象中的n個屬性批量取出,且依然保持響應(yīng)式的能力
  let {name,gender} =  toRefs(person)
    
  // 通過toRef將person對象中的gender屬性取出,且依然保持響應(yīng)式的能力
  let age = toRef(person,'age')

  // 方法
  function changeName(){
    name.value += '~'
  }
  function changeAge(){
    age.value += 1
  }
  function changeGender(){
    gender.value = '女'
  }
</script>

3.8. 【computed】

作用:根據(jù)已有數(shù)據(jù)計算出新數(shù)據(jù)(和Vue2中的computed作用一致)现柠。

<template>
  <div class="person">
    姓:<input type="text" v-model="firstName"> <br>
    名:<input type="text" v-model="lastName"> <br>
    全名:<span>{{fullName}}</span> <br>
    <button @click="changeFullName">全名改為:li-si</button>
  </div>
</template>

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

  let firstName = ref('zhang')
  let lastName = ref('san')

  // 計算屬性——只讀取院领,不修改
  /* let fullName = computed(()=>{
    return firstName.value + '-' + lastName.value
  }) */


  // 計算屬性——既讀取又修改
  let fullName = computed({
    // 讀取
    get(){
      return firstName.value + '-' + lastName.value
    },
    // 修改
    set(val){
      console.log('有人修改了fullName',val)
      firstName.value = val.split('-')[0]
      lastName.value = val.split('-')[1]
    }
  })

  function changeFullName(){
    fullName.value = 'li-si'
  } 
</script>

3.9.【watch】

  • 作用:監(jiān)視數(shù)據(jù)的變化(和Vue2中的watch作用一致)
  • 特點(diǎn):Vue3中的watch只能監(jiān)視以下四種數(shù)據(jù)
  1. ref定義的數(shù)據(jù)。
  2. reactive定義的數(shù)據(jù)够吩。
  3. 函數(shù)返回一個值(getter函數(shù))比然。
  4. 一個包含上述內(nèi)容的數(shù)組。

我們在Vue3中使用watch的時候周循,通常會遇到以下幾種情況:

* 情況一

監(jiān)視ref定義的【基本類型】數(shù)據(jù):直接寫數(shù)據(jù)名即可强法,監(jiān)視的是其value值的改變。

<template>
  <div class="person">
    <h1>情況一:監(jiān)視【ref】定義的【基本類型】數(shù)據(jù)</h1>
    <h2>當(dāng)前求和為:{{sum}}</h2>
    <button @click="changeSum">點(diǎn)我sum+1</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 數(shù)據(jù)
  let sum = ref(0)
  // 方法
  function changeSum(){
    sum.value += 1
  }
  // 監(jiān)視湾笛,情況一:監(jiān)視【ref】定義的【基本類型】數(shù)據(jù)
  const stopWatch = watch(sum,(newValue,oldValue)=>{
    console.log('sum變化了',newValue,oldValue)
    if(newValue >= 10){
      stopWatch()
    }
  })
</script>

* 情況二

監(jiān)視ref定義的【對象類型】數(shù)據(jù):直接寫數(shù)據(jù)名饮怯,監(jiān)視的是對象的【地址值】,若想監(jiān)視對象內(nèi)部的數(shù)據(jù)嚎研,要手動開啟深度監(jiān)視蓖墅。

注意:

  • 若修改的是ref定義的對象中的屬性,newValueoldValue 都是新值临扮,因?yàn)樗鼈兪峭粋€對象论矾。

  • 若修改整個ref定義的對象,newValue 是新值杆勇, oldValue 是舊值贪壳,因?yàn)椴皇峭粋€對象了。

<template>
  <div class="person">
    <h1>情況二:監(jiān)視【ref】定義的【對象類型】數(shù)據(jù)</h1>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年齡:{{ person.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年齡</button>
    <button @click="changePerson">修改整個人</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 數(shù)據(jù)
  let person = ref({
    name:'張三',
    age:18
  })
  // 方法
  function changeName(){
    person.value.name += '~'
  }
  function changeAge(){
    person.value.age += 1
  }
  function changePerson(){
    person.value = {name:'李四',age:90}
  }
  /* 
    監(jiān)視蚜退,情況一:監(jiān)視【ref】定義的【對象類型】數(shù)據(jù)寥袭,監(jiān)視的是對象的地址值,若想監(jiān)視對象內(nèi)部屬性的變化关霸,需要手動開啟深度監(jiān)視
    watch的第一個參數(shù)是:被監(jiān)視的數(shù)據(jù)
    watch的第二個參數(shù)是:監(jiān)視的回調(diào)
    watch的第三個參數(shù)是:配置對象(deep、immediate等等.....) 
  */
  watch(person,(newValue,oldValue)=>{
    console.log('person變化了',newValue,oldValue)
  },{deep:true})
  
</script>

* 情況三

監(jiān)視reactive定義的【對象類型】數(shù)據(jù)杰扫,且默認(rèn)開啟了深度監(jiān)視队寇。

<template>
  <div class="person">
    <h1>情況三:監(jiān)視【reactive】定義的【對象類型】數(shù)據(jù)</h1>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年齡:{{ person.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年齡</button>
    <button @click="changePerson">修改整個人</button>
    <hr>
    <h2>測試:{{obj.a.b.c}}</h2>
    <button @click="test">修改obj.a.b.c</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'
  // 數(shù)據(jù)
  let person = reactive({
    name:'張三',
    age:18
  })
  let obj = reactive({
    a:{
      b:{
        c:666
      }
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeAge(){
    person.age += 1
  }
  function changePerson(){
    Object.assign(person,{name:'李四',age:80})
  }
  function test(){
    obj.a.b.c = 888
  }

  // 監(jiān)視,情況三:監(jiān)視【reactive】定義的【對象類型】數(shù)據(jù)章姓,且默認(rèn)是開啟深度監(jiān)視的
  watch(person,(newValue,oldValue)=>{
    console.log('person變化了',newValue,oldValue)
  })
  watch(obj,(newValue,oldValue)=>{
    console.log('Obj變化了',newValue,oldValue)
  })
</script>

* 情況四

監(jiān)視refreactive定義的【對象類型】數(shù)據(jù)中的某個屬性佳遣,注意點(diǎn)如下:

  1. 若該屬性值不是【對象類型】,需要寫成函數(shù)形式凡伊。
  2. 若該屬性值是依然是【對象類型】零渐,可直接編,也可寫成函數(shù)系忙,建議寫成函數(shù)诵盼。

結(jié)論:監(jiān)視的要是對象里的屬性,那么最好寫函數(shù)式,注意點(diǎn):若是對象監(jiān)視的是地址值风宁,需要關(guān)注對象內(nèi)部洁墙,需要手動開啟深度監(jiān)視。

<template>
  <div class="person">
    <h1>情況四:監(jiān)視【ref】或【reactive】定義的【對象類型】數(shù)據(jù)中的某個屬性</h1>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年齡:{{ person.age }}</h2>
    <h2>汽車:{{ person.car.c1 }}戒财、{{ person.car.c2 }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年齡</button>
    <button @click="changeC1">修改第一臺車</button>
    <button @click="changeC2">修改第二臺車</button>
    <button @click="changeCar">修改整個車</button>
  </div>
</template>

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

  // 數(shù)據(jù)
  let person = reactive({
    name:'張三',
    age:18,
    car:{
      c1:'奔馳',
      c2:'寶馬'
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeAge(){
    person.age += 1
  }
  function changeC1(){
    person.car.c1 = '奧迪'
  }
  function changeC2(){
    person.car.c2 = '大眾'
  }
  function changeCar(){
    person.car = {c1:'雅迪',c2:'愛瑪'}
  }

  // 監(jiān)視热监,情況四:監(jiān)視響應(yīng)式對象中的某個屬性,且該屬性是基本類型的饮寞,要寫成函數(shù)式
  /* watch(()=> person.name,(newValue,oldValue)=>{
    console.log('person.name變化了',newValue,oldValue)
  }) */

  // 監(jiān)視孝扛,情況四:監(jiān)視響應(yīng)式對象中的某個屬性,且該屬性是對象類型的幽崩,可以直接寫苦始,也能寫函數(shù),更推薦寫函數(shù)
  watch(()=>person.car,(newValue,oldValue)=>{
    console.log('person.car變化了',newValue,oldValue)
  },{deep:true})
</script>

* 情況五

監(jiān)視上述的多個數(shù)據(jù)

<template>
  <div class="person">
    <h1>情況五:監(jiān)視上述的多個數(shù)據(jù)</h1>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年齡:{{ person.age }}</h2>
    <h2>汽車:{{ person.car.c1 }}歉铝、{{ person.car.c2 }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年齡</button>
    <button @click="changeC1">修改第一臺車</button>
    <button @click="changeC2">修改第二臺車</button>
    <button @click="changeCar">修改整個車</button>
  </div>
</template>

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

  // 數(shù)據(jù)
  let person = reactive({
    name:'張三',
    age:18,
    car:{
      c1:'奔馳',
      c2:'寶馬'
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeAge(){
    person.age += 1
  }
  function changeC1(){
    person.car.c1 = '奧迪'
  }
  function changeC2(){
    person.car.c2 = '大眾'
  }
  function changeCar(){
    person.car = {c1:'雅迪',c2:'愛瑪'}
  }

  // 監(jiān)視盈简,情況五:監(jiān)視上述的多個數(shù)據(jù)
  watch([()=>person.name,person.car],(newValue,oldValue)=>{
    console.log('person.car變化了',newValue,oldValue)
  },{deep:true})

</script>

3.10. 【watchEffect】

  • 官網(wǎng):立即運(yùn)行一個函數(shù),同時響應(yīng)式地追蹤其依賴太示,并在依賴更改時重新執(zhí)行該函數(shù)柠贤。

  • watch對比watchEffect

    1. 都能監(jiān)聽響應(yīng)式數(shù)據(jù)的變化,不同的是監(jiān)聽數(shù)據(jù)變化的方式不同

    2. watch:要明確指出監(jiān)視的數(shù)據(jù)

    3. watchEffect:不用明確指出監(jiān)視的數(shù)據(jù)(函數(shù)中用到哪些屬性类缤,那就監(jiān)視哪些屬性)臼勉。

  • 示例代碼:

    <template>
      <div class="person">
        <h1>需求:水溫達(dá)到50℃,或水位達(dá)到20cm餐弱,則聯(lián)系服務(wù)器</h1>
        <h2 id="demo">水溫:{{temp}}</h2>
        <h2>水位:{{height}}</h2>
        <button @click="changePrice">水溫+1</button>
        <button @click="changeSum">水位+10</button>
      </div>
    </template>
    
    <script lang="ts" setup name="Person">
      import {ref,watch,watchEffect} from 'vue'
      // 數(shù)據(jù)
      let temp = ref(0)
      let height = ref(0)
    
      // 方法
      function changePrice(){
        temp.value += 10
      }
      function changeSum(){
        height.value += 1
      }
    
      // 用watch實(shí)現(xiàn)宴霸,需要明確的指出要監(jiān)視:temp、height
      watch([temp,height],(value)=>{
        // 從value中獲取最新的temp值膏蚓、height值
        const [newTemp,newHeight] = value
        // 室溫達(dá)到50℃瓢谢,或水位達(dá)到20cm,立刻聯(lián)系服務(wù)器
        if(newTemp >= 50 || newHeight >= 20){
          console.log('聯(lián)系服務(wù)器')
        }
      })
    
      // 用watchEffect實(shí)現(xiàn)驮瞧,不用
      const stopWtach = watchEffect(()=>{
        // 室溫達(dá)到50℃氓扛,或水位達(dá)到20cm,立刻聯(lián)系服務(wù)器
        if(temp.value >= 50 || height.value >= 20){
          console.log(document.getElementById('demo')?.innerText)
          console.log('聯(lián)系服務(wù)器')
        }
        // 水溫達(dá)到100论笔,或水位達(dá)到50采郎,取消監(jiān)視
        if(temp.value === 100 || height.value === 50){
          console.log('清理了')
          stopWtach()
        }
      })
    </script>
    

3.11. 【標(biāo)簽的 ref 屬性】

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

  • 用在普通DOM標(biāo)簽上狂魔,獲取的是DOM節(jié)點(diǎn)蒜埋。

  • 用在組件標(biāo)簽上,獲取的是組件實(shí)例對象最楷。

用在普通DOM標(biāo)簽上:

<template>
  <div class="person">
    <h1 ref="title1">尚硅谷</h1>
    <h2 ref="title2">前端</h2>
    <h3 ref="title3">Vue</h3>
    <input type="text" ref="inpt"> <br><br>
    <button @click="showLog">點(diǎn)我打印內(nèi)容</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref} from 'vue'
    
  let title1 = ref()
  let title2 = ref()
  let title3 = ref()

  function showLog(){
    // 通過id獲取元素
    const t1 = document.getElementById('title1')
    // 打印內(nèi)容
    console.log((t1 as HTMLElement).innerText)
    console.log((<HTMLElement>t1).innerText)
    console.log(t1?.innerText)
    
        /************************************/
        
    // 通過ref獲取元素
    console.log(title1.value)
    console.log(title2.value)
    console.log(title3.value)
  }
</script>

用在組件標(biāo)簽上:

<!-- 父組件App.vue -->
<template>
  <Person ref="ren"/>
  <button @click="test">測試</button>
</template>

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

  let ren = ref()

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


<!-- 子組件Person.vue中要使用defineExpose暴露內(nèi)容 -->
<script lang="ts" setup name="Person">
  import {ref,defineExpose} from 'vue'
    // 數(shù)據(jù)
  let name = ref('張三')
  let age = ref(18)
  /****************************/
  /****************************/
  // 使用defineExpose將組件中的數(shù)據(jù)交給外部
  defineExpose({name,age})
</script>

3.12. 【props】

// 定義一個接口整份,限制每個Person對象的格式
export interface PersonInter {
 id:string,
 name:string,
    age:number
   }
   
// 定義一個自定義類型Persons
export type Persons = Array<PersonInter>

App.vue中代碼:

<template>
  <Person :list="persons"/>
</template>
  
<script lang="ts" setup name="App">
  import Person from './components/Person.vue'
  import {reactive} from 'vue'
    import {type Persons} from './types'
  
    let persons = reactive<Persons>([
     {id:'e98219e12',name:'張三',age:18},
      {id:'e98219e13',name:'李四',age:19},
       {id:'e98219e14',name:'王五',age:20}
     ])
   </script>
  

Person.vue中代碼:

<template>
<div class="person">
 <ul>
     <li v-for="item in list" :key="item.id">
        {{item.name}}--{{item.age}}
      </li>
    </ul>
   </div>
   </template>
  
<script lang="ts" setup name="Person">
import {defineProps} from 'vue'
import {type PersonInter} from '@/types'
  
  // 第一種寫法:僅接收
// const props = defineProps(['list'])
  
  // 第二種寫法:接收+限制類型
// defineProps<{list:Persons}>()
  
  // 第三種寫法:接收+限制類型+指定默認(rèn)值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{
     list:()=>[{id:'asdasg01',name:'小豬佩奇',age:18}]
  })
   console.log(props)
  </script>

3.13. 【生命周期】

  • 概念:Vue組件實(shí)例在創(chuàng)建時要經(jīng)歷一系列的初始化步驟待错,在此過程中Vue會在合適的時機(jī),調(diào)用特定的函數(shù)皂林,從而讓開發(fā)者有機(jī)會在特定階段運(yùn)行自己的代碼朗鸠,這些特定的函數(shù)統(tǒng)稱為:生命周期鉤子

  • 規(guī)律:

    生命周期整體分為四個階段,分別是:創(chuàng)建础倍、掛載烛占、更新、銷毀沟启,每個階段都有兩個鉤子忆家,一前一后。

  • Vue2的生命周期

    創(chuàng)建階段:beforeCreate德迹、created

    掛載階段:beforeMount芽卿、mounted

    更新階段:beforeUpdateupdated

    銷毀階段:beforeDestroy胳搞、destroyed

  • Vue3的生命周期

    創(chuàng)建階段:setup

    掛載階段:onBeforeMount卸例、onMounted

    更新階段:onBeforeUpdateonUpdated

    卸載階段:onBeforeUnmount肌毅、onUnmounted

  • 常用的鉤子:onMounted(掛載完畢)筷转、onUpdated(更新完畢)、onBeforeUnmount(卸載之前)

  • 示例代碼:

    <template>
      <div class="person">
        <h2>當(dāng)前求和為:{{ sum }}</h2>
        <button @click="changeSum">點(diǎn)我sum+1</button>
      </div>
    </template>
    
    <!-- vue3寫法 -->
    <script lang="ts" setup name="Person">
      import { 
        ref, 
        onBeforeMount, 
        onMounted, 
        onBeforeUpdate, 
        onUpdated, 
        onBeforeUnmount, 
        onUnmounted 
      } from 'vue'
    
      // 數(shù)據(jù)
      let sum = ref(0)
      // 方法
      function changeSum() {
        sum.value += 1
      }
      console.log('setup')
      // 生命周期鉤子
      onBeforeMount(()=>{
        console.log('掛載之前')
      })
      onMounted(()=>{
        console.log('掛載完畢')
      })
      onBeforeUpdate(()=>{
        console.log('更新之前')
      })
      onUpdated(()=>{
        console.log('更新完畢')
      })
      onBeforeUnmount(()=>{
        console.log('卸載之前')
      })
      onUnmounted(()=>{
        console.log('卸載完畢')
      })
    </script>
    

3.14. 【自定義hook】

  • 什么是hook悬而?—— 本質(zhì)是一個函數(shù)呜舒,把setup函數(shù)中使用的Composition API進(jìn)行了封裝,類似于vue2.x中的mixin笨奠。

  • 自定義hook的優(yōu)勢:復(fù)用代碼, 讓setup中的邏輯更清楚易懂袭蝗。

示例代碼:

  • useSum.ts中內(nèi)容如下:

    import {ref,onMounted} from 'vue'
    
    export default function(){
      let sum = ref(0)
    
      const increment = ()=>{
        sum.value += 1
      }
      const decrement = ()=>{
        sum.value -= 1
      }
      onMounted(()=>{
        increment()
      })
    
      //向外部暴露數(shù)據(jù)
      return {sum,increment,decrement}
    }     
    
  • useDog.ts中內(nèi)容如下:

    import {reactive,onMounted} from 'vue'
    import axios,{AxiosError} from 'axios'
    
    export default function(){
      let dogList = reactive<string[]>([])
    
      // 方法
      async function getDog(){
        try {
          // 發(fā)請求
          let {data} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
          // 維護(hù)數(shù)據(jù)
          dogList.push(data.message)
        } catch (error) {
          // 處理錯誤
          const err = <AxiosError>error
          console.log(err.message)
        }
      }
    
      // 掛載鉤子
      onMounted(()=>{
        getDog()
      })
      
      //向外部暴露數(shù)據(jù)
      return {dogList,getDog}
    }
    
  • 組件中具體使用:

    <template>
      <h2>當(dāng)前求和為:{{sum}}</h2>
      <button @click="increment">點(diǎn)我+1</button>
      <button @click="decrement">點(diǎn)我-1</button>
      <hr>
      <img v-for="(u,index) in dogList.urlList" :key="index" :src="(u as string)"> 
      <span v-show="dogList.isLoading">加載中......</span><br>
      <button @click="getDog">再來一只狗</button>
    </template>
    
    <script lang="ts">
      import {defineComponent} from 'vue'
    
      export default defineComponent({
        name:'App',
      })
    </script>
    
    <script setup lang="ts">
      import useSum from './hooks/useSum'
      import useDog from './hooks/useDog'
      
      let {sum,increment,decrement} = useSum()
      let {dogList,getDog} = useDog()
    </script>
    

4. 路由

4.1. 【對路由的理解】

4.2. 【基本切換效果】

  • Vue3中要使用vue-router的最新版本,目前是4版本般婆。

  • 路由配置文件代碼如下:

    import {createRouter,createWebHistory} from 'vue-router'
    import Home from '@/pages/Home.vue'
    import News from '@/pages/News.vue'
    import About from '@/pages/About.vue'
    
    const router = createRouter({
      history:createWebHistory(),
      routes:[
          {
              path:'/home',
              component:Home
          },
          {
              path:'/about',
              component:About
          }
      ]
    })
    export default router
    
  • main.ts代碼如下:

    import router from './router/index'
    app.use(router)
    
    app.mount('#app')
    
  • App.vue代碼如下

    <template>
      <div class="app">
        <h2 class="title">Vue路由測試</h2>
        <!-- 導(dǎo)航區(qū) -->
        <div class="navigate">
          <RouterLink to="/home" active-class="active">首頁</RouterLink>
          <RouterLink to="/news" active-class="active">新聞</RouterLink>
          <RouterLink to="/about" active-class="active">關(guān)于</RouterLink>
        </div>
        <!-- 展示區(qū) -->
        <div class="main-content">
          <RouterView></RouterView>
        </div>
      </div>
    </template>
    
    <script lang="ts" setup name="App">
      import {RouterLink,RouterView} from 'vue-router'  
    </script>
    

4.3. 【兩個注意點(diǎn)】

  1. 路由組件通常存放在pagesviews文件夾到腥,一般組件通常存放在components文件夾。

  2. 通過點(diǎn)擊導(dǎo)航蔚袍,視覺效果上“消失” 了的路由組件左电,默認(rèn)是被卸載掉的,需要的時候再去掛載页响。

4.4.【路由器工作模式】

  1. history模式

    優(yōu)點(diǎn):URL更加美觀,不帶有#段誊,更接近傳統(tǒng)的網(wǎng)站URL闰蚕。

    缺點(diǎn):后期項(xiàng)目上線,需要服務(wù)端配合處理路徑問題连舍,否則刷新會有404錯誤没陡。

    const router = createRouter({
       history:createWebHistory(), //history模式
       /******/
    })
    
  2. hash模式

    優(yōu)點(diǎn):兼容性更好,因?yàn)椴恍枰?wù)器端處理路徑。

    缺點(diǎn):URL帶有#不太美觀盼玄,且在SEO優(yōu)化方面相對較差贴彼。

    const router = createRouter({
       history:createWebHashHistory(), //hash模式
       /******/
    })
    

4.5. 【to的兩種寫法】

<!-- 第一種:to的字符串寫法 -->
<router-link active-class="active" to="/home">主頁</router-link>

<!-- 第二種:to的對象寫法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>

4.6. 【命名路由】

作用:可以簡化路由跳轉(zhuǎn)及傳參(后面就講)。

給路由規(guī)則命名:

routes:[
  {
    name:'zhuye',
    path:'/home',
    component:Home
  },
  {
    name:'xinwen',
    path:'/news',
    component:News,
  },
  {
    name:'guanyu',
    path:'/about',
    component:About
  }
]

跳轉(zhuǎn)路由:

<!--簡化前:需要寫完整的路徑(to的字符串寫法) -->
<router-link to="/news/detail">跳轉(zhuǎn)</router-link>

<!--簡化后:直接通過名字跳轉(zhuǎn)(to的對象寫法配合name屬性) -->
<router-link :to="{name:'guanyu'}">跳轉(zhuǎn)</router-link>

4.7. 【嵌套路由】

  1. 編寫News的子路由:Detail.vue

  2. 配置路由規(guī)則埃儿,使用children配置項(xiàng):

    const router = createRouter({
      history:createWebHistory(),
     routes:[
         {
             name:'zhuye',
             path:'/home',
             component:Home
         },
         {
             name:'xinwen',
             path:'/news',
             component:News,
             children:[
                 {
                     name:'xiang',
                     path:'detail',
                     component:Detail
                 }
             ]
         },
         {
             name:'guanyu',
             path:'/about',
             component:About
         }
     ]
    })
    export default router
    
  3. 跳轉(zhuǎn)路由(記得要加完整路徑):

    <router-link to="/news/detail">xxxx</router-link>
    <!-- 或 -->
    <router-link :to="{path:'/news/detail'}">xxxx</router-link>
    
  4. 記得去Home組件中預(yù)留一個<router-view>

    <template>
      <div class="news">
        <nav class="news-list">
          <RouterLink v-for="news in newsList" :key="news.id" :to="{path:'/news/detail'}">
            {{news.name}}
          </RouterLink>
        </nav>
        <div class="news-detail">
          <RouterView/>
        </div>
      </div>
    </template>
    

4.8. 【路由傳參】

query參數(shù)

  1. 傳遞參數(shù)

    <!-- 跳轉(zhuǎn)并攜帶query參數(shù)(to的字符串寫法) -->
    <router-link to="/news/detail?a=1&b=2&content=歡迎你">
      跳轉(zhuǎn)
    </router-link>
                  
    <!-- 跳轉(zhuǎn)并攜帶query參數(shù)(to的對象寫法) -->
    <RouterLink 
      :to="{
        //name:'xiang', //用name也可以跳轉(zhuǎn)
        path:'/news/detail',
        query:{
          id:news.id,
          title:news.title,
          content:news.content
        }
      }"
    >
      {{news.title}}
    </RouterLink>
    
  2. 接收參數(shù):

    import {useRoute} from 'vue-router'
    const route = useRoute()
    // 打印query參數(shù)
    console.log(route.query)
    

params參數(shù)

  1. 傳遞參數(shù)

    <!-- 跳轉(zhuǎn)并攜帶params參數(shù)(to的字符串寫法) -->
    <RouterLink :to="`/news/detail/001/新聞001/內(nèi)容001`">{{news.title}}</RouterLink>
                  
    <!-- 跳轉(zhuǎn)并攜帶params參數(shù)(to的對象寫法) -->
    <RouterLink 
      :to="{
        name:'xiang', //用name跳轉(zhuǎn)
        params:{
          id:news.id,
          title:news.title,
          content:news.title
        }
      }"
    >
      {{news.title}}
    </RouterLink>
    
  2. 接收參數(shù):

    import {useRoute} from 'vue-router'
    const route = useRoute()
    // 打印params參數(shù)
    console.log(route.params)
    

備注1:傳遞params參數(shù)時器仗,若使用to的對象寫法,必須使用name配置項(xiàng)童番,不能用path精钮。

備注2:傳遞params參數(shù)時,需要提前在規(guī)則中占位剃斧。

4.9. 【路由的props配置】

作用:讓路由組件更方便的收到參數(shù)(可以將路由參數(shù)作為props傳給組件)

{
    name:'xiang',
    path:'detail/:id/:title/:content',
    component:Detail,

  // props的對象寫法轨香,作用:把對象中的每一組key-value作為props傳給Detail組件
  // props:{a:1,b:2,c:3}, 

  // props的布爾值寫法,作用:把收到了每一組params參數(shù)幼东,作為props傳給Detail組件
  // props:true
  
  // props的函數(shù)寫法臂容,作用:把返回的對象中每一組key-value作為props傳給Detail組件
  props(route){
    return route.query
  }
}

4.10. 【 replace屬性】

  1. 作用:控制路由跳轉(zhuǎn)時操作瀏覽器歷史記錄的模式。

  2. 瀏覽器的歷史記錄有兩種寫入方式:分別為pushreplace

    • push是追加歷史記錄(默認(rèn)值)根蟹。
    • replace是替換當(dāng)前記錄脓杉。
  3. 開啟replace模式:

    <RouterLink replace .......>News</RouterLink>
    

4.11. 【編程式導(dǎo)航】

路由組件的兩個重要的屬性:$route$router變成了兩個hooks

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

const route = useRoute()
const router = useRouter()

console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)

4.12. 【重定向】

  1. 作用:將特定的路徑,重新定向到已有路由娜亿。

  2. 具體編碼:

    {
        path:'/',
        redirect:'/about'
    }
    

5. pinia

5.1【準(zhǔn)備一個效果】

5.2【搭建 pinia 環(huán)境】

第一步:npm install pinia

第二步:操作src/main.ts

import { createApp } from 'vue'
import App from './App.vue'

/* 引入createPinia丽已,用于創(chuàng)建pinia */
import { createPinia } from 'pinia'

/* 創(chuàng)建pinia */
const pinia = createPinia()
const app = createApp(App)

/* 使用插件 */{}
app.use(pinia)
app.mount('#app')

此時開發(fā)者工具中已經(jīng)有了pinia選項(xiàng)

5.3【存儲+讀取數(shù)據(jù)】

  1. Store是一個保存:狀態(tài)業(yè)務(wù)邏輯 的實(shí)體买决,每個組件都可以讀取沛婴、寫入它。

  2. 它有三個概念:state督赤、getter嘁灯、action,相當(dāng)于組件中的: data躲舌、 computedmethods丑婿。

  3. 具體編碼:src/store/count.ts

    // 引入defineStore用于創(chuàng)建store
    import {defineStore} from 'pinia'
    
    // 定義并暴露一個store
    export const useCountStore = defineStore('count',{
      // 動作
      actions:{},
      // 狀態(tài)
      state(){
        return {
          sum:6
        }
      },
      // 計算
      getters:{}
    })
    
  4. 具體編碼:src/store/talk.ts

    // 引入defineStore用于創(chuàng)建store
    import {defineStore} from 'pinia'
    
    // 定義并暴露一個store
    export const useTalkStore = defineStore('talk',{
      // 動作
      actions:{},
      // 狀態(tài)
      state(){
        return {
          talkList:[
            {id:'yuysada01',content:'你今天有點(diǎn)怪,哪里怪没卸?怪好看的羹奉!'},
                 {id:'yuysada02',content:'草莓、藍(lán)莓约计、蔓越莓诀拭,你想我了沒?'},
            {id:'yuysada03',content:'心里給你留了一塊地煤蚌,我的死心塌地'}
          ]
        }
      },
      // 計算
      getters:{}
    })
    
  5. 組件中使用state中的數(shù)據(jù)

    <template>
      <h2>當(dāng)前求和為:{{ sumStore.sum }}</h2>
    </template>
    
    <script setup lang="ts" name="Count">
      // 引入對應(yīng)的useXxxxxStore  
      import {useSumStore} from '@/store/sum'
      
      // 調(diào)用useXxxxxStore得到對應(yīng)的store
      const sumStore = useSumStore()
    </script>
    
    <template>
     <ul>
        <li v-for="talk in talkStore.talkList" :key="talk.id">
          {{ talk.content }}
        </li>
      </ul>
    </template>
    
    <script setup lang="ts" name="Count">
      import axios from 'axios'
      import {useTalkStore} from '@/store/talk'
    
      const talkStore = useTalkStore()
    </script>
    

5.4.【修改數(shù)據(jù)】(三種方式)

  1. 第一種修改方式耕挨,直接修改

    countStore.sum = 666
    
  2. 第二種修改方式:批量修改

    countStore.$patch({
      sum:999,
      school:'atguigu'
    })
    
  3. 第三種修改方式:借助action修改(action中可以編寫一些業(yè)務(wù)邏輯)

    import { defineStore } from 'pinia'
    
    export const useCountStore = defineStore('count', {
      /*************/
      actions: {
        //加
        increment(value:number) {
          if (this.sum < 10) {
            //操作countStore中的sum
            this.sum += value
          }
        },
        //減
        decrement(value:number){
          if(this.sum > 1){
            this.sum -= value
          }
        }
      },
      /*************/
    })
    
  4. 組件中調(diào)用action即可

    // 使用countStore
    const countStore = useCountStore()
    
    // 調(diào)用對應(yīng)action
    countStore.incrementOdd(n.value)
    

5.5.【storeToRefs】

  • 借助storeToRefsstore中的數(shù)據(jù)轉(zhuǎn)為ref對象细卧,方便在模板中使用。
  • 注意:pinia提供的storeToRefs只會將數(shù)據(jù)做轉(zhuǎn)換筒占,而VuetoRefs會轉(zhuǎn)換store中數(shù)據(jù)贪庙。
<template>
    <div class="count">
        <h2>當(dāng)前求和為:{{sum}}</h2>
    </div>
</template>

<script setup lang="ts" name="Count">
  import { useCountStore } from '@/store/count'
  /* 引入storeToRefs */
  import { storeToRefs } from 'pinia'

    /* 得到countStore */
  const countStore = useCountStore()
  /* 使用storeToRefs轉(zhuǎn)換countStore,隨后解構(gòu) */
  const {sum} = storeToRefs(countStore)
</script>

5.6.【getters】

  1. 概念:當(dāng)state中的數(shù)據(jù)翰苫,需要經(jīng)過處理后再使用時止邮,可以使用getters配置。

  2. 追加getters配置革骨。

    // 引入defineStore用于創(chuàng)建store
    import {defineStore} from 'pinia'
    
    // 定義并暴露一個store
    export const useCountStore = defineStore('count',{
      // 動作
      actions:{
        /************/
      },
      // 狀態(tài)
      state(){
        return {
          sum:1,
          school:'atguigu'
        }
      },
      // 計算
      getters:{
        bigSum:(state):number => state.sum *10,
        upperSchool():string{
          return this. school.toUpperCase()
        }
      }
    })
    
  3. 組件中讀取數(shù)據(jù):

    const {increment,decrement} = countStore
    let {sum,school,bigSum,upperSchool} = storeToRefs(countStore)
    

5.7.【$subscribe】

通過 store 的 $subscribe() 方法偵聽 state 及其變化

talkStore.$subscribe((mutate,state)=>{
  console.log('LoveTalk',mutate,state)
  localStorage.setItem('talk',JSON.stringify(talkList.value))
})

5.8. 【store組合式寫法】

import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'

export const useTalkStore = defineStore('talk',()=>{
  // talkList就是state
  const talkList = reactive(
    JSON.parse(localStorage.getItem('talkList') as string) || []
  )

  // getATalk函數(shù)相當(dāng)于action
  async function getATalk(){
    // 發(fā)請求农尖,下面這行的寫法是:連續(xù)解構(gòu)賦值+重命名
    let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
    // 把請求回來的字符串,包裝成一個對象
    let obj = {id:nanoid(),title}
    // 放到數(shù)組中
    talkList.unshift(obj)
  }
  return {talkList,getATalk}
})

6. 組件通信

Vue3組件通信和Vue2的區(qū)別:

  • 移出事件總線良哲,使用mitt代替盛卡。
  • vuex換成了pinia
  • .sync優(yōu)化到了v-model里面了筑凫。
  • $listeners所有的東西滑沧,合并到$attrs中了。
  • $children被砍掉了巍实。

常見搭配形式:

6.1. 【props】

概述:props是使用頻率最高的一種通信方式滓技,常用與 :父 ? 子

  • 父傳子:屬性值是非函數(shù)棚潦。
  • 子傳父:屬性值是函數(shù)令漂。

父組件:

<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";
    // 數(shù)據(jù)
    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>

6.2. 【自定義事件】

  1. 概述:自定義事件常用于:子 => 父丸边。
  2. 注意區(qū)分好:原生事件叠必、自定義事件。
  • 原生事件:
    • 事件名是特定的(click妹窖、mosueenter等等)
    • 事件對象$event: 是包含事件相關(guān)信息的對象(pageX纬朝、pageYtarget骄呼、keyCode
  • 自定義事件:
    • 事件名是任意名稱
    • <strong style="color:red">事件對象$event: 是調(diào)用emit時所提供的數(shù)據(jù)共苛,可以是任意類型!r烟选隅茎!</strong >
  1. 示例:

    <!--在父組件中,給子組件綁定自定義事件:-->
    <Child @send-toy="toy = $event"/>
    
    <!--注意區(qū)分原生事件與自定義事件中的$event-->
    <button @click="toy = $event">測試</button>
    
    //子組件中嫉沽,觸發(fā)事件:
    this.$emit('send-toy', 具體數(shù)據(jù))
    

6.3. 【mitt】

概述:與消息訂閱與發(fā)布(pubsub)功能類似患膛,可以實(shí)現(xiàn)任意組件間通信。

安裝mitt

npm i mitt

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

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

// 創(chuàng)建emitter
const emitter = mitt()

/*
  // 綁定事件
  emitter.on('abc',(value)=>{
    console.log('abc事件被觸發(fā)',value)
  })
  emitter.on('xyz',(value)=>{
    console.log('xyz事件被觸發(fā)',value)
  })

  setInterval(() => {
    // 觸發(fā)事件
    emitter.emit('abc',666)
    emitter.emit('xyz',777)
  }, 1000);

  setTimeout(() => {
    // 清理事件
    emitter.all.clear()
  }, 3000); 
*/

// 創(chuàng)建并暴露mitt
export default emitter

接收數(shù)據(jù)的組件中:綁定事件耻蛇、同時在銷毀前解綁事件:

import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";

// 綁定事件
emitter.on('send-toy',(value)=>{
  console.log('send-toy事件被觸發(fā)',value)
})

onUnmounted(()=>{
  // 解綁事件
  emitter.off('send-toy')
})

【第三步】:提供數(shù)據(jù)的組件踪蹬,在合適的時候觸發(fā)事件

import emitter from "@/utils/emitter";

function sendToy(){
  // 觸發(fā)事件
  emitter.emit('send-toy',toy.value)
}

注意這個重要的內(nèi)置關(guān)系,總線依賴著這個內(nèi)置關(guān)系

6.4.【v-model】

  1. 概述:實(shí)現(xiàn) 父?子 之間相互通信臣咖。

  2. 前序知識 —— v-model的本質(zhì)

    <!-- 使用v-model指令 -->
    <input type="text" v-model="userName">
    
    <!-- v-model的本質(zhì)是下面這行代碼 -->
    <input 
      type="text" 
      :value="userName" 
      @input="userName =(<HTMLInputElement>$event.target).value"
    >
    
  3. 組件標(biāo)簽上的v-model的本質(zhì)::moldeValueupdate:modelValue事件跃捣。

    <!-- 組件標(biāo)簽上使用v-model指令 -->
    <AtguiguInput v-model="userName"/>
    
    <!-- 組件標(biāo)簽上v-model的本質(zhì) -->
    <AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
    

    AtguiguInput組件中:

    <template>
      <div class="box">
        <!--將接收的value值賦給input元素的value屬性,目的是:為了呈現(xiàn)數(shù)據(jù) -->
         <!--給input元素綁定原生input事件夺蛇,觸發(fā)input事件時疚漆,進(jìn)而觸發(fā)update:model-value事件-->
        <input 
           type="text" 
           :value="modelValue" 
           @input="emit('update:model-value',$event.target.value)"
        >
      </div>
    </template>
    
    <script setup lang="ts" name="AtguiguInput">
      // 接收props
      defineProps(['modelValue'])
      // 聲明事件
      const emit = defineEmits(['update:model-value'])
    </script>
    
  4. 也可以更換value,例如改成abc

    <!-- 也可以更換value刁赦,例如改成abc-->
    <AtguiguInput v-model:abc="userName"/>
    
    <!-- 上面代碼的本質(zhì)如下 -->
    <AtguiguInput :abc="userName" @update:abc="userName = $event"/>
    

    AtguiguInput組件中:

    <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'])
      // 聲明事件
      const emit = defineEmits(['update:abc'])
    </script>
    
  5. 如果value可以更換娶聘,那么就可以在組件標(biāo)簽上多次使用v-model

    <AtguiguInput v-model:abc="userName" v-model:xyz="password"/>
    

6.5.【$attrs 】

  1. 概述:$attrs用于實(shí)現(xiàn)當(dāng)前組件的父組件,向當(dāng)前組件的子組件通信(祖→孫)甚脉。

  2. 具體說明:$attrs是一個對象丸升,包含所有父組件傳入的標(biāo)簽屬性。

    注意:$attrs會自動排除props中聲明的屬性(可以認(rèn)為聲明過的 props 被子組件自己“消費(fèi)”了)

父組件:

<template>
  <div class="father">
    <h3>父組件</h3>
        <Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
  </div>
</template>

<script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { ref } from "vue";
    let a = ref(1)
    let b = ref(2)
    let c = ref(3)
    let d = ref(4)

    function updateA(value){
        a.value = value
    }
</script>

子組件:

<template>
    <div class="child">
        <h3>子組件</h3>
        <GrandChild v-bind="$attrs"/>
    </div>
</template>

<script setup lang="ts" name="Child">
    import GrandChild from './GrandChild.vue'
</script>

孫組件:

<template>
    <div class="grand-child">
        <h3>孫組件</h3>
        <h4>a:{{ a }}</h4>
        <h4>b:{{ b }}</h4>
        <h4>c:{{ c }}</h4>
        <h4>d:{{ d }}</h4>
        <h4>x:{{ x }}</h4>
        <h4>y:{{ y }}</h4>
        <button @click="updateA(666)">點(diǎn)我更新A</button>
    </div>
</template>

<script setup lang="ts" name="GrandChild">
    defineProps(['a','b','c','d','x','y','updateA'])
</script>

6.6. 【refs牺氨、parent】

  1. 概述:

    • $refs用于 :父→子狡耻。
    • $parent用于:子→父。
  2. 原理如下:

    屬性 說明
    $refs 值為對象猴凹,包含所有被ref屬性標(biāo)識的DOM元素或組件實(shí)例夷狰。
    $parent 值為對象,當(dāng)前組件的父組件實(shí)例對象郊霎。

6.7. 【provide沼头、inject】

  1. 概述:實(shí)現(xiàn)祖孫組件直接通信

  2. 具體使用:

    • 在祖先組件中通過provide配置向后代組件提供數(shù)據(jù)
    • 在后代組件中通過inject配置來聲明接收數(shù)據(jù)
  3. 具體編碼:

    【第一步】父組件中,使用provide提供數(shù)據(jù)

    <template>
      <div class="father">
        <h3>父組件</h3>
        <h4>資產(chǎn):{{ money }}</h4>
        <h4>汽車:{{ car }}</h4>
        <button @click="money += 1">資產(chǎn)+1</button>
        <button @click="car.price += 1">汽車價格+1</button>
        <Child/>
      </div>
    </template>
    
    <script setup lang="ts" name="Father">
      import Child from './Child.vue'
      import { ref,reactive,provide } from "vue";
      // 數(shù)據(jù)
      let money = ref(100)
      let car = reactive({
        brand:'奔馳',
        price:100
      })
      // 用于更新money的方法
      function updateMoney(value:number){
        money.value += value
      }
      // 提供數(shù)據(jù)
      provide('moneyContext',{money,updateMoney})
      provide('car',car)
    </script>
    

    注意:子組件中不用編寫任何東西书劝,是不受到任何打擾的

    【第二步】孫組件中使用inject配置項(xiàng)接受數(shù)據(jù)进倍。

    <template>
      <div class="grand-child">
        <h3>我是孫組件</h3>
        <h4>資產(chǎn):{{ money }}</h4>
        <h4>汽車:{{ car }}</h4>
        <button @click="updateMoney(6)">點(diǎn)我</button>
      </div>
    </template>
    
    <script setup lang="ts" name="GrandChild">
      import { inject } from 'vue';
      // 注入數(shù)據(jù)
     let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}})
      let car = inject('car')
     </script>
    

6.8. 【pinia】

參考之前pinia部分的講解

6.9. 【slot】

1. 默認(rèn)插槽

父組件中:
        <Category title="今日熱門游戲">
          <ul>
            <li v-for="g in games" :key="g.id">{{ g.name }}</li>
          </ul>
        </Category>
子組件中:
        <template>
          <div class="item">
            <h3>{{ title }}</h3>
            <!-- 默認(rèn)插槽 -->
            <slot></slot>
          </div>
        </template>

2. 具名插槽

父組件中:
        <Category title="今日熱門游戲">
          <template v-slot:s1>
            <ul>
              <li v-for="g in games" :key="g.id">{{ g.name }}</li>
            </ul>
          </template>
          <template #s2>
            <a href="">更多</a>
          </template>
        </Category>
子組件中:
        <template>
          <div class="item">
            <h3>{{ title }}</h3>
            <slot name="s1"></slot>
            <slot name="s2"></slot>
          </div>
        </template>

3. 作用域插槽

  1. 理解:<span style="color:red">數(shù)據(jù)在組件的自身,但根據(jù)數(shù)據(jù)生成的結(jié)構(gòu)需要組件的使用者來決定庄撮。</span>(新聞數(shù)據(jù)在News組件中背捌,但使用數(shù)據(jù)所遍歷出來的結(jié)構(gòu)由App組件決定)

  2. 具體編碼:

    父組件中:
          <Game v-slot="params">
          <!-- <Game v-slot:default="params"> -->
          <!-- <Game #default="params"> -->
            <ul>
              <li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
            </ul>
          </Game>
    
    子組件中:
          <template>
            <div class="category">
              <h2>今日游戲榜單</h2>
              <slot :games="games" a="哈哈"></slot>
            </div>
          </template>
    
          <script setup lang="ts" name="Category">
            import {reactive} from 'vue'
            let games = reactive([
              {id:'asgdytsa01',name:'英雄聯(lián)盟'},
              {id:'asgdytsa02',name:'王者榮耀'},
              {id:'asgdytsa03',name:'紅色警戒'},
              {id:'asgdytsa04',name:'斗羅大陸'}
            ])
          </script>
    

7. 其它 API

7.1.【shallowRef 與 shallowReactive 】

shallowRef

  1. 作用:創(chuàng)建一個響應(yīng)式數(shù)據(jù),但只對頂層屬性進(jìn)行響應(yīng)式處理洞斯。

  2. 用法:

    let myVar = shallowRef(initialValue);
    
  3. 特點(diǎn):只跟蹤引用值的變化毡庆,不關(guān)心值內(nèi)部的屬性變化。

shallowReactive

  1. 作用:創(chuàng)建一個淺層響應(yīng)式對象烙如,只會使對象的最頂層屬性變成響應(yīng)式的么抗,對象內(nèi)部的嵌套屬性則不會變成響應(yīng)式的

  2. 用法:

    const myObj = shallowReactive({ ... });
    
  3. 特點(diǎn):對象的頂層屬性是響應(yīng)式的,但嵌套對象的屬性不是亚铁。

總結(jié)

通過使用 shallowRef()shallowReactive() 來繞開深度響應(yīng)蝇刀。淺層式 API 創(chuàng)建的狀態(tài)只在其頂層是響應(yīng)式的,對所有深層的對象不會做任何處理徘溢,避免了對每一個內(nèi)部屬性做響應(yīng)式所帶來的性能成本吞琐,這使得屬性的訪問變得更快捆探,可提升性能。

7.2.【readonly 與 shallowReadonly】

readonly

  1. 作用:用于創(chuàng)建一個對象的深只讀副本站粟。

  2. 用法:

    const original = reactive({ ... });
    const readOnlyCopy = readonly(original);
    
  3. 特點(diǎn):

    • 對象的所有嵌套屬性都將變?yōu)橹蛔x黍图。
    • 任何嘗試修改這個對象的操作都會被阻止(在開發(fā)模式下,還會在控制臺中發(fā)出警告)奴烙。
  4. 應(yīng)用場景:

    • 創(chuàng)建不可變的狀態(tài)快照助被。
    • 保護(hù)全局狀態(tài)或配置不被修改。

shallowReadonly

  1. 作用:與 readonly 類似切诀,但只作用于對象的頂層屬性揩环。

  2. 用法:

    const original = reactive({ ... });
    const shallowReadOnlyCopy = shallowReadonly(original);
    
  3. 特點(diǎn):

    • 只將對象的頂層屬性設(shè)置為只讀,對象內(nèi)部的嵌套屬性仍然是可變的幅虑。

    • 適用于只需保護(hù)對象頂層屬性的場景丰滑。

7.3.【toRaw 與 markRaw】

toRaw

  1. 作用:用于獲取一個響應(yīng)式對象的原始對象, toRaw 返回的對象不再是響應(yīng)式的翘单,不會觸發(fā)視圖更新吨枉。

    官網(wǎng)描述:這是一個可以用于臨時讀取而不引起代理訪問/跟蹤開銷,或是寫入而不觸發(fā)更改的特殊方法哄芜。不建議保存對原始對象的持久引用貌亭,請謹(jǐn)慎使用。

    何時使用认臊? —— 在需要將響應(yīng)式對象傳遞給非 Vue 的庫或外部系統(tǒng)時圃庭,使用 toRaw 可以確保它們收到的是普通對象

  2. 具體編碼:

    import { reactive,toRaw,markRaw,isReactive } from "vue";
    
    /* toRaw */
    // 響應(yīng)式對象
    let person = reactive({name:'tony',age:18})
    // 原始對象
    let rawPerson = toRaw(person)
    
    
    /* markRaw */
    let citysd = markRaw([
      {id:'asdda01',name:'北京'},
      {id:'asdda02',name:'上海'},
      {id:'asdda03',name:'天津'},
      {id:'asdda04',name:'重慶'}
    ])
    // 根據(jù)原始對象citys去創(chuàng)建響應(yīng)式對象citys2 —— 創(chuàng)建失敗,因?yàn)閏itys被markRaw標(biāo)記了
    let citys2 = reactive(citys)
    console.log(isReactive(person))
    console.log(isReactive(rawPerson))
    console.log(isReactive(citys))
    console.log(isReactive(citys2))
    

markRaw

  1. 作用:標(biāo)記一個對象失晴,使其永遠(yuǎn)不會變成響應(yīng)式的剧腻。

    例如使用mockjs時,為了防止誤把mockjs變?yōu)轫憫?yīng)式對象涂屁,可以使用 markRaw 去標(biāo)記mockjs

  2. 編碼:

    /* markRaw */
    let citys = markRaw([
      {id:'asdda01',name:'北京'},
      {id:'asdda02',name:'上海'},
      {id:'asdda03',name:'天津'},
      {id:'asdda04',name:'重慶'}
    ])
    // 根據(jù)原始對象citys去創(chuàng)建響應(yīng)式對象citys2 —— 創(chuàng)建失敗书在,因?yàn)閏itys被markRaw標(biāo)記了
    let citys2 = reactive(citys)
    

7.4.【customRef】

作用:創(chuàng)建一個自定義的ref,并對其依賴項(xiàng)跟蹤和更新觸發(fā)進(jìn)行邏輯控制拆又。

實(shí)現(xiàn)防抖效果(useSumRef.ts):

import {customRef } from "vue";

export default function(initValue:string,delay:number){
  let msg = customRef((track,trigger)=>{
    let timer:number
    return {
      get(){
        track() // 告訴Vue數(shù)據(jù)msg很重要儒旬,要對msg持續(xù)關(guān)注,一旦變化就更新
        return initValue
      },
      set(value){
        clearTimeout(timer)
        timer = setTimeout(() => {
          initValue = value
          trigger() //通知Vue數(shù)據(jù)msg變化了
        }, delay);
      }
    }
  }) 
  return {msg}
}

組件中使用:

8. Vue3新組件

8.1. 【Teleport】

  • 什么是Teleport帖族?—— Teleport 是一種能夠?qū)⑽覀兊?strong>組件html結(jié)構(gòu)移動到指定位置的技術(shù)栈源。
<teleport to='body' >
    <div class="modal" v-show="isShow">
      <h2>我是一個彈窗</h2>
      <p>我是彈窗中的一些內(nèi)容</p>
      <button @click="isShow = false">關(guān)閉彈窗</button>
    </div>
</teleport>

8.2. 【Suspense】

  • 等待異步組件時渲染一些額外內(nèi)容,讓應(yīng)用有更好的用戶體驗(yàn)
  • 使用步驟:
    • 異步引入組件
    • 使用Suspense包裹組件竖般,并配置好defaultfallback
import { defineAsyncComponent,Suspense } from "vue";
const Child = defineAsyncComponent(()=>import('./Child.vue'))
<template>
    <div class="app">
        <h3>我是App組件</h3>
        <Suspense>
          <template v-slot:default>
            <Child/>
          </template>
          <template v-slot:fallback>
            <h3>加載中.......</h3>
          </template>
        </Suspense>
    </div>
</template>

8.3.【全局API轉(zhuǎn)移到應(yīng)用對象】

  • app.component
  • app.config
  • app.directive
  • app.mount
  • app.unmount
  • app.use

8.4.【其他】

  • 過渡類名 v-enter 修改為 v-enter-from甚垦、過渡類名 v-leave 修改為 v-leave-from
  • keyCode 作為 v-on 修飾符的支持。

  • v-model 指令在組件上的使用已經(jīng)被重新設(shè)計艰亮,替換掉了 v-bind.sync闭翩。

  • v-ifv-for 在同一個元素身上使用時的優(yōu)先級發(fā)生了變化。

  • 移除了$on迄埃、$off$once 實(shí)例方法男杈。

  • 移除了過濾器 filter

  • 移除了$children 實(shí)例 propert调俘。

    ......

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市旺垒,隨后出現(xiàn)的幾起案子彩库,更是在濱河造成了極大的恐慌,老刑警劉巖先蒋,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骇钦,死亡現(xiàn)場離奇詭異,居然都是意外死亡竞漾,警方通過查閱死者的電腦和手機(jī)眯搭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來业岁,“玉大人鳞仙,你說我怎么就攤上這事”适保” “怎么了棍好?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長允耿。 經(jīng)常有香客問我借笙,道長,這世上最難降的妖魔是什么较锡? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任络它,我火速辦了婚禮虑省,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己换怖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布护戳。 她就那樣靜靜地躺著打掘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪君编。 梳的紋絲不亂的頭發(fā)上跨嘉,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音吃嘿,去河邊找鬼祠乃。 笑死梦重,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亮瓷。 我是一名探鬼主播琴拧,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嘱支!你這毒婦竟也來了蚓胸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤除师,失蹤者是張志新(化名)和其女友劉穎沛膳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汛聚,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锹安,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了倚舀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叹哭。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖痕貌,靈堂內(nèi)的尸體忽然破棺而出风罩,到底是詐尸還是另有隱情,我是刑警寧澤芯侥,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布泊交,位于F島的核電站,受9級特大地震影響柱查,放射性物質(zhì)發(fā)生泄漏廓俭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一唉工、第九天 我趴在偏房一處隱蔽的房頂上張望研乒。 院中可真熱鬧,春花似錦淋硝、人聲如沸雹熬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽竿报。三九已至,卻和暖如春继谚,著一層夾襖步出監(jiān)牢的瞬間烈菌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芽世,地道東北人挚赊。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像济瓢,于是被迫代替她去往敵國和親荠割。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

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