Vue

文章目录

  • Vue
    • 1. 创建Vue实例
    • 2. 插值表达式
    • 3. 响应式特性
    • 4. 常用指令
      • 4.1 内容渲染指令
      • 4.2 条件渲染指令
      • 4.3 事件绑定指令
      • 4.4 单向属性绑定指令
      • 4.5 双向属性绑定指令
      • 4.6 列表渲染指令
    • 5. 修饰符
      • 5.1 事件修饰符
      • 5.2 键盘事件修饰符
      • 5.3 修饰符串联
      • 5.4 v-model 修饰符
    • 6. computed计算属性
    • 7. watch侦听器
    • 8. 生命周期
      • 8.1 生命周期钩子
    • 9. 工程化开发和脚手架
      • 9.1 Vue CLI
      • 9.2 项目目录介绍
      • 9.3 运行流程
    • 10. 组件化开发
      • 10.1 根组件 App.vue
      • 10.2 普通组件局部注册
      • 10.3 普通组件全局注册
      • 10.4 scoped解决样式冲突
      • 10.5 组件中的data
    • 11. 组件通信
      • 11.1 父子间通信
      • 11.2 非父子通信
      • v-model简化代码
      • .sync修饰符
    • 12. ref和$refs
    • 13. $nextTick
    • 14. 自定义指令
    • 15. 插槽
      • 15.1 默认插槽
      • 15.2 默认值
      • 15.3 具名插槽
      • 15.4 作用域插槽
    • 16. 路由
      • 16.1 声明式导航
      • 16.2 声明式导航传参
      • 16.3 重定向
      • 16.4 404界面
      • 16.5 路由跳转方式
    • 17. Vuex
      • 17.1 快速入门
      • 17.2 state
      • 17.3 mapState
      • 17.4 mutations
      • 17.5 mapMutations
      • 17.6 actions
      • 17.7 mapActions
      • 17.8 getters
      • 17.9 module
    • 18. Vue3
      • 18.1 setup
      • 18.2 reactive和ref
      • 18.3 computed
      • 18.4 watch
      • 18.5 生命周期函数
      • 18.6 父子间通信
      • 18.7 模板引用
      • 18.8 provide和inject
      • 18.9 defineOptions
      • 18.10 defineExpose
    • 19. Vite
    • 20. Pinia
      • 20.1 快速入门
      • 20.2 Store解构
      • 20.3 State
      • 20.4 Getter
      • 20.5 Action
      • 20.6 持久化

Vue

Vue (读音 /vjuː/,类似于 view) 是一套 **构建用户界面 ** 的 渐进式框架。

所谓渐进式就是循序渐进,不一定非得把Vue中的所有API都学完才能开发Vue,可以学一点开发一点。

Vue2官网:https://v2.cn.vuejs.org/

1. 创建Vue实例

1)从官网下载vue.js文件

2)在html文件中引入vue.js

3)创建vue实例

<!DOCTYPE html>
<head><meta charset="UTF-8"><title>demo</title></head>
<body>
<div id="app">{{msg}}</div>
<script src="vue.js"></script>
<script>new Vue({el: "#app",data: {msg:"hello Vue"}})
</script>
</body>
</html>

2. 插值表达式

插值表达式是一种Vue的模板语法,我们可以用插值表达式渲染出Vue提供的数据或表达式的结果。

语法:{{ xxx }}

<h3>{{title}}<h3><p>{{nickName.toUpperCase()}}</p><p>{{age >= 18 ? '成年':'未成年'}}</p><p>{{obj.name}}</p><p>{{fn()}}</p>

3. 响应式特性

响应式简单理解就是数据变,视图对应变。

data中的数据, 最终会被添加到实例上

① 访问数据: “实例.属性名”

② 修改数据: “实例.属性名”= “值”

请添加图片描述

4. 常用指令

概念:指令(Directives)是 Vue 提供的带有 v- 前缀 的 特殊 标签属性

vue 中的指令按照不同的用途可以分为如下 6 大类:

  • 内容渲染指令(v-html、v-text)
  • 条件渲染指令(v-show、v-if、v-else、v-else-if)
  • 事件绑定指令(v-on)
  • 属性绑定指令 (v-bind)
  • 双向绑定指令(v-model)
  • 列表渲染指令(v-for)

4.1 内容渲染指令

内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下2 个:

1)v-text(类似innerText)

使用语法:<p v-text="uname">hello</p>,意思是将 uame 值渲染到 p 标签中。

类似 innerText,使用该语法,会覆盖 p 标签原有内容。

2)v-html(类似 innerHTML)

使用语法:<p v-html="intro">hello</p>,意思是将 intro 值渲染到 p 标签中

类似 innerHTML,使用该语法,会覆盖 p 标签原有内容。

类似 innerHTML,使用该语法,能够将HTML标签的样式呈现出来。

示例:

<div id="app"><h2>个人信息</h2><!--    既然指令是vue提供的特殊的html属性,所以咱们写的时候就当成属性来用即可--><p v-text="uname">姓名:</p><p v-html="intro">简介:</p>
</div><script>const app = new Vue({el:'#app',data:{uname:'张三',intro:'<h2>这是一个<strong>非常优秀</strong>的boy<h2>'}})
</script>

4.2 条件渲染指令

条件判断指令,用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:

1)v-show

  1. 作用: 控制元素显示隐藏
  2. 语法: v-show = “表达式” 表达式值为 true 显示, false 隐藏
  3. 原理: 切换 display:none 控制显示隐藏
  4. 场景:频繁切换显示隐藏的场景

2)v-if

  1. 作用: 控制元素显示隐藏(条件渲染)
  2. 语法: v-if= “表达式” 表达式值 true显示, false 隐藏
  3. 原理: 基于条件判断,是否创建 或 移除元素节点
  4. 场景: 要么显示,要么隐藏,不频繁切换的场景

示例:

<script src="vue.js"></script>
<div id="app"><div class="box" v-show="flag">我是v-show控制的盒子</div><div class="box" v-if="flag">我是v-if控制的盒子</div>
</div><script>const app = new Vue({el: '#app',data: {flag: true}})
</script>

3)v-else 和 v-else-if

  1. 作用:辅助v-if进行判断渲染
  2. 语法:v-else v-else-if=“表达式”
  3. 需要紧接着v-if使用

示例:

<script src="vue.js"></script>
<div id="app"><p v-if="gender==1">性别:♂ 男</p><p v-else>性别:♀ 女</p><hr><p v-if="score>=90">成绩评定A</p><p v-else-if="score>=80">成绩评定B</p><p v-else-if="score>=70">成绩评定C</p><p v-else>成绩评定D</p>
</div>
<script>const app = new Vue({el: '#app',data: {gender: 1,score: 95}})
</script>

4.3 事件绑定指令

使用Vue时,如需为DOM注册事件,及其的简单,语法如下:

  • <button v-on:事件名=“内联语句”>按钮
  • <button v-on:事件名=“处理函数”>按钮
  • <button v-on:事件名=“处理函数(实参)”>按钮
  • v-on: 简写为 @

示例一:内联语句

<div id="app"><!--    v-on简写成@--><button @click="count--">-</button><span>{{ count }}</span><button v-on:click="count++">+</button>
</div>
<script>const app = new Vue({el: '#app',data: {count: 100}})
</script>

示例二:处理函数

<div id="app"><button @click="handleClick">切换显示隐藏</button><h1 v-show="isShow">hello Vue</h1>
</div>
<script>const app = new Vue({el: '#app',data: {isShow: true},methods:{handleClick(e){this.isShow = !this.isShow;console.log(e);}}})
</script>

注意:

  • 事件处理函数应该写到一个跟data同级的配置项(methods)中

  • methods中的函数内部的this都指向Vue实例

  • 如果不传递任何参数,则方法无需加小括号;methods方法中可以直接使用 e 当做事件对象

  • 如果传递了参数,则实参 $event 表示事件对象,固定用法。

