admin 管理员组文章数量: 887019
目录
一 、vue2
1.1 简介
1.2 插值表达式
1.3 创建实例
2、指令
2.1 v-html
2.2 v-show
2.3 v-if
2.4 v-else / v-else-if
2.5 v-on
2.6 v-bind
2.7 v-for
2.8 v-model
2.9 v-cloak
2.10 v-once
2.11 v-text
2.12 v-pre
2.13 自定义指令
3、基础api
3.1 computed:计算属性
3.2 watch:侦听器
4、生命周期
5、Vue CLI
5.1 简介
5.2 安装
5.3 脚手架文件目录说明
5.4 使用脚手架打包
5.5 vue.config.js 全局配置
6、组件
6.1 概念
6.2 组件注册及使用
6.3 组件通信
7、插槽
7.1 默认插槽
7.2 具名插槽
7.3 作用域插槽
8、mixin混入
9、vuex
9.1 搭建vuex环境
9.2 state & mapState
9.3 getters & mapGetters
9.4 mutation & mapMutations
9.5 action & mapActions
9.6 module
9.7 扩展
10、Vue Router(3)
10.1 基本概念
10.2 声明式导航
10.3 编程式导航
10.4 缓存路由组件
10.5 导航守卫
10.6 路由的两种模式
二、vue3
1、项目创建
1.1 基于webpack创建
1.2 基于vite创建
1.3 项目目录和文件说明
2、组合式API
2.1 setup
2.2 reactive & ref
2.3 toRefs & toRef
2.4 computed
2.5 watch & watchEffect
2.6 模版引用-ref
2.7 其他
3、生命周期
4、vue-router(4)
5 、pinia
5.1 概念及使用
5.2 持久化
5.3 模块化案例
6、组件通信
6.1 父传子
6.2 子传父
6.3 跨层通信
6.4 v-model
三、免责声明
一 、vue2
1.1 简介
vue是一个用于构建用户界面的渐进式框架。
1.2 插值表达式
概念:插值表达式是一种Vue的模板语法。
语法:{{表达式}}。
注意:
①使用的数据必须存储在(data);
②支持的是表达式,而非语句,比如:if for ...
③不能在标签属性中使用{{ }}插值
1.3 创建实例
步骤:
1.准备容器
2.引包(官网)-开发版本/生产版本
3.创建Vue实例 new Vue()
4.指定配置项→渲染数据,el指定挂载点,data提供数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--
创建Vue实例,初始化渲染
1.准备容器(Vue所管理的范围)
2.引包(开发/生产)
3.创建实例
4.添加配置项=>完成渲染
-->
<div id="app">
<!-- 这里将来会编写一些用于渲染的代码逻辑 -->
{{msg}}
</div>
<!-- 引入的是开发版本包-包含完整的注释和警告 -->
<script src="https://cdn.jsdelivr/npm/vue@2/dist/vue.js"></script>
<script>
//一旦引入VueJs核心包,在全局环境,就有了Vue构造函数
const app = new Vue({
// 通过el配置选择器,指定Vue管理的是哪个盒子
el: '#app',
//通过data提供数据
data: {
msg: 'Hello vue'
}
})
</script>
</body>
</html>
2、指令
2.1 v-html
作用:设置元素的 innerHTML。
语法:v-html = "表达式 "。
2.2 v-show
作用: 控制元素显示隐藏。
语法: v-show = "表达式" ;表达式值 true 显示, false 隐藏。
原理: 切换 display:none 控制显示隐藏。
使用场景: 频繁切换显示隐藏的场景。
2.3 v-if
作用: 控制元素显示隐藏(条件渲染)。
语法: v-if = "表达式" 表达式值 true 显示, false 隐藏。
原理: 基于条件判断,是否 创建 或 移除 元素节点(即控制dom操作)。
使用场景:不频繁切换的场景。
2.4 v-else / v-else-if
作用: 辅助 v-if 进行判断渲染。
语法: v-else 或 v-else-if = "表达式"。
注意: 需要紧挨着 v-if 一起使用。
2.5 v-on
作用: 注册事件 = 添加监听 + 提供处理逻辑。
语法:
① v-on:事件名 = "内联语句";
② v-on:事件名 = "methods中的函数名";调用传参eg:@click="fn(参数1,参数2)"。
简写:@事件名
注意:methods函数内的 this 指向 Vue 实例
常用指令修饰符:
①按键修饰符:@keyup.enter → 键盘回车监听;
②事件修饰符:@事件名.stop → 阻止冒泡;@事件名.prevent → 阻止默认行为
Tips:
1.事件修饰符可以结合使用,eg:@click.prevnt.stop → 先停止默认事件再停止冒泡
2.键盘事件名称结合使用,eg:@keyup.ctrl.y → 同时按下ctrl和y才触发事件
2.6 v-bind
作用: 动态的设置html的标签属性 → src url title ...
语法: v-bind:属性名="表达式"
简写形式 ::属性名="表达式"
对于样式控制的增强:
①操作class
语法 :class = "对象/数组"
对象 : 键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类;适用场景:一个类名,来回切换
<div class="box" :class="{类名1:布尔值,类名2:布尔值}"></div>
数组 :数组中所有的类,都会添加到盒子上,本质就是一个 class 列表;适用场景:批量添加或删除类
<div class="box" :class="[类名1,类名2]"></div>
②操作style:语法 :style = "样式对象"
<div class="box" :style="{ width: '400px', height: '400px' }"></div>
2.7 v-for
作用: 基于数据循环, 多次渲染整个元素 → 数组、对象、数字...
语法:v-for = "(item, index) in 数组"
v-for 中的 key:
语法:key属性 = "唯一标识";
作用:给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用。
v-for 的默认行为会尝试 原地修改元素 (就地复用)
2.8 v-model
作用: 给 表单元素 使用, 双向数据绑定 → 可以快速 获取 或 设置 表单元素内容
语法: v-model = '变量'
原理:
<input type="text" :value="变量" @input="变量=$event.target.value" >
修饰符:v-model.trim → 去除首尾空格;v-model.number → 转数字;v-model.lazy → 失去焦点再收集数据
2.9 v-cloak
作用:使用css配合v-cloak
可以解决网速慢时页面展示出{{xxx}}
的问题,无值
2.10 v-once
作用:v-once
所在节点在初次动态渲染后,视为静态内容
2.11 v-text
作用:向其所在的节点中渲染文本内容
2.12 v-pre
作用:跳过其所在节点的编译
2.13 自定义指令
概念:自己定义的指令, 可以封装一些 dom 操作, 扩展额外功能。
全局注册(main.js):
// 1. 全局注册指令
Vue.directive('指令名', {
// inserted 会在 指令所在的元素,被插入到页面中时触发
inserted (el) {
// el 就是指令所绑定的元素,eg: el.focus(),获取焦点
el.focus()
}
})
局部注册(组件内注册):
// 2. 局部注册指令
directives: {
// 指令名:指令的配置项
指令名: {
inserted (el) {
// el 就是指令所绑定的元素,eg: el.focus(),获取焦点
el.focus()
}
}
}
使用:v-指令名。
指令的值:
语法:在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值 v-指令名= “ 值 ”。
通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数。
inserted:指令所在的dom元素,被插到页面时触发。
<template>
<div>
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
</div>
</template>
<script>
export default {
data () {
return {
color1: 'red',
color2: 'orange'
}
},
directives: {
color: {
// 1. inserted 提供的是元素被添加到页面中时的逻辑
inserted (el, binding) {
// console.log(el, binding.value);
// binding.value 就是指令的值
el.style.color = binding.value
},
// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑
update (el, binding) {
console.log('指令的值修改了');
el.style.color = binding.value
}
}
}
}
</script>
<style>
</style>
配置对象中常用的3个回调函数:
bind(element,binding)
:指令与元素成功绑定时调用
inserted(element,binding)
:指令所在元素被插入页面时调用
update(element,binding)
:指令所在模板结构被重新解析时调用
3、基础api
3.1 computed:计算属性
概念:基于现有的数据,计算出来的新属性。 计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 → 并再次缓存
基础语法:
computed: {
计算属性名 () {
基于现有数据,编写求值逻辑
return 结果
}
}
完整语法:
computed: {
计算属性名: {
get() {
// 计算逻辑
return 结果
},
set(修改的值) {
//修改逻辑
}
}
}
使用方法:在插值表达式中:{{ 计算属性名 }},在其他函数中通过this.计算属性名调用。
3.2 watch:侦听器
作用:监视数据变化,执行一些业务逻辑或异步操作
基础语法:
watch: {
// 该方法会在数据变化时调用执行
数据属性名(newValue,oldValue){
// 业务逻辑,异步操作
},
对象.属性名(newValue,oldValue){
// 业务逻辑,异步操作
}
}
完整写法:
watch:{
数据属性名:{
deep: true, // 深度监视
immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
handler (newValue){
业务逻辑
}
}
}
4、生命周期
概念:一个vue实例从创建到销毁的整个过程。
四个阶段:
创建:响应式处理→响应式数据
挂载:渲染模板
更新:数据修改,更新视图
销毁:销毁实例
生命周期函数(钩子函数):
Vue生命周期过程中,会自动运行一些函数,被称为生命周期钩子 → 让开发者可以在特定阶段运行自己的代码。
// 1. 创建阶段(准备数据)
beforeCreate () {
console.log('beforeCreate 响应式数据准备好之前')
},
created () {
console.log('created 响应式数据准备好之后')
// this.数据名 = 请求回来的数据
// 可以开始发送初始化渲染的请求了
},
// 2. 挂载阶段(渲染模板)
beforeMount () {
console.log('beforeMount 模板渲染之前')
},
mounted () {
console.log('mounted 模板渲染之后')
// 可以开始操作dom了
},
// 3. 更新阶段(修改数据 → 更新视图)
beforeUpdate () {
console.log('beforeUpdate 数据修改了,视图还没更新')
},
updated () {
console.log('updated 数据修改了,视图已经更新')
},
// 4. 卸载阶段
beforeDestroy () {
console.log('beforeDestroy, 卸载前')
console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
},
destroyed () {
console.log('destroyed,卸载后')
}
5、Vue CLI
5.1 简介
概念:Vue CLI(脚手架)是官方提供的一个全局命令工具。可以帮助我们快速创建一个开发Vue项目的标准化基础架子(集成了webpack)。
5.2 安装
① 全局安装 (一次) :yarn global add @vue/cli 或 npm i @vue/cli -g
② 查看 Vue CLI 版本:vue --version
③ 创建项目架子:vue create project-name(项目名-不能用中文)
④ 启动项目: yarn serve 或 npm run serve(默认serve→package.json配置)
Tops:推荐自定义创建项目。
5.3 脚手架文件目录说明
dist:项目打包生成的文件
node_modules:项目依赖文件
public:一般放置一些静态资源(图片),需要注意,放在public文件夹中的静态资源,webpack进行打包的时候会原封不动打包到dist文件夹中
src:程序源代码文件夹
api:ajax请求
assets:一般也放置静态资源(一般放置多个组件共用的静态资源),需要注意的是,放置在此文件夹中,webpack打包时,会把静态资源当成一个模块,打包js文件里面
components:一般放置的是非路由组件(全局组件)
mixins:混入
router:路由模块化文件夹
store:vuex
styles:css/less样式文件
utils:存放自己封装的方法
pages | views:一般放置路由组件
App.vue:唯一根组件
main.js:程序入口文件,也是整个程序当中最先执行的文件
babel.config.js:配置文件(babel)语法降级
package.json:项目记录文件,记录项目叫什么,有哪些依赖,项目怎么运行
package-lock.json:缓存性文件
README.md:说明性文件
Tops:非标准目录,基于项目而异。
5.4 使用脚手架打包
作用: ① 将多个文件压缩合并成一个文件 ② 语法降级 ③ less sass ts 语法解析 ④ .... 命令:npm run build 结果:在项目的根目录会自动创建一个文件夹`dist`, dist中的文件就是打包后的文件,只需要放到服务器中即可。 配置:默认情况下,需要放到服务器根目录打开,如果希望双击运行,需要配置publicPath 配成相对路径// vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
publicPath: './', // 配置publicPath
transpileDependencies: true
})
5.5 vue.config.js 全局配置
全局配置官网:配置参考 | Vue CLI
常见配置1:
module.exports = {
publicPath:'./', // 公共,基本路径
// 输出文件目录,不同的环境打不同包名
outputDir: process.env.NODE_ENV === "development" ? 'devdist' : 'dist',
assetsDir: 'static', // 默认会在目录同时生成三个静态目录:js,css,img
lintOnSave: false, // 关闭eslint代码检查
filenameHashing: false, // 生成的静态资源名, 默认加了hash, 命名.后面的为hash:chunk-2d0aecf8.71e621e9
productionSourceMap:false, // 生产环境下css 分离文件, sourceMap 文件
// css: {
// extract: true, // 是否使用css分离插件 ExtractTextPlugin
// sourceMap: false, // 开启 CSS source maps
// modules: false, // 启用 CSS modules for all css / pre-processor files.
// // css 预设器配置项
// loaderOptions: {
// sass: {
// data: `@import "./src/assets/hotcss/px2rem.scss";`
// }
// }
// },
devServer: {
port:8089,
host: "localhost", // 0.0.0.0
open: true, // 配置自动启动浏览器
https: false,
hotOnly: false,
overlay: {
warnings: true,
errors: true
},
// 配置代理,解决跨域的问题, 只有一个代理
// proxy: null,
// proxy: 'http://api.mc',
// proxy: {
// "/api": {
// target: "http://api.mc",
// changeOrigin: true
// },
// "/foo": {
// target: ""
// }
// },
before: app => {}, // 第三方插件
}
}
配置2:
module.exports = {
publicPath: "./", // 公共路径 默认为"/",建议使用"./"相对路径
devServer: { // 本地服务器配置(npm run serve)
port: 8080, // 端口
host: "localhost", // 域名
https: false, // 是否开启https
open: true // 是否在开启服务器后自动打开浏览器访问该服务器
},
lintOnSave: false, // 取消lint语法检测,此处可不配置
outputDir:"dist", // build打包输出目录
assetsDir:"assets", // 静态文件输出目录,基于dist
indexPath: "index.html", // 输出html文件名
productionSourceMap: false, // 取消.map文件的打包,加快打包速度
configureWebpack: (config) => {
// process.env为环境变量,分别对应.env.development文件和.env.production文件 此处表示加快开发环境打包速度
if (process.env.NODE_ENV !== 'production') return;
config.optimization.minimizer[0].options.terserOptionspress.drop_console = true; //生产环境去掉console.log
return { // 此处配置webpack.config.js的相关配置
plugins: [],
performance: {}
};
}
};
完整配置:
// build时构建文件的目录,构建时传入 --no-clean 可关闭该行为
outputDir: 'dist', // 当运行 vue-cli-service build 时生成的生产环境构建文件的目录
// build时放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录
assetsDir: '',
// 指定生成的 index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径。
indexPath: 'index.html',
// 默认在生成的静态资源文件名中包含hash以控制缓存
filenameHashing: true,
// 构建多页面应用,页面的配置
pages: {
index: {
// page 的入口
entry: 'src/index/main.js',
// 模板来源
template: 'public/index.html',
// 在 dist/index.html 的输出
filename: 'index.html',
// 当使用 title 选项时,
// template 中的 title 标签需要是
// <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Index Page',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// 当使用只有入口的字符串格式时,
// 模板会被推导为 `public/subpage.html`
// 并且如果找不到的话,就回退到 `public/index.html`。
// 输出文件名会被推导为 `subpage.html`。
subpage: 'src/subpage/main.js'
},
// 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码 (在生产构建时禁用 eslint-loader)
lintOnSave: process.env.NODE_ENV !== 'production',
// 是否使用包含运行时编译器的 Vue 构建版本
runtimeCompiler: false,
// Babel 显式转译列表
transpileDependencies: [],
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建
productionSourceMap: true,
// 设置生成的 HTML 中 <link rel="stylesheet"> 和 <script> 标签的 crossorigin 属性
// (注:仅影响构建时注入的标签)
crossorigin: '',
// 在生成的 HTML 中的<link rel="stylesheet">和<script>标签上启用 Subresource Integrity (SRI)
integrity: false,
// 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中
// 如果你需要基于环境有条件地配置行为,或者想要直接修改配置,那就换成一个函数 (该函数会在环境变量被设置之后懒执行)。该方法的第一个参数会收到已经解析好的配置。在函数内,你可以直接修改配置,或者返回一个将会被合并的对象
configureWebpack: {},
// 对内部的 webpack 配置(比如修改、增加Loader选项)(链式操作)
chainWebpack: () =>{
},
// css的处理
css: {
// 当为true时,css文件名可省略 module 默认为 false
modules: true,
// 是否将组件中的 CSS 提取至一个独立的 CSS 文件中,当作为一个库构建时,你也可以将其设置为 false 免得用户自己导入 CSS
// 默认生产环境下是 true,开发环境下是 false
extract: false,
// 是否为 CSS 开启 source map。设置为 true 之后可能会影响构建的性能
sourceMap: false,
//向 CSS 相关的 loader 传递选项(支持 css-loader postcss-loader sass-loader less-loader stylus-loader)
loaderOptions: {
css: {},
less: {}
}
},
// 所有 webpack-dev-server 的选项都支持
devServer: {
open: true, // 设置浏览器自动打开项目
port: 8888, // 设置端口
proxy: { // 设置代理
'/api': {
target: 'http://101.15.22.98',
changeOrigin: true // 开启跨域
// secure: true, // 如果是https接口,需要配置这个参数
}
}
},
// 是否为 Babel 或 TypeScript 使用 thread-loader
parallel: require('os').cpus().length > 1,
// 向 PWA 插件传递选项
pwa: {},
// 可以用来传递任何第三方插件选项
pluginOptions: {}
6、组件
6.1 概念
组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
分类:普通组件,根组件(App.vue)
组件组成:
template:结构 (有且只能一个根元素)
script:js逻辑 :vue 规定 .vue 组件中的 data 必须是函数需return
style:样式; scoped关键字防止样式冲突(原理:添加自定义属性data-v-hash,属性选择器);css预处理语言须装包,style标签添加<style lang="less" scoped>,这里css预处理语言为less。
6.2 组件注册及使用
局部注册:只能在注册的组件内使用
① 在components文件夹创建 .vue 文件 (三个组成部分),eg:TouHeader
② 在使用的组件内(APP.vue)导入并注册
③ 使用:当成 html 标签使用 <TouHeader></TouHeader>
④ 组件名规范 → 大驼峰命名法,如:TouHeader
全局注册:所有组件内都能使用,在main.js 中进行全局注册
// 导入components文件夹下的组件
import TodoHeader from "@/components/TodoHeader.vue";
//注册组件
components: { TodoHeader }
//使用组件
<template>
<TodoHeader></TodoHeader>
</template>
6.3 组件通信
组件通信的方式:
① props:用于父子组件通信;
② 自定义事件:@on , $emit 实现子给父通信;
③ 全局事件总线:$bus 全能;
④ pubsub-js:vue几乎不用 全能;
⑤ 插槽。
6.3.1 props:父组件 通过props向 子组件 传递数据
实现过程:
父组件
//父组件
<template>
<!-- 给子组件添加属性的方式传递数据 -->
<Son :sonMsg='Fmsg'></Son>
</template>
<script>
import Son from '@/components/Son.vue'
export default {
components:{ Son },
data(){
return{
Fmsg:'我是父组件,向子组件发送了这条信息'
}
}
}
</script>
<style lang="less" scoped>
</style>
子组件
//子组件
<template>
<div>我是子组件-这是父组件传递的数据-{{ sonMsg }}</div>
</template>
<script>
export default {
//子组件通过props接收
props:['sonMsg']
}
</script>
<style lang="less" scoped>
</style>
props 的校验写法
// 简单写法
props:{
校验的属性名:数据类型
}
// 完整写法
props:{
校验的属性名:{
type:数据类型,
required:true ,//是否必填
default:默认值,
validator(value){
//自定义校验逻辑
return 是否通过校验
}
}
}
6.3.2 自定义事件实现 子组件 向 父组件 传递信息
实现过程:
子组件
//子组件
<template>
<div @click="sendFa"></div>
</template>
<script>
export default {
methods:{
sendFa(){
//$emit触发事件,向父组件发送数据
this.$emit('receiveSon','子向父传递的数据')
}
}
}
</script>
<style lang="less" scoped>
</style>
父组件
//父组件
<template>
<Son @receiveSon='handler'></Son>
</template>
<script>
import Son from '@/components/Son.vue'
export default {
components:{Son},
methods:{
//提供处理函数,形参中获取数据
handler(sonMsg){
console.log(sonMsg) //子向父传递的数据
}
}
}
</script>
<style lang="less" scoped>
</style>
6.3.3 sync 修饰符 实现父子通信数据的双向绑定
本质: :属性名 和 @update:属性名 合写
父组件
//父组件
<template>
<Son :visible.sync='isshow'></Son>
<!-- <Son :isShow="isShow" @update:isShow="isShow=$event"></Son> -->
</template>
<script>
import Son from'@/components/son'
export default {
data() {
return {
isShow: false,
}
},
components:{ Son }
}
</script>
<style lang="less" scoped>
</style>
子组件
//子组件
<template>
<button class="close" @click="close">x</button>
</template>
<script>
export default {
props: {
isShow: Boolean,
},
methods:{
close(){
this.$emit('update:isShow',false)
}
}
}
</script>
<style lang="less" scoped>
</style>
6.3.4 ref 和 $refs
作用:利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例
特点:查找范围 → 当前组件内 (更精确稳定)
获取 dom 元素:
① 目标标签 – 添加 ref 属性
<div ref="domDiv"></div>
② this.$refs.xxx, 获取目标标签
console.log(this.$refs.domDiv)
获取 组件实例 同上,用 点 “.” 操作符可以调用组件对象里面的方法。
6.3.5 $nextTick
$nextTick:等 DOM 更新后, 才会触发执行此方法里的函数体。
语法: this.$nextTick(回调函数)
7、插槽
7.1 默认插槽
作用:让组件内部的一些 结构 支持 自定义
语法:
① 组件内需要定制的结构部分,改用<slot></slot>占位;
② 使用组件时, 传入结构替换slot;
③ 后备内容,在 <slot> 标签内,放置内容, 作为默认显示内容。
子组件
<template>
<div>
<!-- 在需要定制的位置,使用slot占位 -->
<!-- 往slot标签内部,编写内容,可以作为后备内容(默认值) -->
<slot>我是默认的文本内容</slot>
</div>
</template>
父组件
<template>
<div>
<!-- 在使用组件时,组件标签内填入内容 -->
<Son>
<div> 我 没 k </div>
</Son>
</div>
</template>
<script>
import Son from '@/components/Son.vue'
export default {
data () {
return {}
},
components: {
Son
}
}
</script>
<style>
</style>
7.2 具名插槽
顾名思义,插槽起名,定向分发
父组件
<template>
<div>
<Son>
<!-- 需要通过template标签包裹需要分发的结构,包成一个整体 -->
<template v-slot:head>
<div>我是大标题</div>
</template>
<template v-slot:content>
<div>我是内容</div>
</template>
<!-- v-slot:插槽名 简化 #插槽名 -->
<template #footer>
<button>取消</button>
<button>确认</button>
</template>
</Son>
</div>
</template>
<script>
import Son from '@/components/Son.vue'
export default {
components: {
Son
}
}
</script>
子组件
<template>
<div>
<div class="header">
<!-- 一旦插槽起了名字,就是具名插槽,只支持定向分发 -->
<slot name="head"></slot>
</div>
<div class="content">
<slot name="content"></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
</template>
7.3 作用域插槽
作用:利用插槽传值
使用步骤:
① 给 slot 标签, 以 添加属性的方式 传值
② 所有添加的属性, 都会被收集到一个对象中
③ 在template中, 通过 ` #插槽名= "obj" ` 接收,默认插槽名为 default。
8、mixin混入
概念:混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
在 mixins/test.js 中定义混入:
export default {
methods: {
hello() {
console.log('hello from mixin!')
}
}
}
注意:
- 此处编写的就是Vue组件实例的配置项,通过一定的语法,可以直接混入到组件的内部
- data methods computed 生命周期函数 ...
- 如果此处 和 组件内,提供了同名的data 或 methods,则组件内优先级更高
- 如果编写了生命周期函数,则mixins中的生命周期函数 和 页面的生命周期函数,会用数组管理统一执行
在 组件 中调用:
import test from '@/mixins/test'
export default {
mixins: [test],
created () {
this.hello() //调用hello
}
}
9、vuex
9.1 搭建vuex环境
在Vue CLI中选择vuex,在store/index.js中
import Vue from 'vue'
import Vuex from 'vuex'
// import xxx from 'xxx' 引入其他模块的数据
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 要存储的数据
},
getters: {
// 类似计算属性
},
mutations: {
// 修改state的唯一方法
},
actions: {
// 业务逻辑,异步代码
},
modules: {
// 其他模块数据
}
})
9.2 state & mapState
通过store获取state:
①在组件模板({{ }})中获取:$store.state.xxx
②在组件逻辑中获取:this.$store.state.xxx
③在store模块中获取:store.state.xxx
在组件中通过mapState获取state:
<template>
<div class="box">
{{count,user}}
</div>
</template>
<script>
import { mapState} from 'vuex'
export default {
computed: {
...mapState(['count', 'user']),
}
}
</script>
9.3 getters & mapGetters
getters 定义:
// getters 类似于计算属性
getters: {
// 注意点:
// 1. 形参第一个参数,就是state
// 2. 必须有返回值,返回值就是getters的值
filterList (state) {
return state.list.filter(item => item > 5)
}
}
通过store获取 getters 中的状态:
①在组件模板({{ }})中获取:$store.getters.xxx
②在组件逻辑中获取:this.$store.getters.xxx
③在store模块中获取:store.getters.xxx
在组件中通过mapGetters获取 getters 中的状态:
<template>
<div class="box">
{{count,user}}
</div>
</template>
<script>
import { mapGetters} from 'vuex'
export default {
computed: {
...mapGetters(['count', 'user']),
}
}
</script>
9.4 mutation & mapMutations
mutation 定义:
//通过 mutations 可以提供修改state中数据的方法
mutations: {
// 所有mutation函数,第一个参数,都是 state
// 注意点:mutation参数有且只能有一个,如果需要多个参数,包装成一个对象
addCount (state, n) {
// 修改数据
state.count += n
}
}
在 store 中调用 mutation 中的函数:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 100
},
mutations: {
addCount (state, n) {
state.count += n
}
},
actions: {
// context 上下文 (此处未分模块,可以当成store仓库)
// contextmit('mutation名字', 额外参数)
changeCountAction (context) {
// 这里是setTimeout模拟异步,以后大部分场景是发请求
setTimeout(() => {
contextmit('changeCount', 10)
}, 1000)
},
// 解构写法
changeCountAction ({commit}) {
setTimeout(() => {
commit('changeCount', 10)
}, 1000)
}
}
})
export default store
在 组件 中调用 mutation 中的函数:
通过 store :this.$storemit('addCount', 10)
<template>
<div class="box">
<button @click="fn"></button>
</div>
</template>
<script>
export default {
methods: {
fn(){
this.$storemit('addCount', 10)
}
}
</script>
通过 mapMutations 辅助函数:
<template>
<div class="box">
<button @click="addCount(10)"></button>
</div>
</template>
<script>
import { mapMutations} from 'vuex'
export default {
methods: {
...mapMutations(['addCount'])
}
}
</script>
9.5 action & mapActions
假设 action 中的异步函数:changeCountAction
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 100
},
mutations: {
addCount (state, n) {
state.count += n
}
},
actions: {
changeCountAction ({commit},num) {
setTimeout(() => {
commit('changeCount', num)
}, 1000)
}
}
})
export default store
在 store 中调用 action 中的异步函数:store.dispatch('changeCountAction',10)
在 组件 中调用 action 中的异步函数:
①通过 store :this.$store.dispatch('changeCountAction',10)
②通过 mapActions:
<template>
<div class="box">
<button @click="changeCountAction(10)"></button>
</div>
</template>
<script>
import { mapActions} from 'vuex'
export default {
methods: {
...mapActions(['changeCountAction'])
}
}
</script>
9.6 module
概述:Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。注意:此时模块内的state处于局部状态;mutation、action、getter依然处于全局状态。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state(){
return { ... }
},
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
命名空间:通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
user模块:store/modules/user.js
// user模块
const state = {} // 分模块后,state指代子模块的state
const mutations = {}
const actions = {}
const getters = {}
export default {
namespaced: true,//开启命名空间
state,
mutations,
actions,
getters
}
在 store 中注册:store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user' //导入user模块
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store({
// 注册user模块
modules: {
user
}
})
export default store
此时在 模块内 访问自己的 state、mutation、action、getter方法与 8.2 ~ 8.5 相同。
在 组件中 访问(xxx为模块中定义的变量或方法):
①在组件模板({{ }})中获取:
// state
$store.state.模块名.xxx
// getter
$store.getters['模块名/xxx ']
// mutation
$storemit('模块名/xxx ', 额外参数)
// action
$store.dispatch('模块名/xxx ', 额外参数)
②在组件逻辑中获取:
// state
this.$store.state.模块名.xxx
// getter
this.$store.getters['模块名/xxx ']
// mutation
this.$storemit('模块名/xxx ', 额外参数)
// action
this.$store.dispatch('模块名/xxx ', 额外参数)
③通过辅助函数获取:
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapState('模块名', ['xxx']),
...mapGetters('模块名', ['xxx'])
},
methods: {
...mapMutations('模块名', ['xxx']),
...mapActions('模块名', ['xxx'])
}
}
</script>
Tips:如果不添加模块名则访问的是 全局属性 (store/index.js中的状态)
9.7 扩展
在vue3组合式api中访问vuex:
<script setup>
import { useStore } from 'vuex'
import { computed, ref } from 'vue'
const store = useStore()
// 通过computed函数访问state
const count= computed(() => store.state.moduleName.count)
// 或者 使用ref
// const count = ref(store.state.moduleName.count)
// 通过computed函数访问getter
const double= computed(() => store.getters.moduleName.double)
// 通过commit方法调用mutation
const increment= () => storemit('moduleName/increment',data)
// 通过dispatch方法调用action
const asyncIncrement= () => store.dispatch('moduleName/asyncIncrement',data)
</script>
使用辅助函数 :
<script setup>
import { computed } from 'vue'
import { useStore, mapState, mapGetters, mapMutations, mapActions } from 'vuex'
const $store = useStore()
// 使用 mapState 函数将 Vuex 中的 state 映射到组件中
let { count } = mapState('moduleName', ['count'])
count = computed(count.bind({ $store }))
console.log(count.value)
// 使用 mapGetters 函数将 Vuex 中的 getters 映射到组件中
let { double } = mapGetters('moduleName', ['double'])
double = computed(double.bind({ $store }))
console.log(double.value)
// 使用 mapMutations 函数将 Vuex 中的 mutations 映射到组件中
const { increment } = mapMutations('moduleName', ['increment'])
// ...
// 使用 mapActions 函数将 Vuex 中的 actions 映射到组件中
const { fetchData } = mapActions('moduleName', ['fetchData'])
// ...
</script>
vuex持久化插件:vuex-persistedstate官网:vuex-persistedstate - npm
10、Vue Router(3)
10.1 基本概念
①vue中路由的作用:简单理解就是切换页面的作用(局部刷新)
②创建及配置 router:建议脚手架环境直接创建,在 routes: [ ] 中配置
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入路由组件
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/', // 路径
name: 'home', // 命名
component: HomeView // 导入的路由组件
},
{
path: '/about',
name: 'about',
// 路由懒加载:以函数形式导入路由组件
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
// :path: "*" (任意路径) 当路径找不到匹配时,给个提示页面
{
path: '*',
component: () => import('../views/NotFindView.vue')
}
]
const router = new VueRouter({
routes
})
export default router
③ router-link 与 router-view
router-link:本质是 a 标签,,配置 to 属性指定跳转路径(必须)。通过router-link全局组件实现跳转也叫声明式导航。
router-link 的 replace 属性:控制路由跳转时操作浏览器历史记录的模式
浏览器的历史记录有两种写入模式:分别为replace和push,push是追加历史记录,replace是替换当前记录,路由器跳转时候默认为push。
<router-link repalce to="">NewS</router-link>
router-view:路由出口,展示路由组件
在组件中:
<div id="app">
<h1>Hello App!</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link to="/">Go to Home</router-link>
<router-link to="/about">Go to About</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
④嵌套路由:
配置路由规则,使用children配置项:
const routes =[
{
path:'/about',
component:About,
},
{
path:
component:Home,
//通过children配置子路由
chilren:{
path:'news',//此处一定不要写:/new
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
}
}
]
跳转(要写完整路径):
<router-link to="home/news">News<router-link>
⑤命名路由
作用:可以简化路由的跳转
配置路由规则时,使用name配置项
const routes = [
{
path: '/user/:username',
name: 'user',
component: User,
},
]
跳转:
<div id="app">
<p>当前路由名字: {{ $route.name }}</p>
<ul>
<li><router-link :to="{ name: 'home' }">home</router-link></li>
<li><router-link :to="{ name: 'foo' }">foo</router-link></li>
<li><router-link :to="{ name: 'bar', params: { id: 123 }}">bar</router-link></li>
</ul>
<router-view></router-view>
</div>
⑥ 重定向
重定向也是通过 routes
配置来完成,下面例子是从 /home
重定向到 /
:
const routes = [{ path: '/home', redirect: '/' }]
重定向的目标也可以是一个命名的路由:
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
甚至是一个方法,动态返回重定向目标 ...
⑦路由元信息
将信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta
属性来实现,并且它可以在路由地址和导航守卫上都被访问到。定义路由的时候你可以这样配置 meta
字段:
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true },
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false },
}
]
}
]
页面获取meta:$route.meta.xxx
路由文件中获取meta:
router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: '/login',
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
})
10.2 声明式导航
①跳转传参 - query参数
语法格式1: to="/path?参数名=值"
<router-link to="/home?参数名1=值1&参数名2=值2">Go to Home</router-link>
语法格式2:对象写法
<router-link to="{
path:'/home',
query:{
参数1:值1,
参数2:值2
}
}">
Go to Home
</router-link>
在页面中获取query参数:{{ $route.query.xxx }} 或 this.$route.query.xxx
②跳转传参 - params参数
路由配置:
const routes = [
// 动态字段以冒号开始
{ path: '/users/:id', component: User },
{ path: '/home/:one/:two', component: Home },
// 如果不传参数,也希望匹配,可以加个可选符 "?"
{ path: '/my/:a?', component: My }
]
配置router-link:
语法1:to="/path/参数值"
<router-link to="/users/值1">Go to Home</router-link>
语法2:对象写法
注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置
<router-link to="{
name:'home',
params:{
one:值1,
two:值2
}
}">
Go to Home
</router-link>
在页面中获取params参数:{{ $route.params.xxx }} 或 this.$route.params.xxx
10.3 编程式导航
作用:不借助<router-link>实现路由跳转,让路由器跳转更加灵活
使用:this.$router.push() 或 $router.push()
// 字符串路径-跳转传参 - params参数-{ path: '/users/:xxx',... }
this.$router.push('/users/eduardo')
// 带有路径的对象-跳转传参 - params参数-{ path: '/users/:xxx',... }
this.$router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
this.$router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
this.$router.push({ path: '/register', query: { plan: 'private' } })
/********************************************************************/
const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
this.$router.push(`/user/${username}`) // -> /user/eduardo
// 同样
this.$router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
this.$router.push({ name: 'user', params: { username } }) // -> /user/eduardo
tip:`params` 不能与 `path` 一起使用即:router.push({ path: '/user', params: { username } }) // -> /user
在页面中获取参数同 9.2
$router.replace():在导航时不会向 history 添加新记录,替换当前位置,用法同$router.push()
Tip:
// 向前移动一条记录,与 router.forward() 相同
this.$router.go(1)
// 返回一条记录,与 router.back() 相同
this.$router.go(-1)
10.4 缓存路由组件
作用:让不展示的路由组件保存挂载,不被销毁
使用keep-alive内置组件实现:
<keep-alive>
<router-view></router-view>
</keep-alive>
keep-alive的三个属性 :
① include : 组件名数组,只有匹配的组件会被缓存
② exclude : 组件名数组,任何匹配的组件都不会被缓存
③ max : 最多可以缓存多少组件实例
keep-alive的使用会触发两个生命周期函数:
① activated 当组件被激活(使用)的时候触发 → 进入页面触发
② deactivated 当组件不被使用的时候触发 → 离开页面触发
10.5 导航守卫
1、全局前置守卫
调用时机:初始化的时候被调用,每次路由切换之前被调用
场景:常用于登录拦截
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
to
: 即将要进入的目标
from
: 当前导航正要离开的路由
false
: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from
路由对应的地址。
2个常用登录拦截案例:
import store from '@/store'
// 定义一个数组,专门存放所有需要权限访问的页面
const authUrls = ['/pay', '/myorder']
// 全局前置导航守卫
router.beforeEach((to, from, next) => {
// 看 to.path是否在authUrls中出现过
if (!authUrls.includes(to.path)) {
// 非权限页面直接放行
next()
return
}
// 是权限页面,需要判断token
const token = store.getters.token
token ? next() : next('/login')
})
import { useUserStore } from '@/stores'
router.beforeEach((to) => {
// 如果没有token,且访问的是非登录页,拦截到登录,其他情况正常放行
const useStore = useUserStore()
if (!useStore.token && to.path !== '/login') {
return '/login'
// return { name: 'Login' }
}
})
2、全局后置守卫
调用时机:初始化的时候被调用,每次路由切换之后被调用
场景:分析、更改页面标题、声明页面
router.afterEach((to, from) => {
if(to.meta.title){
docment.title=to.mete.title //修改网页的title
}
else{
docment.title='默认标题'
}
})
3、路由独享的守卫
可以直接在路由配置上定义 beforeEnter
守卫:
beforeEnter
守卫 只在进入路由时触发
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
4、组件内的守卫
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
5、全局解析守卫
你可以用 router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。这里有一个例子,确保用户可以访问自定义 meta 属性 requiresCamera
的路由:
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
6、完整的路由导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫(2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
10.6 路由的两种模式
1.对于一个url来说,什么是hash值-----#及其后面的内容就是hash值
2.hash值不会包含在http请求中,即hash值不会给服务器。
3.hash模式:
3.1地址中永远带着#号,不美观
3.2若以后将地址通过第三方手机app分析,若app校验严格,则地址会被标记不合法
3.3兼容性较好,#后面的地址变化不会引起页面的刷新
4.history模式
4.1地址感觉,美观,地址变化会引起页面刷新,更符合页面地址的规范(开发环境不刷新-webpack配置)
4.2兼容性和hash模式相比略差
4.3应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
const router = new VueRouter({
mode:"hash", // hash模式
mode:"history", // history模式
routes: []
})
二、vue3
1、项目创建
1.1 基于webpack创建
创建方式见vue2中Vue CLI,创建选项选vue3即可,命令:vue create xxx。
1.2 基于vite创建
-
前提条件: 已安装 18.0 或更高版本的 Node.js,查看版本:node -v
-
创建项目:npm create vue@latest
-
启动项目:npm run dev
-
项目打包:npm run build
Tips:npm create vue@latest这一指令将会安装并执行 create-vue,create-vue是Vue官方新的脚手架工具,底层切换到了 vite,官网:开始 | Vite 官方中文文档。
1.3 项目目录和文件说明
- vite.config.js - 项目的配置文件 基于vite的配置
- package.json - 项目包文件 核心依赖项变成了 Vue3.x 和 vite
- main.js - 入口文件 createApp函数创建应用实例
- app.vue - 根组件 SFC单文件组件 script - template - style
- index.html - 单页入口 提供id为app的挂载点
2、组合式API
2.1 setup
概念:setup是Vue3中一个新的配置项,值是一个函数,它是 Composition API ,组件中所用到的:数据、方法、计算属性、监视......等等,均配置在setup中。
特点:
- setup函数返回的对象中的内容,可直接在模板中使用,若返回一个函数,则可以自定义渲染内容。
- setup中访问 this 指向 undefined。
- setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的
-
语法糖:setup可以独立出去,即<script setup >
Tips:vue3 中组件名字默认为文件名,如需指定组件名字(不推荐)有两种方式:方式一:再编写一个不写setup的script标签,去指定组件名字;方式二:使用defineOptions编译宏添加。
<script setup>
defineOptions({
name: 'loginINdex'
})
</script>
2.2 reactive & ref
reactive :接受对象类型数据的参数传入并返回一个响应式的对象。
使用:
<!-- 模板中使用 -->
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<script setup>
import { reactive } from 'vue'
// 定义
let person = reactive({ name: '张三', age: 18 })
// 修改
function changeAge() {
person.age += 1
}
</script>
ref:接收简单类型或者对象类型的数据传入并返回一个响应式的对象。
使用:
<!-- 模板中使用 -->
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>性别:{{gender}}</h2>
<script setup>
import { ref } from 'vue'
// 定义
let person = ref({ name: '张三', age: 18 })
let gender = ref('男')
// 修改
function changeAge() {
person.value.age += 1
}
</script>
区别:
- ref用来定义:基本类型数据、对象类型数据,若ref接收的是对象类型,内部其实也是调用了reactive函数。
- reactive用来定义:对象类型数据。
-
ref创建的变量必须使用.value(可以使用volar插件自动添加.value)。
-
reactive重新分配一个新对象(不推荐),会失去响应式(可以使用Object.assign整体替换)。
2.3 toRefs & toRef
作用:将一个响应式对象中的每一个属性,转换为 ref 对象。
备注:toRefs与toRef功能一致,但toRefs可以批量转换。
场景:对响应式对象进行解构。
语法及示例如下:
<template>
<div class="person">
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>性别:{{person.gender}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeGender">修改性别</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,reactive,toRefs,toRef} from 'vue'
// 数据
let person = reactive({name:'张三', age:18, gender:'男'})
// 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
let {name,gender} = toRefs(person)
// 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力
let age = toRef(person,'age')
// 方法
function changeName(){
name.value += '-'
}
function changeAge(){
age.value += 1
}
function changeGender(){
gender.value = '女'
}
</script>
2.4 computed
作用:根据已有数据计算出新数据(和Vue2中的computed作用一致)。
computed()
方法期望接收一个 getter 函数,返回值为一个计算属性 ref。
基本语法:
const 计算属性 = computed(()=>{
return 计算返回的结果
})
计算属性只读:
<script setup>
import { computed, ref } from 'vue'
// 声明数据
const list = ref([1, 2, 3, 4, 5, 6, 7, 8])
// 基于 list 派生一个计算属性,从list中过滤出
const computedList = computed(() => list.value.filter((item) => item > 2))
// 在<script>中取值:computedList.value
</script>
<template>
<div>原始数据:{{ list }}</div>
<div>计算后的数据:{{ computedList }}</div>
</template>
计算属性可读可修改:
<script setup>
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1 // 修改计算属性,执行set
console.log(count.value) // 0
</script>
2.5 watch & watchEffect
2.5.1 watch:
Vue3中的watch只能监视以下四种数据:
- ref定义的数据
- reactive定义的数据
- 函数返回一个值(getter函数)
- 一个包含上述内容的数组。
基本语法:
watch(source,callback,options)
// 第一个参数是:被监视的对象
// 第二个参数是:监视的回调
// 第三个参数是:配置对象(deep、immediate等等.....)
停止侦听器:
const stop = watch(source, callback)
// 当已不再需要该侦听器时:
stop()
情况一:监视 ref 定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变。
<script setup>
import {ref,watch} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum(){
sum.value += 1
}
// 监视,情况一:监视【ref】定义的【基本类型】数据
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
if(newValue >= 10){
stopWatch()
}
})
</script>
<template>
<div class="person">
<h1>情况一:监视【ref】定义的【基本类型】数据</h1>
<h2>当前求和为:{{sum}}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
情况二:监视 ref 定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
<script setup>
import {ref,watch} from 'vue'
// 数据
let person = ref({
name:'张三',
age:18
})
// 方法
function changeName(){
person.value.name += '~'
}
function changeAge(){
person.value.age += 1
}
function changePerson(){
person.value = {name:'李四',age:90}
}
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{deep:true})
</script>
<template>
<div class="person">
<h1>情况二:监视【ref】定义的【对象类型】数据</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
</div>
</template>
情况三:监视 reactive 定义的【对象类型】数据,默认开启深度监视。
情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式,若该属性是对象类型的,可以直接写,也能写函数,更推荐写函数。
// 数据源
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
// 基本类型
watch(()=> person.name,(newValue,oldValue)=>{
console.log('person.name变化了',newValue,oldValue)
})
// 对象类型
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
情况五:监视多个数据
// 数据
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
watch([()=>person.name,person.car],(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
2.5.2 watchEffect
定义:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
watch 与 watchEffect 对比:
-
都能监听响应式数据的变化,不同的是监听数据变化的方式不同;
-
watch:需明确指出监视的数据;
-
watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
使用方法:
// 数据源
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 输出 0
count.value++
// -> 输出 1
停止侦听语法与watch一致。
2.6 模版引用-ref
概念: 通过 ref标识 获取真实的 dom对象或者组件实例对象。 获取dom:<script setup>
import {ref} from 'vue'
// 通过ref获取元素
let h1 = ref()
let h2 = ref()
function getValue(){
console.log(h1.value)
console.log(h2.value)
}
</script>
<template>
<div class="person">
<h1 ref="h1">HTML</h1>
<h2 ref="h2">CSS</h2>
<button @click="getValue">获取</button>
</div>
</template>
获取组件及子组件暴露的值或方法:
使用 <script setup>
的组件是默认关闭的——即通过模板引用或者 $parent
链获取到的组件的公开实例,不会暴露任何在 <script setup>
中声明的绑定。
可以通过 defineExpose
(内置)
编译器宏来显式指定在 <script setup>
组件中要暴露出去的属性。
// 子组件 Son.vue
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
const add = () => {
b.value += 1
}
// 向外暴露
defineExpose({ a, b, add })
</script>
// 父组件 Fa.vue
<script setup>
// 导入子组件
import Son from './components/Son.vue'
import {ref} from 'vue'
// 获取组件
let son = ref()
function getValue(){
console.log(son.value.a)
console.log(son.value.b)
son.value.add() // 调用子组件方法
}
</script>
<template>
<Son ref="son"/>
<button @click="getValue">获取</button>
</template>
Tips:通过 ref 对象.value即可访问到绑定的元素(必须在渲染完成后) 。
2.7 其他
shallowRef:
作用:创建一个响应式数据,但只对顶层属性进行响应式处理。
shallowReactive:
作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的。
readonly:
作用:用于创建一个对象的深只读副本。
shallowReadonly:
作用:与 `readonly` 类似,但只作用于对象的顶层属性。
toRaw:
作用:用于获取一个响应式对象的原始对象, `toRaw` 返回的对象不再是响应式的,不会触发视图更新。
markRaw:
作用:标记一个对象,使其 永远不会 变成响应式的。
customRef:
作用:创建一个自定义的`ref`,并对其依赖项跟踪和更新触发进行逻辑控制。
3、生命周期
vue2与vue3生命周期对比:
生命周期 | vue2 | vue3 |
创建阶段 | beforeCreate | setup |
created | ||
挂载阶段 | beforeMount | onBeforeMount |
mounted | onMounted | |
更新阶段 | beforeUpdate | onBeforeUpdate |
updated | onUpdated | |
销毁阶段 | beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
vue3常用钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)
示例代码:
<script setup>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount,
onUnmounted } from 'vue'
console.log('setup')
// 生命周期钩子
onBeforeMount(()=>{
console.log('挂载之前')
})
onMounted(()=>{
console.log('挂载完毕')
})
onBeforeUpdate(()=>{
console.log('更新之前')
})
onUpdated(()=>{
console.log('更新完毕')
})
onBeforeUnmount(()=>{
console.log('卸载之前')
})
onUnmounted(()=>{
console.log('卸载完毕')
})
</script>
4、vue-router(4)
基本方法与vue-router(3)一致。
源码解析:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue')
}
]
})
export default router
- 创建路由实例由 createRouter 实现
- history 模式使用 createWebHistory(),地址栏不带#
- hash 模式使用 createWebHashHistory(),地址栏带#
- 参数是基础路径,默认是 '/'
- import.meta.env.BASE_URL 是 vite.config.js 中的 base 配置项 默认值为 '/'
在 vue3 组合式API中获取 路由:
<script setup>
import { useRoute, useRouter } from 'vue-router'
// 获取路由对象:
const router = useRouter()
// 获取路由参数:
const route = useRoute()
// 获取路由参数及方法
console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)
</script>
5 、pinia
5.1 概念及使用
概念:一个集中式状态(数据)管理方案。
官网:Pinia | The intuitive store for Vue.js
安装:在脚手架中安装或通过npm安装(见官网)
两种风格:Option Store & Setup Store(推荐,本笔记基于Setup Store)
定义:
// store/counter.js
import { defineStore } from 'pinia'
// Option Store选项式风格
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
}
})
// Setup Store 组合式风格(推荐)
import { computed, ref } from "vue"
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
Tips:
- 命名:可以对 defineStore() 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 Store 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
- 第一个参数是 (counter)Store 的唯一 ID。
- Setup Store 一定要return
在组件中访问store(Setup Store):
<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 store 变量
const store = useCounterStore()
// 访问 store 中count, doubleCount
const count = store.count
const doubleCount = store.doubleCount
// 调用 store 中 increment 方法
setTimeout(() => {
store.increment()
}, 1000)
</script>
关于解决 store 在组件中 解构 失去响应式 的问题:
① 场景描述:
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
const { count, doubleCount } = store // 这样解构会失去响应性
const { increment } = store // 方法可以直接解构
</script>
② 解决方案:storeToRefs()
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia' // 通过插件添加的属性也会被提取为 ref
const store = useCounterStore()
// 解决如下
const { count, doubleCount } = storeToRefs(store)
</script>
在路由中访问store:
// router/index.js
import { useUserStore } from '@/stores' // 引入
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: []
})
router.beforeEach((to) => {
const useStore = useUserStore() // 访问;此时pinia已安装
if (!useStore.token && to.path !== '/login') {
return '/login'
}
})
export default router
Tips:访问 store 时 确保 Pinia 已激活。
在其他文件中访问store可参考上述方案。
5.2 持久化
使用 pinia-plugin-persistedstate(官方推荐) 插件对pinia中的数据进行持久化处理。
官网:Home | pinia-plugin-persistedstate
安装:npm i pinia-plugin-persistedstate
基本使用:
在main.js中配置插件:
// main.js
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
// 导入持久化插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
import router from './router'
const app = createApp(App)
// 创建pinia实例
const pinia = createPinia()
// 将插件添加到 pinia 实例上
app.use(pinia.use(piniaPluginPersistedstate))
app.use(router)
app.mount('#app')
在 store 中开启持久化:
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useStore = defineStore(
'main',
() => {
const someState = ref('你好 pinia')
return { someState }
},
{
persist: true, // 开启持久化(网页端配置)
},
)
默认配置:
- 默认使用 localStorage 进行存储;
- store 的唯一 ID作为 storage 默认的 key;
- 使用 JSON.stringify/JSON.parse 进行序列化/反序列化;
- 整个 state 默认将被持久化。
在 persist 中修改配置(其他配置见官网):
persist: {
storage: sessionStorage // 这个 store 将被持久化存储在 sessionStorage中
key: 'my-custom-key', // 存储在 sessionStorage 中的 my-custom-key key 中
}
uni-app小程序中配置:
persist: {
// 调整为兼容多端的API
storage: {
setItem(key, value) {
uni.setStorageSync(key, value)
},
getItem(key) {
return uni.getStorageSync(key)
},
},
}
5.3 模块化案例
需求:将 pinia 模块化,在模块中定义user模块用于存储用户信息(token、username...)
实现步骤:
① 创建相关目录及文件夹;
② 在stores/index.js中初始化pinia
// stores/index.js
import { createPinia } from 'pinia' // 导入pinia
import persist from 'pinia-plugin-persistedstate' // 导入持久化插件
const pinia = createPinia() // 创建pinia实例
pinia.use(persist) // 将插件添加到 pinia 实例上
export default pinia // 向外暴露pinia
export * from './modules/user' // 在stores/index.js中导入并暴露 user 模块
③ 修改main.js导入stores/index.js
//main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from '@/stores/index' // 导入
const app = createApp(App)
app.use(pinia) //注册
app.use(router)
app.mount('#app')
④ 定义stores/modules/user.js模块
import { userGetInfoService } from '@/api/user' // 导入获取用户信息的请求
import { defineStore } from 'pinia'
import { ref } from 'vue'
//用户模块 token setToken removeToken...
export const useUserStore = defineStore(
'userInfo',
() => {
// 1、token
// 定义token
const token = ref('')
// 更新token
const setToken = (newToken) => {
token.value = newToken
}
// 移除token
const removeToken = () => {
token.value = ''
}
// 2、用户信息
// 定义用户信息
const user = ref({})
// 获取用户信息
const getUser = async () => {
const res = await userGetInfoService()
user.value = res.data.data
}
// 设置用户信息
const setUser = (obj) => {
user.value = obj
}
return { token, setToken, removeToken, user, getUser, setUser }
},
{
persist: true // 开启持久化
}
)
⑤ 在组件或路由中使用user模块
// 统一在stores/index.js中按需导入
import { useUserStore } from '@/stores'
// 使用方法与5.1一致
6、组件通信
6.1 父传子
实现方法:父组件通过向子组件添加自定义属性传值,子组件通过 defineProps 接收。
示例:
<!-- 父组件 -->
<script setup>
import Son from '@/components/Son.vue'
import {reactive} from 'vue'
let toSon = reactive([
{ id : 1, msg:'我是你爹' },
{ id : 2, msg:'我是你爸' }
])
</script>
<template>
<Son :msgs="toSon"/>
</template>
<!------------------------------------------------------------>
<!-- 子组件 -->
<script setup>
// 写法1:接收+限制类型(js写法)
const props = defineProps({ msgs: Array }) // 将 msgs 保存
console.log(props.msgs) // 使用 msgs
// 写法2:接收+限制类型+限制必要性(TS写法)
defineProps<{msgs: Array, foo? : number}>()
// 写法3:接收+限制类型+限制必要性+指定默认值(TS写法)
withDefaults(defineProps<{msgs: Array, foo? : number[]}>(), {
msgs: ['没有收到老头子的消息','我是默认消息'],
foo: () => [1, 2]
}
</script>
<template>
<div v-for="item in msgs" :key="item.id">{{ item.msg }}</div>
</template>
Tips:编译器宏 不需要 import 导入,例如:defineExpose,defineProps,defineEmits ...
6.2 子传父
方式1:通过 ref 与 defineExpose 组合,详见2.6。
方式2:通过给子组件注册自定义事件传值。
<!-- 子组件 -->
<script setup>
// 通过 defineEmits 生成 emit 方法
const emit = defineEmits(["getSonMsg"])
// emit(事件名,传值)
emit("getSonMsg", 123456)
</script>
<!---------------------------------------------------------------->
<!-- 父组件 -->
<script setup>
import Son from "@/components/Son.vue"
// 自定义事件 getSonMsg 的触发函数 getMsgHandler 形参中接收子组件传的值
const getMsgHandler = msg => console.log(msg)
</script>
<template>
<Son @getSonMsg="getMsgHandler"></Son>
</template>
6.3 跨层通信
实现:顶层组件使用provide函数传递数据,底层组件使用inject接收数据。
顶层组件
<!-- 顶层组件 -->
<script setup>
import center from '@/components/center--.vue'
import { provide, ref } from 'vue'
// 1.跨层传递普通数据 provide(键,值)
provide('theme-color', 'pink')
// 2.跨层级传递响应式数据
const count = ref(100)
provide('money', count)
setTimeout(() => {
count.value = 200
}, 2000)
// 3.跨层传递函数,修改数据
provide('changeCount', (newCount) => {
count.value = newCount
})
</script>
<template>
<div>
<h1>我是顶层组件</h1>
<center></center>
</div>
</template>
中间件
<!-- 中间件 -->
<script setup>
import bottom from './bottom--.vue'
</script>
<template>
<div>
<h2>我是中间组件</h2>
<bottom></bottom>
</div>
</template>
底层组件
<!-- 底层组件 -->
<script setup>
import { inject } from 'vue'
const themeColor = inject('theme-color') //接收普通数据
const count = inject('money') //接收响应式数据
const changeCount = inject('changeCount') //接收函数
</script>
<template>
<div>
<h3>我是底层组件--{{ themeColor }}--{{ count }}</h3>
<button @click="changeCount(300)">更新count</button>
</div>
</template>
6.4 v-model
v-model除了可以绑定表单元素,在Vue 3.4+中通过defineModel() 宏可以在组件上使用以实现双向绑定。
使用方法:
<!-- 父组件 -->
<script setup>
import Son from "@/components/Son.vue";
import { ref } from "vue";
const count = ref(1);
const inputPH = ref("请输入电话号码");
</script>
<template>
<Son v-model="count" v-model:title="inputPH" />
</template>
<!---------------------------------------------------------------------------->
<!-- 子组件 -->
<script setup>
const model = defineModel(); // v-model="count"
function update() {
model.value++;
}
const placeholder = defineModel("title"); // v-model:title="inputPH"
</script>
<template>
<div>parent bound v-model is: {{ model }}</div>
<button @click="update">更新数据</button>
<br />
<input type="text" v-model="placeholder" />
</template>
defineModel配置项:
// 使 v-model 必填
const model = defineModel({ required: true })
// 提供一个默认值
const model = defineModel({ default: 0 })
// 也可以配置set函数定制修饰符,详情见官网
//例如:
const title = defineModel('title', { required: true, default: '请输入账户名称' })
三、免责声明
1、本博客中的文章摘自网上的众多博客,仅作为自己知识的补充和整理,并分享给其他需要的 coder,不会用于商用;
2、因为很多博客的地址已经记不清楚了,所以不会在这里标明出处;
3、vue官网:Vue.js - 渐进式 JavaScript 框架 | Vue.js
4、vuex官网:Vuex 是什么? | Vuex
5、vue cli官网:Vue CLI
6、vue router官网:Vue Router | Vue.js 的官方路由
7、pinia官网:Pinia | The intuitive store for Vue.js
8、pinia-plugin-persistedstate官网:Home | pinia-plugin-persistedstate
9、vite官网:Vite | 下一代的前端工具链
10、webpack官网:webpack | webpack中文文档 | webpack中文网
11、electron官网:Electron框架 Electron中文网 官网
12、soybeanadmin官网:SoybeanAdmin | A fresh and elegant admin template
13、vue-element-plus-admin官网:vue-element-plus-admin
14、element-plus官网:一个 Vue 3 UI 框架 | Element Plus
15、vant官网:Vant 4 - A lightweight, customizable Vue UI library for mobile web apps.
16、ESLint官网: ESLint - 插件化的 JavaScript 代码检查工具
17、Babel官网:Babel 中文文档 | Babel中文网 · Babel 中文文档 | Babel中文网
18、vue-quill官网:VueQuill | Rich Text Editor Component for Vue 3
19、Echarts官网:Apache ECharts
20、axios官网:Axios中文文档 | Axios中文网
版权声明:本文标题:Vue2 & 3 学习笔记 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1729002303h1305533.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论