Vue 3 几个生态的使用

一、Vue 简介

1.1 MVVM

  • MVVM 是Vue实现属数据驱动视图双向数据绑定的核心原理。它把每个HTML 都拆分了三个部分:

image-20220124133055794

在MVVM中:

  • Model 代表当前页面渲染时所依赖的数据源
  • View 表示当前页面所渲染的DOM 结构
  • ViewMode 表示Vue 的实例,它时MVVM的实例

1.2 MVVM 的工作原理

1、 ViewModel 会监听数据源的变化,当数据源变化后会同步更新到View的视图中

2、随后ViewModel会监听View的DOM的变化,当View变化后会同步变化到Model的数据源中

1.2.3 Vue 3 的新特性

#值得注意的新特性

Vue 3 中一些需要关注的新功能包括:

#非兼容的变更

下面列出了从 2.x 开始的非兼容的变更:

#全局 API

#模板指令

#组件

#渲染函数

#自定义元素

#其他小改变

#被移除的 API

二、Vue 3 与 Vue 2 的区别

2.1 Vite 的基本使用

2.1.1 vite 项目的运行流程

在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。

其中:

① App.vue 用来编写待渲染的模板结构

② index.html 中需要预留一个 el 区域

③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中

//1、  从Vue 中按需导入 createApp 函数
//2、  createApp 创建 vue 的单页面应用程序实例
import { createApp } from 'vue'
// 导入带渲染的App组件
import App from './App.vue'

//3、 调用 createApp 函数,返回时“ 单页面应用的实例 ” 用常量 spa_app 进行接收,
// 同时把App组件作为参数传给 createApp  函数,表示渲染App 到index.html 页面
const spa_app = createApp(App)
//4、 调用 spa_app 实例的 mount 方法,用来指定 vue 实际要控制的区域
spa_app.mount("#app")

2.2 在 template 中定义根节点

在 vue 2.x 的版本中, 节点内的 DOM 结构仅支持单个根节点

但是,在 vue 3.x 的版本中, 中支持定义多个根节点:

<template>
<h1>第一个</h1>
<h2>第二个</h2>
</template>

2.3 解决组件样式冲突

为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:

<template>
 <h3 data-v--001> 卢波图</h3>
</template>

<style>
    /* 
    通过中括号属性选择器,来防止组件之间的样式冲突,因为每个组件分配的自定义属性是唯一的
    */
    h3[data-v--001]{
	border:1px solid red;
    }
</style>

后者在style 标签上加上scoped 属性,也可以自动生产唯一的自定义属性

2.3.1 / depp / 样式穿透

2.4 组件之间的数据共享

父节点可以通过provide方法,对子孙组件共享数据

provide(){
    return {  //  该函数里包含了向子孙组件共享的数据
        color : this.color
    }
}

子组件可以通过 inject数组 接受父级向下共享的数据

inject:['color']
<h1>{{color}}</h1>

父组件也可以通过computed 函数向下共享响应式数据

provied(){
    return {
        // 通过computed 函数,将数据包装为响应式的
        color:computed(()=> this.color)
    }
}

子组件使用 则要加上.value

inject:['color']
// 响应式数据必须加上.value
<h1>{{color.value}}</h1>

总结:组件之间可以使用的通信

  • 父子关系
    1. 父 --> 子 属性绑定
    2. 子 --> 父 事件绑定
    3. 父 <–> 子 组件上的v-model
  • 兄弟关系
    • EventBus
  • 后代关系
    • provide & inject
  • 全局数据共享
    • vuex

三、 vue 3 配置 axios

在全局配置axios

// 在main.js 
// 为 axios 配置请求的根路径
axios.defaults.baseURL = 'http://api.com'

//  将axios 挂载为app的全局自定义属性
app.config.globalProperties.$http = axios

解决跨域

  • cors ,服务器返回数据,携带一些特殊的响应头

  • jsonp 只能解决 get 跨域请求问题

  • 代理服务器

    • nginx
    • vue-cli
  • vue-cli

    //vue.config.js
    module.exports = {
    	...,
    	//方式一
    	devServer: {
    		proxy:'http://localhost:5000'
    	}
    }
    

    重新启动脚手架 npm run serve

    • 方式一不足:1.只能配置一个代理,2.只有本地没有找到的资源才走代理
    //vue.config.js
    module.exports = {
    	...,
    	//方式二
    	devServer: {
    		proxy:{
    			//请求前缀
    			'/api':{ //匹配所以以 'api1'开头的请求路径
    				target:'<url>',
                    pathRewrite:{'^/api':''}
                    ws: true, //用于支持websocket
                    changeOrigin: true //用于控制请求头中的host值
    			},
                '/foo':{
                 target:'<other_url>' ,
                    pathRewrite:{'^/foo':''}
                 }
    		}
    	}
    }
    

vue 2.x 全局配置 axios

