一、Vue简介
Vue.js - 渐进式 JavaScript 框架 | Vue.js
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
上面的示例展示了 Vue 的两个核心功能:
- 声明式渲染:Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。
- 响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM。
Vue特性
- 遵循 MVVM 模式
- 编码简洁,体积小,运行效率高,适合移动/PC端开发
- 它本身只关注 UI,可以引入其它第三方库开发项目
- 采用组件化模式,提高代码复用率、且让代码更好维护
Vue的作者参考了MVVM模型,设计出了Vue
MVVM模型
- M:模型(Model) :data中的数据
- V:视图(View) :模板代码
- VM:视图模型(ViewModel):Vue实例
MVC
二、入门案例
创建Vue实例
1:准备容器
2:引包
3:创建Vue实例newVue()
4:指定配置项----渲染数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">//准备容器
{{ message }} <br/>
<button @click="count++">{{count}}</button>
</div>
<div>
<button @click="count++">{{count}}</button>
</div>
<!-- CND引入在线的vue-->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const {createApp, ref} = Vue
/* 创建vue的应用实例 */
createApp({
setup() {
const message = ref('Hello vue!')
const count = ref(0)
return {
message, count
}
}
}).mount('#app') //id为app的div被vue接管
</script>
</body>
</html>
三、Node.js
简单的说 Node.js 就是运行在服务端的 JavaScript。不需要浏览器,直接使用node.js运行JavaScript代码。
Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。
四、NPM
NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;
也是Node.js的包管理工具,相当于前端的Maven
- node -v 检查版本
- 配置npm 使用淘宝镜像
npm config set registry http://registry.npm.taobao.org/
五、基于vite创建工程
vite 是新一代前端构建工具,官网地址:Vite中文网,vite的优势如下:
- 轻量快速的热重载(HMR),能实现极速的服务启动。
- 对 TypeScript、JSX、CSS 等支持开箱即用。
- 真正的按需编译,不再等待整个应用编译完成。
npm create vue@latest
createApp(App)函数创建vue实例,并且调用mount('#app')来控制html中元素,#app就是index.html中div的id值,最终vue会接管默认首页index.html中的div。
import App from './App.vue',导入App.vue并起了别名App,调用createApp(App)时候作为参数传递过去,最终App.vue里面有什么内容首页的div里面就展示什么内容,实际开发中重点关注.vue里面内容怎么写从而控制页面的展示,默认首页index.html和main.js里面代码大部分都是自动生成的,一般不需要修改。
*.vue是Vue项目中的组件文件,在Vue项目中也称为单文件组件(SFC,Single-File Components)
Vue 的单文件组件会将一个组件的逻辑 (JS),模板 (HTML) 和样式 (CSS) 封装在同一个文件里(*.vue)
六、Vue2 OptionsAPI 与Vue3 CompositionAPI区别
vue2于vue3区别不vue3加了ref:响应式数据
Vue2的API设计是Options(配置)风格的。
Vue3的API设计是Composition(组合)风格的。
Options API 的弊端
Options类型的 API,数据、方法、计算属性等,是分散在:data、methods、computed中的,若想新增或者修改一个需求,就需要分别修改:data、methods、computed,不便于维护和复用。
选项式API,可以用包含多个选项的对象来描述组件的逻辑,如:data,methods,mounted等。
组合式API:
- setup:是一个标识,告诉Vue需要进行一些处理,让我们可以更简洁的使用组合式API
- ref():接收一个内部值,返回一个响应式的ref对象,此对象只有一个指向内部值的属性 value
- onMounted():在组合式API中的钩子方法,注册一个回调函数,在组件挂载完成后执行
Composition API 的优势
可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。
vue2:
components/Person.vue
<template>
<div class="person">
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script>
export default {
name: 'Person',
data() {
return {
name: '张三',
age: 18,
}
},
methods: {
// 修改姓名
changeName() {
this.name = 'zhang-san'
},
// 修改年龄
changeAge() {
this.age += 1
}
}
}
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
</style>
<script>
export default {
name: "Person",
setup() {
console.log(this) //setup中的this是undefined,Vue3在弱化this了
// 数据,原来是写在data中的,此时的name、age、tel都不是响应式的数据
let name = '张三1';
let age = 19;
//方法
function changeName() {
//this.name = 'zhang-san';
name = 'zhang-san'; //注意:这样修改name,页面是没有变化的
console.log(name);//name确实改了,但name不是响应式的
}
function changeAge() {
//this.age += 1;
age += 1; //注意:这样修改age,页面是没有变化的
console.log(age) //age确实改了,但age不是响应式的
}
//将数据和方法交出去,模版中才能使用
return {name, age, changeName, changeAge};
}
}
</script>
<template>
<div class="person">
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 数据,原来是写在data中的,此时的name、age、tel都不是响应式的数据
let name = ref('张三1');
let age = ref(19);
//方法
function changeName() {
//this.name = 'zhang-san';
name.value = 'zhang-san';
console.log(name);
}
function changeAge() {
//this.age += 1;
age.value += 1;
console.log(age)
}
</script>
<template>
<div class="person">
<h2>姓名: {{name}}</h2>
<h2>年龄: {{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
七、setup 概述
setup是Vue3中一个新的配置项,值是一个函数,它是 Composition API “表演的舞台”,组件中所用到的:数据、方法、计算属性、监视......等等,均配置在setup中。setup() 钩子是在组件中使用组合式 API 的入口。
特点如下:
- setup函数返回的对象中的内容,可直接在模板中使用。
- setup中访问this是undefined。
- setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。
我们可以使用响应式 API 来声明响应式的状态,在 setup() 函数中返回的对象会暴露给模板和组件实例。
其他的选项也可以通过组件实例来获取 setup() 暴露的属性
setup函数有一个语法糖,这个语法糖,可以让我们把setup独立出去
八、响应式基础
响应式:数据改页面跟着变
1. ref用来定义:基本类型数据、对象类型数据;
2. reactive用来定义:对象类型数据
1、ref()
定义响应式变量
import { ref } from 'vue'
const count = ref(0)
ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
要在组件模板中访问 ref,请从组件的 setup() 函数中声明并返回它们:
注意,在模板中使用 ref 时,我们不需要附加 .value
为了方便起见,当在模板中使用时,ref 会自动解包
<script setup> import { ref } from 'vue' const count = ref(0) function increment() { count.value++ } </script> <template> <button @click="increment">{{ count }}</button> </template>
2、reactive()
创建:对象类型的响应式数据,修改数据时候不需要使用.value
reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)
/car = {brand:'奥拓',price:1} //这么写页面不更新的
//car = reactive({brand:'奥拓',price:1}) //这么写页面不更新的
Object.assign(car, {brand:'奥拓',price:1})
<script setup>
import { ref, reactive } from 'vue'
let age = ref(18)
//let car = {brand: '奔驰', price: 200}
let car = reactive({brand: '奔驰', price: 200})
function changeAge() {
age.value++;
}
function changeCarPrice() {
car.price += 100
console.log(car)
}
function changeCar() {
//car = {brand:'奥拓',price:1} //这么写页面不更新的
//car = reactive({brand:'奥拓',price:1}) //这么写页面不更新的
Object.assign(car, {brand:'奥拓',price:1})
console.log(car)
}
</script>
<template>
<div class="person">
<h2>年龄:{{age}}</h2>
<h2>汽车信息:一台{{car.brand}},价格{{car.price}}</h2>
<button @click="changeAge">改变年龄</button>
<button @click="changeCarPrice">改变年龄</button>
<button @click="changeCar">改变Car</button>
</div>
</template>
3、toRefs 与 toRef
作用:将一个响应式对象中的每一个属性,转换为ref对象。
备注:toRefs与toRef功能一致,但toRefs可以批量转换。
import {ref,reactive,toRefs,toRef} from 'vue'
let person = reactive({name:'张三', age:18, gender:'男'})
// 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
let {name,gender} = toRefs(person)
九、常用指令
指令 | 作用 |
v-for | 列表渲染,遍历容器的元素或者对象的属性 |
v-bind | 为HTML标签绑定属性值,如设置 href , css样式等 |
v-if/v-else-if/v-else | 条件性的渲染某元素,判定为true时渲染,否则不渲染 |
v-show | 根据条件展示某元素,区别在于切换的是display属性的值 |
v-model | 在表单元素上创建双向数据绑定 |
v-on | 为HTML标签绑定事件 |
v-for
作用:列表渲染,遍历容器的元素或者对象的属性
语法: v-for = "(item,index) in items"
参数说明:
- items 为遍历的数组
- item 为遍历出来的元素
- index 为索引/下标,从0开始 ;可以省略,省略index语法: v-for = "item in items"
遍历的数组,必须在data中定义; 要想让哪个标签循环展示多次,就在哪个标签上使用 v-for 指令。
v-bind
作用:动态为HTML标签绑定属性值,如设置href,src,style样式等。
语法:v-bind:属性名="属性值"
简化::属性名="属性值"
v-if & v-show
作用:这两类指令,都是用来控制元素的显示与隐藏的
v-if
- 语法:v-if="表达式",表达式值为 true,显示;false,隐藏
- 其它:可以配合 v-else-if / v-else 进行链式调用条件判断
- 原理:基于条件判断,来控制创建或移除元素节点(条件渲染)
- 场景:要么显示,要么不显示,不频繁切换的场景
v-show
- 语法:v-show="表达式",表达式值为 true,显示;false,隐藏
- 原理:基于CSS样式display:'none'来控制显示与隐藏
- 场景:频繁切换显示隐藏的场景
v-on
作用:为html标签绑定事件
语法: v-on:事件名="函数名"
简写为 @事件名="函数名"
十二、标签的 ref 属性
- 用在普通DOM标签上,获取的是DOM节点。
- 用在组件标签上,获取的是组件实例对象。
<template>
<div class="person">
<h1>中国</h1>
<h2 ref="title">北京</h2>
<button @click="show">点我输出h2这个元素</button>
</div>
</template>
<!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
<script setup name="Person">
import {ref} from 'vue'
// 创建一个title,用于存储ref标记的内容
let title = ref()
function show() {
console.log(title.value)
}
let name = ref('张三')
let age = ref(18)
// 使用defineExpose将组件中的数据交给外部
defineExpose({name, age})
</script>
子组件Person.vue中要使用defineExpose暴露内容
父组件App.vue
<template>
<Person ref="per"/>
<button @click="show">测试</button>
</template>
<script setup lang="ts">
import Person from './components/Person.vue'
import {ref} from 'vue'
let per = ref()
function show() {
console.log(per.value.name)
console.log(per.value.age)
}
</script>
十三、Vue生命周期
组件的一生
概念:Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子
生命周期整体分为四个阶段,分别是:
- 创建 beforeCreate、created setup函数代替了这两个函数
- 挂载 beforeMount、mounted
- 更新 beforeUpd ate、updated
- 卸载 beforeUnmount、unmounted
每个阶段都有两个钩子,一前一后。
常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)
十四、路由(重要)
多页面应用中index.html可以跳转到product.html、category.html、order.html,order.html又可以跳转到product.html,这种页面之间跳来跳去就叫多页面应用。
多页面的缺陷:页面来回跳转,页面是抖动的。
单页面应用只有一个页面,无法跳转,页面不会刷新。
什么是路由?
1、一个路由就是一组映射关系(key - value)
2、key为路径,value是component,用于展示页面内容
工作过程:当浏览器的路径改变时,对应的组件就会显示
前端路由工作原理:
- 用户点击了页面上的路由链接
- 导致URL地址栏中的地址发生变化
- 前端路由监听到了地址的变化
- 前端路由把当前地址对应的组件渲染到浏览器中
npm i vue-router
import './assets/bootstrap.css'
// 引入createApp用于创建应用
import {createApp} from 'vue'
// 引入App根组件
import App from './App.vue'
// 引入路由器方式1
//import router from './router/index.js'
// 引入路由器方式2 文件名如果是index.js可以省略
//import router from './router'
// 引入路由器方式3
import router from '@/router'
// 创建一个应用
const app = createApp(App)
// 使用路由器
app.use(router)
// 挂载整个应用到app容器中
app.mount('#app')
//createApp(App).use(router).mount('#app')
router/index.js
//创建一个路由器,并暴露出去
//第一步:引入createRouter、组件
import {createRouter, createWebHistory} from "vue-router";
//引入组件
// import Home from "../components/Home.vue";
// import About from "../components/About.vue";
import Home from "@/components/Home.vue";
import About from "@/components/About.vue";
//第二步:创建路由器
const router = createRouter({
history: createWebHistory(), //路由器工作模式
routes: [ //一个一个路由规则
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
})
//暴露出去router
export default router
十七、axios(重要)
Axios,是一个基于promise的网络请求库,作用于node.js和浏览器中
vue中使用axios发送网络请求
安装axios : npm install axios
<template>
<div class="back">
名字:<input type="text" v-model="searchCondition.name">
年龄:<input type="text" v-model="searchCondition.age">
<button @click="search">搜索</button>
<table border="1" cellspacing="0">
<tr>
<td>ID</td>
<td>名字</td>
<td>年龄</td>
</tr>
<tr v-for="admin of adminList" :key="admin.id">
<td>{{ admin.id }}</td>
<td>{{ admin.name }}</td>
<td>{{ admin.age }}</td>
</tr>
</table>
</div>
</template>
<script setup name="Admin">
import {reactive, ref} from 'vue'
import axios from 'axios'
let adminList = ref([])
//搜索条件
let searchCondition = reactive({
name: '',
age: ''
})
//发送异步请求,获取所有文章数据
/*axios.get('http://localhost:8080/admin/list').then(result => {
console.log(result)
adminList.value = result.data.data
}).catch(error => {
})*/
search()
function search() {
axios.get('http://localhost:8080/admin/list', {params: searchCondition}).then(response => {
console.log(response)
adminList.value = response.data.data
})
}
</script>
上面代码的问题是代码难以复用,接口调用的js代码一般会封装到.js文件中, 并且以函数的形式暴露给外部
api/admin.js
import axios from 'axios'
// 将所有的针对admin的请求,都封装到一个对象中
const adminApi = {
list(conditions) {
return axios.get('http://localhost:8080/admin/list', {params: conditions})
.then((response) => {
console.log(response)
return response.data
})
}
}
export default adminApi
Admin.vue
<script setup name="Admin">
import {reactive, ref} from 'vue'
import adminApi from '@/api/admin'
let adminList = ref([])
//搜索条件
let searchCondition = reactive({
name: '',
age: ''
})
search()
function search() {
adminApi.list(searchCondition).then(result => {
adminList.value = result.data
})
}
</script>
十九、跨域问题
1、什么是跨域
跨域就是违背了浏览器同源策略,所谓同源策略(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。
当一个请求url的协议、域名、端口三者之间任意一个与当前页面不同即为跨域
http://www.test.com/ http://www.test.com/index.html 否 同源(协议、域名、端口号相同)
http://www.test.com/ https://www.test.com/index.html 跨域 协议不同(http/https)
http://www.test.com/ 百度一下,你就知道 跨域 主域名不同(test/baidu)
http://www.test.com/ http://blog.test.com/ 跨域 子域名不同(www/blog)
http://www.test.com:8080/ http://www.test.com:7001/ 跨域 端口号不同(8080/7001)
2、方案一:CROSS
CROS(跨域资源共享 Cross-Origin Resourse Sharing)
CORS(Cross-Origin Resource Sharing,跨域资源共享)方案,就是通过服务器设置一系列响应头来实现跨域,而客户端不需要做什么额外的事情。
3、方案二:代理
代理转发的原理:在前端服务和后端接口服务之间架设一个中间代理服务,它的地址保持和前端服务一致,那么:
1. 代理服务和前端服务之间由于协议域名端口三者统一不存在跨域问题,可以直接发送请求
2. 代理服务和后端服务之间由于并不经过浏览器没有同源策略的限制,可以直接发送请求
代理方式一:nginx反向代理
反向代理和代理很像,都是在客户端与服务端中间架设一个中间代理服务器,不同点在于代理是代表客户端向服务端发送请求,反向代理是代表服务端接收客户端发送的请求。
代理方式二:Vue配置代理
跨域是发生在浏览器端的,如果不通过浏览器通过服务端发起就没有跨域问题
const baseURL = 'http://localhsot:8080' 直接写这个就违反了同源策略,所以改为const baseURL = '/api'
utils/request.js
import axios from 'axios'
//定义一个变量,记录公共的前缀 baseURL
// const baseURL = 'http://localhost:8080'
const baseURL = 'api'
//这个service和axios具有相同的功能
const service = axios.create({baseURL})
vite.config.ts
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
proxy: {
'/api': { //请求路径中包含了/api
target: 'http://localhost:8080', //要更换的源,也就是后台服务的源
changeOrigin: true, //要不要更换源
rewrite: (path) => path.replace(/^\/api/, '') //路径重写/api替换为’’
}
}
}
})