1.Vue3简介

  • ·2020年9月18日,Vue.js发布版3.0版本,代号:0ne Piece  (海贼王)
  • ·经历了:4800+次提交40+个RFC600+次PR300+贡献者
  • ·官方发版地址:Release v3.0.0 One Piece · vuejs/core
  • ·截止2023年10月,最新的公开版本为:3.3.4

1.1. 【性能的提升】

  • 打包大小减少41%。
  • 初次渲染快55%, 更新渲染快133%。
  • 内存减少54%。

1.2.【 源码的升级】

  • 使用Proxy代替defineProperty实现响应式。
  • 重写虚拟DOM的实现和Tree-Shaking。

1.3. 【拥抱TypeScript】

  • Vue3可以更好的支持TypeScript。

1.4. 【新的特性】

1.Composition API(组合API):

  1. setup
  2.  ref与reactive
  3.  computed与watch…

2.新的内置组件:

  1.  Fragment
  2. Teleport
  3. Suspense…

3.其他改变:

  1. 新的生命周期钩子
  2. data 选项应始终被声明为一个函数
  3.  移除keyCode支持作为 v-on 的修饰符
     

2.创建Vue3项目

2.1.【基于vue-cli创建】- webpack

点击查看官方文档

备注:目前vue-cli已处于维护模式,官方推荐基于 Vite 创建项目。

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

## 安装或者升级你的@vue/cli 
npm install -g @vue/cli

## 执行创建命令
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 创建】(推荐)

vite 是新一代前端构建工具,官网地址:https://vitejs.cnvite的优势如下:

  • 轻量快速的热重载(HMR),能实现极速的服务启动。
  • TypeScriptJSXCSS 等支持开箱即用。
  • 真正的按需编译,不再等待整个应用编译完成。
  • webpack构建 与 vite构建对比图如下:

## 1.创建命令
npm create vue@latest

## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript?  Yes
## 是否添加JSX支持
√ Add JSX Support?  No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development?  No
## 是否添加pinia环境
√ 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

安装官方推荐的vscode插件:

总结:

  • Vite 项目中,index.html 是项目的入口文件,在项目最外层。
  • 加载index.html后,Vite 解析 <script type="module" src="xxx"> 指向的JavaScript
  • Vue3中是通过 createApp 函数创建一个应用实例。

注:开发中如想在Chrome中使用扩展程序:极简插件-vue

2.3. 【一个简单的效果】

Vue3向下兼容Vue2语法,且Vue3中的模板中可以没有根标签。

<template>
  <div class="Preson">
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <button @click="chanageName">修改名字</button>
    <button @click="chanageAge">修改年龄</button>
    <button @click="showTel">查看联系方式</button>
  </div>
</template>

<script lang="ts">
export default {
  name: "Preson",
  data() {
    return {
      name: "张三",
      age: 18,
      te1: "13888888888"
    };
  },
  methods: {
    chanageName() {
      this.name = "zhaotongtong";
    },
    chanageAge() {
      this.age += 1
    },
    showTel() {
      alert(this.te1);
    }
  }
};
</script>

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 00 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0 5px;
}
</style>

3. Vue3核心语法

   3.1. 【OptionsAPI 与 CompositionAPI】

  •    Vue2的API设计是Options(配置)风格的。
  •    Vue3的API设计是Composition(组合)风格的。

Options API 的弊端:

       Options类型的 API,数据、方法、计算属性等,是分散在:datamethodscomputed中的,若想新增或者修改一个需求,就需要分别修改:datamethodscomputed,不便于维护和复用。

Composition API 的优势:

可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。

3.2. 【拉开序幕的 setup】

setup 概述

setupVue3中一个新的配置项,值是一个函数,它是 Composition API “表演的舞台”,组件中所用到的:数据、方法、计算属性、监视…等等,均配置在setup中。

特点如下:

  • setup函数返回的对象中的内容,可直接在模板中使用。
  • setup中访问thisundefined
  • setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。
<template>
  <div class="Preson">
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <button @click="chanageName">修改名字</button>
    <button @click="chanageAge">修改年龄</button>
    <button @click="showte1">查看联系方式</button>
  </div>
</template>

<script lang="ts">
export default {
  name: "Preson",
  setup() {
    // 数据,原来写在data中(注意:此时的name、age、te1数据都不是响应式数据)
    //  console.log('@',this)//setup函数中的this是undefined
    let name = "张三"; //注意此时的name不是响应式的
    let age = 18; //注意此时的age不是响应式的
    let te1 = "13888888888"; //注意此时的te1不是响应式的

    //方法,原来写在methods中
    function chanageName() {
      name = "zhaotongtong"; //注意:此时这么修改name页面是不变化的
    }
    function chanageAge() {
      age += 1; //注意:此时这么修改name页面是不变化的
    }
    function showte1() {
      alert(te1);
    }
    return { name, age, te1, chanageName, chanageAge, showte1 };
  }
};
</script>

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0 5px;
}
</style>

setup 的返回值

  • 若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用**(重点关注)。**
  • 若返回一个函数:则可以自定义渲染内容,代码如下:
setup(){
  return ()=> '你好啊!'
}

setup 与 Options API 的关系

  • Vue2 的配置(datamethos…)中可以访问到 setup中的属性、方法。
  • 但在setup不能访问到Vue2的配置(datamethos…)。
  • 如果与Vue2冲突,则setup优先。

setup 语法糖

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

<template>
  <div class="Preson">
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <button @click="chanageName">修改名字</button>
    <button @click="chanageAge">修改年龄</button>
    <button @click="showte1">查看联系方式</button>
  </div>
