Vue3于Vue2的不同点

本文是Vue3从进阶到学习的笔记,介绍了Vue3相比Vue2的改变,如采用组合式API、数据响应式原理重实现等,还详细讲解了SetUp函数、响应式数据、计算属性、监视属性、自定义指令等内容,以及VueX、Vue Router等插件的使用。

Vue3

简介

Vue2进阶到Vue3的学习笔记,记录Vue3Vue2的不同点,新增特性等

其中,最大的改变就是组合式API(简单来说就是写JavaScript逻辑的语法发生了些改变),当然你也可以继续使用Vue2的写法,因为Vue3向下兼容它们;但我认为这是一种趋势,因此最好还是掌握组合式API的语法写法

Vue2 写法的改变

关于 Vue2 语法写法在 Vue3 中最大的改变就是:Vue2 可以使用this,得到 Vue 实例对象,虽然在 Vue3 中也依然支持 Vue2 的语法写法( 用该写法可以获取this );但若使用 Vue3 写法,setupbeforeCreatecreate前执行,因此 Vue 实例对象还没有被创建,无法获取到this;所以在 Vue3 写法中,大部分的改动围绕着曾经使用this编写的代码,现在如何编写;

那么我们首先从一个问题开始,为什么使用 Vue3 呢?

  • Vue2 使用的是optionsAPI(选项式API),Vue3 开始使用 compositionAPI(组合式API),代码组织更方便了, 逻辑复用更方便了 非常利于维护
  • 数据响应式 原理重新实现 ( ES6 的 proxy 替代了 ES5 的 Object.defineProperty),大大优化了响应式监听的性能,原来检测对象属性的变化, 需要一个个对属性递归监听, proxy 可以直接对整个对象劫持
  • template 模板可以有多个根元素(可以不用使用一个根元素包裹整个组件了)
  • 源码用 typescript 重写, 有更好的 类型推导 (类型检测更为严格, 更稳定)

总结:

vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便

SetUp函数

是 Vue3 最常用也是最重要的API,使用组合式API必须使用setup函数!setup 函数是一个新的组件选项,作为组件中 compositionAPI 的起点

它解决的问题:

解决组件内容庞大后,理解和维护组件变得困难的问题。即 vue2 中 data、computed、methods、watch 等内容非常多以后,同一业务逻辑的 data 中的数据和 methods 中的方法在 vue 文件中 “相隔甚远” ,看代码时,经常需要根据 data 中的数据去搜索找到对应的 methods 方法,上下跳跃查看代码,非常不方便。而在 setup 中,则可以把 data 中的数据和 methods 方法写在相临的位置,方便查看和维护

它的简单使用:

<!-- 使用方法1: -->
<script>
export default {
  setup() {
      /* setup函数体 */
    return {
        /* setup函数返回的变量或对象 */
    }
  }
}
</script>
<!-- 使用方法2(推荐): -->
<script setup>
/* 定义的所有内容都是setup的返回变量或对象,它们的定义以及内容都在标签中 */
</script>

那么它是大概是怎么用的呢?

<script setup>
let name = 'bokey'
let nameChange = () => {
    name = 'xxx'
}
</script>
<!-- 会被编译成 ↓  -->
<script>
data() {
    return {
        name: 'bokey', // 但需要注意:该name值没有响应式(vue不会监听该值变化)!
    }
},
methods: {
    nameChange() {
        name = 'xxx'
    },
}
</script>

注意事项:

  • 它没有this指针,因为从生命周期角度来看, setup 会在 beforeCreate 钩子函数之前执行
  • template 模版中需要使用的数据和函数,需要在 setup 返回

相信到这里,你已经理解了setup函数的作用,但这还不具备响应式,也就是说 Vue3 不会监听定义内容是否变化;为它们添加响应式就是下面要讲的

响应式数据及读取(ref / reactive)

<template>
  <div id="root">
    <!-- data数据创建 -->
    <h1>非对象数据:我是{{ name }}</h1>
    <h1>对象数据:我是{{ Bokey.name }},我的性别为:{{ Bokey.sex }}</h1>
    <!-- method方法创建 -->
    <button @click="changeName">一键改名</button>
  </div>
