1、概述
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架,发布于 2014 年 2 月。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库(如:vue-router:跳转,vue-resource:通信,vuex:管理)或既有项目整合。
2、第一个Vue程序
以下基于vue3的语法
- 用下面的代码可以实现CDN导入
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 在头文件的内容里导入vue的CDN-->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<!--创建一个可供使用的div-->
<body>
<div id="app">
{{msg}}
</div>
<script>
<!-- 这个写法是选项式挂载,你也可以用解构赋值:const { createApp } = Vue-->
// 或者是ES6模块:import { createApp } from 'vue',注意这个写法只在
<!-- <script type="module">或者自定义语法糖的setup的《script》使用-->
const app=Vue.createApp({
data() {
return {
msg: 'Hello Vue!'
}
}
}).mount('#app');//mount挂载到app上
</script>
</body>
</html>
Vue app是干嘛的?
- 一个vueapp存了一些数据和操作,它挂在哪个标签哪个就有这些数据和操作
- Vue 应用就像一个“数据+操作的包”,挂载到哪个标签,哪个标签就获得这些能力和数据。
// 挂载到不同的标签
app1.mount('#app1') // 区域1 获得了这些数据和方法
app2.mount('#app2') // 区域2 也获得了这些数据和方法
---------------------------------------------
你可以这么做
const app=createApp({});
app.data(){
};
app.mount("#div");
MVVM模式(Model-View-ViewModel)
MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model),有几大好处
- 低耦合:视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
- 可复用:你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。
- 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
- 可测试:界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。
- 上面的图体现了mv,如何体现vm(ViewModel)呢,如下图 数据变化自动更新视图,视图变化自动更新数据,数据逻辑和视图分离,通过 ViewModel 桥接
- View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
3、Vue语法
1、V-bind
v-bind 是 Vue 中用于动态绑定属性的指令,它的核心作用是将数据(JavaScript 表达式)与 HTML 属性的值关联起来。
简单理解:HTML 的静态属性(如 src、href、class、style 等)的值通常是固定的字符串,而 v-bind 可以让这些属性的值动态化,从 Vue 实例的数据中获取。
<!-- 静态写法:图片地址是固定的 -->
<img src="logo.png">
<!-- 动态写法:图片地址从数据中获取 -->
<img v-bind:src="imageUrl">
<!-- 或简写为 -->
<img :src="imageUrl">
- 静态写法:
<img src="logo.png">就像是用笔在纸上画了一个图案。这个图案是固定的,除非你拿橡皮擦掉重画(修改HTML文件),否则它永远不变。 - 动态写法:
<img :src="imageUrl">就像是把这个图案和一个“遥控器”绑定在了一起。你不需要去碰那张纸,只需要按下遥控器(改变imageUrl的值),纸上的图案就会自动改变。
2、v-if/v-else
- 和Java里的if,else-if,else逻辑一样
<div id="app">
<h1 v-if="msg=='a'">a</h1>
<h1 v-else-if="msg=='b'">b</h1>
<h1 v-else-if="msg=='c'">c</h1>
<h1 v-else>msg为false</h1>
</div>
<script>
const app=Vue.createApp({
data() {
return {
msg: "a"
}
}
}).mount('#app');//mount挂载到app上
</script>
3、v-for遍历
- vue如何动态操作数组?
| 方法 | 作用 | 是否改变原数组 | 触发视图更新 | 使用示例 |
|---|---|---|---|---|
| push() | 在数组末尾添加一个或多个元素 | ✅ 是 | ✅ 是 | this.items.push({msg: "d"}) |
| pop() | 删除数组最后一个元素 | ✅ 是 | ✅ 是 | this.items.pop() |
| shift() | 删除数组第一个元素 | ✅ 是 | ✅ 是 | this.items.shift() |
| unshift() | 在数组开头添加一个或多个元素 | ✅ 是 | ✅ 是 | this.items.unshift({msg: "新"}) |
| splice() | 删除/插入/替换元素(万能方法) | ✅ 是 | ✅ 是 | 见下方详解 |
| sort() | 对数组元素进行排序 | ✅ 是 | ✅ 是 | this.items.sort((a,b) => a.id - b.id) |
| reverse() | 反转数组元素的顺序 | ✅ 是 | ✅ 是 | this.items.reverse() |
| ============================================================================ |
| 用法 | 语法 | 示例 | 效果 |
|---|---|---|---|
| 删除 | splice(起始索引, 删除个数) | this.items.splice(1, 2) | 从索引1开始删除2个元素 |
| 插入 | splice(起始索引, 0, 插入的元素) | this.items.splice(2, 0, {msg:"新"}) | 在索引2的位置插入新元素 |
| 替换 | splice(起始索引, 删除个数, 插入的元素) | this.items.splice(1, 1, {msg:"替换"}) | 删除索引1的1个元素,再插入新元素 |
4、事件绑定
<body>
<div id="app">
<button v-on:click=sayHi>click me</button>
<!-- 这个是简化的写法-->
<button @click="sayHi2">click me</button>
</div>
<script>
const app=Vue.createApp({
data() {
return {
msg:"Hello World"
}
},
methods:{
// 这是简化的写法
sayHi (){
alert(this.msg);
},
// 这是传统写法
sayHi2:function (){
alert(this.msg);
}
}
}).mount('#app');//mount挂载到app上
</script>
鼠标事件
| 事件 | 触发时机 | 使用示例 | 说明 |
|---|---|---|---|
| click | 点击元素 | @click="handleClick" | 最常用,鼠标左键点击 |
| dblclick | 双击元素 | @dblclick="handleDblClick" | 鼠标双击 |
| mouseenter | 鼠标进入元素 | @mouseenter="handleEnter" | 进入元素区域(不冒泡) |
| mouseleave | 鼠标离开元素 | @mouseleave="handleLeave" | 离开元素区域(不冒泡) |
| mouseover | 鼠标悬停 | @mouseover="handleOver" | 进入元素或其子元素 |
| mouseout | 鼠标移出 | @mouseout="handleOut" | 离开元素或其子元素 |
| mousemove | 鼠标移动 | @mousemove="handleMove" | 在元素上移动时连续触发 |
| mousedown | 按下鼠标 | @mousedown="handleDown" | 在元素上按下任意鼠标键 |
| mouseup | 松开鼠标 | @mouseup="handleUp" | 在元素上松开鼠标键 |
| contextmenu | 右键菜单 | @contextmenu.prevent="handleMenu" | 鼠标右键点击(常用禁用右键) |
键盘事件
| 事件 | 触发时机 | 使用示例 | 说明 |
|---|---|---|---|
| keydown | 按下键盘 | @keydown="handleKey" | 按下任意键 |
| keyup | 松开键盘 | @keyup="handleKeyUp" | 松开任意键 |
| keypress | 按下字符键 | @keypress="handlePress" | 按下产生字符的键(已废弃,不推荐) |
5、v-model
v-model 是 Vue 中用于双向数据绑定的核心指令,它实现了表单输入和应用状态之间的自动同步。它会自动把data数据与表单元素的value关联起来,比如单选框的value,选中了即把value给了data
v-bind 是单向绑定(数据 → 视图),v-model 是双向绑定(数据 ⇄ 视图),即 v-model = v-bind + input 事件监听。
<div id="app">
<input type="text" v-model="msg">{{msg}}
</div>
<script>
const app=Vue.createApp({
data() {
return {
msg:"Hello World"
}
},
}).mount('#app');//mount挂载到app上
</script>
4、组件
什么是组件
组件是可复用的 Vue 实例,说白了就是一组可以重复使用的模板,跟 JSTL 的自定义标签、Thymeleaf 的 th:fragment 等框架有着异曲同工之妙。通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。浏览时总是一个组件在变化,其他组件不变
<body>
<div id="app">
<app1></app1>
</div>
<script>
const app = Vue.createApp({});
app.component("app1", {
template: `<h1>这是一个组件</h1>`
});
app.mount("#app")
</script>
</body>
下面这个代码体现了props数据传递与根组件是老板,子组件是员工的关系
根组件是数据源(老板),子组件是执行者(员工),props 是传递数据的通道(任务单)。
老板(根组件)通过 v-for 循环派发任务,用 :item="任务内容" 把数据递给员工,员工(子组件)通过 props: ['item'] 接收,然后用 template 展示出来。数据从上往下单向流动,根组件管数据,子组件管显示。
<body>
<div id="app">
<app1
v-for="(item, index) in items"
:item="item"
></app1>
</div>
<script>
const app = Vue.createApp({
// 数据放根组件
data(){
return{
items: [
"java",
"javascript",
"html"
]
}
}
});
app.component("app1", {
props: ['item'],
template: '<li>{{item}}</li>',
});
app.mount("#app")
</script>
</body>
5、AXIOS异步通信
Axios 是一个开源的可以用在浏览器端和 NodeJS 的异步通信框架,她的主要作用就是实现 AJAX 异步通信,其功能特点如下:
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API [JS中链式编程]
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF(跨站请求伪造)
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// 1. 你写的请求代码
const loginData = {
username: 'zhangsan',
password: 'mypassword123'
};
axios.post('/api/login', loginData) // 请求后端接口
.then(response => {
// 3. 后端返回数据,你在这里处理
if (response.data.code === 200) {
console.log('登录成功!用户token是:', response.data.data.token);
}
});
@RestController
@RequestMapping("/api")
public class LoginController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
String username = request.getUsername();
String password = request.getPassword();
if ("zhangsan".equals(username) && "mypassword123".equals(password)) {
String token = generateTokenForUser(username);
return ResponseEntity.ok(new LoginResponse(200, "登录成功", token));
} else {
return ResponseEntity.status(401).body(new ErrorResponse(401, "用户名或密码错误"));
}
}
}
Axios 语法
// GET 请求
axios.get(url)
axios.get(url, { params: { 参数名: 值 } })
// POST 请求
axios.post(url, { 参数名: 值 })
// PUT 请求(修改)
axios.put(url, { 参数名: 值 })
// DELETE 请求(删除)
axios.delete(url)
// 写法1:Promise(.then)
axios.get('/user').then(response => {
console.log(response.data) // 拿数据
}).catch(error => {
console.log(error) // 处理错误
})
// 写法2:async/await(推荐)
async function getData() {
try {
const response = await axios.get('/user')
console.log(response.data)
} catch (error) {
console.log(error)
}
}
xios.get(网址).then(拿数据) 或 await axios.get(网址),就这么简单。
6、计算属性 computed
计算属性是通过已有数据计算出来的新数据,它最大的特点是“带缓存”——只要依赖的数据没变,它就不会重新计算,直接返回上次的结果。 比如 fullName 依赖 firstName 和 lastName,只要这两个没变,多次使用 fullName 也不会重复执行逻辑。这跟方法不一样,方法每次调用都会重新执行。所以计算属性适合做数据派生(如全名、总价、过滤列表),方法适合做实时获取(如时间戳、随机数)。
<div id="app">
<p>{{currentTime()}}</p>
<p>{{currentTime1}}</p>
</div>
<script>
const app=Vue.createApp({
methods: {
currentTime(){
return Date.now();
}
},
computed:{
currentTime1(){
return Date.now();
}
}
});
const a=app.mount("#app")
//你这里挂载才是实例化了vue app,上面相当于定义对象,所以调用方法是a.方法。
</script>
</body>
你可以看到,这两个方式返回的时间戳一致,但是调用产生的效果大不一样
{{currentTime()}}:每次刷新页面或重新渲染,时间戳都会变{{currentTime1}}:时间戳固定不变(因为被缓存了)- currentTime1是作为一个属性被访问的,没有括号
- 计算属性就是 Vue 实例的一个属性
7、插槽 slot
插槽(slot)是子组件里的占位符,父组件可以在组件标签中间写内容,这些内容会自动替换到子组件的 <slot> 位置。 比如子组件模板写了 <slot></slot>,父组件用 <my-button>点我</my-button>,最终渲染出来就是“点我”出现在子组件那个位置。它的作用是让子组件的部分内容由父组件来决定,实现组件的灵活复用。
- 父组件就是调用子组件的组件,下面的例子里body里的组件调用带有slot的组件,所以就是父组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<my-slot>
<button>插入按钮</button>
</my-slot>
<my-slot>
<h3>插入字</h3>
</my-slot>
<my-slot>
<test-slots>
</test-slots>
</my-slot>
</div>
<script>
const app=Vue.createApp({
components:{
mySlot:{
template:`
<slot></slot>
<h2>这是我的插槽 </h2>
`
},
testSlots:{
template:`
<p>插入一个模板</p>`
}
}
});
const a=app.mount("#app")
</script>
</body>
</html>
8、自定义事件
子组件:
<template>
<button @click="sendData">点我传值</button>
</template>
<script>
export default {
methods: {
sendData() {
this.$emit('my-event', '来自子组件的数据')
}
}
}
</script>
父组件:
<template>
<Child @my-event="handleEvent" />
</template>
<script>
import Child from './Child.vue'
export default {
components: { Child },
methods: {
handleEvent(data) {
console.log('接收到的数据:', data)
}
}
}
</script>
this.$emit('my-event', 数据)
它的意思是:触发一个叫 my-event 的自定义事件,并带上数据
my-event 不是父组件的方法名,而是“事件名”
真正的方法是你绑定的 handleEvent
9、Vite
Vite = 新一代工具
≈(脚手架 + 构建工具)的组合
基本取代了:Vue CLI + Webpack 这一整套
Vite 用于快速生成一个 vue 的项目模板;
预先定义好的目录结构及基础代码,就好比咱们在创建 Maven 项目时可以选择创建一个骨架项目,这个骨架项目就是脚手架,我们的开发更加的快速;
主要的功能:
- 统一的目录结构
- 本地调试
- 热部署
- 单元测试
- 集成打包上线
1.安装nodejs
nodejs是让 JavaScript 可以在“浏览器之外(比如服务器)运行”的环境
nodejs会自动添加系统变量,自带依赖下载工具npm
为npm配置国内镜像
npm config set registry https://registry.npmmirror.com
2、创建一个模板vue项目
- cd到你想创建vue项目的目录
- 执行创建命令
npm create vite@latest
- 选择项目名,语言,模板等
- 安装依赖
npm install
- 运行
npm run dev
3、vue-router
Vue Router 是 Vue 官方的路由管理库,用来在单页面应用(SPA)中根据不同的 URL(比如 /home、/about)切换显示不同的组件,而不需要刷新整个页面;简单说,它就是帮你实现“页面跳转”的工具,让前端应用像多页面网站一样导航,但本质仍然是一个页面在动态切换内容。
在项目目录执行:
npm install vue-router
1、创建router
新建一个router目录来存放所有的路由,一个index.js来配置所有的路由,这是约定俗成的实践
import {createRouter, createMemoryHistory, createWebHistory} from "vue-router";
import App from "../App.vue";
const routes=[
{path:'/',component:App},
{path: '/about',component: ()=>import('../components/About.vue')}
]
export default createRouter(
{
history:createWebHistory(),
routes
}
)
- 嵌套路由
- 父组件必须有
<router-view>
- 父组件必须有
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/user',
component: () => import('../views/User.vue'),
children: [
{
path: 'profile',
component: () => import('../views/Profile.vue')
},
{
path: 'orders',
component: () => import('../views/Orders.vue')
}
]
}
]
export default createRouter({
history: createWebHistory(),
routes
})
- history 决定了 URL 长什么样 + 路由怎么工作的
| 模式 | URL 样子 | 刷新是否 404 | 使用场景 |
|---|
createWebHistory | /about | ⚠️ 可能 | 正常项目(推荐) |
|---|
createWebHashHistory | /#/about | ❌ 不会 | 简单部署 / 静态站 |
|---|
createMemoryHistory | 无,不变 | 不涉及 | 测试 / SSR |
|---|
2、在main.js中注册
main.js 不是类似 XML 的“配置文件” ,它更像是程序的入口代码(启动器),main.js 是启动 Vue 应用的“总开关”,负责创建、配置、并挂载整个应用
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
const app=createApp(App)
app.use(router)
app.mount('#app')
3、在页面上显示
- 建一个跳转目的地
<script setup lang="ts">
</script>
<template>
<h1>关于</h1>
</template>
<style scoped>
</style>
- 配置跳转
<script setup>
</script>
<template>
<router-link to="/about">关于</router-link>
<br>
<router-view/>
</template>
如果想直接跳转a标签就可以实现
4、结合ElementUi组件库
1、安装element-ui
npm install element-plus --save