4.4 单向属性绑定指令

  1. **作用:**动态设置html的标签属性 比如:src、url、title
  2. 语法:**v-bind:**属性名=“表达式”
  3. **v-bind:**可以简写成 => :

示例:

<div id="app"><img v-bind:src="imgUrl" v-bind:title="msg" alt="" width="200px"><img :src="imgUrl" :title="msg" alt="" width="200px">
</div>
<script>const app = new Vue({el: '#app',data: {imgUrl: './1.png',msg: 'hello Vue'}})
</script>

4.5 双向属性绑定指令

所谓双向绑定就是数据改变后,呈现的页面结果会更新,页面结果更新后,数据也会随之而变。

作用:表单元素(input、radio、select)使用,双向绑定数据,可以快速 获取设置 表单元素内容

**语法:**v-model=“变量”

示例:

<div id="app">账户:<input type="text" v-model="username"> <br><br>密码:<input type="password" v-model="password"> <br><br>
</div>
<script>const app = new Vue({el: '#app',data: {username: '',password: ''},})
</script>
<template><div><input type="checkbox" id="checkbox" v-model="checked"><label for="checkbox">{{ checked }}</label></div>
</template><script>
export default {data() {return {checked: false}}
}
</script>

原理:v-model本质上是一个语法糖。例如应用在输入框上,就是value属性 和 input事件 的合写

<template><div id="app" ><input v-model="msg" type="text"><input :value="msg" @input="msg = $event.target.value" type="text"></div>
</template>

4.6 列表渲染指令

Vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。

v-for 指令需要使用 (item, index) in arr 形式的特殊语法,其中:

  • item 是数组中的每一项
  • index 是每一项的索引,不需要可以省略
  • arr 是被遍历的数组

此语法也可以遍历对象和数字

示例1:

<div id="app">
<!--
遍历对象:value:对象中的值key:对象中的键index:遍历索引从0开始
--><div v-for="(value, key, index) in object">{{key}} : {{value}}</div><!--
遍历数字:
item从1开始
--><p v-for="item in 10">{{item}}</p></div>
<script>const app = new Vue({el: '#app',data: {object:{name: "obejct1",age: "18",gender: "male"}},})
</script>

v-for中的key属性作为唯一标识,便于Vue进行列表项的正确排序复用

<div v-for="item in items" :key="item.id">{{ item.name }}
</div>

注意:

  • :key表示key的值是动态绑定的。

  • key 的值只能是字符串 或 数字类型

  • key 的值必须具有唯一性

  • 推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(会变化,不对应)

5. 修饰符

修饰符(Modifiers)是一种特殊的后缀,用于指示Vue对某些绑定的DOM事件或属性绑定行为进行特别处理。它们通常以点(.)开始,添加到指令后,用来执行一些特定的操作或行为调整。

5.1 事件修饰符

事件修饰符允许我们处理DOM事件的细节,例如事件冒泡、事件捕获、事件修饰键等。一用的事件修饰符:

  • .stop - 调用event.stopPropagation(),阻止事件继续传播。

  • .prevent - 调用event.preventDefault(),阻止事件的默认行为。

  • .capture - 使用事件捕获模式添加事件监听器。

  • .once - 事件将只触发一次。

5.2 键盘事件修饰符

键盘事件修饰符用于监听键盘事件,使得只有在特定键被按下时才触发某个操作。例如:

  • .enter
  • .delete(捕获“删除”和“退格”键)
  • .space
  • .up
  • .down

5.3 修饰符串联

修饰符可以串联,即可以在同一个指令上使用多个修饰符,例如:

<button @click.stop.prevent="doThat">点击我</button>

上述代码同时使用了.stop.prevent修饰符,既停止事件冒泡也阻止默认行为。

5.4 v-model 修饰符

用于创建双向数据绑定的v-model指令也支持几个修饰符,来定制如何绑定和监听输入控件的值:

  • .lazy - 取代input事件监听更新,使用change事件。
  • .number - 输入字符串转为有效的数字。
  • .trim - 输入去除首尾空格。

6. computed计算属性

计算属性是一种特殊的属性,它可以根据依赖的数据动态计算出一个新的值。计算属性的值会被 Vue.js 缓存,只有当依赖的数据发生改变时,才会重新计算这个属性的值。

计算属性的特点:

  1. 数据变化时会重新计算,提高了性能。适合用在复杂逻辑的计算上。
  2. 计算属性的结果会被缓存,依赖数据改变之前,多次访问计算属性会直接返回之前的计算结果,而不会重新计算。

示例:

<div id="app"><div>a + b 的结果为:{{sum}}</div>
</div><script>const app = new Vue({el: '#app',data: {a: 10,b: 20},computed: {sum: function (){return this.a + this.b;}}})
</script>

计算属性默认的简写,只能读取访问,不能 “修改”,如果要 “修改” 需要写计算属性的完整写法

请添加图片描述

示例:

<div id="app">姓:<input type="text" v-model="firstName"> +名:<input type="text" v-model="lastName"> ={{ name }}<br><br><button @click="name='张飞'">改名卡</button>
</div>
<script>const app = new Vue({el: '#app',data: {firstName: '刘',lastName: '备'},computed: {name: {get: function () {return this.firstName + this.lastName},set: function (newValue) {this.firstName = newValue.charAt(0);this.lastName = newValue.slice(1);}}}})
</script>

7. watch侦听器

作用:监视数据变化,执行一些业务逻辑或异步操作

1)简化语法:声明在data同级的配置项中

watch: {// 该方法会在数据变化时,触发执行数据属性名 (newValue, oldValue) {一些业务逻辑 或 异步操作。 }
}

简化写法示例:

<div id="app">{{ count }}<button @click="count++">count++</button>
</div>
<script>const app = new Vue({el: '#app',data: {count: 0,},watch: {count: function (oldVal, newVal){console.log(oldVal + "->" + newVal)}}})
</script>

2)完整语法示例:

<div id="app">{{ count }}<button @click="count++">count++</button>
</div>
<script>const app = new Vue({el: '#app',data: {count: 0,},watch: {count: {handler: function (oldVal,newVal){console.log(oldVal + "->" + newVal)},deep: true,immediate: true}}})
</script>

注意:

  • deep为深度监听,即被监听的对象属性发生变化时也会触发。(若不开启则只有对象引用发生变化时才会触发)
  • immediate表示在数据第一次加载时也会触发。

8. 生命周期

生命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁

1.创建阶段:创建响应式数据

2.挂载阶段:渲染模板

3.更新阶段:修改数据,更新视图

4.销毁阶段:销毁Vue实例

请添加图片描述

8.1 生命周期钩子

以下是Vue的生命周期钩子:

  • beforeCreate:组件创建之后遇到的第一个生命周期函数,这个阶段 data 和 methods 以及 dom 结构都未
    被初始化,也就是获取不到 data 的值,不能调用 methods 中的函数
  • created:这个阶段组件的 data 和 methods 中的方法已初始化结束,可以访问,但是 dom 结构未初始化,页面未渲染
  • beforeMount:当模板在内存中编译完成,此时内存中的模板结构还未渲染至页面上,看不到真实的数据
  • mounted:此时,页面渲染好,用户看到的是真实的页面数据, 生命周期创建阶段完毕,进入到了运行中的阶段
  • beforeUpdate:当执行此函数,数据池的数据新的,但是页面是旧的
  • updated:页面已经完成了更新,此时,data 数据和页面的数据都是新的
  • beforeDestroy:当执行此函数时,组件即将被销毁,但是还没有真正开始销毁,此时组件的 data、methods数据或方法 还可被调用
  • destroyed:组件已经完成了销毁

请添加图片描述

示例:

<div id="app">{{ count }}<button @click="count++">count++</button>
</div>
<script>const app = new Vue({el: '#app',data: {count: 0,},beforeUpdate: function (){console.log(this.count);}})
</script>

9. 工程化开发和脚手架

Vue的工程化开发是指通过一系种工具和规范,将Vue的开发流程进行标准化、自动化,提高开发效率和项目质量。它包括了项目初始化、开发、构建、部署、测试等环节。