</template>
<script setup>
import { ref , reactive } from 'vue'
// 创建非对象数据
const name = ref("Bokey")
// 创建对象数据
const Bokey = reactive({
  name: 'bokey',
  sex: 'male',
})
// 方法创建,于数据的读取
const changeName = () => {
  name.value = 'LZH'
  Bokey.name = 'LZH'
}
</script>

结果:

image-20230921155224146

点击后:

image-20230921155236416

计算属性

<template>
  <div id="root">
    姓:<input type="text" v-model="Bokey.firstName" /><br /><br />
    名:<input type="text" v-model="Bokey.lastName" /><br /><br />
    全名:<span>{{ fullName }}</span><br>
    全名:<span>{{ fullNameGetAndSet }}</span>
    <hr>
    <button @click="changeFullName">修改fullName</button>
    <button @click="changeFullNameGetAndSet">修改fullNameGetAndSet</button>
  </div>
</template>
<script setup>
import { reactive, computed } from 'vue'
const Bokey = reactive({
  firstName: '张',
  lastName: '三',
})
// 计算属性(简写形式,只有getter时)
let fullName = computed(() => {
  return Bokey.firstName + Bokey.lastName
})
let changeFullName = () => { // 修改计算属性fullName的点击事件回调函数
  fullName.value = '修改了1-修改了2'
}
// 需要getter和setter时
let fullNameGetAndSet = computed({
  get() {
    return Bokey.firstName + '-' + Bokey.lastName
  },
  set(value) {
    const arr = value.split('-')
    Bokey.firstName = arr[0] // 修改firstName
    Bokey.lastName = arr[1] // 修改lastName
  }
})
let changeFullNameGetAndSet = () => { // 修改计算属性fullNameGetAndSet的点击事件回调函数
  fullNameGetAndSet.value = '修改了1-修改了2'
}
</script>

结果:

image-20230921161932372

修改姓:

image-20230921161956155

修改名:

image-20230921162008595

点击修改fullName按钮:

image-20230921162125438

点击修改fullNameGetAndSet按钮:

image-20230921162149224

监视属性

<template>
  <div id="root">
    <h2>今天天气很{{ info ? '炎热' : '寒冷' }}</h2>
    <button @click="changeWeather">切换天气</button>
  </div>
</template>
<script setup>
import { ref, watch } from 'vue'
let info = ref(true)
let changeWeather = function () {
  info.value = !info.value
}
// 监听info属性
watch(info, (newValue, oldValue) => {
  console.log('isHot被修改:' + oldValue + "->" + newValue);
}, {
  immediate: true, // 初始化也调用监听的回调函数
})
</script>

结果:

image-20230921163834237

点击切换天气:

image-20230921163857426

深度监视
<template>
  <div id="root">
    <h3>a的值为:{{ numbers.a }}</h3>
    <button @click="numbers.a++">点我a++</button>
    <h3>b的值为:{{ numbers.b }}</h3>
    <button @click="numbers.b++">点我b++</button>
  </div>
</template>
<script setup>
import { reactive, watch } from 'vue'
let numbers = reactive({
  a: 1,
  b: 1
})
// 监视numbers
watch(numbers, (newValue, oldValue) => {
  console.log('numbers被修改:a: ' + oldValue.a + "->" + newValue.a + ' | b: ' + oldValue.b + '->' + newValue.b);
}, {
  deep: true, // 深度监视(注意:在vue3中这里的deep是false也无法关闭深度监视,历史遗留问题)
})
</script>

结果:(先点一次a++,再点一次b++)

image-20230921164901240

可以看到Vue3的历史遗留问题:当监听的对象为Proxy代理对象(也就是对象)时,oldValue没有办法正确获取,都是最新值

【注意】:直接侦听一个对象时有 2 点需要注意:

  1. 无法正确的获取 oldValue,因为:新值变了同时旧值也跟着变了——改变一个对象里属性的值时,新值和旧值指向的是同一块内存区,所以无法拿到旧值,也可以理解为是浅拷贝的问题
  2. 默认开启深度监听(deep: true),且将 deep 置为 false 无效

因此,建议直接监听对象里的属性,而不是监听一整个对象

下面给出解决方法:

watch([() => numbers.a,() => numbers.b] , ([aNewValue, aOldValue],[bNewValue,bOldValue]) => {
  console.log('numbers被修改:a: ' + aOldValue + "->" + aNewValue + ' | b:' + bOldValue + '->' + bNewValue);
}, {
  immediate: true
})

