Vue3 – Composition API(生命周期-路由-xuex-pinia-axios)

306 阅读4分钟

Options API的弊端:

  1. 当实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中
  2. 当组件变得更大、更复杂时,同一个功能的逻辑就会被拆分的很分散

setup函数

在setup中定义的数据提供响应式的特性,那么可以使用reactive的函数

在模板中引入 ref 的值时,Vue会自动进行解包,所以并不需要在模板中通过 ref.value

reactive要求我们必须传入的是一个对象或者数组类型

  1. reactive应用于本地的数据(账号密码)
  2. reactive应用于多个数据之间是有联系的
  3. 其他的基本都用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函数将其编程响应式对象的

image.png

readonly

readonly 会返回原生对象的只读代理(这是一个proxy的set方法被劫持,并且不能对其修改)

image.png

toRefs

image.png

不可以使用this是因为组件实例还没有被创建出来

computed

image.png

ref

只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可

image.png

生命周期

onMounted(() => {
  console.log('onMounted');
})
image.png

Provide和Inject

image.png

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> 中声明的绑定

image.png

路由的基本使用

安装路由: npm install vue-router

image.png
  • 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')},
]

动态路由

image.png

NotFound页面匹配规则

const routes = [
    // 在/:pathMatch(.*)后面又加了一个 *区别在于是否解析
    {
        path: ':/pathMath(.*)*',
        component: () => import('../components/NotFound.vue')
    },
]

其他元素跳转

通过调用 history.back() 回溯历史。相当于 router.go(-1)

通过调用 history.forward() 在历史中前进。相当于 router.go(1)

image.png

动态添加路由

image.png

删除路由有以下三种方式:

  1. 添加一个name相同的路由
  2. 通过removeRoute方法,传入路由的名称
  3. 通过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

  1. 不能直接改变 store 中的状态, 改变store中的状态的唯一途径就显示提交 mutation
  2. 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,不同在于:

  1. Action提交的是mutation,而不是直接变更状态
  2. Action可以包含任意异步操作;

image.png

image.png

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、甚至是嵌套子模块

image.png

Pinia状态管理

Vuex相比,Pinia有很多的优势:

  1. mutations 不再存在(经常被认为是非常冗长;最初带来了 devtools 集成,但这不再是问题)
  2. 更友好的TypeScript支持,Vuex之前对TS的支持很不友好
  3. 不再有modules的嵌套结构(可以灵活使用每一个store,它们是通过扁平化的方式来相互使用的)
  4. 不再有命名空间的概念,不需要记住它们的复杂关系

基础使用 image.png

image.png

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))