Vue 学习
Vue 基础使用
Vue3 - 插值
举例:
js:
let fruits = {
apple:"苹果";
banana:"香蕉";
}
html:
{{fruits.apple}}
{{fruits.banana}}
Vue3 - 常用指令
官方网站:https://cn.vuejs.org/api/built-in-directives.html
指令:v-xxx
基础指令:v-html、v-text
事件指令:v-on 事件绑定
判断指令:v-if
循环指令:v-for ; 例如:<li v-for="(f,i) in fruits">{{f}}==>{{i}}</li>
Vue3 - 属性绑定
v-bind: 语法糖==> : 属性绑定
v-on: 语法糖 ==> @ 例如:@click 事件绑定
v-model: 表单的双向绑定
Vue3 - 响应式
ref()
1、把基本数据、对象类型使用 ref() 包装成响应式数据
2、修改值时 代理对象.value = ""
3、页面取值、属性绑定时 直接 {{变量名}}
const count = ref(0)
function increment(){
count.value++;
}
-----------------------------------------
reactive()
1、把对象类型使用 reactive()包装成响应式数据
2、修改值时 代理对象 = ""
3、页面取值、属性绑定时 直接 {{变量名}}
const animals = reactive({count:0})
function increments() {
animals.count++
}
最佳实践
1、我们该使用哪个函数?
答:可用 ref() 一把梭,也可以 ref() 包装基本数据,reactive()包装对象
2、使用 const 声明响应式常量
Vue3 - 插件安装
浏览器中的扩展程序中搜索 Vue.js devtools 并安装下载
Vue3 - 计算属性
js:
const count = ref(0)
const animals = reactive({count:0})
function increment() {
count.value++
}
function increments() {
animals.count++
}
const totalPrice = computed(()=>count.value * animals.count);
html:
<h4>总计:{{totalPrice}}</h4>
Vue 进阶使用
Vue3 - 监听 - watch()
// watch 监听
const num = ref(1);
watch(num,(value,oldValue,onCleanup)=>{
console.log("value",value);
console.log("oldValue",oldValue);
if(num.value >3){
alert("超出限购数量")
num.value = 3
}
})
<button @click="num++">加量 {{num}}</button>
Vue3 - 监听 - watchEffect()
const num = ref(1);
const count = ref(0);
watchEffect(()=>{
if(num>3){
alert("超出限购数量")
num.value = 3
}
if(count.value>3){
alert("数量达到最大")
count.value = 3
}
})
Vue3 - 生命周期
onBeforeMount()
元素在挂载到页面之前
onMounted()
元素已经挂载到页面
onBeforeUpdate()
页面元素更新之前
onUpdated()
页面元素更新完成
Vue3 - 组件传值 - 父传子
父传子 - Props
defineProps()
父组件给子组件传递值;
单向数据流效果:
- 父组件修改值,子组件发生变化
- 子组件修改值,父组件不会感知
Father 组件
<template>
<div style="background-color: #f9f9f9;">
<h2>我是父组件</h2>
<button @click="data.money+=10">充值</button>
<!-- <Son :money="money" :books="books" /> -->
<!-- <Son v-bind="data"></Son> -->
<Son :money="data.money" :books="data.books"/>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue';
import Son from './Son.vue';
//1、父传子,单项数据流
// const money = ref(100);
// const books = ref(["西游记","水浒传"]);
const data =reactive({
money:100,
books:["西游记","水浒传"],
})
Son 组件
<template>
<div style="background-color: blue; color: white;">
<h2>我是子组件</h2>
<div>账户:{{props.money}}</div>
<div>图书:
<li v-for="b in props.books">
{{b}}
</li>
</div>
</div>
</template>
<script setup>
//1、定义属性,只读
// let props = defineProps(['money',"books"]);
let props = defineProps({
money:{
type:Number,
required:true,
default:200
},
books:{
type:Array
}
})
Vue3 - 组件传值 - 子传父
**子传父 - Emit **
defineEmits()
props 用来父传子,emit 用来子传父
Emit 是一种事件机制,子组件发生事件,通知父亲即可
Father 组件
父组件感知事件和接受事件值 ==> @buy="moneyDown"
<template>
<div style="background-color: #f9f9f9;">
<h2>我是父组件</h2>
<button @click="data.money+=10">充值</button>
<!-- <Son v-bind="data"></Son> -->
<Son :money="data.money" @buy="moneyDown"/>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue';
import Son from './Son.vue';
const data =reactive({
money:100
})
function moneyDown(arg){
// alert("感知购买事件" + arg);
data.money +=arg;
}
</script>
Son 组件
子组件定义发生事件
<template>
<div style="background-color: blue; color: white;">
<h2>我是子组件</h2>
<div>账户:{{props.money}}</div>
<button @click="buy()">买东西</button>
</div>
</template>
<script setup>
//2、使用 emit:定义事件
let emits = defineEmits(['buy']);
function buy(){
emits('buy',-5);
}
</script>
Vue3 - 插槽
子组件可以使用插槽 接受页面模板内容。
v-slot
的简写为 #
具名插槽 <slot name="xxx" />
Father 组件
<template>
<div style="background-color: #f9f9f9;">
<Son v-bind="data">
<template v-slot:title>
哈哈SonSon
</template>
<template #btn>
买东西
</template>
</Son>
</div>
</template>
Son 组件
<template>
<div style="background-color: blue; color: white;">
<h2>我是子组件</h2>
<h3>
<slot name="title">
</slot>
</h3>
<button @click="buy()">
<slot name="btn" />
</button>
</div>
</template>
Vue3 - 路由
使用 Vue-Router
第一步,下载
# vue3下载 vue-router@4
npm install vue-router@4
第二步
//配置vue-router 并在 main.js中导入使用 router
import router from './router'
app.use(router);
//创建专属router的index.js
//定义路由表,举例
const routes = [
{ path: '/', component: Home },
{ path: '/hello', component: Hello},
{ path: '/haha', component: ()=> import('../views/HAHA.vue') },
]
//创建路由器
import {createRouter,createMemoryHistory} from 'vue-router';
const router = createRouter({
history: createMemoryHistory(),
routes,
})
//导出路由器
export default router;
第三步
Vue 实例使用router
第四步
配置 <router-link to="/">首页</router-link> 和 <router-view></router-view>
完成路由的跳转功能
路径参数
index.js
const routes = [
{ path: '/haha/:id', component: ()=> import('../views/HAHA.vue') },
]
HAHA.vue
<template>
<div>
<h1>我是哈哈 {{ $route.params.id }}</h1>
</div>
</template>
App.vue
<template>
<router-link to="/haha/111">Haha111</router-link>
<router-link to="/haha/abc">Hahaabc</router-link>
<hr/>
<router-view></router-view>
</template>
在页面中点击 路由匹配的路径 /haha/:id ,页面会跳转并显示 $route.params.id 内容
嵌套路由
const routes = [
{ path: '/', component: Home },
{
path:"/user/:id",
component:User,
children:[
{
// /user/007/profile
path:"profile",
component:UserProfile
},
{
// /user/007/posts
path:"posts",
component:UserPosts
},
],
}
]
App.vue
<template>
<router-link to="/">首页</router-link>
<router-link to="/user/007">用户中心</router-link>
<hr/>
<router-view></router-view>
</template>
User.vue 包含 两个导航 UserProfile:个人信息 和 UserPosts:我的邮件
<template>
<div>
<h1>欢迎您:{{ $route.params.id }}</h1>
<router-link to="/user/007/profile">个人信息</router-link>
<router-link to="/user/007/posts">我的邮件</router-link>
</div>
<div style="border:5px solid red">
<router-view></router-view>
</div>
</template>
UserPosts.vue
<template>
<div>
<h3>未读邮件42封</h3>
</div>
</template>
UserProfile.vue
<template>
<div>
<h3>我是007,性别男</h3>
</div>
</template>
编程式导航
useRoute: 路由数据
路由传参跳转到指定页面后,页面需要取到传递过来的值,可以使用
useRoute
方法;拿到当前页路由数据;可以做
1.获取到当前路径
2.获取到组件名
3.获取到参数
4.获取到查询字符串
<template>
<div>
<h3>个人详情</h3>
<button @click="getInfo">页面信息</button>
</div>
</template>
<script setup>
import { useRoute} from 'vue-router';
//路由
const route = useRoute();
function getInfo() {
console.log(route.params);//参数
console.log(route.name);//组价名
console.log(route.path);//路径
//....
}
</script>
useRouter: 路由器
拿到路由器;可以控制跳转、回退等。
<template>
<div>
<h3>个人详情</h3>
<button @click="getHome">回到首页</button>
<button @click="backFrontPage">返回上一页</button>
</div>
</template>
<script setup>
import {useRouter } from 'vue-router';
//路由器
const router = useRouter();
//返回首页
function getHome(){
router.push('/')
}
//返回上一页
function backFrontPage(){
router.go(-1);
}
路由传参
1. params 参数
params 传参方式有 使用useRouter 的 router.push 编程式传参 和 使用 router-link 式的 传参,
其中,编程式传参 和 router-link 式,都有路径传参 和 对象传参 这两种方式。
App.vue
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
// 编程式传参 使用 useRouter 的push
function paramsTest(){
// params 在对象传参方式时 必须使用组件 name
//路径传参
// router.push('/haha/222/zhangsan/50')
//对象传参
router.push({
name:'HAHA',
params:{
id:888,
name: 'zhangsan',
age: '333'
}
})
}
</script>
<template>
<router-link to="/">首页</router-link>
<!-- link式传参 使用路径传,或者动态绑定:to,用对象传-->
<router-link to="/haha/111/zhangsan/18">路径传参</router-link>
<router-link :to="{
name:'HAHA',
params:{
id:777,
name: 'zhangsan',
age: '22'
}
}">对象传参</router-link>
<button @click="paramsTest">params传参</button>
<hr/>
<router-view></router-view>
</template>
index.js
const routes = [
{ path: '/', component: Home },
{ path: '/hello', component: Hello},
{ path: '/haha/:id/:name/:age', // 路径 params 参数
name: 'HAHA',
component: ()=> import('../views/HAHA.vue') },
]
HAHA.vue
<template>
<!--params 取值要么用内置对象$route 要么用 useRoute 的route-->
<div>
<h1>我是哈哈 {{$route.params.id}} ==> {{$route.params.name}} ==> {{$route.params.age}}</h1>
<h5>对象传参 {{ route.params.id }} ==>{{ route.params.name}} ==> {{route.params.age}}</h5>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router';
let route = useRoute();
</script>
2. query 参数
地址栏上会显示拼接 如http://127.0.0.1:5173/hello?id=7788&name=haha
params 传参方式有 使用 useRouter 的 router.push 编程式传参 和 使用 router-link 式的传参,
其中,编程式传参 和 router-link 式传参,都有路径传参 和 对象传参 这两种方式。
Hello.vue
<template>
<!-- query 取值要么用内置对象$route 要么用 useRoute 的route -->
<div>
<h1>
我是Hello
{{ $route.query.id }} ==> {{ $route.query.name }}
</h1>
<div>
<h1>
{{ route.query.id }} ==> {{ route.query.name }}
</h1>
</div>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
</script>
App.vue
<template>
<!-- query传参,路径和对象方式 -->
<router-link to="/hello?id=1&name=zhangsan">Hello query路径传参</router-link>
<router-link :to="{
path:'/hello',
query:{
id:7788,
name:'haha'
}
}">Hello query对象传参</router-link>
<button @click="helloQuery">hello 传参,编程式传参 使用 useRouter 的push</button>
</template>
<script>
function helloQuery(){
//路径传参
// router.push('/hello?id=1&name=aaaaa')
// query 在对象传参方式时 必须使用组件 name
//对象传参
router.push({
path:'/hello',
query:{
id:1,
name:'xxx'
}
})
}
</script>
路由(导航)守卫
是一种拦截器思想,在路由器跳转到指定页面之前进行拦截,后面加上自己的逻辑判断来实现需求。
全局前置守卫
router.beforeEach
每次跳转之前拦截
//2、创建路由器
const router = createRouter({
history: createWebHistory(),
routes,
})
router.beforeEach((to, from) => {
console.log("to",to);
console.log("from",from);
// ...
// 返回 false 以取消导航
//1、返回 false :取消导航
//2、返回 true,不返回:继续导航
//3、返回'路径':跳转到指定页面
// return true
if(to.path==='/hello'){
console.log("禁止访问");
return "/"
}
})
or
router.beforeEach(async (to, from) => {
if (
// 检查用户是否已登录
!isAuthenticated &&
// ❗️ 避免无限重定向
to.name !== 'Login'
) {
// 将用户重定向到登录页面
return { name: 'Login' }
}
})
可选的第三个参数 next
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
全局解析守卫
你可以用
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
}
}
}
})
router.beforeResolve
是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置
全局后置钩子
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
Axios
简介
Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js
npm install axios
import axios from "axios"
axios.get('/user')
.then(res => console.log(resp.data))
API 测试服务器:http://43.139.239.29/
发送请求
<template>
<div>
<button @click="getInfo">GET 请求</button>
<button @click="getInfoParam">GET 请求 携带参数</button>
<button @click="postInfo">POST 请求</button>
<button @click="postInfoParam">POST 请求 携带参数</button>
</div>
</template>
<script setup>
import axios from 'axios'
// get 无参
function getInfo(){
axios.get("http://43.139.239.29/get")
.then(resp =>{
console.log(resp.data);
})
}
// get 携带参数
function getInfoParam(){
axios.get("http://43.139.239.29/get",{
params:{
id:1,
username:'zhangsan',
}
}).then(resp =>{
console.log(resp.data);
})
}
// post 无参
function postInfo(){
axios.post("http://43.139.239.29/post").then(resp =>{
console.log(resp);
})
}
// post 携带参数
function postInfoParam(){
//数据会自动转为json
axios.post("http://43.139.239.29/post",{
id:222,
username:'lisi',
age:18,
}).then(resp =>{
console.log(resp);
})
}
</script>
实例配置
详细配置参考 https://www.axios-http.cn/docs/instance
创建文件夹 utils/http/index.js 进行配置
import axios from 'axios'
const http = axios.create({
baseURL: 'http://43.139.239.29',
timeout: 1000, // `timeout` 指定请求超时的毫秒数。
// 如果请求时间超过 `timeout` 的值,则请求会被中断
// 默认值是 `0` (永不超时)
headers: {'X-Custom-Header': 'foobar'}
});
export default http;
使用例子:
<script setup>
import http from './utils/http/index.js'
// get 无参
function getInfo(){
http.get("/get")
.then(resp =>{
console.log(resp.data);
})
}
// get 携带参数
function getInfoParam(){
http.get("/get",{
params:{
id:1,
username:'zhangsan',
}
}).then(resp =>{
console.log(resp.data);
})
}
// post 无参
function postInfo(){
http.post("/post").then(resp =>{
console.log(resp);
})
}
// post 携带参数
function postInfoParam(){
//数据会自动转为json
http.post("/post",{
id:222,
username:'lisi',
age:18,
}).then(resp =>{
console.log(resp);
})
}
</script>
Axios 拦截器
在请求或响应被 then 或 catch 处理前拦截它们。
axios封装的index.js中添加 请求拦截器 和 响应拦截器并配置
import axios from 'axios'
const http = axios.create({
baseURL: 'http://43.139.239.29',
timeout: 1000, // `timeout` 指定请求超时的毫秒数。
// 如果请求时间超过 `timeout` 的值,则请求会被中断
// 默认值是 `0` (永不超时)
headers: {'X-Custom-Header': 'foobar'}
});
// 添加请求拦截器
http.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
console.log("请求错误"+error);
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
http.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
console.log("服务器错误"+error);
return Promise.reject(error);
});
export default http;
Pinia
核心概念
Vue.js中做状态管理的库,保存状态数据,在组件间共享数据。(Pinia做状态管理)
为什么要使用 Pinia?
Pinia做状态管理
数据单元:defineStore()
State:核心数据
Getter:定义读取=>数据获取
Action:定义操作=>数据操作
案例实战
安装依赖
npm install pinia
在main.js中配置
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
创建 stores文件夹 编写相关 js配置
import { defineStore } from "pinia";
import { ref,computed } from 'vue'
// 1.Option 写法:定义一个money储存单元
export const useMoneyStore = defineStore('money', {
state: () => ({ money: 100 }),
getters: {
rmb: (state) => state.money,
usd: (state) => state.money * 0.14,
eur: (state) => state.money * 0.13,
},
actions: {
win(arg) {
this.money+=arg;
},
pay(arg){
this.money -= arg;
}
},
})
// 2.Setup写法:
export const useMoneyStore = defineStore('money', ()=>{
const money = ref(100)
const rmb = computed(() => money.value)
const usd = computed(() => money.value*0.14)
const eur = computed(() => money.value*0.13)
const win = (arg)=>{
money.value += arg
}
const pay = (arg)=>{
money.value -= arg
}
return {money,rmb,usd,eur,win,pay}
})
在 Setup Store 中:
ref()
就是state
属性computed()
就是getters
function()
就是actions
注意,要让 pinia 正确识别 state
,你必须在 setup store 中返回 state
的所有属性。这意味着,你不能在 store 中使用私有属性。不完整返回会影响 SSR ,开发工具和其他插件的正常运行。
Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂
Wallet.vue
<template>
<div>
工资:{{ moneyStore.money }}
<h2>¥:{{ moneyStore.rmb }}</h2>
<h2>$:{{ moneyStore.usd }}</h2>
<h2>€:{{ moneyStore.eur }}</h2>
</div>
</template>
<script setup>
import { useMoneyStore } from '../stores/money.js'
let moneyStore = useMoneyStore();
</script>
Game.vue
<template>
<div>
<button @click="guaguale">刮刮乐</button>
<button @click="buybangbang">买棒棒糖</button>
</div>
</template>
<script setup>
import { useMoneyStore } from '../stores/money.js'
let moneyStore = useMoneyStore();
function guaguale() {
moneyStore.win(100)
}
function buybangbang() {
moneyStore.pay(5)
}
</script>
脚手架
在我们创建项目时,需要自己手动下载很多组件包,如 npm create vite
、npm install vue-router@4
、``npm install pinia` 等等,现在可以使用工具链 create-vue 一次性安装这些依赖
npm create vue@latest
npm create vue@latest
会自动安装 router,pinia。
/cookbook/composables.html) ,开发工具和其他插件的正常运行。
Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂
Wallet.vue
<template>
<div>
工资:{{ moneyStore.money }}
<h2>¥:{{ moneyStore.rmb }}</h2>
<h2>$:{{ moneyStore.usd }}</h2>
<h2>€:{{ moneyStore.eur }}</h2>
</div>
</template>
<script setup>
import { useMoneyStore } from '../stores/money.js'
let moneyStore = useMoneyStore();
</script>
Game.vue
<template>
<div>
<button @click="guaguale">刮刮乐</button>
<button @click="buybangbang">买棒棒糖</button>
</div>
</template>
<script setup>
import { useMoneyStore } from '../stores/money.js'
let moneyStore = useMoneyStore();
function guaguale() {
moneyStore.win(100)
}
function buybangbang() {
moneyStore.pay(5)
}
</script>
脚手架
在我们创建项目时,需要自己手动下载很多组件包,如 npm create vite
、npm install vue-router@4
、``npm install pinia` 等等,现在可以使用工具链 create-vue 一次性安装这些依赖
npm create vue@latest
npm create vue@latest
会自动安装 router,pinia。