结果:

image-20230921171252218

自定义指令

全局定义

main.js中定义,但需要把createApp(App).mount('#app')拆开,先创建实例,定义全局自定义指令,再绑定到App组件上

定义形式:

/* vue实例 */.directive(/* 指令名称 */,/* 指令配置 */)

实例(指令名称为v-focusOverall):

import { createApp } from 'vue'
import App from './App.vue'
// createApp(App).mount('#app') 需要拆开!
let app = createApp(App)
// 在app实例上定义全局自定义指令
app.directive("focusOverall",{
    created() { // 在绑定元素的 attribute 或事件监听器被应用之前调用
        console.log("Create - Overall")
      },
      beforeMount() { // 在元素被插入到 DOM 前调用
        console.log("BeforeMount - Overall");
      },
      mounted() { // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用,大部分自定义指令都写在这里
        console.log("Mounted - Overall");
      },
      beforeUpdate() { // 绑定元素的父组件更新前调用
        console.log("BeforeUpdate - Overall");
      },
      updated() { // 在绑定元素的父组件及他自己的所有子节点都更新后调用
        console.log("Update - Overall");
      },
      beforeUnmount() { // 绑定元素的父组件卸载前调用
        console.log("BeforeUnmount - Overall");
      },
      unmounted() { // 绑定元素的父组件卸载后调用
        console.log("Unmounted - Overall");
      },
})
// 绑定app
app.mount("#app")

组件使用:

<template>
  <div id="root">
      <!-- ↓ 使用指令 -->
    <div v-focusOverall v-if="showOrNot" >{{ msg }}</div>
    <button @click="msg = '测试元素被改变了'">更新测试元素</button>
    <button @click="showOrNot = !showOrNot;">卸载元素</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
let msg = ref('测试元素')
let showOrNot = ref(true)
</script>

结果:

image-20230922094615636

点击更新元素:

image-20230922094640749

点击卸载元素:

image-20230922094703373

局部定义

形式:

注意:此处的指令名必须以v字母开头并以首字母大写驼峰形式来定义,Vue3才会把它们当作自定义指令处理

const /* 指令名 */ = {
    /* 指令配置 */
}

例子(指令名:v-focus):

<template>
  <div id="root">
    <div v-focus v-if="showOrNot" >{{ msg }}</div>
    <button @click="msg = '测试元素被改变了'">更新测试元素</button>
    <button @click="showOrNot = !showOrNot;">卸载元素</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
let msg = ref('测试元素')
let showOrNot = ref(true)
const vFocus = {
  created() { // 在绑定元素的 attribute 或事件监听器被应用之前调用
    console.log("Create")
  },
  beforeMount() { // 在元素被插入到 DOM 前调用
    console.log("BeforeMount");
  },
  mounted() { // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用,大部分自定义指令都写在这里
    console.log("Mounted");
  },
  beforeUpdate() { // 绑定元素的父组件更新前调用
    console.log("BeforeUpdate");
  },
  updated() { // 在绑定元素的父组件及他自己的所有子节点都更新后调用
    console.log("Update");
  },
  beforeUnmount() { // 绑定元素的父组件卸载前调用
    console.log("BeforeUnmount");
  },
  unmounted() { // 绑定元素的父组件卸载后调用
    console.log("Unmounted");
  },
}
</script>

结果:

image-20230922100030070

点击更新元素:

image-20230922100048320

点击卸载元素:

image-20230922100107193

自定义指令的参数表列

修改v-focus的内容如下

<template>
  <div id="root">
    <div v-focus:[arg]="msg" v-if="showOrNot" >{{ msg }}</div>
    <button @click="msg = '测试元素被改变了'">更新测试元素</button>
    <button @click="showOrNot = !showOrNot;">卸载元素</button>
  </div>
</template>
<script setup>
import { ref , reactive} from 'vue'
let msg = ref('测试元素')
let arg = reactive({
  arg_one: 1,
  arg_two: 2,
})
let showOrNot = ref(true)
const vFocus = {
  updated(el,binding,vnode,oldVnode) {
    console.log("Update的参数表列");
    console.log("el - 指令所绑定元素的DOM",el);
    console.log("bind - 自定义指令接收的参数",binding);
    console.log("vnode - Vue编译生成的虚拟节点",vnode);
    console.log("oldVnode - 更新前的虚拟节点",oldVnode);
  },
}
</script>