实际项目开发中,几乎每个组件中都会使用 axios 发起数据请求。此时会遇到如下两个问题:

  • 每个组件中都需要导入 axios(代码臃肿)
  • 每次发请求都需要填写完整的请求路径(不利于后期的维护)

main.js 文件中进行配置:

// 配置请求根路径
axios.defaults.baseURL = 'http://api.com'

// 把 axios 挂载到 Vue 原型上
Vue.prototype.$http = aixos

优点:每个组件可以通过 this.$http.get 直接发起请求,无需再导入 axios ;若根路径发生改变,只需修改 axios.defaults.baseURL ,有利于代码维护。

缺点:无法实现 API 的复用。即多个组件需要对同一个接口发起请求,那么每个组件都需要重复书写 this.$http.get('/users') 类似的代码,造成冗余。(视频上的说法,个人认为真正的缺点是如果存在多个根路径,这种方式无法解决,所以才会有下面的改进方式。)

改进:对于每一个根路径,独立封装一个 request.js 模块,组件导入所需根路径对应的 axios 进行使用。

import axios from 'axios'

// 创建 axios 实例
const request = axios.create({
  baseURL: 'http://api.taobao.com',
})

export default request

四、vue 3 配置路由

1、安装vue-router

npm i vue-router@next -s

2、 从vue-router导入方法

import { createRouter,createWebHashHistory } from 'vue-router'

3、 创建路由实例

// 1. 导入对应组件
const Report = () => import('../components/report/Report')// 路由懒加载导入
import Home from './componens/home'  // 正常导入
// 2, 创建路由实例
const router = createRouter({
    history: createWebHashHistory(),
    routes:[
        { path :"/home",components: Home},
        { path :"/report" ,components:Report}
    ]
})
// 4. 向外暴露路由实例对象
export default router

4、 main.js 挂载路由模块

import router from './router'

app.use(router)

4.2 配置路由导航守卫

// 创建罗友实例对象
const router = createRouter({...})
                             
// 全局前置守卫
router.beforeEach((to,from,next)=>{
    //to 目标路由
    // from 当前导航正要离开的路由对象
    // next 函数,表示放行
// 如果访问登录页面直接放行
  if(to.path === '/login') return next()
  // 查看用户有没token有token放行没toke跳回登录页
  const tokenStr = window.sessionStorage.token
  if(!tokenStr) return next('/login')
  next()
})

五、 Vuex 状态管理

1、安装axios

npm install vuex@next --save

2、使用vuex