9.1 Vue CLI

Vue的脚手架是Vue CLI,它是一个全局安装的命令行工具,提供了项目创建、开发和构建等功能。Vue CLI能够帮助开发者快速创建Vue项目,它预设了一些项目模板,包括了webpack、Babel、ESLint等开发工具的配置,让开发者可以专注于编写业务代码,而不需要花费大量时间去配置这些工具。

使用步骤:

  1. 全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g
  2. 查看vue/cli版本: vue --version
  3. 创建项目架子:vue create project-name(项目名不能使用中文)
  4. 启动项目:yarn serve 或者 npm run serve(命令不固定,找package.json)

9.2 项目目录介绍

请添加图片描述

9.3 运行流程

请添加图片描述

10. 组件化开发

组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。

好处:便于维护,利于复用 → 提升开发效率。

请添加图片描述

组件由三部分构成:

  • template:结构 (有且只能一个根元素)
  • script: js逻辑
  • style: 样式 (可支持less,需要装包)

10.1 根组件 App.vue

App.vue为整个应用最上层的组件,包裹所有普通小组件

请添加图片描述

10.2 普通组件局部注册

特点:只能在注册的组件内使用

1)创建三个组件

Header:

<template><div>我是头部组件</div>
</template>
<script>
export default {name: 'Header'
}
</script>

Main:

<template><div>我是主体组件</div>
</template>
<script>
export default {name: 'Main'
}
</script>

Footer:

<template><div>我是底部组件</div>
</template>
<script>
export default {name: 'Footer'
}
</script>

2)引入组件并在局部注册

<template><Header /><Main /><Footer />
</template><script>import Header from '@/components/Header.vue'import Footer from '@/components/Footer.vue'import Main from '@/components/Main.vue'export default {name: 'HomeView',components: {Header,Footer,Main}}
</script>

10.3 普通组件全局注册

特点:全局注册的组件,在项目的任何组件中都能使用

在main.js中注册:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import MyComponent from './components/MyComponent.vue'const app = createApp(App)app.component('my-component', MyComponent)app.use(router).mount('#app')

10.4 scoped解决样式冲突

写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。

  1. 全局样式: 默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响

  2. 局部样式: 可以给组件加上scoped 属性,可以让样式只作用于当前组件

<template><div class="base-one">BaseOne</div>
</template><script>export default {}
</script><style scoped>
</style>

原理:

  1. 当前组件内标签都被添加data-v-hash值 的属性
  2. css选择器都被添加 [data-v-hash值] 的属性选择器

最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

10.5 组件中的data

一个组件的 data 选项必须是一个函数。目的是为了:保证每个组件实例,维护独立的一份数据对象。

每次创建新的组件实例,都会新执行一次data 函数,得到一个新对象。

请添加图片描述

baseCount.vue:

<template><div class="base-count"><button @click="count--">-</button><span>{{ count }}</span><button @click="count++">+</button></div>
</template><script>
export default {data: function () {return {count: 100,}},
}
</script>

App.vue:

<template><div class="app"><BaseCount></BaseCount></div>
</template><script>
import BaseCount from './components/BaseCount'
export default {components: {BaseCount,},
}
</script><style>
</style>

11. 组件通信

组件通信,就是指组件与组件之间的数据传递

  • 组件的数据是独立的,无法直接访问其他组件的数据。
  • 想使用其他组件的数据,就需要组件通信

11.1 父子间通信

父组件通过 props 将数据传递给子组件

子组件利用 $emit 通知父组件修改更新

请添加图片描述

父向子传值步骤:

  1. 给子组件以添加属性的方式传值
  2. 子组件内部通过props接收
  3. 模板中直接使用 props接收的值

子向父传值步骤:

  1. $emit触发事件,给父组件发送消息通知
  2. 父组件监听$emit触发的事件
  3. 提供处理函数,在函数的性参中获取传过来的参数

Son.vue:

<template>
<div>我是Son组件<br /><button @click="handleClick(count - 1)"> 点我减一 </button><span>count = {{ count }} </span><button @click="handleClick(count + 1)"> 点我加一 </button></div>
</template><script>export default {props: {count: Number},methods: {handleClick(count) {this.$emit("changeCount", count)}}} 
</script>

App.vue:

<template>
<div>我是APP组件<br /><Son :count="count" @changeCount="changeCount"></Son></div>
</template><script>import Son from "@/components/Son.vue"export default {data() {return {count: 100}},components: {Son},methods: {changeCount(count) {this.count = count;}}}
</script>

props完整写法:

props: {校验的属性名: {type: 类型,  // Number String Boolean ...required: true, // 是否必填default: 默认值, // 默认值validator (value) {// 自定义校验逻辑return 是否通过校验}}
},

11.2 非父子通信

父组件 provide提供数据

子/孙组件 inject获取数据

父组件:

export default {provide () {return {// 普通类型【非响应式】color: this.color, // 复杂类型【响应式】userInfo: this.userInfo, }}
}

子/孙组件:

export default {inject: ['color','userInfo'],created () {console.log(this.color, this.userInfo)}
}

注意:

  • provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)
  • 子/孙组件通过inject获取的数据,不能在自身组件内修改

v-model简化代码

v-model其实就是 :value 和 @input事件的简写

  • 子组件:props通过value接收数据,事件触发 input
  • 父组件:v-model直接绑定数据

子组件:

<select :value="value" @change="handleChange">...</select>
props: {value: String
},
methods: {handleChange (e) {this.$emit('input', e.target.value)}
}

父组件:

<BaseSelect v-model="selectId"></BaseSelect>

.sync修饰符

作用:可以实现 子组件父组件数据双向绑定,简化代码

本质:.sync修饰符 就是 :属性名@update:属性名 合写

父组件:

//.sync写法
<BaseDialog :visible.sync="isShow" />
---------------------------------------
//完整写法
<BaseDialog :visible="isShow" @update:visible="isShow = $event" 
/>

子组件:

props: {visible: Boolean
},this.$emit('update:visible', false)

12. ref和$refs

ref 是 Vue.js 中的一个特殊属性,可以用来绑定到渲染的 DOM 元素或子组件上。当 ref 和 HTML 元素一起使用时,ref 的值将是该元素;当 ref 和组件一起使用时,ref 的值将是组件实例。

$refs 是一个对象,持有已注册过 ref 的所有 DOM 元素和组件实例。它可以在组件的方法中通过 this.$refs 来访问。

示例:

<template><div><input ref="myInput"><button @click="focusInput">Focus the input</button></div>
</template><script>
export default {methods: {focusInput() {this.$refs.myInput.focus();}}
}
</script>

13. $nextTick

$nextTick 用于在下一次 DOM 更新循环结束之后延迟执行一段代码。在 DOM 更新之后,基于新的 DOM 状态来执行一些操作时非常有用。

示例:

<template>
<div><button @click="updateMessage">Update</button><p ref="message">{{ message }}</p></div>
</template><script>export default {data() {sreturn {message: 'Hello!'}},methods: {updateMessage() {this.message = 'Updated!';this.$nextTick(() => {console.log(this.$refs.message.textContent) // 'Updated!'})}}}
</script>

14. 自定义指令

  • 内置指令:v-html、v-if、v-bind、v-on… 这都是Vue给咱们内置的一些指令,可以直接使用

  • 自定义指令:同时Vue也支持让开发者,自己注册一些指令。这些指令被称为自定义指令,每个指令都有自己各自独立的功能

示例一:局部注册

<template><div><input ref="myInput" v-focus type="text" /></div>
</template><script>
export default {directives: {"focus"(el) {el.focus();}}
}
</script>

示例二:全局注册

main.js:

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";const app = createApp(App);
app.directive("color", (el, binding) => {el.style.color = binding.value; // 获取指令的值
});
app.use(router).mount("#app");

app.vue:

<template><div><span v-color="color">蓝色</span></div>
</template><script>
export default {data() {return {color: 'blue'}}
}
</script>

15. 插槽

作用:让组件内部的一些 结构 支持 自定义

15.1 默认插槽

语法:

  1. 组件内需要定制的结构部分,改用****占位
  2. 使用组件时, ****标签内部, 传入结构替换slot
  3. 给插槽传入内容时,可以传入纯文本、html标签、组件

示例:

子组件:

<template><div class="dialog"><div class="dialog-header"><h3>友情提示</h3><span class="close">✖️</span></div><div class="dialog-content"><slot></slot></div><div class="dialog-footer"><button>取消</button><button>确认</button></div></div>
</template><script>
export default {data() {return {}}
}
</script>
<style scoped>
* {margin: 0;padding: 0;
}.dialog {width: 470px;height: 230px;padding: 0 25px;background-color: #ffffff;margin: 40px auto;border-radius: 5px;
}.dialog-header {height: 70px;line-height: 70px;font-size: 20px;border-bottom: 1px solid #ccc;position: relative;
}.dialog-header .close {position: absolute;right: 0px;top: 0px;cursor: pointer;
}.dialog-content {height: 80px;font-size: 18px;padding: 15px 0;
}.dialog-footer {display: flex;justify-content: flex-end;
}.dialog-footer button {width: 65px;height: 35px;background-color: #ffffff;border: 1px solid #e1e3e9;cursor: pointer;outline: none;margin-left: 10px;border-radius: 3px;
}.dialog-footer button:last-child {background-color: #007acc;color: #fff;
}
</style>

父组件:

<template><div><Son>是否确认删除?</Son></div>
</template><script>
import Son from "@/components/Son.vue"
export default {components: {Son}
}
</script>

15.2 默认值

封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)。

示例:

<template><div class="dialog"><div class="dialog-header"><h3>友情提示</h3><span class="close">✖️</span></div><div class="dialog-content"><!-- 添加默认值,若父组件未传值则使用此内容 --><slot>我是默认内容</slot></div><div class="dialog-footer"><button>取消</button><button>确认</button></div></div>
</template>

15.3 具名插槽

当一个组件内有多处结构,需要外部传入标签进行定制时就需要给每个结构定义名字加以区分。

语法:

  • 多个slot使用name属性区分名字
  • template配合v-slot:名字来分发对应标签,其中v-slot可简写成#

示例:

子组件:

<template><div class="dialog"><div class="dialog-header"><slot name="head"></slot></div><div class="dialog-content"><slot name="content"></slot></div><div class="dialog-footer"><slot name="footer"></slot></div></div></template>

父组件:

<template><div><Son><template v-slot:head><h3>友情提示</h3></template><template #content><div>是否确认删除?</div></template><template #footer><button>取消</button><button>确认</button></template></Son></div>
</template><script>
import Son from "@/components/Son.vue"
export default {components: {Son}
}
</script>

15.4 作用域插槽

定义slot 插槽的同时, 是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以用

使用方式:

1)给 slot 标签, 以 添加属性的方式传值

<slot :id="item.id" msg="测试文本"></slot>

2)所有添加的属性, 都会被收集到一个对象中

{ id: 3, msg: '测试文本' }

3)在template中, 通过 #插槽名= "obj" 接收,默认插槽名为 default

<MyTable :list="list"><template #default="obj"><button @click="del(obj.id)">删除</button></template>
</MyTable>

示例:

子组件:

<template><table class="my-table"><thead><tr><th>序号</th><th>姓名</th><th>年纪</th><th>操作</th></tr></thead><tbody><tr v-for="(item, index) in data" :key="item.id"><td>{{ index }}</td><td>{{ item.name }}</td><td>{{ item.ages }}</td><td><slot :id="item.id"></slot></td></tr></tbody></table>
</template><script>
export default {props: {data: Array}
}
</script>

父组件:

<template><div><MyTable :data="list"><template #default="obj"><button @click="del(obj)">删除</button></template></MyTable><MyTable :data="list2"><template #default="obj"><button @click="sel(obj)">查看</button></template></MyTable></div>
</template><script>
import MyTable from './components/Son.vue'
export default {data() {return {list: [{ id: 1, name: '张小花', age: 18 },{ id: 2, name: '孙大明', age: 19 },{ id: 3, name: '刘德忠', age: 17 },],list2: [{ id: 1, name: '赵小云', age: 18 },{ id: 2, name: '刘蓓蓓', age: 19 },{ id: 3, name: '姜肖泰', age: 17 },]}},components: {MyTable}
}
</script>

16. 路由

Vue路由是单页面应用的核心,所有的页面跳转都通过路由来完成。

Vue Router是路由管理器

配置路由:

// 引入 vue-router 的两个方法:createRouter 和 createWebHistory
import { createRouter, createWebHistory } from 'vue-router'
// 引入 HomeView 组件
import HomeView from '../views/HomeView.vue'// 定义路由映射
const routes = [{// 当用户访问根路径(即 '/')时,将会渲染 HomeView 组件path: '/home',name: 'home',component: HomeView},{// 当用户访问 '/about' 路径时,将会渲染 AboutView 组件// 这里使用了懒加载的方式来引入 AboutView 组件,即当路由被访问时才加载对应组件,可以提高首屏加载速度path: '/about',name: 'about',component: () => import('../views/AboutView.vue')}
]// 创建 router 实例
const router = createRouter({// 使用 HTML5 的 History 路由模式history: createWebHistory(process.env.BASE_URL),// 应用的路由映射routes
})// 导出 router 实例
export default router

其中路由模式:

  • hash路由(默认) 例如: http://localhost:8080/#/home
  • history路由(常用) 例如: http://localhost:8080/home

16.1 声明式导航

App.vue:

<template><div><!-- 导航栏 --><div class="footer_wrap"><!-- 使用 router-link 组件来导航。通过传入 `to` 属性指定链接,当点击这个链接后,地址栏会变为 '/home',同时匹配的组件会被渲染到 <router-view> 中 --><router-link to="/home">home</router-link><!-- 当点击这个链接后,地址栏会变为 '/about',同时匹配的组件会被渲染到 <router-view> 中 --><router-link to="/about">about</router-link></div><!-- 主要内容区域 --><div class="top"><!-- 路由出口。匹配的组件将会在这里显示 --><router-view></router-view></div></div>
</template><style scoped>
.footer_wrap {display: flex;justify-content: space-around;background-color: #f8f8f8;padding: 20px 0;
}.footer_wrap a {text-decoration: none;color: #333;
}.top {padding: 20px;background-color: #fff;
}
</style>

我们可以通过在样式中添加.router-link-active来设置选中链接的样式,从而实现选中高亮的效果。例如,我们可以设置选中链接的颜色为蓝色:

<style scoped>
/* 其他样式 */.router-link-active {background-color: blue;
}
</style>

.router-link-active是模糊匹配,只要路径中含有配置路由即可高亮(使用较多)。

.router-link-exact-active是精确匹配,路径必须与配置路由一致才可匹配。

在路由配置时使用linkActiveClasslinkExactActiveClass也可自己定义类目

const router = new VueRouter({routes: [...],linkActiveClass: 'active', // 配置模糊匹配的类名linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})

16.2 声明式导航传参

我们可以通过两种方式,在跳转的时候把所需要的参数传到其他页面中

1)查询参数传参

  • 传参方式:<router-link to="/path?参数名=值"></router-link>
  • 获取参数:$router.query.参数名

2)动态路由传参

  • 配置动态路由:动态路由后面的参数可以随便起名,但要有语义

    const router = new VueRouter({routes: [...,{ path: '/search/:words', component: Search }]
    })
    
  • 配置导航链接:to="/path/参数值"

  • 对应页面组件接受参数:$route.params.参数名

示例一:查询参数传参

<template><div><div class="footer_wrap"><router-link to="/home">home</router-link><router-link to="/about?key=myabout">about</router-link></div><div class="top"><router-view></router-view></div></div>
</template>
<template><div class="about"><h1>This is an about page</h1>{{ $route.query.key }}</div>
</template><script>
export default {created() {console.log(this.$route.query.key)}
}</script>

示例二:动态路由传参

router:

const routes = [{path: "/home",name: "home",component: HomeView,},{path: "/about/:key",name: "about",component: () => import("../views/AboutView.vue"),},
];

App.vue:

<template><div><div class="footer_wrap"><router-link to="/home">home</router-link><router-link to="/about/myabout">about</router-link></div><div class="top"><router-view></router-view></div></div>
</template>

about.vue:

<template><div class="about"><h1>This is an about page</h1>{{ $route.params.key }}</div>
</template><script>
export default {created() {console.log(this.$route.params.key)}
}</script>

注意:

/search/:key 表示,必须要传参数。如果不传参数,也希望匹配,可以加个可选符

{path: "/about/:key?",name: "about",component: () => import("../views/AboutView.vue"),
}

16.3 重定向

语法:{ path: 匹配路径, redirect: 重定向到的路径 }

示例:

const routes = [{path: "/",redirect: "/home",},{path: "/home",name: "home",component: HomeView,},{path: "/about/:key",name: "about",component: () => import("../views/AboutView.vue"),},
];

16.4 404界面

当路径找不到匹配时,需要给个提示页面

path: “*” (任意路径) – 前面不匹配就命中最后这个

import NotFind from '@/views/NotFind'const router = new VueRouter({routes: [...{ path: '*', component: NotFind } //最后一个]
})

16.5 路由跳转方式

1)query传参跳转

//简单写法
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
//完整写法
this.$router.push({path: '/路径',query: {参数名1: '参数值1',参数名2: '参数值2'}
})

示例:

<template><div class="about"><button @click="handleClick">点我跳转home</button></div>
</template><script>
export default {methods: {handleClick() {this.$router.push('/home')}}
}
</script>

2)动态路由传参

//简单写法
this.$router.push('/路径/参数值')
//完整写法
this.$router.push({path: '/路径/参数值'
})

17. Vuex

Vuex 是一个插件,可以帮我们管理 Vue 通用的数据 (多组件共享的数据)。例如:购物车数据 个人信息。

17.1 快速入门

1)安装

安装vuex与vue-router类似,vuex是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。

yarn add vuex@3 或者 npm i vuex@3

2)新建 store/index.js 专门存放 vuex

为了维护项目目录的整洁,在src目录下新建一个store目录其下放置一个index.js文件。 (和 router/index.js 类似)

3)创建仓库 store/index.js

