Options API的弊端:
- 当实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中
- 当组件变得更大、更复杂时,同一个功能的逻辑就会被拆分的很分散
setup函数
在setup中定义的数据提供响应式的特性,那么可以使用
reactive的函数在模板中引入
ref的值时,Vue会自动进行解包,所以并不需要在模板中通过 ref.value
reactive要求我们必须传入的是一个对象或者数组类型
- reactive应用于本地的数据(账号密码)
- reactive应用于多个数据之间是有联系的
- 其他的基本都用ref (从网络获取的数据)
import { reactive, ref } from "vue";
let message = reactive({
userName:'wqq',
password:'123'
})
<h2>counter:{{ counter }}</h2>
import { ref } from "vue";
let counter = ref(0)
const changeCounter = () => {
counter.value ++
}
当使用 reactive 函数处理数据之后,数据再次被使用时就会进行依赖收集
当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面)
我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的
readonly
readonly 会返回原生对象的只读代理(这是一个proxy的set方法被劫持,并且不能对其修改)
toRefs
不可以使用this是因为组件实例还没有被创建出来
computed
ref
只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可
生命周期
onMounted(() => {
console.log('onMounted');
})
Provide和Inject
watch
<script setup>
import {reactive, ref, watch} from "vue"
const message = ref('hello')
const info = reactive({
name: 'www',
age: 19
})
watch(message, (newV, oldV) => {
console.log(newV, oldV);
})
watch(info, (newV, oldV) => {
console.log(newV, oldV);
}, {
immediate: true, // 立即执行
deep: true //深度监听
})
watch(() => ({...info}), (newV, oldV) => {
console.log(newV, oldV);
})
</script>
当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用 watchEffect
import {ref, watchEffect} from "vue"
const counter = ref(0)
// 传入的函数会被默认执行 会自动收集依赖
const stopWatch = watchEffect(() => {
if(counter.value >= 5 ){
stopWatch() // 停止监听
}
})
defineProps() 和 defineEmits()
// defineProps 定义props
const props = defineProps({
name:{
type:String,
default:''
}
})
// defineEmits 绑定函数,发出事件
const emits = defineEmits(['changAge'])
function changAge() {
emits('changAge',19)
}
defineExpose()
通过模板 ref $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定
路由的基本使用
安装路由: npm install vue-router
- router-link有很多属性可以配置:
to属性: 切换的路径
replace属性: 当返回点击时,会调用 router.replace(),而不是 router.push()
active-class属性: 设置激活a元素后应用的class,默认是router-link-active
exact-active-class属性: 链接精准激活时,应用于渲染 a 的class,默认是router-link-exact-active
const routes = [
{path: '/', redirect: '/home'}, // 默认路径,redirect:重定向
{path: '/home', component: Home},
{path: '/about', component: About},
]
import {createRouter,createWebHistory,createWebHashHistory} from "vue-router"; // hash history
路由懒加载
const routes = [
{path: '/', redirect: '/home'},
{path: '/home', component: () => import('../components/Home.vue')},
{path: '/about', component: () => import('../components/About.vue')},
]
动态路由
NotFound页面匹配规则
const routes = [
// 在/:pathMatch(.*)后面又加了一个 *区别在于是否解析
{
path: ':/pathMath(.*)*',
component: () => import('../components/NotFound.vue')
},
]
其他元素跳转
通过调用 history.back() 回溯历史。相当于 router.go(-1)
通过调用 history.forward() 在历史中前进。相当于 router.go(1)
动态添加路由
删除路由有以下三种方式:
- 添加一个name相同的路由
- 通过removeRoute方法,传入路由的名称
- 通过addRoute方法的返回值回调
导航守卫
to:即将进入的路由Route对象;
from:即将离开的路由Route对象
Vuex状态管理
安装vuex: npm install vuex
- Vuex的状态存储是响应式的
创建store
import {createStore} from "vuex";
const store = createStore({
state: () => ({
counter: 99
})
})
export default store
在main.js中导入
import store from "./store";
createApp(App).use(store).mount('#app')
在模板中使用: <h2>APP当前计数:{{ $store.state.counter}}</h2>
Mutation
- 不能直接改变 store 中的状态, 改变store中的状态的唯一途径就显示提交 mutation
- mutation 必须是同步函数
// --------------app.vue---------------
<h2>APP当前计数:{{ $store.state.counter }}</h2>
<h2>computed当前计数:{{ storeCounter }}</h2>
<button @click="addClick"> +1</button>
<script>
import {mapMutations} from "vuex";
import {CHANGE_NAME} from "@/store/mutation_types";
export default {
computed:{
storeCounter(){
return this.$store.state.counter // 拿到state中的counter
}
},
methods: {
addClick() {
this.$store.commit('addNum') // 提交的事件
},
...mapMutations(['addNum',CHANGE_NAME]) // 映射完可以直接调用,不需要再 this.$store.commit
}
}
</script>
<script setup>
import {useStore,mapMutations} from "vuex";
const store = useStore()
const { counter } = toRefs(store.state) // 返回的是ref对象,默认解构不是响应式
function addClick() {
store.commit('addNum') // 提交的事件
}
function changeName() {
store.commit('CHANGE_NAME','qqq')
}
const mutations = mapMutations(['addNum', CHANGE_NAME]) // 手动映射和绑定
const newMutations = {}
Object.keys(mutations).forEach(key => {
const store = useStore()
newMutations[key] = mutations[key].bind({$store: store})
})
const {addNum,CHANGE_NAME} = mutations
</script>
// ------------store -> index.js--------------
const store = createStore({
state: () => ({
counter: 99,
name:'www'
}),
mutations:{
addNum(state){
state.counter++
},
// 将方法名定义为常量(设计规范)
[CHANGE_NAME](state,payload){
state.name = payload
}
}
})
// ------------mutation_types.js--------------
export const CHANGE_NAME = 'changeName'
actions(异步操作)
Action类似于mutation,不同在于:
- Action提交的是mutation,而不是直接变更状态
- Action可以包含任意异步操作;
mapState 辅助函数
这种表达式$store.state.counter过长,可以使用计算属性
<h2>{{ name }} - {{sName}} - {{ age }} - {{ sAge }}</h2>
<script>
import {mapState} from "vuex";
export default {
computed: {
...mapState(['name', 'age']), // 数组方式
...mapState({ // 对象方式
sName:state => state.name,
sAge:state => state.age
})
}
}
</script>
// --------------------setup用法----------------------------
<script setup>
import {mapState,useStore} from "vuex";
import {computed,toRefs} from "vue";
const {name, age} = mapState(['name', 'age'])
const store = useStore()
const cName = computed(name.bind({$store:store}))
const cAge = computed(age.bind({$store:store}))
const {counter} = toRefs(store.state) // 推荐使用解构包裹toRefs
</script>
getters
<h2>doubleCounter:{{ $store.getters.doubleCounter }}</h2>
<h2>getFriendId:{{ $store.getters.getFriendId(112)}}</h2>
<h1>{{$store.getters.getFriendInfo}}</h1>
const store = createStore({
state: () => ({
counter: 100,
friends: [
{id: 111,name:'www'},
{id: 112,name:'wqq'},
]
}),
getters: {
// 1. 基础使用
doubleCounter(state) {
return state.counter * 2
},
// 2. 返回一个函数,调用这个函数可以传递参数
getFriendId(state){
return (id) => {
return state.friends.find(item => item.id === id)
}
},
// 2. 第二个参数,getters属性中可以获取其他的getters
getFriendName(state){
return state.counter
},
getFriendInfo(state,getters) {
let msg = ''
for (const item of state.friends) {
msg += item.name + getters.getFriendName
}
return msg
}
}
})
mapGetters 辅助函数
<h2>doubleCounter:{{ doubleCounter }}</h2>
<h2>getFriendId:{{ getFriendId(112) }}</h2>
<h1>{{ message }}</h1>
<h1>{{ getFriendInfo }}</h1>
<script>
import {mapGetters} from "vuex";
export default {
computed: {
...mapGetters(['doubleCounter', 'getFriendId', 'getFriendInfo']),
}
}
</script>
<script setup>
import {mapGetters, useStore} from "vuex";
import {computed, toRefs} from "vue";
// 使用mapGetters
const store = useStore()
const {getFriendInfo: msg} = mapGetters(['getFriendInfo'])
const message = computed(msg.bind({$store: store}))
// 推荐使用解构包裹toRefs
const {getFriendInfo} = toRefs(store.getters)
</script>
module
Vuex 允许将 store 分割成模块,每个模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块
Pinia状态管理
Vuex相比,Pinia有很多的优势:
- mutations 不再存在(经常被认为是非常冗长;最初带来了 devtools 集成,但这不再是问题)
- 更友好的TypeScript支持,Vuex之前对TS的支持很不友好
- 不再有modules的嵌套结构(可以灵活使用每一个store,它们是通过扁平化的方式来相互使用的)
- 不再有命名空间的概念,不需要记住它们的复杂关系
基础使用
user.js
import {defineStore} from "pinia";
import useCounter from "./counter";
const useUser = defineStore('user', {
state: () => ({
name: 'www',
age: 19,
counter: 100,
friend: [
{id: 111, name: 'wq'},
{id: 222, name: 'qq'}
],
banners:[]
}),
getters: {
doubleCounter(state) { // 1. 基本使用
return state.counter * 2
},
doubleCountAdd() { // 2. 一个getter引入另一个getter
return this.doubleCounter + 1 // this:store实例
},
getFriendById(state) { // 3. 也支持返回一个函数
return function (id) {
return state.friend.find(item => item.id === id)
}
},
showMessage(state) { // 4. 用到别的store中的数据
const counterStore = useCounter() // 获取counter的数据
return `name:${state.name}-message:${counterStore.msg}`
}
},
actions: {
addCement(num) {
this.counter += num
},
async getDateBanners() {
const res = await fetch('http://123.207.32.32:8080/home/multidata')
const data = await res.json()
this.banners = data.data.banner.list
},
return new Promise(async (resolve,reject) => { // 写法二
const res = await fetch('http://123.207.32.32:8080/home/multidata')
const data = await res.json()
this.banners = data.data.banner.list
resolve('bbb')
})
}
})
export default useUser
state的使用
template>
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="changeState">修改state</button>
<button @click="resetState">重置state</button>
</template>
<script setup>
import useUser from "../pinia/user";
import {storeToRefs} from "pinia";
const userStore = useUser()
const {name, age} = storeToRefs(userStore)
function changeState() {
// 1.一个一个的修改
userStore.name = 'wqq'
userStore.age = 20
// 2.一次修改多个
userStore.$patch({
name:'qqq',
age: 21
})
userStore.$state = { // 3.替换state
name:'美女'
}
}
function resetState() { // 重置
userStore.$reset()
}
</script>
getters的使用
<h2>{{userStore.doubleCounter}}</h2>
<h2>{{userStore.doubleCountAdd}}</h2>
<h2>{{userStore.getFriendById(111)}}</h2>
<h2>{{userStore.showMessage}}</h2>
<script setup>
import useUser from "../pinia/user";
const userStore = useUser()
</script>
actions的使用
可以使用 defineStore() 中的 actions 属性定义,并且它们非常适合定义业务逻辑
<ul>
<template v-for="item in userStore.banners">
<li>{{ item.title }}</li>
</template>
</ul>
<script setup>
import useUser from "../pinia/user";
const userStore = useUser()
userStore.getDateBanners().then(res => {
console.log('action已经完成');
})
</script>
axios库
常用请求方式
// get发送请求写法1
axios.get('http://123.207.32.32:9001/lyric?id=500665346').then(res => {
console.log(res);
})
// get发送请求写法2(常用)
axios.get('http://123.207.32.32:9001/lyric', {
params: {id: 500665346 }
}).then(res => {
console.log(res.data);
})
// 发送post请求
axios.post('http://252.136.185.210:5000/login',{
name:'wqq',
password:'123456'
}).then(res => {
console.log(res.data);
})
// 同时发送多个请求,两个请求都有结果才会回调then
axios.all([
axios.get('/home'),
axios.get('/multi')
]).then(res => {
console.log(res);
})
baseUrl
axios.get('/lyric', {
params: { id: 500665346 }
}).then(res => {
console.log(res.data);
})
某些请求需要特定的baseURL或者timeout等,可以创建新的实例,传入属于该实例的配置信息
const instance1 = axios.create({
baseURL:'http://123.207.32.32:9001',
timeout:6000, // 6s没响应就超时
headers:{}
})
instance1.get('/hoem',{
params:{ id:'10000' }
}).then(res => console.log(res) )
请求和响应拦截器
// 请求拦截
axios.interceptors.request.use((config) => {
console.log('请求成功的拦截');
// 1. 显示loading动画
// 2. 对原来的配置文件进行修改 headers、认证登录(token\cookie)、请求参数进行转化
return config
}, err => console.log(err, '请求失败的拦截'))
// 响应拦截
axios.interceptors.response.use((res) => {
console.log('响应成功的拦截');
// 1. 结束loading动画
// 2. 对数据进行转化,在返回数据
return res.data
}, error => console.log(error, '响应失败的拦截'))
axios.get('http://123.207.32.32:9001/lyric?id=500665346').then(res => {
console.log(res);
}).catch(err => console.log(err))
axios请求封装
import axios from "axios";
class WQRequest {
constructor(baseUrl,timeOut=1000) {
this.instance = axios.create({
baseUrl,
timeOut
})
}
request(config) {
return new Promise((resolve, reject) => {
this.instance.request(config).then(res => resolve(res.data)).catch(err => reject(err))
})
}
get(config) {
return this.request({...config,method:'get'})
}
post(config) {
return this.request({...config,method:'post'})
}
}
const wqRequese1 = new WQRequest('http://123.207.99.88:2023') // 可以创建很多实例
export default new WQRequest('http://123.207.32.32:9001')
// 用法
import WQRequest from "./service/index"
WQRequest.request('/lyric?id=500665346').then(res => {
console.log(res);
}).catch(err => console.log(err))