</template>

<script lang="ts">
export default {
  name: "Preson"
};
</script>

<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
// 数据,原来写在data中(注意:此时的name、age、te1数据都不是响应式数据)
//  console.log('@',this)//setup函数中的this是undefined
let name = "张三"; //注意此时的name不是响应式的
let age = 18; //注意此时的age不是响应式的
let te1 = "13888888888"; //注意此时的te1不是响应式的

//方法,原来写在methods中
function chanageName() {
  name = "zhaotongtong"; //注意:此时这么修改name页面是不变化的
}
function chanageAge() {
  age += 1; //注意:此时这么修改name页面是不变化的
}
function showte1() {
  alert(te1);
}
</script>

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0 5px;
}
</style>

扩展:上述代码,还需要编写一个不写setupscript标签,去指定组件名字,比较麻烦,我们可以借助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() ]
})

    3.第三步:<script setup lang="ts" name="Person">

3.3. 【ref 创建:基本类型的响应式数据】

  • **作用:**定义响应式变量。
  • 语法let xxx = ref(初始值)
  • **返回值:**一个RefImpl的实例对象,简称ref对象或refref对象的value属性是响应式的。
  • 注意点:
  1. JS中操作数据需要:xxx.value,但模板中不需要.value,直接使用即可。
  2. 对于let name = ref('zhaotongtong')来说,name不是响应式的,name.value是响应式的。
<template>
  <div class="Preson">
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <button @click="chanageName">修改名字</button>
    <button @click="chanageAge">修改年龄</button>
    <button @click="showte1">查看联系方式</button>
  </div>
</template>

<script lang="ts">
export default {
  name: "Preson"
};
</script>

<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref } from "vue";

// 数据
//  console.log('@',this)//setup函数中的this是undefined
// name和age是一个RefImpl的实例对象,简称ref对象,它们的value属性是响应式的。
let name = ref("张三");
let age = ref(18);
let te1 = "13888888888";

//方法,原来写在methods中
function chanageName() {
  name.value = "zhaotongtong"; // JS中操作ref对象时候需要.value

  // 注意:name不是响应式的,name.value是响应式的,所以如下代码并不会引起页面的更新。
  // name = ref('zhang-san')
}
function chanageAge() {
  age.value += 1; // JS中操作ref对象时候需要.value
}
function showte1() {
  alert(te1);
}
</script>

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0 5px;
}
</style>

3.4. 【reactive 创建:对象类型的响应式数据】

  • 作用:定义一个响应式对象(基本类型不要用它,要用ref,否则报错)
  • 语法:let 响应式对象= reactive(源对象)。
  • **返回值:**一个Proxy的实例对象,简称:响应式对象。
  • 注意点:reactive定义的响应式数据是“深层次”的。
<template>
  <div class="Preson">
    <h2>汽车信息:一辆{{ car.brand }}汽车,价值{{ car.price }}万元</h2>
    <button @click="changePrice">修改汽车售价</button>
    <br />
    <br />
    <h2>游戏列表:</h2>
    <ul>
      <li v-for="(item,index) in games" :key="index">{{ item.name }}</li>
    </ul>
    <button @click="changegames">修改游戏</button>

    <br />
    <h2>测试:{{ obj.a.b.c }}</h2>
    <button @click="changeObj">修改游戏</button>
  </div>
</template>

<script lang="ts">
export default {
  name: "Preson"
};
</script>

<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref, reactive } from "vue";

// 数据

// 初识使用reactive
let car = reactive({
  brand: "宝马",
  price: 666
});

// 修改数组数据
let games = reactive([
  {
    name: "穿越火线"
  },
  {
    name: "元神"
  },
  {
    name: "三国志"
  }
]);

//多层次对象
let obj = reactive({
  a: {
    b: {
      c: "喜羊羊"
    }
  }
});

//方法
function changePrice() {
  car.price += 888;
}

function changegames() {
  games[1].name = "刺激战场";
}

function changeObj() {
  obj.a.b.c = obj.a.b.c == "喜羊羊" ? "灰太狼" : "喜羊羊";
}
</script>

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0 5px;
}
</style>

3.5. 【ref 创建:对象类型的响应式数据】

  • 其实ref接收的数据可以是:基本类型对象类型
  • ref接收的是对象类型,内部其实也是调用了reactive函数。
<template>
  <div class="Preson">
    <h2>汽车信息:一辆{{ car.brand }}汽车,价值{{ car.price }}万元</h2>
    <button @click="changePrice">修改汽车售价</button>
    <br />
    <br />
    <h2>游戏列表:</h2>
    <ul>
      <li v-for="(item,index) in games" :key="index">{{ item.name }}</li>
    </ul>
    <button @click="changegames">修改游戏</button>
  </div>
</template>

<script lang="ts">
export default {
  name: "Preson"
};
</script>

<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref } from "vue";

// 数据

// 初识使用reactive
let car = ref({
  brand: "宝马",
  price: 666
});

// 修改数组数据
let games = ref([
  {
    name: "穿越火线"
  },
  {
    name: "元神"
  },
  {
    name: "三国志"
  }
]);

//方法
function changePrice() {
  car.value.price += 888;
}

function changegames() {
  games.value[1].name = "刺激战场";
}
</script>

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0 5px;
}
</style>

3.6. 【ref 对比 reactive】