// 导入 vue
import Vue from "vue";
// 导入 vuex
import Vuex from "vuex";
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex);// 创建仓库 store
const store = new Vuex.Store();// 导出仓库
export default store;

4)在 main.js 中导入挂载到 Vue 实例上

import Vue from 'vue'
import App from './App.vue'
import store from './store'Vue.config.productionTip = falsenew Vue({render: h => h(App),store
}).$mount('#app')

此刻起,就成功创建了一个空仓库

17.2 state

State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。

打开项目中的store.js文件,在state对象中可以添加我们要共享的数据。

设置数据:

// 创建仓库 store
const store = new Vuex.Store({// state 状态, 即数据, 类似于vue组件中的data,// 区别:// 1.data 是组件自己的数据, // 2.state 中的数据整个vue项目的组件都能访问到state: {count: 101}
})

获取数据语法:

获取 store:1.Vue模板中获取 this.$store2.js文件中获取 import 导入 store模板中:     {{ $store.state.xxx }}
组件逻辑中:  this.$store.state.xxx
JS模块中:   store.state.xxx

示例:

<template><div class="about">count1: {{ $store.state.count }}<br />count2: {{ count }}</div>
</template><script>
export default {data() {return {count: this.$store.state.count}}
}
</script>

17.3 mapState

mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便的用法

用法:

1)导入mapState (mapState是vuex中的一个函数)

import { mapState } from 'vuex'

2)采用数组形式引入state属性

mapState(['count']) 

上面代码类似于:

count () {return this.$store.state.count
}

3)利用展开运算符将导出的状态映射给计算属性

  computed: {...mapState(['count'])}

示例:

<template><div class="about">count1: {{ $store.state.count }}<br />count2: {{ count }}</div>
</template><script>
import { mapState } from 'vuex'
export default {computed: {...mapState(['count'])}
}
</script>

17.4 mutations

在组件中无法直接修改store中的数据,需要借助mutation修改数据。

mutations是一个对象,对象中存放修改state的方法

1)在store.js中添加方法:

state: {count: 0,
},
mutations: {addCount1(state) {state.count++;},addCount2(state, num) {state.count += num;},
}

2)调用方法:

<template><div class="about">count1: {{ $store.state.count }}<br />count2: {{ count }}<button @click="addCount1">add 1</button><button @click="addCount2(5)">add 5</button><button @click="addCount2(10)">add 10</button></div>
</template><script>
import { mapState } from 'vuex'
export default {computed: {...mapState(['count'])},methods: {addCount1() {this.$store.commit('addCount1')},addCount2(num) {this.$store.commit('addCount2', num)}}
}
</script>

17.5 mapMutations

mapMutations和mapState很像,它把位于mutations中的方法提取了出来,我们可以将它导入

import  { mapMutations } from 'vuex'
methods: {...mapMutations(['addCount'])
}

上面代码的含义是将mutations的方法导入了methods中,等价于

methods: {// commit(方法名, 载荷参数)addCount () {this.$store.commit('addCount')}}

此时,就可以直接通过this.addCount调用了

<button @click="addCount">值+1</button>

17.6 actions

actions负责进行异步操作

1)添加actions方法

mutations: {changeCount (state, newCount) {state.count = newCount}
}actions: {setAsyncCount (context, num) {// 一秒后, 给一个数, 去修改 numsetTimeout(() => {context.commit('changeCount', num)}, 1000)}
},

2)组件中通过dispatch调用方法

setAsyncCount () {this.$store.dispatch('setAsyncCount', 666)
}

17.7 mapActions

mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中

import { mapActions } from 'vuex'
methods: {...mapActions(['changeCountAction'])
}

直接通过 this.方法 就可以调用

<button @click="changeCountAction(200)">+异步</button>

17.8 getters

除了state之外,有时我们还需要从state中筛选出符合条件的一些数据,这些数据是依赖state的,此时会用到getters

例如,state中定义了list,为1-10的数组,

state: {list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}

组件中,需要显示所有大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它。

1)定义getters

getters: {// getters函数的第一个参数是 state// 必须要有返回值filterList:  state =>  state.list.filter(item => item > 5)
}

2)使用getters

原生方式:

<div>{{ $store.getters.filterList }}</div>

辅助函数:

computed: {...mapGetters(['filterList'])
}
 <div>{{ filterList }}</div>

17.9 module

如果把所有的状态都放在state中,当项目变得越来越大的时候,Vuex会变得越来越难以维护。由此,有了Vuex的模块化。

请添加图片描述

在store目录下创建一个module文件夹,定义两个模块 usersetting

user中管理用户的信息状态 userInfo modules/user.js

const state = {userInfo: {name: 'zs',age: 18}
}const mutations = {}const actions = {}const getters = {}export default {namespaced: true,state,mutations,actions,getters
}

setting中管理项目应用的 主题色 theme,描述 desc, modules/setting.js

const state = {theme: 'dark'desc: '描述真呀真不错'
}const mutations = {}const actions = {}const getters = {}export default {namespaced: true,state,mutations,actions,getters
}

store/index.js文件中的modules配置项中,注册这两个模块

import user from './modules/user'
import setting from './modules/setting'const store = new Vuex.Store({modules:{user,setting}
})

使用模块中的数据, 可以直接通过模块名访问 $store.state.模块名.xxx => $store.state.setting.desc

也可以通过 mapState 映射

获取数据:

$store直接访问

$store.state.user.userInfo.name

mapState辅助函数访问(需要开启命名空间)

...mapState('user', ['userInfo']),
...mapState('setting', ['theme', 'desc']),

使用模块中 getters 中的数据:

1)直接通过模块名访问 $store.getters['模块名/xxx ']

2)通过 mapGetters 映射

  • 默认根级别的映射 mapGetters([ 'xxx' ])
  • 子模块的映射 mapGetters('模块名', ['xxx']) - 需要开启命名空间

获取模块内的mutations方法:

1)直接通过 store 调用 $store.commit('模块名/xxx ', 额外参数)

2)通过 mapMutations 映射

  • 默认根级别的映射 mapMutations([ ‘xxx’ ])
  • 子模块的映射 mapMutations(‘模块名’, [‘xxx’]) - 需要开启命名空间

获取模块内的actions方法:

1)直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)

2)通过 mapActions 映射

  • 默认根级别的映射 mapActions([ ‘xxx’ ])
  • 子模块的映射 mapActions(‘模块名’, [‘xxx’]) - 需要开启命名空间

18. Vue3

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

18.1 setup

setup 函数是 Vue 组件中的新选项,它是所有组合式 API 特性的入口点。它的主要目的是替代 Vue 2 中的 data、methods、computed 和 lifecycle hooks 等选项,提供一个更灵活和组织性更强的方式来组织组件逻辑。

特点:

  1. 执行时机setup 函数在组件创建之前执行,此时组件的 props 已经被解析,并且可以在函数内访问。这使得 setup 成为定义响应式状态和其他响应式逻辑的理想位置。
  2. 参数setup 函数接收两个参数:
    • props:一个包含组件接收的所有 props 的对象。注意,Vue 对这些 props 进行了代理,你应当避免解构它们,以保持响应性。
    • context:一个普通的 JavaScript 对象,包含了 Vue 组件实例的 attrsslotsemit 等属性。
  3. 返回值setup 函数的返回值可以是一个对象,该对象的属性将会被暴露给组件的其它部分(如模板或其他选项API),或者返回一个渲染函数。
  4. 响应式状态: 在 setup 中,你可以使用 refreactive 来创建响应式状态。ref 用于定义响应式的基本类型值,而 reactive 则用于对象或数组。
  5. 生命周期钩子: Vue 3 的生命周期钩子也可以在 setup 中使用,但它们的名称有所更改,如 onMountedonUpdatedonUnmounted 等。

示例:

import { ref, onMounted } from 'vue';export default {setup(props, { emit }) {const count = ref(0);function increment() {count.value++;emit('update:count', count.value);}onMounted(() => {console.log('Component is mounted!');});return { count, increment };}
}

Vue 3 中引入了 setup 语法糖 script setup。这是一种新的语法糖,允许在 <script> 标签中使用 setup 语法,而无需定义 export default 对象。

script setup 有以下特性:

  1. 模块化作用域:在 <script setup> 中定义的所有变量都自动暴露给模板,无需通过 return 语句。
  2. 类型推断:对于 TypeScript 用户,<script setup> 允许更好的类型推断,因为它是基于模块作用域的。
  3. 更简洁:不需要 setup 函数和 export default,代码更加简洁。
  4. 支持生命周期钩子:可以直接在 <script setup> 中使用生命周期钩子,如 onMounted
  5. 支持 Props 和 Emit:可以通过 definePropsdefineEmits 函数来定义 props 和 emits。

示例:

<script setup>
import { ref, onMounted } from 'vue'const count = ref(0)function increment() {count.value++
}onMounted(() => {console.log('Component is mounted!')
})
</script><template><button @click="increment">{{ count }}</button>
</template>

18.2 reactive和ref

reactive: 接收一个对象类型的数据,返回一个响应式的对象

示例:

import { reactive } from 'vue'const state = reactive({count: 100
})const setCount = () => {state.count++
}

ref: 接收简单类型 或 复杂类型,返回一个响应式的对象

本质:是在原有传入数据的基础上,外层包了一层对象,包成了复杂类型,再借助 reactive 实现的响应式

注意点:

  • 脚本中访问数据,需要通过 .value
  • 在template中,.value不需要加 (帮我们扒了一层)
  • 推荐声明数据统一使用ref

示例:

import { ref } from 'vue'
const count = ref(0)
const setCount = () => {count.value++
}
</script><template><div><div>{{ count }}</div><button @click="setCount">+1</button></div>
</template>

18.3 computed

在 Vue 组件中使用 computed,可以让我们基于现有数据动态计算新的数据值,而且这些值会在依赖的响应式数据发生变化时自动更新。

示例:

<script setup>
// const 计算属性 = computed(() => {
//    return 计算返回的结果
// })import { computed, ref } from 'vue'// 声明数据
const list = ref([1, 2, 3, 4, 5, 6, 7, 8])// 基于list派生一个计算属性,从list中过滤出 > 2
const computedList = computed(() => {return list.value.filter(item => item > 2)
})// 定义一个修改数组的方法
const addFn = () => {list.value.push(666)
}
</script><template><div><div>原始数据: {{ list }}</div><div>计算后的数据: {{ computedList }}</div><button @click="addFn" type="button">修改</button></div>
</template>

18.4 watch

watch 用于观察 Vue 实例上的响应式数据的变化。当需要在数据变化时执行一些操作(如数据获取、验证或复杂的逻辑)时,watch 是非常有用的。

示例:

<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const nickname = ref('张三')const changeCount = () => {count.value++
}
const changeNickname = () => {nickname.value = '李四'
}// 1. 监视单个数据的变化
//  watch(ref对象, (newValue, oldValue) => { ... })watch(count, (newValue, oldValue) => {   console.log(newValue, oldValue)
})// 2. 监视多个数据的变化
//  watch([ref对象1, ref对象2], (newArr, oldArr) => { ... })watch([count, nickname], (newArr, oldArr) => {console.log(newArr, oldArr)
})</script><template><div>{{ count }}</div><button @click="changeCount">改数字</button><div>{{ nickname }}</div><button @click="changeNickname">改昵称</button><div>-----------------------</div><div>{{ userInfo }}</div><button @click="setUserInfo">修改userInfo</button>
</template>

可选参数:

  • deep :用于深度监听对象的内部变化
  • immediate :使得侦听器在初次创建时立即触发一次,而不是等待数据变化

示例:

<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const nickname = ref('张三')const changeCount = () => {count.value++
}
const changeNickname = () => {nickname.value = '李四'
}//  immediate 立刻执行 
watch(count, (newValue, oldValue) => {console.log(newValue, oldValue)
}, {immediate: true
})// --------------------------------------------
// 	  deep 深度监视, 默认 watch 进行的是 浅层监视
//    const ref1 = ref(简单类型) 可以直接监视
//    const ref2 = ref(复杂类型) 监视不到复杂类型内部数据的变化
const userInfo = ref({name: 'zs',age: 18
})
const setUserInfo = () => {// 修改了 userInfo.value 修改了对象的地址,才能监视到// userInfo.value = { name: 'ls', age: 50 }userInfo.value.age++// deep 深度监视
watch(userInfo, (newValue) => {console.log(newValue)
}, {deep: true
})
</script><template><div>{{ count }}</div><button @click="changeCount">改数字</button><div>{{ nickname }}</div><button @click="changeNickname">改昵称</button><div>-----------------------</div><div>{{ userInfo }}</div><button @click="setUserInfo">修改userInfo</button>
</template>

18.5 生命周期函数

请添加图片描述

示例:

<script setup>
import { onMounted } from 'vue';// beforeCreate 和 created 的相关代码
// 一律放在 setup 中执行const getList = () => {setTimeout(() => {console.log('发送请求,获取数据')}, 2000)
}
// 一进入页面的请求
getList()// 如果有些代码需要在mounted生命周期中执行
onMounted(() => {console.log('mounted生命周期函数 - 逻辑1')
})// 写成函数的调用方式,可以调用多次,并不会冲突,而是按照顺序依次执行
onMounted(() => {console.log('mounted生命周期函数 - 逻辑2')
})</script><template><div></div>
</template>

18.6 父子间通信