触发更新:

image-20230922103449163

注意:

  • 除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行

生命周期

image-20230922104029244

总结相应对比:

vue2           ------->      vue3

beforeCreate   -------->      setup(()=>{})
created        -------->      setup(()=>{})
beforeMount    -------->      onBeforeMount(()=>{})
mounted        -------->      onMounted(()=>{})
beforeUpdate   -------->      onBeforeUpdate(()=>{})
updated        -------->      onUpdated(()=>{})
beforeDestroy  -------->      onBeforeUnmount(()=>{})
destroyed      -------->      onUnmounted(()=>{})
activated      -------->      onActivated(()=>{})
deactivated    -------->      onDeactivated(()=>{})
errorCaptured  -------->      onErrorCaptured(()=>{})
/*
其中:
onActivated:被keep-alive缓存的组件激活时调用
onDeactivated:被keep-alive缓存的组件停用时调用
onErrorCaptured:当捕获一个来自子孙组件的错误时被调用,有三个参数:错误对象、发生错误的组件实例、一个包含错误来源信息的字符串;此钩子会返回false来阻止改错误继续向上传播

额外还增加了两个用于调试的钩子:
1.onRenderTracked
2.onRenderTriggered
它们会告诉你哪个操作跟踪了组件以及该操作的目标对象和键,会检测什么依赖造成了组件的重新渲染
三个参数:key(更新的键), target(操作的目标), type(操作类型)
*/

使用方法:

<template>
  <div id="root">
    <div>n的值:{{ n }}</div>
    <button @click="n++">更新组件n值</button>
  </div>
</template>
<script setup>
import {  onBeforeMount , onMounted , onBeforeUpdate , onUpdated , onBeforeUnmount , onUnmounted , ref } from 'vue'
let n = ref(1)
onBeforeMount(() => {
  console.log("onBeforeMount");
})
onMounted(() => {
  console.log("onMounted");
})
onBeforeUpdate(() => {
  console.log("onBeforeUpdate");
})
onUpdated(() => {
  console.log("onUpdate");
})
onBeforeUnmount(() => {
  console.log("onBeforeUnmount");
})
onUnmounted(() => {
  console.log("onUnmounted");
})
</script>

结果:

image-20230922111049103

点击更新:

image-20230922111102884

获取标签的DOM(ref标记)

依然使用ref来标记标签,只是不再用this.$refs.xxx来得到标签DOM,而是通过let xxx = ref(null),来得到标签的DOM

注意:只有在该标签被挂载后,才能得到DOM,否则为null

<template>
  <div id="root">
    <div ref="div">n的值:{{ n }}</div>
    <button @click="getDivRef" ref="btn">获取div更新n值</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
let n = ref(1)
let div = ref(null)
const getDivRef = () => {
  console.log("ref实例",div);
  console.log("DOM",div.value);
  div.value.innerHTML = "使用DOM修改了"
}
</script>

结果:

image-20230922150945182

点击按钮:

image-20230922151052090

附:

上面的DOM明明先输出,后修改,为什么输出的是修改后的内容呢,我认为输出的是DOM的最新状态,而不是输出时DOM的状态

props(父组件与子组件通信):

props支持父子间通信,两种形式:

可以接收父组件给的数据,也可以接收父组件给的函数;

  • 数据名称必须和父组件的数据名称保持一致
  • 限制形式也可以不限制进行接收,按需求
// 1. 直接接收形式
const props = defineProps(['数据名称','数据名称',/*...*/])
// 2. 限制形式
const props = defineProps({
    /* 数据名称 */ : {
        type: /* 数据类型 */,
        default: /* 该数据默认值 */,
        require: /* 是否必须 */,
    },
    /* 数据名称 */ : /* 数据类型 */,
    /* 数据名称 */ : {},
})

// 父组件的传递形式,在子组件的标签中传递数据或者函数(提供给子组件让它发送信息给父组件)
</* 子组件名称 */ /* 数据名称 */="数据内容" :/* 数据名称 */="数据变量(对象)" />
父组件给子组件传递数据

App组件:

<template>
  <div id="root">
    <appSon name="Bokey" :age="20" sex="男"></appSon>
  </div>
</template>
<script setup>
import appSon from "./components/appSon.vue";
</script>

appSon组件:

