Appearance
Vue3
简介
概述
Vue是一种流行的JavaScript框架,用于构建用户界面和单页面应用程序(SPA)。
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
Vue基于MVVM(模型-视图-视图模型)架构,它允许开发人员构建可重用的组件,并且可以很容易地将组件组合起来形成复杂的UI。Vue非常灵活,支持多种方式来定义组件的样式、逻辑和行为。Vue的另一个优点是其响应式系统,它可以自动追踪数据的变化,并在必要时更新UI。
Vue还提供了一系列工具和插件,包括Vue CLI(用于构建Vue项目的命令行界面工具)、Vue Router(用于构建SPA的路由库)和Vuex(用于管理Vue应用程序的状态管理库)等。
MVVM 双向数据绑定模式
Model:模型层,在这里表示 JavaScript 对象
View:视图层,在这里表示 DOM(HTML 操作的元素)
ViewModel:连接视图和数据的中间件,Vue.js 就是 MVVM 中的 ViewModel 层的实现者
在 MVVM 架构中,是不允许
数据
和视图
直接通信的,只能通过 ViewModel 来通信,而 ViewModel 就是定义了一个Observer` 观察者
ViewModel 能够观察到数据的变化,并对视图下对应的内容进行更新
ViewModel 能够监听到视图的变化,并能够通知数据发生改变
Vue.js 就是一个 MVVM 的实现者,他的核心就是实现了 DOM 监听 与 数据绑定
Vue.js 的两大核心要素
数据驱动
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你可能需要安装 vue-devtools 来获取更加友好的检查接口。
每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
组件化
页面上每个独立的可交互的区域视为一个组件
每个组件对应一个工程目录,组件所需的各种资源在这个目录下就近维护
页面不过是组件的容器,组件可以嵌套自由组合(复用)形成完整的页面
快速入门
用插件表达式完成数据展示到前端
用v-model完成视图操作数据
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
<script src="https://cdn.bootcss.com/vue/3.2.47/vue.global.min.js"></script>
</head>
<body>
<div id="app">
<span>{{ msg }}</span><br/>
<input type="text" v-model="msg">
</div>
</body>
<script>
Vue.createApp({
data() {
return {
msg: "hello vue!"
}
}
}).mount("#app);
</script>
</html>
Vue内部指令
条件指令
v-if
v-else-if
v-else
v-show
条件指令中的if-elseif-else与show的区别在于 如果条件不满足if-elseif-else的元素根本在DOM就没有,但是show还是有的,只不过不显示而已 在用户层面,没有任何区别上的感知 所以具体在项目中到底用哪一个,哪一个都行
html
<div id="app">
<input v-model="score"><br/>
<span v-if="score >= 90">优</span>
<span v-else-if="score >= 80">良</span>
<span v-else-if="score >= 60">及格</span>
<span v-else>不及格</span>
</div>
html
<div id="app">
<button v-show="login">退出</button>
<button v-show="!login">登录</button>
</div>
循环指令
v-for
html
<div id="app">
<ul>
<li v-for="menuitem of menu">{{menuitem}}</li>
</ul>
</div>
内容设置指令
v-html
v-text
{{}}
html
<div id="app">
<span v-text="inner"></span>
</div>
html
<div id="app">
<span v-html="inner"></span>
</div>
数据绑定指令
v-model
v-bind
:
凡是可以与用户交互的元素,都可使用v-model
当需要设置元素属性时,属性值来源于vue当中的变量,就需要使用v-bind
html
<div id="app">
<span>{{msg}}</span><br/>
<input v-model="msg">
</div>
html
<div id="app">
<img v-bind:src="img">
</div>
html
<div id="app">
<img :src="img">
</div>
事件绑定指令
v-on
@
@click.stop
, 阻止冒泡@click.prevent
, 阻止跳转@click.self
, 阻止子冒泡@click.once
, 点击后立即解绑
在Vue.createApp时,除了data以外,需要指定methods,每个方法如下填写:
js
Vue.createApp({
methods: {
fn1: function () {
console.log("fn1");
},
fn2: function () {
console.log("fn2");
}
}
}).mount("#app");
在html标签上,编写v-on指令
html
<div id="app">
<button v-on:click="fn1">按钮1</button>
<button @click="fn2()">按钮2</button>
</div>
Idea中报红的处理
- 打开 Idea 全局配置
- 找到菜单项:
Editor
->Inspections
->XML
- 取消勾选
Unbound namespace prefix
,确认
全局API
data
绑定数据
methods
绑定方法
计算属性
- computed
计算: 行为 属性: 状态 目的在于,想要初始化某个属性时,需要经历一些过程 并不是每一次调用,都会触发 只会在第一次调用时才会被触发
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/vue/3.2.47/vue.global.min.js"></script>
</head>
<body>
<div id="app">
件数: <input type="text" v-model="count"><br/>
单价: <input type="text" v-model="price"><br/>
总价: <input type="text" v-model="total" readonly disabled>
</div>
</body>
<script>
Vue.createApp({
data: {
count: 1,
price: 9.9
},
computed: {
total() {
return this.count * this.price;
}
}
}).mount("#app");
</script>
</html>
监听器
- watch
用于监听某属性的变化 既得到旧值,又能得到新值
javascript
Vue.createApp({
data() {
return {
count:1,
minusDisable: true
}
},
methods: {
minus() {
this.count--;
},
plus() {
this.count++;
}
},
watch: {
count(newval, oldval) {
if (newval == 1) {
this.minusDisable = true;
} else {
this.minusDisable = false;
}
}
}
}).mount("#app");
过滤器【了解】
过滤器是对即将显示的数据做进一步的筛选处理,然后进行显示,值得注意的是过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据。
过滤器分全局过滤器和本地过滤器(局部过滤器)。
需要特别注意的是:过滤器中不能使用this指针。
全局过滤器
下面定义一个全局过滤器,用于在数据前加上大写的VUE。需要注意的是,全局过滤器定义必须始终位于Vue实例之上,否则会报错。
html
<div id="app">
{{message | toAdd}}
</div>
<script src="https://cdn.bootcss.com/vue/3.2.47/vue.global.min.js"></script>
<script>
Vue.filter("toAdd", function(value) {
return 'VUE' + value
})
Vue.createApp({
data: {
message: '过滤器',
}
}).mount("#app");
</script>
注意
- Vue.filter() 后有两个参数:过滤器名,过滤器处理函数。
- 过滤器处理函数也有一个参数:要过滤的数据。
本地过滤器
本地过滤器存储在vue组件中,作为filters属性中的函数,我们可以注册多个过滤器存储在其中。
html
<div id="app">
<p>{{message | Reverse}}</p>
<p>{{message | Length}}</p>
</div>
<script src="https://cdn.bootcss.com/vue/3.2.47/vue.global.min.js"></script>
<script>
Vue.createApp({
data: {
message: '过滤器',
},
filters: {
Reverse: function(value) {
if (!value){
return '';
}
return value.toString().split('').reverse().join('');
},
Length: function(value) {
return value.length;
},
},
}).mount("#app")
</script>
过滤器传参
过滤器也是可以传递参数的。
html
<div id="app">
{{msg1 | toDou(1,2)}}
{{msg2 | toDou(10,20)}}
</div>
<script src="https://cdn.bootcss.com/vue/3.2.47/vue.global.min.js"></script>
<script>
Vue.filter('toDou', function(value, param1, param2) {
if (param2 < 10) {
return value + param1;
} else {
return value + param2;
}
});
Vue.createApp({
data: {
msg1: 9,
msg2: 12,
}
}).mount("#app")
</script>
注意
过滤器处理函数的第一个参数,仍然是要过滤的数据。从第二个参数开始,才是过滤器本身要传递的参数。
串联过滤器
多个过滤器可以串联使用
html
<div id="app">
{{money | prefix | suffix}}
</div>
<script src="https://cdn.bootcss.com/vue/3.2.47/vue.global.min.js"></script>
<script>
Vue.filter("prefix", function(value) {
return '¥' + value
})
Vue.filter("suffix", function(value) {
return value + '元'
})
Vue.createApp({
el: '#app',
data: {
money:10
}
}).mount("#app")
</script>
Vue钩子函数
也叫做全局函数 也叫做生命周期函数
VUE 生命周期所经历的四件事
- 创建 - 一次
beforeCreate
created
- 挂载 - 一次
beforeMount
mounted
- 更新 - N 次
beforeUpdate
updated
- 销毁 - 一次
beforeDestroy(vue2)destroyed(vue2)beforeUnmount(vue3)
unmounted(vue3)
Vue模板
template
html
<body>
<div id="app"></div>
</body>
javascript
Vue.createApp({
el: "#app",
template: `
<div>
<span>{{msg}}</span><br/>
<input v-model="msg">
</div>
`,
data() {
return {
msg: "Hello Vue!"
}
},
methods: {}
}).mount("#app")
单文件组件
安装node与pnpm
去官网下载并安装nodejs
测试安装是否成功
注意
npm(node package manager,node包管理器)会随着node的安装,自动安装。但是npm的版本号和node的版本号不是一致的
bashnode -v npm -v
安装pnpm
npm i -g pnpm
测试pnpm
pnpm -v
创建vue脚手架项目
运行以下命令创建项目
pnpm create vue
控制台交互
交互窗口
Project name:
项目名称
Add TypeScript?
No
Add JSX Support?
No
Add Vue Router for Single Page Application development?
Yes
Add Pinia for state management?
Yes
Add Vitest for Unit Testing?
No
Add an End-to-End Testing Solution?
No
Add ESLint for code quality?
No
Add Vue DevTools extension for debugging? (experimental)?
Yes
安装依赖
bashcd 项目名称 pnpm install pnpm dev
脚手架项目结构
public
用于存储静态资源
index.html
全局html,vue项目是单页面项目,肉眼可见的页面转换,其实是单页面切换组件
src
main.js
通过main.js创建根组件,挂载到index.html相应的位置 此处用脚手架创建的项目是挂载到了id为app的元素上
App.vue
在main.js把App.vue的组件,加载到了index.html上
vite.config.js
vue脚手架项目的全局配置
package.json
里面维护了本项目需要依赖的模块、本项目能够使用的命令...
集成Idea
- 点击 Idea 窗口上边的
Edit Configurations
- 点击弹出窗口左上角的加号
- 选择 npm
- 在 script 的下拉菜中选择
dev
- 再点 OK
- 窗口上边的运行和 debug 按钮就可以点
- 在点之前,一定要确认左侧的到底是 build 还是 lint 还是 serve
单文件组件开发
template标签
以前怎么写,现在还怎么写
script标签
注意组件不再写template属性 注意要导出
export default
注意要导入import CartView from './views/CartView'
注意引用子组件:components: { CartView: CartView }
style标签
分别能够使用lang属性和scoped属性
scoped属性用于描述样式只在当前组件中生效 但是注意也区别于父组件和子组件 如果在父组件里边,哪怕写了scoped属性,那么父组件里面的所有子组件依然能够继承样式
lang属性可以指定预编译css 在使用脚手架创建本项目时,指定预编译css使用scss/sass
<style lang='scss' scoped> ul { background: antiquewhite; li { color: red; } } </style>
组件化开发
Vue3语法
javascript
Vue.createApp({
data() {},
methods: {},
computed: {},
watch: {},
filters: {},
template: ``,
props: [],
emits: []
}).mount("#app")
组件的父子关系-铺垫
- ChildView.vue
vue
<template>
<div>{{msg}}</div>
<span>{{msg}}</span>
</template>
<script>
export default {
data() {
msg: 'Hello Vue'
}
}
</script>
- App.vue
vue
<template>
<div>
<ChildView></ChildView>
</div>
<div>
<ChildView></ChildView>
</div>
</template>
<script>
import ChildView from "@/views/ChildView.vue";
export default {
components: {
ChildView
}
}
</script>
组件通信
props down
events up
ref 通信
$refs
事件总线
props
通信【单向传递】
注意
所有的props都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。
props传向子组件中的数据,无法在子组件中修改。
参考官方文档:
https://cn.vuejs.org/guide/components/props.html#one-way-data-flow
父组件知道数据,子组件不知道,需要由父组件传给子组件
在子组件中,需要告知我子组件需要某个属性,由别人传过来
用props关键字告知
在父组件中,给子组件传值
假设给子组件传name
以下方式传的是字面值
<component name="传给你的值"></component>
以下方式传的是表达式的结果
<component :name="cartname"></component>
- TodoItem.vue
vue
<template>
<li>
<span>{{info.task}}</span>
<input type="checkbox" v-model="info.complete">
</li>
</template>
<script>
export default {
props: ["info"]
}
</script>
- App.vue
vue
<template>
<ul>
<TodoItem
v-for="item of todoList"
:info="item"/>
</ul>
</template>
<script>
import TodoItem from "@/views/TodoItem.vue";
export default {
components: {TodoItem},
data() {
return {
todoList: [{
task: "完成数学课作业",
complete: true
}, {
task: "准备化学实验报告",
complete: false
}, {
task: "参加历史课的小组讨论",
complete: false
}, {
task: "查找关于生物学项目的研究资料",
complete: false
}]
}
}
}
</script>
v-model
通信【双向传递】
https://cn.vuejs.org/guide/components/v-model.html
- 父组件
vue
<template>
<DiseaseSearchDialog v-model="diseaseSearchDialogVisible"/>
</template>
<script>
data() {
return {
diseaseSearchDialogVisible: false
}
},
methods: {
addDisease() {
this.diseaseSearchDialogVisible = true;
}
}
</script>
- 子组件
vue
<template>
<el-dialog
v-model="dialogVisible"
title="Tips"
width="30%">
</el-dialog>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
computed: {
dialogVisible: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
}
}
}
}
</script>
emit
通信
- Counter.vue
vue
<template>
<button @click="tap">点击计数</button>
</template>
<script>
export default {
methods: {
tap() {
this.$emit("postCount")
}
},
emits: ["postCount"]
}
</script>
- App.vue
vue
<template>
<div>点击次数 {{ total }}</div>
<Counter @postCount="count"></Counter>
</template>
<script>
import Counter from "@/views/Counter.vue";
export default {
data() {
return {
total: 0
}
},
methods: {
count() {
this.total++;
}
},
components: {Counter}
}
</script>
ref
通信
- MenuView.vue
vue
<template>
<ul>
<li v-for="item of list">{{item}}</li>
<li><input v-model="newitem"><button @click="add">添加</button></li>
</ul>
</template>
<script>
export default {
name: "RefView",
data() {
return {
newitem: "",
list: [
"宫保鸡丁",
"鱼香肉丝",
"京酱肉丝"
]
}
},
methods: {
add() {
this.list.push(this.newitem);
}
}
}
</script>
- App.vue
vue
<template>
<MenuView ref="menuView"></RefView>
<button @click="getChildProperty">点击获取子组件中的属性</button>
</template>
<script>
import MenuView from "@/views/MenuView.vue";
export default{
methods: {
getChildProperty() {
console.log(this.$refs.menuView.list);
}
},
components: {MenuView}
}
</script>
事件总线
不管在vue2还是在vue3中,都建议使用vuex来进行状态管理,而不是手动用事件总线来实现通信
Vue3从实例中移除了
$on
、$off
和$once
方法,保留了$emit
方法如果希望继续使用全局事件总线,可使用第三方库
Vue3官方推荐 mitt 或 tiny-emitter
建议在SFC(单文件组件)中使用
参考:
Vue3总线-mitt
挂载使用
安装
bashpnpm i mitt
挂载
- main.js
jsimport { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import mitt from "mitt" let app = createApp(App); app.config.globalProperties.$bus = new mitt() app.use(store); app.use(router); app.mount('#app');
发布消息
jsthis.$bus.emit("event", "Hello");
订阅消息
jsthis.$bus.on("event", (msg) => { console.log(msg); })
模块使用
注意
因为mitt中的总线,需要调用mitt()
方法获取,每次获取到的总线都不是同一个对象
所以需要将mitt()
的结果封装到一个js模块并导出,使用时再导入
安装
bashpnpm i mitt
封装
- bus/index.js
jsimport mitt from 'mitt' const emitter = mitt() export default emitter
发布消息
jsimport emitter from "@/bus" emitter.emit('event', 'Hello');
订阅消息
jsimport emitter from "@/bus" // listen to an event emitter.on('event', msg => console.log(msg) ) // listen to all events emitter.on('*', (type, e) => console.log(type, e) )
Vue3总线-tiny-emitter
挂载使用
安装
bashpnpm i tiny-emitter
挂载
jsimport { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import emitter from 'tiny-emitter'; let app = createApp(App); app.config.globalProperties.$bus = new emitter() app.use(store); app.use(router); app.mount('#app');
使用
jsthis.$bus.on(...args); this.$bus.once(...args); this.$bus.off(...args); this.$bus.emit(...args);
模块使用
安装
bashpnpm i tiny-emitter
使用
jsimport emitter from 'tiny-emitter/instance' export default { $on: (...args) => emitter.on(...args), $once: (...args) => emitter.once(...args), $off: (...args) => emitter.off(...args), $emit: (...args) => emitter.emit(...args) }
插槽
匿名插槽
注意
匿名插槽的本质,是使用了默认命名default
vue
<template>
<div>----------</div>
<slot></slot>
<div>----------</div>
</template>
vue
<template>
<Swiper>
<div class="swiper-part1">轮播第一张</div>
<div class="swiper-part2">轮播第二张</div>
</Swiper>
</template>
<script>
import Swiper from "@/views/Swiper.vue";
export default {
components: {Swiper}
}
</script>
命名插槽
既具有名字的插槽,当插槽个数不止一个,那么到底将元素放到哪个插槽中,就需要通过名字指定
vue
<template>
<div>----------</div>
<slot>
<div>----------</div>
<slot name="second"></slot>
<div>----------</div>
<slot name="third"></slot>
<div>----------</div>
</template>
vue
<template>
<Swiper>
<template v-slot:default>
<span>插到第一个插槽</span>
</template>
<template v-slot:second>
<span>插到第二个插槽</span>
</template>
<template v-slot:third>
<span>插到第三个插槽</span>
</template>
</Swiper>
</template>
<script>
import Swiper from "@/views/Swiper.vue";
export default {
components: {Swiper}
}
</script>
缩写
vue
<template>
<div>----------</div>
<slot>
<div>----------</div>
<slot name="second"></slot>
<div>----------</div>
<slot name="third"></slot>
<div>----------</div>
</template>
vue
<template>
<Swiper>
<template #default>
<span>插到第一个插槽</span>
</template>
<template #second>
<span>插到第二个插槽</span>
</template>
<template #third>
<span>插到第三个插槽</span>
</template>
</Swiper>
</template>
<script>
import Swiper from "@/views/Swiper.vue";
export default {
components: {Swiper}
}
</script>
插槽传参
vue
<template>
<div>
<slot :text="msg" :count="1"></slot>
</div>
</template>
vue
<template>
<Child>
<template #default="scope">
{{ scope.text }} {{ scope.count }}
</template>
</Child>
</template>
动态组件【了解】
有时,我们可能需要根据状态来决定使用那个组件,比如下面的例子:
动态组件使用component标签,并且需要添加
:is
属性 保持组件的缓存,需要在component外边,添加keep-alive
标签
javascript
Vue.createApp({
template: `
<div>
<button @click="mine">我的</button>
<button @click="cart">购物车</button><br/>
<keep-alive>
<component :is="activecomponent"></component>
</keep-alive>
</div>
`,
methods: {
mine() {
this.activecomponent = "mine";
},
cart() {
this.activecomponent = "cart";
}
},
data() {
return {
activecomponent: "mine"
}
},
components: {
mine: {
template: `
<div>
我的页
</div>
`
},
cart: {
template: `
<div>
购物车页
<ul>
<li v-for="cartname of cart">{{cartname}}</li>
<li><input ref="newprod"><button @click="addprod">添加菜品</button></li>
</ul>
</div>
`,
data() {
return {
cart: ["宫保鸡丁", "水煮肉片", "乾隆白菜"]
}
},
methods: {
addprod() {
this.cart.push(this.$refs.newprod.value);
this.$refs.newprod.value = "";
}
}
}
}
}).mount(".container");
异步组件【了解】
在实际开发中,一个应用可能会非常复杂。它可能会由很多组件组成。如果在应用启动时就加载所有组件,势必会造成效率低下。因此,正确的方式应该是按需加载。也就是先加载必要组件,然后根据需求在加载其它组件。
为了实现这个需求,Vue为我们提供了异步组件。
官网解释:在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
html
<body>
<div id="app">
<mycomponent></mycomponent>
</div>
<template id="mytemplate">
<div>
<subcomponents1></subcomponents1>
<subcomponents2></subcomponents2>
</div>
</template>
<script src="https://cdn.bootcss.com/vue/3.2.47/vue.global.min.js"></script>
<script>
Vue.createApp({
components: {
mycomponent: {
template: '#mytemplate',
data() {
return {}
},
components:{ //声明子组件
subcomponents1:{
template:`<div>第一个组件</div>`,
},
subcomponents2:Vue.defineAsyncComponent(()=>{
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve({
template:`<div>第二个组件</div>`,
});
}, 3000);
})
})
}
}
}
}).mount('#app');
</script>
</body>
- 上面代码中声明了两个子组件,一个是同步组件,一个是异步组件。
- 使用 Vue.defineAsyncComponent 来声明异步组件,并且它要求返回一个Promise对象。
- 代码运行后,先加载第一个子组件,3秒钟后加载第二个组件。
反向代理
配置
vue.config.js配置方案
javascript
module.exports = defineConfig({
devServer: {
proxy: {
'/maoyan': {
target: 'https://i.maoyan.com',
changeOrigin: true,
pathRewrite: {
'^/maoyan': ''
}
}
}
}
}
- vite.config.js
javascript
export default defineConfig({
server: {
proxy: {
'/maoyan': {
target: 'https://i.maoyan.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/maoyan/, '')
}
}
}
}
测试
查看以下地址的内容是否一致
http://localhost:5174/maoyan/api/mmdb/movie/v3/list/hot.json?ct=大连&ci=65&channelId=4#/
https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=大连&ci=65&channelId=4
路由
想要查看不同的页面,浏览器的地址也随之变得不同
快速入门
使用router模块
不使用vue-router系统自带模块
而是使用vue脚手架在本项目中创建的模块
- main.js
javascript
import router from './router'
createApp(App).use(router).mount('#app')
配置router
- router/index.js
javascript
// @的意图是本项目中的src目录
import CartView from '@/views/CartView'
import MineView from '@/views/MineView'
const routes = [
{
path: '/cart',
name: 'cart',
component: CartView
},
{
path: '/mine',
name: 'mine',
component: MineView
}
]
预留路由视图
vue
<template>
<div>
<button @click="toggleMine">我的页</button>
<button @click="toggleCart">购物车页</button>
<router-view></router-view>
</div>
</template>
声明式路由
声明式路由标签
tex
router-link标签
标签伪装
在vue3中,router-link标签的tag属性已经被弃用
应使用以下方式替代:
--- https://router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link
html
<!-- vue2 -->
<router-link to="/about" tag="button">About Us</router-link>
<!-- vue3 -->
<router-link to="/about" custom v-slot="{ navigate }">
<button @click="navigate" @keypress.enter="navigate" role="link">
About Us
</button>
</router-link>
标签高亮
- 自定义高亮样式
html
<router-link to="/mine" active-class="active">
我的页
</router-link>
<router-link to="/cart" active-class="active">
购物车页
</router-link>
- 伪装后高亮样式
html
<router-link to="/"
custom
v-slot="{ navigate, isActive, isExactActive }">
<button @click="navigate"
@keypress.enter="navigate"
role="link"
:class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']">
Home
</button>
</router-link>|
编程式路由
javascript
export default {
methods: {
toggleMine () {
this.$router.push("/mine");
},
toggleCart () {
this.$router.push("/cart");
}
}
}
重定向
- router/index.js
javascript
const routes = [
{
path: '/',
redirect: '/mine'
},
{
path: '/cart',
name: 'cart',
component: CartView
},
{
path: '/mine',
name: 'mine',
component: MineView
},
{
path: '/:pathMatch(.*)*',
redirect: '/mine'
}
]
二级路由
- router/index.js
javascript
const routes = [{
path: '/cinema',
component: CinemaView,
children: [{
path: '/cinema',
redirect: '/cinema/hot'
},{
path: '/cinema/hot',
component: HotView
},{
path: '/cinema/coming',
component: ComingView
}]
}]
路由传参
注意
在版本号 4.1.4 (2022-08-22) 时,params形式的传参已被弃用,目前只能够使用query形式
https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#414-2022-08-22
英文文档解析
This change will break your app. This behavior has worked in some scenarios but has been advised against for years as it's an anti-pattern in routing for many reasons, one of them being reloading the page lose the params.
很多理由证明,这种路由是一种反模式。其中一个重要的理由是,重新加载页面会丢失参数。
命名路由
javascript
const routes = [
{
path: '/moviedetail',
name: 'moviedetail',
component: MovieDetailView
}
]
路由传参-声明式路由
除了可以使用query之外,也可以使用params
但一定要注意,传参用query,接收也就必须用query。
params同上
vue
<template>
<div>
正在热映
<ul>
<li v-for="movie of hot" :key="movie.nm">
<router-link :to='{name: "moviedetail", query: {id: movie.id}}'>
{{ movie.nm }}
</router-link>
</li>
</ul>
</div>
</template>
路由传参-编程式路由
javascript
methods: {
routejump() {
this.$router.push({
name: 'moviedetail',
query: {id: '这个是编程式路由传的参'}
});
}
}
路由接参
javascript
export default {
name: "MovieDetailView",
mounted() {
console.log(this.$route.query.id);
}
}
路由守卫
全局守卫
javascript
router.beforeEach((to, from) => {
let isLogin = false;
if (to.name == 'profile' && !isLogin) {
return {name: 'login', query: {from: to.name}};
}
});
局部守卫
javascript
beforeRouteEnter(to, from) {
let isLogin = false;
if (!isLogin) {
return {name: 'login', query: {from: 'profile'}};
}
},
beforeRouteUpdate(to, from) {
},
beforeRouteLeave(to, from) {
}
历史模式【了解】
https://router.vuejs.org/zh/guide/essentials/history-mode.html
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
state中的数据,可以直接访问,但是不建议修改。
创建store对象时,如果使用
strict: true
开启了严格模式,修改state中的数据会报错。如果要修改,使用mutations
进行数据修改
mutations
中定义的方法,参数有state
和value
。其中state
代表store
中的state
数据区,value
则代表传过来的实参。使用this.$store.commit("setTabbarShow", false)
可触发getters可理解为vue组件中的计算属性,参数有
state
。其中state
代表store
中的state
数据区。使用this.$store.getters.getTabbarShow
可访问
actions
可用于执行异步方法,参数有context
。其中context
代表store
对象。使用this.$store.dispatch("requestData")
可触发。虽然actions
是store
对象的属性,但是修改state
中的值,应仍旧使用commit
触发mutation
状态管理模式
状态自管理应用包含以下几个部分
状态,驱动应用的数据源;
视图,以声明方式将状态映射到视图;
操作,响应在视图上的用户输入导致的状态变化。
state
定义
javascript
import { createStore } from 'vuex'
export default createStore({
state: {
isTabbarShow: true
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
编程式引用
注意
在路由守卫
场景下,beforeRouteEnter
中,无法使用this.$store
引用vuex
此时可通过import
导入store
模块解决
javascript
import store from '@/store'
export default {
name: "ProfileView",
beforeRouteEnter(to, from) {
if (!store.state.isLogin) {
return {name: 'login', query: {to: 'profile'}};
}
}
}
声明式引用
html
<ul v-if="$store.state.isTabbarShow">
</ul>
mutations
javascript
import { createStore } from 'vuex'
export default createStore({
strict: true,
state: {
isTabbarShow: true
},
getters: {
},
mutations: {
setTabbarShow(state, value) {
state.isTabbarShow = value;
}
},
actions: {
},
modules: {
}
})
vue
<template>
<div>detail...</div>
</template>
<script>
export default {
name: "MovieDetailView",
mounted() {
this.$store.commit("setTabbarShow", false);
},
unmounted() {
this.$store.commit("setTabbarShow", true);
}
}
</script>
<style scoped>
</style>
getters
注意
虽然getters
的本质是一个方法,但是在引用时不能使用小括号,这也就意味着getters
不能有任何参数
javascript
import { createStore } from 'vuex'
export default createStore({
strict: true,
state: {
isTabbarShow: true
},
getters: {
getTabbarShow(state) {
return state.isTabbarShow;
}
},
mutations: {
setTabbarShow(state, value) {
state.isTabbarShow = value;
}
},
actions: {
},
modules: {
}
})
vue
<ul v-if="$store.getters.getTabbarShow">
</ul>
actions
javascript
import { createStore } from 'vuex'
import axios from 'axios'
export default createStore({
strict: true,
state: {
isTabbarShow: true,
hot: []
},
getters: {
getTabbarShow(state) {
return state.isTabbarShow;
}
},
mutations: {
setTabbarShow(state, value) {
state.isTabbarShow = value;
},
setHot(state, value) {
state.hot = value;
}
},
actions: {
requestData(context) {
axios.get('/maoyan/api/mmdb/movie/v3/list/hot.json?ct=%E5%A4%A7%E8%BF%9E&ci=65&channelId=4')
.then(res => {
context.commit('setHot', res.data.data.hot);
})
}
},
modules: {
}
})
vue
<template>
<div>
正在热映
<ul v-if="$store.state.isTabbarShow">
<li @click="routejump">编程式路由...</li>
<li v-for="movie of $store.state.hot" :key="movie.nm">
<router-link :to='{name: "moviedetail", query: {id: movie.id}}'>
{{ movie.nm }}
</router-link>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: "HotView",
data() {
return {
hot: []
}
},
methods: {
routejump() {
this.$router.push({
name: 'moviedetail',
query: {id: '这个是编程式路由传的参'}
});
}
},
mounted() {
this.$store.dispatch('requestData');
}
}
</script>
<style scoped>
</style>
mapState风格【了解】
https://vuex.vuejs.org/zh/guide/state.html#mapstate-辅助函数
mutations常量风格【了解】
https://vuex.vuejs.org/zh/guide/mutations.html#使用常量替代-mutation-事件类型
pinia
定义
注意
以下代码已存在
- store/counter.js
js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
使用
vue
<template>
<h1>count: {{ counterStore.count }}</h1>
<h1>double count: {{ counterStore.doubleCount }}</h1>
<button @click="counterStore.increment()"> +1 </button>
</template>
<script setup>
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
</script>
与vuex对比
ref <==> state
computed <==> getters
function <==> actions/mutations
总结
- 内部指令
tex
v-if
v-else-if
v-else
v-show
v-for
v-text
v-html
v-model
v-bind/:
v-on/@
{{}}
v-slot <- 只在template标签才能够使用的指令,用于指定插槽名
- 全局 API
tex
el <- 挂载到哪个元素上,在vue3时不再建议使用
data <- 数据区,vue2时是obj类型;vue3时是function类型
methods <- 方法区,obj类型
computed <- 计算属性,obj类型,只会初始化一次,而且是第一次使用时才会计算。
// 例外情况为:假如在计算属性方法内部,有其他属性参与运算
// 则只要属性变更了,计算属性方法就会再执行一次,而不是使用缓存值
watch <- 监听器,obj类型,监听某个属性的变化
template <- 用于替换元素的outerHTML,obj类型
components <- 定义组件,obj类型
props <- 用于父组件用v-bind传输属性的,obj类型
name <- 组件名称,String类型
钩子函数 <- 一共有8个
路由守卫函数 <- 一共3个(beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave)
// 参数有2个,分别是to和from,
// 如果想要跳转到其他的页面,可返回一个对象
// 此对象有两个属性: name、params或query...
- 内置属性
tex
$emit
$on
$refs
$router.push({name: "...", params: {...}, query: {...})
$router.replace({name: "...", params: {...}, query: {...})
$router.go(数字)
$route.params
$route.query
$store.state.prop
$store.getters.prop
$store.commit("fn", value)
$store.dispatch("fn")
- 新标签
tex
<keep-alive>
<component :is="activeComponent"></component>
</keep-alive>
<template v-slot:someSlot></template>
<router-view></router-view>
<router-link to="/" custom v-slot="{ navigate }" active-class='active'>
<button @click="navigate" @keypress.enter="navigate" role="link">
Go to Home
</button>
</router-link>
附录
IDEA窗口禁止运行脚本的问题
以管理员身份运行PowerShell
执行以下命令,根据提示,输入
A
,回车
set-ExecutionPolicy RemoteSigned
日期格式转换工具monent
安装
bash
pnpm i moment
使用
vue
<template>
<el-form-item label="出生日期">
<el-date-picker
v-model="form.birthday"
type="date"
placeholder="选择日期"
/>
</el-form-item>
</template>
<script>
let d1 = moment(this.form.birthday).format("yyyy-MM-DD");
let d2 = moment().format("yyyy-MM-DD");
</script>
参考资料
vue3知识地图
参考网站
https://cn.vuejs.org/guide/introduction.html
https://router.vuejs.org/zh/guide/#html
https://router.vuejs.org/zh/guide/essentials/history-mode.html
https://cli.vuejs.org/zh/config/#devserver-proxy
https://space.bilibili.com/165659472