父组件通过props向子组件传递数据,子组件通过事件向父组件发送消息。

  • Props:父组件通过属性的方式传递数据给子组件。子组件通过定义props接收这些数据。
  • Events:子组件使用$emit触发事件,父组件通过监听这些事件来响应子组件的行为。

父组件:

<template><ChildComponent :msg="message" @child-event="handleEvent" />
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const message = ref('Hello from parent');function handleEvent(data) {console.log('Received from child:', data);
}
</script>

子组件:

<template><div><p>{{ msg }}</p><button @click="emitToParent">Click me</button></div>
</template><script setup>
import { defineProps, defineEmits } from 'vue';const props = defineProps({msg: String
});const emit = defineEmits(['child-event']);function emitToParent() {emit('child-event', 'Data from child');
}
</script>

18.7 模板引用

模板引用(template refs)允许直接访问DOM元素或组件实例。这在需要直接操作DOM或访问组件的特定方法时非常有用。

用法:

可以使用ref属性在模板中创建一个引用。这个属性的值应该是一个字符串,表示引用的名称。然后,可以在JavaScript部分通过ref API访问这个元素。

<template><div><input ref="inputRef" /><button @click="focusInput">Focus the Input</button></div>
</template><script setup>
import { ref, onMounted } from 'vue';const inputRef = ref(null);onMounted(() => {inputRef.value.focus();
});function focusInput() {inputRef.value.focus();
}
</script>

注意事项:

  • 访问时机:只有在Vue组件挂载(mounted)后,模板引用才会被填充。因此,在onMounted钩子中访问模板引用是安全的。
  • 响应式:虽然模板引用本身不是响应式的,但通过.value访问的DOM元素是实时的,意味着它总是指向当前的DOM元素或组件实例。

18.8 provide和inject

provideinject 它们允许跨组件树传递数据,非常适合用于深层嵌套组件或需要共享数据的场景,而不必通过每个组件层手动传递 props。

provide 选项允许一个组件向其所有子孙组件提供数据或方法,而无需通过每个单独的组件手动传递。你可以在任何组件中使用 provide 选项来定义数据或方法,然后在组件的任何后代中使用 inject 来访问它。

inject 选项用于在任何子组件中接收从祖先组件通过 provide 提供的数据或方法。

父组件:

<script setup>
import { provide, reactive } from 'vue';const state = reactive({user: 'Alice',age: 25
});provide('userData', state);const updateUser = (name) => {state.user = name;
};
provide('updateUser', updateUser);
</script><template><div><h1>User: {{ state.user }}</h1><!-- 子组件会在这里插入 --></div>
</template>

子/孙组件:

<script setup>
import { inject } from 'vue';const userData = inject('userData');
const updateUser = inject('updateUser');
</script><template><div><p>User name: {{ userData.user }}</p><button @click="updateUser('Bob')">Change Name to Bob</button></div>
</template>

18.9 defineOptions

defineOptions 是 Vue 3.2 在 <script setup> 标签中引入的一个编译时的宏,它允许在单文件组件的 <script setup> 块中直接定义组件的选项,如 name, props, emits 等。

示例:

<script setup>
import { defineOptions } from 'vue'// 使用 defineOptions 设置组件选项
defineOptions({name: 'MyComponent',  // 组件名props: {title: String,},emits: ['click']
})const title = 'Hello, Vue 3!'
</script><template><div @click="$emit('click')">{{ title }}</div>
</template>

18.10 defineExpose

使用 <script setup> 的组件是默认关闭的——即通过模板引用获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。

可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性

<script setup>
import { ref } from 'vue'const a = 1
const b = ref(2)defineExpose({a,b
})
</script>

当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number } (ref 会和在普通实例中一样被自动解包)

19. Vite

Vite 是一种现代化的前端构建工具,由原 Vue.js 创建者尤雨溪开发。它使用了原生的 ES 模块(ESM)作为开发时的模块格式,可以实现快速的冷启动和即时模块热更新。Vite 提供了丰富的功能,如 TypeScript 支持、CSS 预处理、代码拆分等,而且通过插件系统可以进一步扩展。

与 Vue CLI 相比,Vite 有几个显著的优点:

  • 启动速度:Vite 在开发模式下不需要打包操作,使用 ES 模块导入直接加载文件,这使得启动速度非常快。而 Vue CLI 使用的是 Webpack 进行模块打包,需要更多的启动时间尤其是在大型项目中。
  • 构建速度:在生产构建方面,Vite 使用 Rollup 打包,这通常比 Webpack 更快,特别是在处理 ES 模块时。
  • 现代化支持:Vite 完全支持现代浏览器,利用原生 ES 模块加载,而 Vue CLI 的 Webpack 设置默认包括对旧浏览器的兼容性处理。
  • 简化的配置:Vite 旨在提供开箱即用的体验,配置相对简单直观。虽然 Vue CLI 也提供了很多预设的配置,但自定义配置可能更复杂一些。

使用步骤:

1)安装 Node.js: 确保你的系统已经安装了 Node.js。Vite 要求 Node.js 版本至少为 12.0.0。

2)创建新项目: 使用 Vite 提供的命令行界面快速创建一个新项目。打开你的终端或命令提示符,然后运行以下命令:

npm create vite@latest my-vue-app

这个命令会创建一个名为 my-vue-app 的新目录,里面包含一个基于 Vue 的模板项目。

3)进入项目目录

cd my-vue-app

4)安装依赖

npm install

5)启动开发服务器

npm run dev

20. Pinia

Pinia 是 Vue.js 生态系统中的一个状态管理库,通常用于 Vue 3,作为更易用的 Vuex 替代品。Pinia 提供了一种简洁和强大的方式来管理和维护应用的状态。

20.1 快速入门

1)安装

npm install pinia

2)创建一个 pinia 实例 (根 store) 并将其传递给应用:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const pinia = createPinia()
const app = createApp(App)app.use(pinia)
app.mount('#app')

3)定义Store

import { defineStore } from "pinia";export const useUserStore = defineStore("user", {state: () => {return {name: "张三",}},actions: {// 修改state中的数据changeName(name) {this.name = name;}}
})

4)组件中使用

<script setup>
import { useUserStore } from './store/user';
const userStore = useUserStore();
</script><template><div>{{ userStore.name }}</div><button @click="userStore.changeName('李四')">点击修改名字</button>
</template>

20.2 Store解构

为了从 store 中提取属性时保持其响应性,需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当只使用 store 的状态而不调用任何 action 时,它会非常有用。

<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>

20.3 State

在 Pinia 中,state 被定义为一个返回初始状态的函数。

import { defineStore } from 'pinia'const useStore = defineStore('storeId', {// 为了完整类型推理,推荐使用箭头函数state: () => {return {// 所有这些属性都将自动推断出它们的类型count: 0,name: 'Eduardo',isAdmin: true,items: [],hasChanged: true,}},
})

20.4 Getter

Getter 完全等同于 store 的 state 的计算值。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数。

export const useCounterStore = defineStore('counter', {state: () => ({count: 0,}),getters: {doubleCount: (state) => state.count * 2,},
})
export const useUserListStore = defineStore('userList', {getters: {getUserById: (state) => {return (userId) => state.users.find((user) => user.id === userId)},},
})

20.5 Action

Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。

类似 getter,action 也可通过 this 访问整个 store 实例,不同的是,action 可以是异步的,可以在它们里面 await 调用任何 API,以及其他 action。

export const useCounterStore = defineStore('main', {state: () => ({count: 0,}),actions: {increment() {this.count++},randomizeCounter() {this.count = Math.round(100 * Math.random())},},
})
const api = mande('/api/users')export const useUsers = defineStore('users', {state: () => ({userData: null,// ...}),actions: {async registerUser(login, password) {try {this.userData = await api.post({ login, password })} catch (error) {return error}},},
})

20.6 持久化

持久化是指将应用状态保存在用户的浏览器中,即使在浏览器刷新或关闭后,状态也能被保留。

要在 Pinia 中实现状态的持久化,可以使用第三方插件 pinia-plugin-persist。这个插件允许状态自动保存到浏览器的存储系统中,并在应用加载时重新加载这些状态。

使用步骤:

1)安装插件

npm install pinia-plugin-persist

2)创建 Pinia Store