<template>
    <h2>姓名:{{ props.name }}</h2>
    <h2>年龄:{{ myAge }}</h2> <!-- 注意:这里是MyBokey组件的属性MyAge -->
    <h2>性别:{{ props.sex }}</h2>
    <button @click="updateAge">点我加一岁</button>
</template>
<script setup>
import { defineProps , ref} from 'vue'
// 通过defineProps获得父组件的通信内容(参数数据)
const props = defineProps({
    name:String,
    age:Number,
    sex:String,
})
// 若子组件中需要修改内容,需要内化为子组件自己的数据;因为props的数据为只读数据,不可以修改!
let myAge = ref(props.age)
const updateAge = () => {
    myAge.value++
}
</script>

结果:

image-20230922154543958

点击按钮:

image-20230922154555299

子组件给父组件传递数据

App组件:

<template>
  <div id="root" style="padding: 20px;border: 1px red solid;">
    <appSon :getSonData="getSonData"></appSon>
    <div>儿子的数据:{{ sonData }}</div>
  </div>
</template>
<script setup>
import appSon from "./components/appSon.vue";
import { ref } from "vue"
let sonData = ref(null)
const getSonData = (data) => {
  sonData.value = data
}
</script>

appSon组件:

<template>
    <div style="padding: 10px;border: 1px black solid;">
        <button @click="props.getSonData(myMsg)">点我发送数据给父组件</button>
    </div>
</template>
<script setup>
import { defineProps, ref } from 'vue'
const props = defineProps(['getSonData'])
let myMsg = ref("我的重要信息,收好~")
</script>

结果:

image-20230922161425425

点击按钮:

image-20230922161435918

自定义事件

自定义事件一般用于子组件给父组件数据,形式:

// 1. 父组件提供函数给子组件
</* 子组件名称 */ @/* 自定义事件名 */="提供的函数名"/>
// 2. 子组件触发事件
import { defineEmits } from 'vue'
$emit('自定义事件名',/* 参数表列 */)

App组件:

<template>
  <div id="root" style="padding: 20px;border: 1px red solid;">
    <div>你好!{{ sonName }}</div>
    <appSon @callFather="getSonName"></appSon>
  </div>
</template>
<script setup>
import appSon from "./components/appSon.vue";
import { ref  } from "vue"
let sonName = ref(null)
let getSonName = (data) => {
  sonName.value = data
}
</script>

appSon组件:

<template>
    <div style="padding: 10px;border: 1px black solid;">
        <button @click="$emit('callFather',myName)">点我发送信息给父组件</button>
    </div>
</template>
<script setup>
import { defineEmits } from 'vue'
let myName = "Bokey"
let $emit = defineEmits(['callFather'])
</script>

结果:

image-20230922165210278

点击按钮:

image-20230922165242063

附:

Vue3默认不支持全局事件总线,也就是说Vue3 没有和Vue2 $on 和 $emit 一样的的全局事件系统。

这是因为在 Vue 3 中,官方推荐使用 Composition API 以及更灵活的函数式组件,同时提倡使用更明确的通信方式

不过依然可以使用一些库来实现全局事件的管理(eg:mitt)

全局变量 / 方法挂载

main.js中:

const app = createApp(App)
app.config.globalProperties.$/* 方法名 */ = function(/* 参数表列 */){ /* 函数体 */ }
app.config.globalProperties./* 变量名 */  = /* 变量值 */
app.mount("#app")

例:

mian.js

const app = createApp(App)
app.config.globalProperties.$say = function(msg){ console.log('点击按钮了,信息:' + msg) }
app.config.globalProperties.globalText = "hi"
app.mount("#app")

app.vue

<template>
  <div id="root">
      <!-- 在结构中,可以通过函数名或变量名直接访问全局,可不用代理 -->
    <button @click="$say(globalText)">say hi</button>
  </div>
</template>
<script setup>
// 获取getCurrentInstance得到vue实例代理,才能得到全局变量或函数
import { getCurrentInstance , onMounted} from 'vue';
const { proxy } = getCurrentInstance()
onMounted(() => {
  console.log("vue的实例代理",proxy);
  console.log(proxy.$say);
  console.log(proxy.globalText);
}) 
</script>

结果(点击按钮):

image-20230922181721813

插件使用

插件的使用都在main.js中引入插件文件就可以,如下

main.js