import { createApp } from 'vue'
import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
  state () {
    return {
      count: 0
    }
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

const app = createApp({ /* 根组件 */ })

// 将 store 实例作为插件安装
app.use(store)

Vuex

官网传送门(opens new window)

概述

何为 Vuex ?

Vuex 专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理,也是一种组件间通信的方式,适用于任意组件间通信

何时用 Vuex ?

  • 多个组件依赖于同一状态
  • 来自不同组件的行为需要变更同一状态

Vuex 工作原理图:

Vuex工作原理

官方 Vuex 项目结构示例:

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块

Vuex 核心概念

state

  • Vuex 管理的状态对象
  • 唯一的

actions

  • 值为一个对象,包含多个响应用户动作的回调函数
  • 通过 commit()触发 mutation 中函数的调用,间接更新 state
  • 可包含异步代码

mutations

  • 值为一个对象,包含多个直接更新 state 的方法
  • 不能写异步代码,只能单纯地操作 state

getters

  • 值为一个对象,包含多个用于返回数据的函数
  • 类似于计算属性,getters 返回的数据依赖于 state 的数据

modules

  • 一个 module 是一个 store 的配置对象,与一个组件对应

搭建 Vuex 环境

安装 Vuex:npm install vuex --save

创建文件 src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const actions = {}
const mutations = {}
const state = {}

export default new Vuex.Store({
  actions,
  mutations,
  state,
})

main.js 配置 store

import store from './store'

new Vue({
  el: '#app',
  store,
  render: (h) => h(App),
})

基本使用

组件实例与 ActionsMutations 对话:

  • 若没有网络请求或其他业务逻辑,组件中也可以越过 actions ,即不写dispatch,直接编写commit
methods: {
  increment() {
    this.$store.commit('ADD', this.number)
  },
  incrementOdd() {
    this.$store.dispatch('addOdd', this.number)
  },
  incrementAsync() {
    this.$store.dispatch('addAsync', this.number)
  }
}

定义 ActionsMutations

  • context 是一个迷你版的 store,可访问 dispatch, commit 方法和 state
  • mutations 的动作类型一般用大写,与 actions 区分
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const actions = {
  // context 是一个迷你版的 store
  // 可访问 dispatch, commit 方法和 state
  addOdd(context, value) {
    if (context.state.sum % 2 !== 0) {
      context.commit('ADD', value)
    }
  },
  addAsync(context, value) {
    setTimeout(() => {
      context.commit('ADD', value)
    })
  },
}
const mutations = {
  // mutations 的动作类型一般用大写,与 actions 区分
  ADD(state, value) {
    state.sum += value
  },
}

const state = {
  sum: 0,
}

export default new Vuex.Store({
  actions,
  mutations,
  state,
})

组件访问 Vuex 的数据:

<p>{{ $store.state.sum }}</p>

getters 的使用

  • state 中的数据需要经过加工后再使用时,可以使用 getters 加工
  • 它不是必须的,当加工逻辑复杂且需要复用时,可以考虑使用
  • stategetters 的关系有点像 datacomputed 的关系
  • 组件读取:$store.getters.bigSum
...
const getters = {
  bigSum(state) {
    return state.sum * 10
  }
}

export default new Vuex.Store({
  actions,
  mutations,
  state,
  getters
})

四个 mapXxx 方法

mapState()

  • state 状态映射为计算属性
  • 对象写法:键为自取的计算属性名,值为对应的状态(必须为字符串)
  • 数组写法:当键值同名,可直接写状态名(字符串)
  • 函数返回一个对象:{sum: f, price: f}
  • 注意对象的 ...{} 展开写法
import { mapState } from 'vuex'

computed: {
  // 手动写法
  sum() {
    return this.$store.state.sum
  },
  price() {
    return this.$store.state.price
  },

  // 对象写法
  ...mapState({sum: 'sum', price: 'price'}),

  // 数组写法
  ...mapState(['sum', 'price'])
}

mapGetters

  • getters 的数据映射为计算属性
import { mapGetters } from 'vuex'

computed: {
  bigSum() {
    return this.$store.getters.bigSum
  },
  double() {
    return this.$store.getters.double
  },

  // 对象写法
  ...mapGetters({bigSum: 'bigSum', double: 'double'}),

  // 数组写法
  ...mapGetters(['bigSum', 'double']),
}

mapActions

  • 生成与 actions 对话的函数,即包含 $store.dispatch()
  • mapActions 生成的函数不会传入参数,需要在调用时手动传入数据,不传参默认传入 $event
  • 数组写法要注意函数名和 actions 动作类型同名,调用时勿写错
import { mapActions } from 'vuex'

methods: {
  // 手动写法
  incrementOdd() {
    this.$store.dispatch('addOdd', this.number)
  },
  incrementAsync() {
    this.$store.dispatch('addAsync', this.number)
  },

  // 对象写法
  ...mapActions({incrementOdd: 'addOdd', incrementAsync: 'addAsync'}),

  // 数组写法
  ...mapActions(['addOdd', 'addAsync']),
}
<button @click="incrementOdd(number)">奇数+1</button>

mapMutations

  • 生成与 mutations 对话的函数,即包含 $store.commit()
  • 同样注意传递参数,以及数组形式函数名的问题
import { mapMutations } from 'vuex'

methods: {
  increment() {
    this.$store.commit('ADD', this.number)
  },
  decrement() {
    this.$store.commit('SUB', this.number)
  },

  // 对象写法
  ...mapMutations({increment: 'ADD', decrement: 'SUB'}),

  // 数组写法
  ...mapMutations(['ADD', 'SUB']),
}

Vuex 模块化&命名空间

让代码更好维护,让多种数据分类更加明确,每一类数据及其相关操作对应一个 store

// src/store/index.js
const countAbout = {
  // 开启命名空间
  namespaced: true,
  state: {
    sum: 0
  },
  actions: {...},
  mutations: {...},
  getters: {
    bigSum(state) {
      return state.sum * 10
    }
  }
}
const personAbout = {
  // 开启命名空间
  namespaced: true,
  state: {
    personList: []
  },
  actions: {...},
  mutations: {...},
  getters: {...}
}

export default new Vuex.Store({
  modules: {
    countAbout,
    personAbout
  }
})

开启命名空间后,组件中读取 state 数据:

// 直接读取
this.$store.state.personAbout.personList

// mapState 读取
...mapState('countAbout',['sum','school']),

开启命名空间后,组件中读取 getters 数据:

// 直接读取
this.$store.getters['countAbout/bigSum']

// mapGetters 读取:
...mapGetters('countAbout',['bigSum'])

开启命名空间后,组件中调用 dispatch

// 直接 dispatch
this.$store.dispatch('countAbout/addODdd', this.number)
// 借助 mapActions
...mapActions('countAbout', {incrementOdd:'addOdd', incrementWait:'addAsync'})

开启命名空间后,组件中调用 commit

// 直接 commit
this.$store.commit('personAbout/ADD_PERSON', person)
// 借助 mapMutations
...mapMutations('countAbout',{increment:'ADD',decrement:'SUB'}),

版权声明:本文为m0_52409770原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。