Vue 快速复习
基本语法
模板语法
1. {{ msg }}
2. v-html="attName"
3. v-bind:xxx="attName" or :xxx="attName" ==> <tag xxx="val" />
4. 在模板中使用 js 表达式,比如 {{ ok ? "YES":"NO" }}, v-html="ok ? '<hr/>' : '<br/>'"
条件渲染
1. v-if="attName" ==> 惰性、真正的条件渲染(基于 dom 增删)、有切换开销,适用于条件改变比较少的元素
2. v-else => <p v-if="attName">true</p><p v-else>false</p>
3. v-show="attName" ==> 元素总是会被渲染,基于 css 切换显示状态、有初始渲染开销,适用于条件切换频繁的元素
列表渲染
<tag v-for="item in items" :key="item.id">{{item.title}}</tag>
<tag v-for="(item,index) in items" :key="item.id|index">{{item.title}}</tag>
“推荐在任何可行的时候为 v-for 提供一个 key attribute”
ref:https://cn.vuejs.org/guide/essentials/list.html#maintaining-state-with-key
事件处理
<button v-on:click="cnt += 1">click me</button>
<button @click="clicked">click me</button>
<button @click="m2('blabla')">m3</button>
<li v-for="item in items" :key="item.id" @click="handle(item.id)">{{item.title}}</li>
-------------------------
export default {
name: "",
data() {
cnt: 1
},
methods: {
clicked(event) {
console.log("clicked!", event) // 原生 event 对象
},
m3(data) {
console.log(data)
},
handle(id) {
console.log(id)
}
}
}
表单输入绑定
<input v-model="username" />
<input v-model.lazy="username" /> 只在失去焦点或会车时进行数据同步,减少同步开销
<input v-model.trim="username" /> 去掉输入的首尾空格
组件相关
单文件组件
<template></template> 必须
<script> 非必须
export default {
name: "组件名"
}
</script>
<style scope></style> 非必须
加载组件
1. 引入组件 import MyComp from './comps/MyComp.vue'
2. 挂载组件 components: { MyComp }
3. 显示组件 <my-comp /> 或 <MyComp />
数据交互之正向传值(将数据从父组件传递到子组件)
<h3 style="color: red">{{ title }} - {{ msg }} - {{ cnt }}</h3>
export default {
name: 'MyComp',
props: {
msg: {
type: String,
default: 'null',
},
title: {
type: String,
default: 't',
},
cnt: {
type: Number,
default: 0,
},
}
}
----
<!-- 使用 v-bind:attName="attr" 给子组件传值 -->
<my-comp :msg="msg" title="消息" :cnt="1" />
import MyComp from './MyComp.vue'
export default {
name: 'App',
data() {
return {
msg: '小坏蛋',
}
},
components: {
MyComp,
},
}
----
注意:
1. props 中定义的属性数据类型可以为 String|Number|Boolean|Array|Object|Function
2. props 中定义的属性数据类型为数组或对象时,默认返回值需要返回值是方法。比如
props: {
ids: {
type: Array,
default: function() { return [] }
}
}
数据交互之使用自定义事件反向传值
<button @click="handleClick">click-me</button>
methods: {
handleClick() {
// 子组件通过“自定义事件名”将消息传递给父组件
this.$emit('onMyEvent', this.message)
},
},
--------------
<h3>data={{ msg }}</h3>, <my-sub-comp @onMyEvent="handleData" />
methods: {
handleData(data) {
this.msg = data
},
},
组件生命周期
主要包含八个生命周期钩子函数:
beforeCreate:创建还没开始创建
created: 创建已经创建完成,单位显示出来
beforeMount: 组件渲染之前
mounted: 组件已经渲染完毕,常用来做网络请求获取数据,获取数据后进行数据渲染 ⭐
beforeUpdate:组件更新之前
updated: 组件更新完
beforeUnmount:组件卸载之前,卸载消耗性能的资源,比如定时器
unmounted 组件已经卸载
参考:https://cn.vuejs.org/guide/essentials/lifecycle.html#lifecycle-diagram
引用第三方
Vue 官方整理的资源列表:https://github.com/vuejs/awesome-vue#menu,在使用时注意是否支持 vue2 或 vue3。
Swiper
开源、免费、强大的触摸滑动插件;
Axios
简单使用
npm i axios --save
npm i querystring --save
局部使用
-------
import axios from 'axios'
import querystring from 'querystring'
export default {
name: 'AxiosTest',
data() {
return {
title: '',
}
},
mounted() {
axios({
method: 'get',
url: 'http://x.y.z/a/b/get/1024',
}).then((res) => {
this.title = res.data.prodName
})
axios({
method: 'post',
url: 'http://x.y.z/a/b/add/1024',
// 使用 querystring 工具类转为 json 字符串
data: querystring.stringify({
prodName: '男士内裤',
prodPrice: 50,
}),
}).then((res) => {
console.log(res.data.message)
})
//快捷操作
axios.get('url').then(res=>{ ... })
axios.post('url', data: ... ).then(res=>{ ... })
},
}
全局使用
------- main.js ---------
import axios from 'axios'
//...
var app = createApp(App)
app.config.globalProperties.$axios = axios //挂载到全局属性集合中
app.mount('#app')
---- 使用
this.$axios.get('url').then(res => { ... })
axios 封装
- request.js 工具类
import axiox from 'axios'
import querystring from 'querystring'
const processErrorResponse = (status, info) => {
switch (status) {
case 400:
console.log('语义有误')
break
case 401:
console.log('服务器认证失败')
break
case 403:
console.log('服务器拒绝访问')
break
case 404:
console.log('地址错误')
break
case 500:
console.log('服务器内部错误')
break
case 502:
console.log('服务器无响应')
break
default:
console.log(info)
}
}
const instance = axiox.create({
baseURL: 'https://x.y.z/api/',
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' },
})
//请求拦截器
instance.interceptors.request.use(
(config) => {
// 如果是 post 请求,则将其中的对象转为字符串
if (config.method === 'post') {
config.data = querystring.stringify(config.data)
}
// config 对象包含请求的所有信息
return config
},
(error) => {
return Promise.reject(error)
}
)
//响应拦截器
instance.interceptors.response.use(
(response) => {
return response.status === 200
? Promise.resolve(response)
: Promise.reject(response)
},
(error) => {
const { response } = error
processErrorResponse(response.status, response.info)
}
)
export default instance
参考 ruoyi-vue/ruoyi-ui
的 request.js
- 编写
api/user.js
(参考 ruoyi-vue)
import request from '@/utils/request'
export function list() {
return request({
url: '/user/list',
method: 'get',
})
}
export function get(id) {
return request({
url: '/user/' + id,
method: 'get',
})
}
export function add(name, age, email, password) {
return request({
url: '/user',
method: 'post',
data: { // 请求拦截器会进行处理
name,
age,
email,
password,
},
})
}
参考 ruoyi-vue/ruoyi-ui
的 api 目录
其他:
- 使用配置工具 process.env
跨域问题及解决
同源策略是什么?是浏览器的一项安全策略,它只允许 JS 代码请求和当前所在服务器域名、端口和协议相同的接口。
解决方法:
- 后台:配置 CORS;
- 前台:使用代理
// vue.config.js
devServer: {
proxy: {
'/api': {
target: 'http://a.d.s.w',
changeOrigin: true,
},
},
},
// vite.config.js
server: {
port: 5111,
host: true,
open: true,
proxy: {
'/api': {
target: 'http://a.d.s.w',
changeOrigin: true,
},
},
},
路由配置
路由基本使用
安装
npm install vue-router --save
在 views 下创建两个文件 HomeView.vue、AboutView.vue。
创建 @/router/index.html
,创建并配置路由对象
---> @/router/index.html
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
component: HomeView,
},
{
path: '/about',
component: () => import('../views/AboutView.vue'),
},
],
})
export default router
在入口文件中引入并使用创建的 router 对象
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
在 App.vue 中编写路由导航
<template>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
<br />
<router-view />
</template>
路由传参
在路由链接中添加路由参数,在目标组件中取出路由参数。
添加路由链接
<template>
<li v-for="i in idList" :key="i">
<router-link :to="'/info/' + i">{{ i }}</router-link>
</li>
</template>
添加路由
{
// 使用 :xxx 的方式指定路由参数,xxx 为参数名
path: '/info/:id',
component: () => import('../views/AboutInfo.vue'),
},
创建子组件
<template>
<h3>info, id = {{ id }}</h3>
</template>
<script>
export default {
name: 'AboutInfo',
data() {
return {
id: '',
}
},
created() {
// 从 this.$route.params 中获取路由上的参数
this.id = this.$route.params.id
},
}
</script>
嵌套路由
嵌套路由,比如菜单。
创建两个组件 P1View.vue、P2View.vue,将其作为子路由添加到 /
下
{
path: '/',
name: 'home',
redirect: '/p1', // 重定向 / 到 /p1
component: HomeView,
children: [ // 子路由
{
path: 'p1', // 开头不加斜杠
component: () => import('@/views/P1View.vue'),
},
{
path: 'p2',
component: () => import('@/views/P2View.vue'),
},
],
},
在 HomeView.vue 中添加子路由链接和路由视图
<template>
<h3>首页</h3>
<router-link to="/p1">饮食区</router-link>|
<router-link to="/p2">娱乐区</router-link>
<!-- 注: 匹配路由的组件会被渲染到这里 -->
<router-view />
</template>
https://router.vuejs.org/guide/#html
状态管理
一个集中式的状态管理方案,使组件之间数据交互更加方便。
简单使用
npm i vuex --save
创建 @/store/index.js
,创建并暴露 store 实例
---> @/sotre/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
cnt: 128,
},
})
使用 store 实例,在 main.js 中
import store from './store'
createApp(App).use(store).mount('#app')
在任何组件中使用 cnt 这个属性
<p>app.vue, cnt = {{ $store.state.cnt }}</p>
如果在一个组件中需要多次读取 store 中的属性,如何进行简化?
答:使用 vuex 提供的 mapState。
<p>{{ cnt }}</p>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['cnt']),
},
}
</script>
getter - 数据过滤
export default createStore({
state: {
cnt: 128,
},
getters: {
getCnt(state) {
return state.cnt > 0 ? state.cnt : 'cnt无效'
},
},
}
<!-- 直接读取 -->
<p>getter, {{ $store.getters.getCnt }}</p>
<!-- mapGetters 方式读取 -->
<p>{{ getCnt }}</p>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['getCnt']),
},
}
</script>
mutaions - 修改数据
export default createStore({
state: {
cnt: 128,
},
mutations: {
incrCnt(state) {
state.cnt++
},
addCnt(state, num) {
state.cnt += num
},
},
}
<!-- 直接调用 -->
<p>mutation, <button @click="$store.commit('incrCnt')">incr</button></p>
<p><button @click="$store.commit('addCnt', 10)">add 10</button></p>
<!-- 使用 mapMutations 抽取 $store 中的 mutations 为普通函数,直接调用即可 -->
<p><button @click="incrCnt">incr by mapMutations</button></p>
<p><button @click="addCnt(10)">add 10 by mapMutations</button></p>
<script>
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations(['incrCnt', 'addCnt']),
},
}
</script>
注意:mutations 处理器中的方法必须是同步的,参考。
比如,下边的例子在 mutations 方法中使用异步,不推荐这种方式
mutations: {
reqNumAadd2Cnt(state) {
axios.get('http://localhost:8080/robots.txt').then((res) => {
state.cnt += parseInt(res.headers['content-length'])
})
},
}
action - 异步修改
export default createStore({
state: {
cnt: 128,
},
mutations: {
addCnt(state, num) {
state.cnt += num
},
},
actions: {
//异步处理
asyncChgCnt({ commit }) {
axios.get('http://localhost:8080/robots.txt').then((res) => {
// 交给 mutation 同步处理
commit('addCnt', parseInt(res.headers['content-length']))
})
},
},
}
<button @click="$store.dispatch('asyncChgCnt')">req network and chg cnt - 1</button>
<button @click="asyncChgCnt">req network and chg cnt - 2</button>
---------------
<script>
mutations: {
addCnt(state, num) {
state.cnt += num
},
},
actions: {
//异步处理
asyncChgCnt({ commit }) {
axios.get('http://localhost:8080/robots.txt').then((res) => {
// 交给 mutation 做同步处理
commit('addCnt', parseInt(res.headers['content-length']))
})
},
},
</script>
------------- vite.config.js --------------
server: {
port: 8080,
open: true,
proxy: {
'/robots.txt': {
target: 'http://baidu.com',
changeOrigin: true,
},
},
},
pinia
https://pinia.vuejs.org/zh/introduction.html#why-should-i-use-pinia
Vue3 新特性
6 大亮点:
- Composition API:组合 API ⭐;
- 性能比 Vue2 强;
- 可以“剪辑”掉无用模块,仅打包所需模块;
- “碎片”、“悬念”;
- 更好的 TS 支持(Vue3 底层是 TS 写的);
- 暴露了自定义渲染 API;
组合式 API — setup()
我们可以使用响应式 API 来声明响应式的状态,在
setup()
函数中返回的对象会暴露给模板和组件实例。参考:https://cn.vuejs.org/api/composition-api-setup.html#basic-usage
注意:setup()
在 beforeCreate 生命周期钩子之前执行。
ref 和 reactive 定义属性
- ref:声明简单变量,比如字符串、数字
- reactive:声明复杂变量,比如数组、对象
可使用 ref 和 reactive 声明变量,并通过 return 将其暴露出去。与 vue2 中的 data(){...}
类似。
<p>{{ lang }}</p>
<ul>
<li v-for="(cty, index) in cties.countries" :key="index">{{ cty }}</li>
</ul>
---------------------
import { ref, reactive } from 'vue'
export default {
name: 'App',
setup() {
const lang = ref('english')
const cties = reactive({
countries: ['China', 'England', 'Indian', 'Thailand'],
})
return {
lang,
cties,
}
},
}
定义函数
<p>{{ lang }}</p>
<button @click="clickHandler">click me</button>
-------------------
import { ref, reactive } from 'vue'
export default {
components: {
TheWelcome,
},
name: 'App',
setup() {
const lang = ref('English')
const clickHandler = () => {
lang.value = 'Chinese' // 注意:在 setup 内部需要使用【变量名.value】的方式获取属性
console.log('clicked!')
}
return {
lang,
clickHandler,
}
},
}
使用 props
--------------- 传参 ----------------->
----> @/App.vue
<TheWelcome msg="Hello Everyone!" />
-------------------
import TheWelcome from './components/TheWelcome.vue'
export default {
components: {
TheWelcome,
},
}
--------------- 接参 ----------------->
---> @/components/TheWelcom.vue
<p>{{ message }}</p>
------------------
export default {
props: { // 声明入参
msg: String,
},
setup(props) {
const message = props.msg
return {
message, // es6 语法
}
},
}
</script>
使用 ctx(当前实例对象)
在 setup 中不能使用 this,而应该用 ctx,因为 ctx 就代表当前实例对象
setup(props, ctx) {
console.log(ctx) // ctx 就代表当前实例对象
},
------------> 打印该 ctx
Object:
- attrs: (…)
- emit: (…)
- expose: (exposed) => {…}
- slots: (…)
- get attrs: ƒ attrs()
- get emit: emit() { return (event, ...args) => {…}
- get slots: ƒ slots()
- [[Prototype]]: Object
生命周期函数
可以在 setup 内部使用 6 中生命周期钩子
组件的钩子 | setup 内部钩子 |
---|---|
beforeCreate | x |
created | x |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
export default {
setup() {
//可存在多个相同的钩子函数
onMounted(() => {
console.log('mounted - 1')
})
onMounted(() => {
console.log('mounted - 2')
})
},
provide / inject
- 使用 provide() 和 inject() 可以实现嵌套组件之间的数据传递;
- 这两个函数只能在 setup 函数中使用;
- 只能从父组件传值给子组件:父组件使用 provide() 函数向下传递数据,子组件中使用 inject() 函数获取上层传递过来的数据;
import { provide } from 'vue'
setup() {
provide('myQQ', '51383719')
}
-----------------------
import { inject } from 'vue'
setup() {
const myQQ = inject('myQQ')
console.log(myQQ)
}
Vue3 集成 ElementPlus
https://element-plus.org/zh-CN/guide/design.html
加载方式
- 全量加载
- 按需加载(推荐)
....