宏观角度看:

  1. ref用来定义:基本类型数据对象类型数据
  2. reactive用来定义:对象类型数据
  • 区别:
  1. ref创建的变量必须使用.value(可以使用volar插件自动添加.value)。

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

  • 使用原则:
  1. 若需要一个基本类型的响应式数据,必须使用ref
  2. 若需要一个响应式对象,层级不深,refreactive都可以。
  3. 若需要一个响应式对象,且层级较深,推荐使用reactive

3.7. 【toRefs 与 toRef】

  • 作用:将一个响应式对象中的每一个属性,转换为ref对象。
  • 备注:toRefstoRef功能一致,但toRefs可以批量转换。
  • 语法如下:
<template>
  <div class="Preson">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }},toRef_age:{{ age_big }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "Preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { reactive, toRefs, toRef } from "vue";
// 数据
let person = reactive({
  name: "zhaotongtong",
  age: 18,
});

// 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
let { name, age } = toRefs(person);
// 通过toRef将person对象中的age属性取出,且依然保持响应式的能力
let age_big = toRef(person, "age");

//方法
//点击修改名字
function changeName() {
  name.value += "~";
}

// 点击修改年龄
function changeAge() {
  age.value += 1;
  age_big.value += 1;
}
</script>
<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0 5px;
}
</style>

3.8. 【computed】

作用:根据已有数据计算出新数据(和Vue2中的computed作用一致)。

<template>
  <div class="Preson">
    <h2>姓:<input type="text" v-model="firstName" /></h2>
    <h2>名:<input type="text" v-model="lastName" /></h2>
    <button @click="changeFullName">将全名改为li-si</button>
    <h1>全名:{{ fullName }}</h1>
  </div>