// store.js
import { defineStore } from 'pinia'export const useMainStore = defineStore('main', {state: () => ({counter: 0}),actions: {increment() {this.counter++;}}
});

3)启用持久化

在store 中启用持久化。可以选择哪些状态需要被持久化,以及使用哪种存储方式(例如 localStorage 或 sessionStorage)。

// store.js
import { defineStore } from "pinia";export const useMainStore = defineStore("main", {state: () => {return {counter: 0,};},actions: {increment() {this.counter++;},},persist: {enabled: true,strategies: [{key: "my-main-store",storage: localStorage, // 或者 sessionStorage},],},
});

4)在 Vue 应用中使用 Pinia 和持久化插件

在 Vue 应用入口文件中,安装 Pinia 并确保持久化插件被正确加载。

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import PiniaPersist from "pinia-plugin-persist";
import './style.css'
import App from './App.vue'
const pinia = createPinia()
pinia.use(PiniaPersist);
createApp(App).use(pinia).mount('#app')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/2684.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android SDK Manager安装Google Play Intel x86 Atom_64 System Image依赖问题

Package Google Play Intel x86 Atom_64 System Image,Android API R, revision 2 depends on SDK Platform Android R Preview, revision 2 问题 一开始以为网络还有依赖包没有勾选&#xff0c;尝试了很多次&#xff0c;勾选这边报错对应的license即可。此时点击一下其他licen…

CountDownLatch使用错误+未最终断开连接导致线程池资源耗尽

错误描述&#xff1a; 我设置了CountDownLatch对线程的协作做出了一些限制&#xff0c;但是我发现运行一段时间以后便发现定时任务不运行了。 具体代码&#xff1a; public void sendToCertainWeb() throws IOException, InterruptedException {List<String> urlList …

如何利用美国站群服务器实现有效的SEO优化策略?

如何利用美国站群服务器实现有效的SEO优化策略? 在当今数字化时代&#xff0c;SEO优化对于网站的可见性和吸引力至关重要。站群服务器作为一种有效的SEO策略&#xff0c;可以通过多个相关联的网站在不同服务器上的部署&#xff0c;增强网站的权威性和链接多样性。尤其是在利用…

ZYNQ之嵌入式开发04——自定义IP核实现呼吸灯、固化程序

文章目录 自定义IP核——呼吸灯实验固化程序 自定义IP核——呼吸灯实验 Xilinx官方提供了很多IP核&#xff0c;在Vivado的IP Catalog中可以查看这些IP核&#xff0c;在构建自己复杂的系统时&#xff0c;只使用Xilinx官方的免费IP核一般满足不了设计的要求&#xff0c;因此很多…

机器人自动驾驶时间同步进阶

0. 简介 之前时间同步也写过一篇文章介绍机器人&自动驾驶中的时间同步。在最近的学习中发现一些额外需要阐述学习的内容&#xff0c;这里就再次写一些之前没写到的内容。 1. NTP NTP 是网络时间协议&#xff0c;用来同步网络中各计算机时间的协议&#xff0c;把计算机的时…

品牌差异化战略:Kompas.ai如何打造独特的内容声音

在当今竞争激烈的商业环境中&#xff0c;品牌差异化已成为企业获取市场优势的关键策略。一个鲜明的品牌形象和独特的内容声音不仅能够帮助企业吸引目标客户&#xff0c;还能够在消费者心中建立起独特的地位。本文将深入探讨品牌差异化的重要性&#xff0c;分析Kompas.ai如何帮助…

揭秘 IDM:下载管理大师的全面指南与实用技巧深度解析

IDM&#xff08;Internet Download Manager&#xff09;是一款流行的下载管理软件&#xff0c;它可以帮助用户以更快的速度下载文件&#xff0c;并且支持多种协议和浏览器。IDM 通过将大文件分割成多个部分并同时下载这些部分来加快下载速度&#xff0c;这种技术被称为多线程下…

Linux中手工创建一个用户

当我们需要新创建一个用户时&#xff0c;有两种方法 1&#xff0c;使用useradd命令&#xff1a; [rootlocalhost /]# useradd tmg 然后给它设置一个密码 [rootlocalhost etc]# passwd tmg Changing password for user tmg. New password: BAD PASSWORD: The password is a…

通过 USB 或网络控制安卓设备:无需 root 权限 | 开源日报 No.231

barry-ran/QtScrcpy Stars: 15.6k License: Apache-2.0 QtScrcpy 是一款支持通过 USB 或网络显示和控制 Android 设备的实时显示控制软件&#xff0c;不需要 root 权限。 其主要功能和优势包括&#xff1a; 轻量化&#xff08;仅显示设备屏幕&#xff09;高性能&#xff08;3…

RAG:智能图书馆员引领AI的知识之旅

想象一下&#xff0c;你是一个法官&#xff0c;面对一个复杂的案件&#xff0c;你需要查阅大量的法律文献来找到相关的案例和法律条文。在AI的世界里&#xff0c;也有一个类似的“法官”——大型语言模型&#xff08;LLMs&#xff09;。它们能够回答各种问题&#xff0c;但有时…

vue 表格获取当前行索引,加颜色

vue 表格获取当前行索引&#xff0c;加颜色 <span styledisplay:inline-block;width:10px;height:10px;border-radius:50% :style"{background:color[scope.$index]}" />//定义颜色color: [#5387F7, #A794E0, #F3543C, #999999, #77D3F8, #FFA1B4, #26CEBA, #…

C++从入门到精通——C++动态内存管理

C动态内存管理 前言一、C/C内存分布分类1分类2题目选择题sizeof 和 strlen 区别示例sizeofstrlen 二、C语言中动态内存管理方式malloc/calloc/realloc/free示例例题malloc/calloc/realloc的区别malloc的实现原理 三、C内存管理方式new/delete操作内置类型new和delete操作自定义…

vue3 引入@tsparticles/vue3和@tsparticles/slim 实现粒子特效

1.安装&#xff1a; yarn add tsparticles/vue3 tsparticles/slim2.main.ts 引入 import Particles from "tsparticles/vue3"; import { loadSlim } from "tsparticles/slim";app.use(Particles as any, {init: async (engine: any) > {await loadSli…

力扣刷题 70.爬楼梯

题干 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2. 2 阶 示例 2&…

电子邮件免费版有哪些?免费注册电子邮箱

电子邮件有付费版和免费版两种类型&#xff0c;付费版通常具有更大的电子邮箱容量和更强大的电子邮箱功能。但是对于我们个人用户或者是中小型企业来说注册电子邮箱免费版的就够日常使用了。电子邮件的免费版提供商有Zoho Mail、微软、腾讯等&#xff0c;今天我们就来具体了解下…

Django中的实时通信:WebSockets与异步视图的结合

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在现代Web应用程序中&#xff0c;实时通信已经成为了必不可少的功能之一。无论是在线聊天、…

光纤、以太网电缆和 DSL 的比较:技术指南

了解光纤、以太网电缆和 DSL 之间的差异对于做出有关互联网连接的明智决策至关重要。本技术指南对这些技术进行了全面比较&#xff0c;讨论了它们的独特功能、性能指标和应用。它旨在为您提供必要的知识&#xff0c;以选择最适合您的特定需求的选项。 光纤、以太网电缆和 DSL …

【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序(万字博文)

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 目录 …

【C++】优先队列

优先队结构的不同物理结构与常用操作算法 优先队列是一种特殊的队列,队列中的元素具有优先级,每次弹出操作会弹出优先级最高的元素。 优先队列常用的物理结构有: 1. 数组:简单但不高效,插入和删除操作需要移动大量元素,时间复杂度高。 2. 二叉堆:是一种完全二叉树,通常用数…

Nacos采坑:非集群Nacos不要使用同一个MySQL数据库

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Nacos 致力于帮助您…