目录
一 、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.net/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 使用脚手架打包
// 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.com',
// proxy: {
// "/api": {
// target: "http://api.mc.com",
// 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.terserOptions.compress.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仓库)
// context.commit('mutation名字', 额外参数)
changeCountAction (context) {
// 这里是setTimeout模拟异步,以后大部分场景是发请求
setTimeout(() => {
context.commit('changeCount', 10)
}, 1000)
},
// 解构写法
changeCountAction ({commit}) {
setTimeout(() => {
commit('changeCount', 10)
}, 1000)
}
}
})
export default store
在 组件 中调用 mutation 中的函数:
通过 store :this.$store.commit('addCount', 10)
<template>
<div class="box">
<button @click="fn"></button>
</div>
</template>
<script>
export default {
methods: {
fn(){
this.$store.commit('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
$store.commit('模块名/xxx ', 额外参数)
// action
$store.dispatch('模块名/xxx ', 额外参数)
②在组件逻辑中获取:
// state
this.$store.state.模块名.xxx
// getter
this.$store.getters['模块名/xxx ']
// mutation
this.$store.commit('模块名/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= () => store.commit('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>
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
<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中文网