</template>
<script lang="ts">
export default {
  name: "Preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref, computed } from "vue";
let firstName = ref("张");
let lastName = ref("三");

// 这么定义的fullName是一入计算属性,且是只读的
// let fullName = computed(() => {
//   return firstName.value + lastName.value;
// });

// 这么定义的fullName是一入计算属性,且可读可写的
let fullName = computed({
  get() {
    return firstName.value + lastName.value;
  },
  set(val) {
    let [str1, str2] = val.split("-");
    firstName.value = str1;
    lastName.value = str2;
    console.log(val);
  },
});

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

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
input {
  width: 320px;
  height: 42px;
  border: none;
  font-size: 19px;
}

button {
  margin: 0 5px;
}
</style>

3.9.【watch】

  • 作用:监视数据的变化(和Vue2中的watch作用一致)
  • 特点:Vue3中的watch只能监视以下四种数据
  1. ref定义的数据。
  2. reactive定义的数据。
  3. 函数返回一个值(getter函数)。
  4. 一个包含上述内容的数组。

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

*情况一

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

<template>
  <div class="Preson">
    <h1>情况一:监视【ref】定义的【基本类型】数据</h1>
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="changeSum">点我sum加1</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "Preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref, watch } from "vue";

//数据
let sum = ref(0);

//方法
function changeSum() {
  sum.value += 1;
}

//监视
// 情况一:监视【ref】定义的【基本类型】数据
let stopWatch = watch(sum, (newVal, oldVal) => {
  console.log(newVal, oldVal);
  if (newVal >= 10) {
    //当前监视的newVal值大于等于10时,停止进行sum的监视
    stopWatch();
  }
});
</script>

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
</style>

*情况二

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

注意:

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

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

<template>
  <div class="preson">
    <h1>情况二:监视【ref】定义的【对象类型】数据</h1>
    <h2>姓名:{{ preson.name }}</h2>
    <h2>年龄:{{ preson.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changePerson">修改全部信息</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref, watch } from "vue";

//数据
let preson = ref({
  name: "zhaotong",
  age: 18,
});

//方法
function changeName() {
  preson.value.name += "~";
}
function changeAge() {
  preson.value.age += 1;
}
function changePerson() {
  preson.value = {
    name: "zhaotongtong",
    age: 24,
  };
}
//监视
// 情况二:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化需要手动开启深度监视
// deep:true  深度监听
// immediate:true 初始立即监听

//watch的第一个参数是:被监视的数据
//watch的第二个参数是:监视的回调
// watchi的第三个参数是:配置对象(deep、immediate等)
watch(
  preson,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  { deep: true, immediate: true }
);
</script>

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>

*情况三

监视reactive定义的【对象类型】数据,且默认开启了深度监视(不可关闭深度监听)。

<template>
  <div class="preson">
    <h1>情况三:监视reactive定义的【对象类型】数据,且默认开启了深度监视。</h1>
    <h2>姓名:{{ preson.name }}</h2>
    <h2>年龄:{{ preson.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changePerson">修改全部信息</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { reactive, watch } from "vue";

//数据
let preson = reactive({
  name: "zhaotong",
  age: 18,
});

//方法
function changeName() {
  preson.name += "~";
}
function changeAge() {
  preson.age += 1;
}
function changePerson() {
  preson = Object.assign(preson, {
    name: "zhaotongtong",
    age: 24,
  });
}
//监视
// 情况三:监视reactive定义的【对象类型】数据,且默认开启了深度监视。
watch(preson, (newVal, oldVal) => {
  console.log(newVal, oldVal);
});
</script>

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>

*情况四

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

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

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

<template>
  <div class="preson">
    <h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</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">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { reactive, watch } from "vue";

//数据
let person = reactive({
  name: "zhaotong",
  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: "电动车" };
}

//监视
// 情况四:监视响应式对象中的某个属性,且该属性时基本类型的,要写成函数式
// watch(
//   () => person.name,
//   (newVal, oldVal) => {
//     console.log(newVal, oldVal);
//   }
// );

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

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>

*情况五

监视上述的多个数据

<template>
  <div class="preson">
    <h1>情况五:监视上述的多个数据</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">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { reactive, watch } from "vue";

//数据
let person = reactive({
  name: "zhaotong",
  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: "电动车" };
}

//监视
//情况五:监视上述的多个数据
watch(
  [() => person.name, () => person.car],
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  {
    deep: true,
  }
);
</script>

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>

3.10. 【watchEffect】

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

  • watch对比watchEffect

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

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

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

  • 示例代码:
<template>
  <div class="preson">
    <h2>需求:当水温达到60度,或水位达到80cm时,给服务器发请求</h2>
    <h2>当前水温:{{ temp }}℃</h2>
    <h2>当前水位:{{ height }}cm</h2>
    <button @click="changeTemp">水温加十</button>
    <button @click="changeHeiht">水温加十</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref, watch, watchEffect } from "vue";

//数据
let temp = ref(10);
let height = ref(0);

//方法
function changeTemp() {
  temp.value += 10;
}
function changeHeiht() {
  height.value += 10;
}

//监视

// watch监视该需求  当水温达到60度,或水位达到80cm时,给服务器发请求
// watch([temp, height], (value) => {
//   let [newTemp, newHeight] = value;
//   if (newTemp >= 60 || newHeight >= 80) {
//     console.log("给服务器发送请求");
//   }
// });

// watchEffect监视该需求  当水温达到60度,或水位达到80cm时,给服务器发请求
watchEffect(() => {
  if (temp.value >= 60 || height.value >= 80) {
    console.log("给服务器发送请求");
  }
});
</script>

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>

3.11. 【标签的 ref 属性】

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

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

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

  1. 用在普通DOM标签上:
<template>
  <div class="preson">
    <h1 ref="title1">中国</h1>
    <h2 ref="title2">北京</h2>
    <h3 ref="title3">河北</h3>
    <button @click="showLog">点我输出这两个元素</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref, watch, watchEffect } from "vue";

let title1 = ref();
let title2 = ref();
let title3 = ref();

//方法
function showLog() {
  console.log(title1.value);
  console.log(title2.value);
  console.log(title3.value);
}
</script>

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>
  1. 用在组件标签上:
子组件:

<template>
  <div class="preson">
    <h1 ref="title1">中国</h1>
    <h2 ref="title2">北京</h2>
    <h3 ref="title3">河北</h3>
    <button @click="showLog">点我输出这两个元素</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { ref, defineExpose } from "vue";

//数据
let title1 = ref();
let title2 = ref();
let title3 = ref();
let name = ref("张三");
let age = ref(18);

// 使用defineExpose将组件中的数据交给外部
defineExpose({ name, age,title1 });

//方法
function showLog() {
  console.log(title1.value);
  console.log(title2.value);
  console.log(title3.value);
}
</script>

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>






父组件:

<template>
  <Preson ref="ren"></Preson>
  <button @click="test">测试</button>
</template>
<script lang="ts">
import Preson from "./components/Preson/index.vue";
export default {
  name: "App",
  components: { Preson },
};
</script>
<script lang="ts" setup>
import { ref } from "vue";

let ren = ref();

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

3.12. 回顾TS中的接口泛型自定义类型

注:在 src 路径下创建 types/index.ts 进行接口泛型自定义类型的放置。

types/index.ts

// 定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
  id: number;
  name: string;
  age: number;
}


// 定义一个自定义类型Persons
// export type Persons = Array<PersonInter>
export type Persons PersonInter[]

index.ts(此处也就是使用接口泛型自定义类型的页面模块)

<template>
  <div class="preson"></div>
</template>
<script lang="ts">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { type PersonInter, type Persons } from "@/types/index";

//一个对象数据限制
// let person:PersonInter = reactive({
//   id: 1,
//   name: "zhaotongtong",
//   age: 18,
// });

// //对数组中多个对象进行限制
// let personList: Array<PersonInter> = reactive([
//   {
//     id: 1,
//     name: "zhaotongtong",
//     age: 18,
//   },
//   {
//     id: 2,
//     name: "zha",
//     age: 28,
//   },
//   {
//     id: 3,
//     name: "lisi",
//     age: 38,
//   },
// ]);

//自定义Persons 对数组中多个对象进行限制
let personList: Persons = reactive([
  {
    id: 1,
    name: "zhaotongtong",
    age: 18,
  },
  {
    id: 2,
    name: "zha",
    age: 28,
  },
  {
    id: 3,
    name: "lisi",
    age: 38,
  },
]);
</script>

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>

3.13. 【props】(父传子)

// 定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
  id: number;
  name: string;
  age: number;
}


// 定义一个自定义类型Persons
// export type Persons = Array<PersonInter>
export type Persons PersonInter[]

App.vue中代码(父组件):

<template>
  <Preson :list="persons"></Preson>
</template>
<script lang="ts">
import Preson from "./components/Preson/index.vue";
export default {
  name: "App",
  components: { Preson },
};
</script>
<script lang="ts" setup>
import { reactive } from "vue";
import { type Persons } from "./types/index";

let persons = reactive<Persons>([
  { id: 0, name: "张三", age: 18 },
  { id: 1, name: "李四", age: 19 },
  { id: 2, name: "王五", age: 20 },
]);
</script>
<style>
</style>    

Preson.vue中的代码(子组件):

<template>
  <div class="preson">
    <ul>
      <li v-for="item in list" :key="item.id">
        姓名:{{ item.name }} / 年龄:{{ item.age }}
      </li>
    </ul>
  </div>
</template>
<script lang="ts">
export default {
  name: "preson",
};
</script>
<!-- 下面的写法是setup语法糖 -->
<script lang="ts" setup>
import { reactive, ref, defineProps, withDefaults } from "vue";
import { type Persons } from "../../types/index";

//接收list
// defineProps(["list"]);

//接收list+限制类型
// defineProps<{ list: Persons }>();

//在父组件未传list参数时候出发 默认值
// list?  加 '?' 号 意思是可传可不传
//接收list+限制类型+限制必要性+指定默认值
withDefaults(defineProps<{ list?: Persons }>(), {
  list: () => [{ id: 0, name: "哇哈哈", age: 6 }],
});

//接收list,同时将list保存起来
// let names = defineProps(["list"]);
// console.log(names.list);
</script>

<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>

3.14. 【生命周期】

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

  • 规律:

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

  • vue2生命周期

创建阶段:beforeCreatecreated

挂载阶段:beforeMountmounted

更新阶段:beforeUpdateupdated

销毁阶段:beforeDestroydestroyed

  • vue3生命周期

创建阶段:setup

挂载阶段:onBeforeMountonMounted

更新阶段:onBeforeUpdateonUpdated

卸载阶段:onBeforeUnmountonUnmounted

  • 常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)

  • vue2vue3生命周期的改变:
beforeCreate -> use setup()

created -> use setup()

beforeMount -> onBeforeMount

mounted -> onMounted

beforeUpdate -> onBeforeUpdate

updated -> onUpdated

beforeUnmount -> onBeforeUnmount

unmounted -> onUnmounted

errorCaptured -> onErrorCaptured

renderTracked -> onRenderTracked

renderTriggered -> onRenderTriggered

activated -> onActivated

deactivated -> onDeactivated

vue2:


vue3

  • 示例代码:
<template>
  <div class="preson">
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="changeSum">点我sum+1</button>
  </div>
</template>

<!-- vue3写法 -->
<script lang="ts" setup name="Person">
import {
  ref,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
} from "vue";

// 数据
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>


<style scoped>
.preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

button {
  margin: 0px 12px;
}
</style>

3.15. 【自定义hook】

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

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

示例代码(src路径下创建hooks文件夹 hooks中创建useSum.ts与useDog.ts):

  • useSum.ts中内容如下:

import { ref, computed,onMounted} from "vue";

export default function () {
  //数据
  let sum = ref(0);

  // 方法
  function add() {
    sum.value += 1;
  }
  //hooks中支持使用计算属性等
  let bigSum = computed(() => {
    return sum.value * 10;
  });

  //hooks中支持使用钩子函数
  onMounted(()=>{
    add()
  })

  //   向外部提供东西
  return { sum, add, bigSum };
}
  • useDog.ts中内容如下:
import axios from "axios";
import { reactive, onMounted } from "vue";

export default function () {
  //数据
  let dogList = reactive([
    "https://images.dog.ceo/breeds/pembroke/n02113023_7243.jpg",
  ]);
  // 方法

  async function addDog() {
    try {
      let result = await axios.get(
        "https://dog.ceo/api/breed/pembroke/images/random"
      );

      dogList.push(result.data.message);
    } catch (error) {
      alert(error);
    }
  }

  //hooks中支持使用钩子函数
  //   上来挂载完毕获取一个小狗照片
  onMounted(() => {
    addDog();
  });

  //   向外部提供东西
  return {
    addDog,
    dogList,
  };
}
  • 组件中具体使用:
<template>
  <div class="Preson">
    <h2>当前求和为:{{ sum }},sum乘以十倍为:{{ bigSum }}</h2>

    <button @click="add">点我sum加一</button>
    <hr />
    <img v-for="(dog, index) in dogList" :key="index" :src="dog" alt="" />
    <button @click="addDog">再来一只小狗</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "Preson",
};
</script>
<script lang="ts" setup>
import useSum from "@/hooks/useSum";
import useDog from "@/hooks/useDog";

const {sum,add,bigSum} = useSum();
const {dogList,addDog} = useDog();
</script>

<style scoped>
.Preson {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}

img {
  height: 100px;
  margin-right: 6px;
}
</style>

4:路由

前述:【对路由的理解】

4.1:【路由基本切换效果】

  • 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>
    <!-- 导航区 -->
    <div class="navigate">
      <RouterLink to="/home" active-class="active">首页</RouterLink>
      <RouterLink to="/news" active-class="active">新闻</RouterLink>
      <RouterLink to="/about" active-class="active">关于</RouterLink>
    </div>
    <!-- 展示区 -->
    <div class="main-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

<script lang="ts" setup name="App">
  import {RouterLink,RouterView} from 'vue-router'  
</script>

4.2. 【两个注意点】

  1. 路由组件通常存放在pages  views文件夹,一般组件通常存放在components文件夹。

  2. 通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载

4.3.【路由器工作模式】

  • history模式

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

缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404错误。

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

  • hash模式

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

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

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

4.4. 【to的两种写法】

<!-- 第一种:to的字符串写法 -->
<router-link active-class="active" to="/home">主页</router-link>

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

4.5. 【命名路由】

作用:可以简化路由跳转及传参。

给路由规则命名:

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

路由跳转:

<!--简化前:需要写完整的路径(to的字符串写法) -->
<router-link to="/news/detail">跳转</router-link>

<!--简化后:直接通过名字跳转(to的对象写法配合name属性) -->
<router-link :to="{name:'guanyu'}">跳转</router-link>