import /* 插件名称 */ from "插件入口文件所在位置"
createApp(App).use(/* 插件名称 */).mount("#app")
// 使用多个插件:createApp(App).use(/* 插件名称 */).use(/* 插件名称 */)./* 其他插件... */.mount("#app")
VueX

防止你忘了,把VueX的工作流程放下面

State:VueX的数据存储模块,用来存储所有共享信息的状态

Actions:负责发送异步请求,请求后端数据,是VueX的行为模块

Mutations:负责加工Actions请求到的数据,是VueX的加工模块

Getters:负责进行数据展示的加工处理,是VueX的计算属性,具有缓存功能

image-20230924094143418

基本使用流程:

(1)大致使用步骤:
	1.在src/store/index.js文件中初始化Vuex的数据
	2.组件中获取 VueX 的Store:import { useStore } from 'vuex';const store = useStore()
	3.组件中使用 "store.state.数据名称"(组件JS)"{{store.state.数据名称}}"(组件HTML结构中) 读取/使用Vuex中的数据
	4.组件中使用 "store.getters.数据名称"(组件JS)"{{store.getters.数据名称}}"(组件HTML结构中) 读取/使用Vuex中的计算属性
	5.组件逻辑中使用 "store.dispatch('actions方法名',传递数据)""store.commit('mutations方法名,传递数据')" 修改Vuex中的数据
	6.在src/store/index.js文件中定义相关的actions方法,mutations方法,getters属性

(2)src/store/index.js文件中actions和mutations的函数写法:
	const actions = {
        // 该方法使用 store.dispatch(函数名,数据参数) 调用
        /*函数名*/(context,value) { // context是上下文对象,value是传递的参数(数据),value可以有也可以没有
            // 可以在这里面发送异步网络请求
            // 函数体中可以使用context.state获取当前Vuex中的数据
            // 函数体中可以使用context.commit(函数名,数据参数)或者context.dispatch(函数名,数据参数) 推进Vuex进程
        }
    }
    const mutations = {
        // 该方法使用 store.commit(函数名,数据参数) 或者 context.commit(函数名,数据参数) 调用
        /*函数名*/(state,value) { // state是Vuex的数据,value是传递的参数,value可以有也可以没有
            // 函数体中利用actions部分得到的数据来处理加工或修改Vuex的数据
        }
    }

(3)附加一些事项:
	1.在Vuex的actions中的方法我们习惯使用小写开头,以便区分
	2.在Vuex的mutations中的方法我们习惯使用大写开头,以便区分
	3.在Vue组件的逻辑结构(JS代码)中,我们可以直接使用"store.commit()"来调用Vuex的mutations部分方法(若不需要使用Vuex的actions部分的方法请求后端数据或不需要对数据进行判断校验等操作)

最后附上基本的src/store/index.js文件内容结构:

// 引入vuex的store工厂
import { createStore } from 'vuex'
export default createStore({
  // 准备state-用于存储数据
  state: { },
  // 准备mutations-用于操作数据(state)
  mutations: { },
  // 准备actions--用于响应组件中的动作
  actions: { },
  // 准备getter--用于处理数据便于展示
  getters: { },
  // 使用modules引用模块和命名空间
  modules: { }
})

例(BokeyLZH组件共享Vuex中的age数据):

src/store/index.js文件( VueX 的store配置文件):

// 引入vuex的store工厂
import { createStore } from 'vuex'
export default createStore({
  // 准备state-用于存储数据
  state: {
    age:20,
  },
  // 准备mutations-用于操作数据(state)
  mutations: {
    ADD_AGE(state,value){ // 年龄加n
        state.age += value
    },
    REAL_AGE(state,value){ // 修改为真实年龄
        state.age = value
    },
    ADD_AGE_ODD(state){ // 偶数才加1
        state.age++
    }
  },
  // 准备actions--用于响应组件中的动作
  actions: {
    realAge(context){ // 修改为真实年龄
        // 模拟请求服务器,返回的值为20
        var age = 20
        context.commit('REAL_AGE',age) // 传递给mutations部分操作数据
    },
    AddAgeOdd(context){ // 偶数才加1
        if (context.state.age % 2) return // 判断是否为奇数
        context.commit('ADD_AGE_ODD') // 为偶数就传递给mutations部分操作数据
    }
  },
  getters: {
    ten_age(state){ // 计算属性:年龄的十倍
        return state.age*10
    }
  },
  // 使用modules引用模块和命名空间
  modules: {  }
})