4.6. 【嵌套路由】

  • 编写News的子路由:Detail.vue

  • 配置路由规则,使用children配置项:

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
  • 跳转路由(记得要加完整路径):

<router-link to="/news/detail">xxxx</router-link>
<!-- 或 -->
<router-link :to="{path:'/news/detail'}">xxxx</router-link>
  • 记得去Home组件中预留一个<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.7. 【路由传参】

query参数
  • 传递参数

<!-- 跳转并携带query参数(to的字符串写法) -->
<router-link to="/news/detail?a=1&b=2&content=欢迎你">
	跳转
</router-link>
				
<!-- 跳转并携带query参数(to的对象写法) -->
<RouterLink 
  :to="{
    //name:'xiang', //用name也可以跳转
    path:'/news/detail',
    query:{
      id:news.id,
      title:news.title,
      content:news.content
    }
  }"
>
  {{news.title}}
</RouterLink>
  • 接收参数

import {useRoute} from 'vue-router'
const route = useRoute()
// 打印query参数
console.log(route.query)

params参数
  • 传递参数

<!-- 跳转并携带params参数(to的字符串写法) -->
<RouterLink :to="`/news/detail/001/新闻001/内容001`">{{news.title}}</RouterLink>
				
<!-- 跳转并携带params参数(to的对象写法) -->
<RouterLink 
  :to="{
    name:'xiang', //用name跳转
    params:{
      id:news.id,
      title:news.title,
      content:news.title
    }
  }"
>
  {{news.title}}
</RouterLink>
  • 接收参数

import {useRoute} from 'vue-router'
const route = useRoute()
// 打印params参数
console.log(route.params)

备注1:传递params参数时,若使用to的对象写法,必须使用name配置项,不能用path

备注2:传递params参数时,需要提前在规则中占位。

4.8. 【路由的props配置】

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

{
	name:'xiang',
	path:'detail/:id/:title/:content',
	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
  }
}




.vue中:

在子组件使用参数时候:
根据 defineProps进行参数接收使用
如:defineProps(['id','title',content'])

4.9. 【 replace属性】

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

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

    • push是追加历史记录(默认值)。
    • replace是替换当前记录。
  3. 开启replace模式:

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

4.10. 【编程式导航】

路由组件的两个重要的属性:$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【准备一个效果】

5.2【搭建 pinia 环境】

第一步:npm install pinia

第二步:操作src/main.ts

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

/* 引入createPinia,用于创建pinia */
import { createPinia } from 'pinia'

/* 创建pinia */
const pinia = createPinia()
const app = createApp(App)

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

此时开发者工具中已经有了pinia选项:

5.3【存储+读取数据】

  • Store是一个保存:状态业务逻辑 的实体,每个组件都可以读取写入它。

  • 它有三个概念:stategetteraction,相当于组件中的: data、 computed 和 methods

  • 具体编码:src/store/count.ts

// 引入defineStore用于创建store
import { defineStore } from "pinia";
// 定义并暴露一个store
export const useCountStore = defineStore("count", {
  // 动作
  actions: {},
  //状态   真正存储数据的地方
  state() {
    return {
      sum: 6,
    };
  },
  // 计算
  getters: {},
});
  • 具体编码:src/store/loveTalk.ts
// 引入defineStore用于创建store
import { defineStore } from "pinia";
// 定义并暴露一个store
export const useTalkStore = defineStore("loveTalk", {
  // 动作
  actions: {},
  //状态   真正存储数据的地方
  state() {
    return {
      talkList: [
        { id: "yuysada01", title: "你今天有点怪,哪里怪?怪好看的!" },
        { id: "yuysada02", title: "草莓、蓝莓、蔓越莓,你想我了没?" },
        { id: "yuysada03", title: "心里给你留了一块地,我的死心塌地" },
      ],
    };
  },
  // 计算
  getters: {},
});
  • 组件中使用state中的数据

count.vue

<template>
  <h2>当前求和为:{{ sumStore.sum }}</h2>
</template>

<script setup lang="ts" name="Count">
  // 引入对应的useXxxxxStore	
  import {useSumStore} from '@/store/sum'
  
  // 调用useXxxxxStore得到对应的store
  const sumStore = useSumStore()
</script>

loveTalk.vue

<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/loveTalk'

  const talkStore = useTalkStore()
</script>

5.4.【修改数据】(三种方式)

  • 第一种修改方式,直接修改
countStore.sum = 666
  • 第二种修改方式:批量修改
 //第二种 批量修改
  // countStore.$patch({
  //   sum:666,
  //   school:"wahh",
  //   address:"河北"
  // });

  • 第三种修改方式:借助action修改(action中可以编写一些业务逻辑)
count.vue 

<template>
  <div class="count">
    <h2>当前求和为:{{ countStore.sum }}</h2>
    <h2>欢迎来到:{{ countStore.school }},地址在:{{ countStore.address }}</h2>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="add">加</button>
    <button @click="minus">减</button>
  </div>
</template>

<script lang="ts" setup name="count">
import { ref } from "vue";
//引入countStore
import { useCountStore } from "@/store/count";

//使用useCountstore,得到一个专门保存count相关的sore
const countStore = useCountStore();
// 以下两种方式都可以拿到state中的数据
// console.log(countStore.sum);
// console.log(countStore.$state.sum);

let n = ref(1); //用户选择的数字

//加
function add() {
  //第一种修改
  // countStore.sum += 1;

  //第二种 批量修改
  // countStore.$patch({
  //   sum:666,
  //   school:"wahh",
  //   address:"河北"
  // });

  //第三种 借助actions修改
  countStore.increment(n.value);
}

//减
function minus() {
  //第一种修改
  // countStore.sum -= 1;
}
</script>

<style>
.count {
  background-color: skyblue;
  padding: 10px;
  border-radius: 10px;
  box-shadow: 0 0 10px;
}

select {
  width: 120px;
  height: 32px;
}

button {
  width: 62px;
  height: 32px;
  cursor: pointer;
  margin-left: 12px;
  cursor: pointer;
}
</style>







store/count.ts

// 引入defineStore用于创建store
import { defineStore } from "pinia";
// 定义并暴露一个store
export const useCountStore = defineStore("count", {
  // actions.里面放置的是一个一个的方法,用于响应组件中的“动付”
  actions: {
    increment(value) {
      if (this.sum < 10) {
        console.log("increment被调用了", value);
        //修改数据 (this是当前的store)
        this.sum += value;
      }
    },
  },
  //状态   真正存储数据的地方
  state() {
    return {
      sum: 6,
      school: "zhongguodaxue",
      address: "北京",
    };
  },
  // 计算
  getters: {},
});

5.5.【storeToRefs】

  • 借助storeToRefsstore中的数据转为ref对象,方便在模板中使用。
  • 注意:pinia提供的storeToRefs只会将数据做转换,而VuetoRefs会转换store中数据。
<template>
	<div class="count">
		<h2>当前求和为:{{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转换countStore,随后解构 */
  const {sum} = storeToRefs(countStore)
</script>

5.6.【getters】

  1. 概念:当state中的数据,需要经过处理后再使用时,可以使用getters配置。

  2. 追加getters配置。

    // 引入defineStore用于创建store
    import {defineStore} from 'pinia'
    
    // 定义并暴露一个store
    export const useCountStore = defineStore('count',{
      // 动作
      actions:{
        /************/
      },
      // 状态
      state(){
        return {
          sum:1,
          school:'atguigu'
        }
      },
      // 计算
      getters:{
        bigSum:(state):number => state.sum *10,
        upperSchool():string{
          return this. school.toUpperCase()
        }
      }
    })
    
  3. 组件中读取数据:

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

5.7.【$subscribe】

通过 store  $subscribe() 方法侦听 state 及其变化

loveTalk.vue

<template>
  <div class="talk">
    <button @click="getLoveTalk">获取一句土味情话</button>
    <ul>
      <li v-for="(item, index) in talkList" :key="index">
        {{ item.title }}
      </li>
    </ul>
  </div>
</template>
<script lang="ts" setup name="loveTalk">
import { useTalkStore } from "@/store/loveTalk";
import { storeToRefs } from "pinia";

// 1:下载
// npm i nanoid
// 2:引入
import { nanoid } from "nanoid"; //随机生成一个id
// 3:使用
// console.log(nanoid());

// 以下两种方式都可以拿到state中的数据
const talkStore = useTalkStore();
/* 使用storeToRefs转换countStore,随后解构 */
// storeToRefs只会关注sotre中数据,不会对方法进行ref包裹
const { talkList } = storeToRefs(talkStore);

// 通过 store 的 $subscribe() 方法侦听 state 及其变化
talkStore.$subscribe((mutate, state) => {
  console.log(mutate, state, "talkStore里面某个参数发生变化");
  localStorage.setItem("talkStore", JSON.stringify(state.talkList));
});

function getLoveTalk() {
  talkStore.getATalk();
}
</script>
  
<style scoped>
.talk {
  background-color: skyblue;
  padding: 10px;
  border-radius: 10px;
  box-shadow: 0 0 10px;
  margin-top: 12px;
}
button {
  width: 162px;
  height: 32px;
  cursor: pointer;
  margin-left: 12px;
  cursor: pointer;
}
</style>

store/loveTalk.ts

// 引入defineStore用于创建store
import { defineStore } from "pinia";
import axios from "axios";
import { nanoid } from "nanoid";

// 定义并暴露一个store
export const useTalkStore = defineStore("loveTalk", {
  // 动作
  actions: {
    async getATalk() {
      //发请求,下面这行的写法是:连续解构赋值+重命名
      let {
        data: { content: title },
      } = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json");
      // 将请求到的数据 放置到talkList中展示
      this.talkList.unshift({ id: nanoid(), title });
    },
  },
  //状态   真正存储数据的地方
  state() {
    return {
      // talkList: [
      //   { id: "yuysada01", title: "你今天有点怪,哪里怪?怪好看的!" },
      //   { id: "yuysada02", title: "草莓、蓝莓、蔓越莓,你想我了没?" },
      //   { id: "yuysada03", title: "心里给你留了一块地,我的死心塌地" },
      // ],
      talkList: JSON.parse(localStorage.getItem("talkStore") as string) || [],
    };
  },
  // 计算
  getters: {},
});

5.8. 【store组合式写法】

store/loveTalk.ts

// 引入defineStore用于创建store
import { defineStore } from "pinia";
import axios from "axios";
import { nanoid } from "nanoid";

// 定义并暴露一个store
// export const useTalkStore = defineStore("loveTalk", {
//   // 动作
//   actions: {
//     async getATalk() {
//       //发请求,下面这行的写法是:连续解构赋值+重命名
//       let {
//         data: { content: title },
//       } = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json");
//       // 将请求到的数据 放置到talkList中展示
//       this.talkList.unshift({ id: nanoid(), title });
//     },
//   },
//   //状态   真正存储数据的地方
//   state() {
//     return {
//       // talkList: [
//       //   { id: "yuysada01", title: "你今天有点怪,哪里怪?怪好看的!" },
//       //   { id: "yuysada02", title: "草莓、蓝莓、蔓越莓,你想我了没?" },
//       //   { id: "yuysada03", title: "心里给你留了一块地,我的死心塌地" },
//       // ],
//       talkList: JSON.parse(localStorage.getItem("talkStore") as string) || [],
//     };
//   },
//   // 计算
//   getters: {},
// });



// ⬆
// 比较
// ⬇

//组合式store
import { reactive } from "vue";
export const useTalkStore = defineStore("loveTalk", () => {
  //数据 talkList就是state
  let talkList = reactive(
    JSON.parse(localStorage.getItem("talkStore") as string) || []
  );

  // getATalk函数相当于action
  async function getATalk() {
    //发请求,下面这行的写法是:连续解构赋值+重命名
    let {
      data: { content: title },
    } = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json");
    // 将请求到的数据 放置到talkList中展示
    talkList.unshift({ id: nanoid(), title });
  }

  return {
    talkList,
    getATalk,
  };
});

6. 组件通信

Vue3组件通信和Vue2的区别:

  • 移出事件总线,使用mitt代替。
  • vuex换成了pinia
  • .sync优化到了v-model里面了。
  • $listeners所有的东西,合并到$attrs中了。
  • $children被砍掉了。

常见搭配形式:

6.1. 【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>

6.2. 【自定义事件】

  1. 概述:自定义事件常用于:子 => 父。
  2. 注意区分好:原生事件、自定义事件。
  • 原生事件:

            (1):事件名是特定的(clickmosueenter等等)
            (2):事件对象$event: 是包含事件相关信息的对象(pageXpageYtargetkeyCode

  • 自定义事件:

            (1):事件名是任意名称
            (2):事件对象$event: 是调用emit时所提供的数据,可以是任意类型!!!

示例1:

<!--在父组件中,给子组件绑定自定义事件:-->
<Child @send-toy="toy = $event"/>

<!--注意区分原生事件与自定义事件中的$event-->
<button @click="toy = $event">测试</button>
//子组件中,触发事件:
this.$emit('send-toy', 具体数据)

示例2:

父组件:

<template>
  <div class="father">
    <h3>父组件,</h3>
	<h4 v-show="toy">儿子给的玩具:{{ toy }}</h4>
		<Child @send-toy="saveToy"/>
  </div>
</template>

<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import { ref } from "vue";
    
	//数据
	let toy=ref('');

	function saveToy(value:string){
		console.log(value);
		toy.value=value;	
	}
</script>

子组件:

<template>
  <div class="child">
    <h3>子组件</h3>
    <h4>玩具:{{ toy }}</h4>
    <button @click="emit('send-toy', toy)">测试</button>
  </div>
</template>
  
  <script setup lang="ts" name="Child">
import { ref } from "vue";
//数据
let toy = ref("奥特曼");
//声明事件
const emit = defineEmits(["send-toy"]);
</script>

6.3. 【mitt】

概述:与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信。

安装mitt

npm i mitt

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

//引入mitt
import mitt from "mitt";
//调用mitt得到emitter,emitter可以绑定事件、触发事件
const emitter = mitt();

// 绑定事件
// emitter.on('test1',()=>{
//     console.log('test1被调用了');
// })
// emitter.on('test2',()=>{
//     console.log('test2被调用了');
// })

//调用事件
// setInterval(()=>{
//   emitter.emit('test1')
//   emitter.emit('test2')
// },1000)

//解绑事件
// setTimeout(()=>{
//     //单独解绑
//   emitter.off('test1')
//   emitter.off('test2')

//   //清空所有绑定事件
//   emitter.all.clear()
// },3000)

// 暴露emitter
export default emitter;

接收数据的组件中:绑定事件、同时在销毁前解绑事件:

兄弟组件1:

<template>
  <div class="child">
    <h3>子组件</h3>
    <h4>玩具:{{ toy }}</h4>
    <button @click="emitter.emit('send-toy', toy)">玩具给弟弟玩</button>
  </div>
</template>
  
  <script setup lang="ts" name="Child">
import { ref } from "vue";
import emitter from "./utils/emitter";

// 数据
let toy = ref("奥特曼");
</script>

兄弟组件2:

<template>
  <div class="child">
    <h3>子组件1</h3>
    <h4>我有:{{ computer }}</h4>
    <h4>我哥给我的玩具:{{ toy }}</h4>
  </div>
</template>
  
  <script setup lang="ts" name="Child">
import { ref, onUnmounted } from "vue";
import emitter from "./utils/emitter";
//数据
let computer = ref("电脑");
let toy = ref("");

//给emitter绑定send-toy事件
emitter.on("send-toy", (value: any) => {
  // console.log("send-toy", value);
  toy.value = value;
});

//组件卸载时候 进行解绑send-toy事件
onUnmounted(() => {
  emitter.off("send-toy");
});
</script>

注意这个重要的内置关系,总线依赖着这个内置关系

【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"
>

     3. 组件标签上的v-model的本质::moldeValue + update:modelValue事件。

<!-- 组件标签上使用v-model指令 -->
<AtguiguInput v-model="userName"/>

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

   AtguiguInput组件中:

<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" name="AtguiguInput">
  // 接收props
  defineProps(['modelValue'])
  // 声明事件
  const emit = defineEmits(['update:model-value'])
</script>

      4. 也可以更换value,例如改成abc

<!-- 也可以更换value,例如改成abc-->
<AtguiguInput v-model:abc="userName"/>

<!-- 上面代码的本质如下 -->
<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可以更换,那么就可以在组件标签上多次使用v-model

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

GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