Bokey组件:

<template>
    <div class="Bokey">
        <h1>我是Bokey</h1>
        <h3>我的年龄:{{ store.state.age }}</h3>
        <select v-model.number="n">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>
        <button @click="ageAdd">点我年龄+n</button>
        <button @click="realAge">点我请求服务器,修改年龄为真实年龄</button>
    </div>
</template>
<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex';
const store = useStore()
let n = ref(1)
let ageAdd = () => {
    store.commit('ADD_AGE',n.value)
}
let realAge = () => {
    store.dispatch('realAge')
}
</script>
<style>
.Bokey {
    background-color: skyblue;
}
</style>

LZH组件:

<template>
    <div class="LZH">
        <h1>我是LZH</h1>
        <h3>我的年龄:{{ store.state.age }}</h3>
        <h3>我年龄的10倍是:{{ store.getters.ten_age }}</h3> <!-- 计算属性 -->
        <button @click="AddAgeOdd">点我只有年龄为偶数时+1</button>
    </div>
</template>

<script setup>
import { useStore } from 'vuex';
const store = useStore()
let AddAgeOdd = () => {
    store.dispatch('AddAgeOdd')
}
</script>
<style>
.LZH {
    background-color: pink;
}
</style>

结果:

image-20230924102048587

VueX模块化

模块化方式:

(1)`src/store`目录下创建并编写模块文件(习惯名为"xxxOptions"),暴露并开启命名空间,文件中写基本结构:
	export default {
    namespaced: true, //开启命名空间
    actions: { /* actions对话方法... */ }, // 该模块对应的action部分
    mutations: { /* mutations对话方法... */ }, // 该模块对应的mutations部分
    state: { /* state数据... */ }, // 该模块对应的state数据
    getters: { /* getter数据... */ } // 该模块对应的getter数据
}
(2)`src/store/index.js`文件中引入并使用模块:  
	import { createStore } from 'vuex'
    import /* 模块引用名称 */ from './xxxOptions'
	export default createStore({
    	modules: { // 使用modules引用模块和命名空间
            /* 模块名 */:/* 模块引用名称 */
        }
	})
(3)组件中读取(命别名)state或getters数据的方式改变:
    首先都需要获取store:
	import { useStore } from 'vuex';
	const store = useStore()
    1.state数据的读取(命别名)的方式:
    	store.state./* 模块名 */./* 数据名 */
	2.getters数据的读取(命别名)的方式:
    	store.getters['模块名/数据名']
(4)组件中配置对话的方式改变:
    首先都需要获取store:
	import { useStore } from 'vuex';
	const store = useStore()
    1.Actions对话调用方式:
    	store.dispatch('模块名/Actions对话函数名',/* 参数表列 */)
    2.Mutation对话调用方式:
         store.commit('模块名/Mutations对话函数名',/* 参数表列 */)

例:

store/ageOptions.js

// Vuex年龄数据模块
export default {
    namespaced: true, //开启命名空间
    actions: {
        realAge(context) {
            var age = 20
            context.commit('REAL_AGE', age)
        }
    },
    mutations: {
        ADD_AGE(state, value) {
            state.age += value
        },
        REAL_AGE(state, value) {
            state.age = value
        },
        ADD_AGE_ODD(state) {
            state.age++
        }
    },
    state: {
        age: 20
    },
    getters: {
        ten_age(state) {
            return state.age * 10
        }
    }
}

store/personOptions.js

// Vuex人员模块
export default {
    namespaced: true, //开启命名空间
    actions: {
        add(context, value) {
            value = {
                id: context.state.personList.length + 1,
                name: value
            }
            context.commit('ADD', value)
        }
    },
    mutations: {
        ADD(state, value) {
            state.personList.push(value)
        }
    },
    state: {
        personList: [
            { id: 1, name: '张三' },
            { id: 2, name: '李四' }
        ]
    },
    getters: {}
}

store/index.js

import { createStore } from 'vuex'
// 引入各个模块
import ageOptions from './ageOptions'
import personOptions from './personOptions'
export default createStore({
  // 使用modules引用模块和命名空间
  modules: { 
    ageAbout: ageOptions,
    personOptions,
   }
})

Bokey.vue

<template>
    <div class="Bokey">
        <h1>我是Bokey</h1>
        <!-- 读取模块化的state和gtter -->
        <h3>我的年龄:{{ store.state.ageAbout.age }}</h3>
        <h3>年龄的十倍:{{ store.getters['ageAbout/ten_age'] }}</h3>
        <h3 style="color:red">人员列表中的人数:{{ store.state.personOptions.personList.length }}</h3>
        <select v-model.number="n">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>
        <button @click="ageAdd">点我年龄+n</button>
        <button @click="realAge">点我请求服务器,修改年龄为真实年龄</button>
    </div>
</template>
<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex';
const store = useStore()
let n = ref(1)
// 调用模块化的mutations和actions
let ageAdd = () => {
    store.commit('ageAbout/ADD_AGE', n.value)
}
let realAge = () => {
    store.dispatch('ageAbout/realAge')
}
</script>
<style>
.Bokey {
    background-color: skyblue;
}
</style>

LZH.vue

<template>
    <div class="LZH">
        <h3>人员列表:</h3>
        <ul>
            <!-- 读取模块化的state -->
            <li v-for="item in store.state.personOptions.personList" :key="item.id">
                id:{{ item.id }},姓名:{{ item.name }}
            </li>
        </ul>
        <!-- 读取模块化的state -->
        <h3 style="color:red">Bokey的年龄为:{{ store.state.ageAbout.age }}</h3>
        <input type="text" placeholder="请输入人员姓名" v-model="name" />
        <button @click="add">点我添加该人</button>
    </div>
</template>

<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex';
const store = useStore()
let name = ref(null)
// 调用模块化的mutations和actions
const add = () => {
    store.dispatch('personOptions/add',name.value)
}
</script>
<style>
.LZH {
    background-color: pink;
}
</style>

结果:

image-20230925154933484

Vue Router

基本使用:

src/router/index.js

import { createRouter,createWebHashHistory } from "vue-router";
// 定义路由规则
const routes = [
    {
        path: '...', // 路由路径
        component: () => import('...') // 该路径下匹配的路由组件
    },
]
// 创建路由对象及暴露
export default createRouter({
    history:createWebHashHistory(), // 注意这里是方法!
    routes
})

main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 使用router
createApp(App).use(router).mount("#app")

然后就阔以在项目中使用啦~


下面是具体小栗子:

创建三个页面/src/page/xxxPage.vue(HomePage.vueLoginPage.vueRegisterPage.vue)

<!-- userHome中的结构 -->
<template>
  <h2>I am Home Page</h2>
</template>
<!-- userLogin中的结构 -->
<template>
  <h2>I am Login Page</h2>
</template>
<!-- userRegister中的结构 -->
<template>
  <h2>I am Register Page</h2>
</template>

编写路由规则

const routes = [
    {
        path: '/', 
        component: () => import('../pages/HomePage') 
    },
    {
        path: '/login',
        component: () => import('../pages/LoginPage')
    },
    {
        path: '/register',
        component: () => import('../pages/RegisterPage')
    }
]

App.vue

<template>
  <div id="root">
    <router-link active-class="chose" class="item" to="/" exact>首页</router-link>
    <router-link active-class="chose" class="item" to="/login">登录</router-link>
    <router-link active-class="chose" class="item" to="/register">注册</router-link>
    <router-view></router-view>
  </div>
</template>

结果(点击后就会显示不同内容):

image-20230925161958962

编程式路由导航

由于 Vue3 没有this,所以我们无法通过this.$router得到路由对象,下面给出了 Vue3 得到路由对象的方法;其实和 VueX 类似,就是通过一个工厂方法拿到该插件对象

App.vue

<template>
  <div id="root">
    <router-link active-class="chose" to="/" exact>首页</router-link>
    <router-link active-class="chose" to="/login">登录</router-link>
    <router-link active-class="chose" to="/register">注册</router-link>
    <button @click="gotoByPath('/')">点我跳转到首页</button>
    <button @click="gotoByPath('/login')">点我跳转到登录</button>
    <router-view></router-view>
  </div>
</template>
<script setup>
import { useRouter } from "vue-router"
const router = useRouter()
let gotoByPath = (path) => {
  router.push({
    path: path,
  })
}
</script>

结果(和普通跳转一样的跳转效果):

image-20230925162913763

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值