transData
讲述Vue3中数据传输的方法以及异步操作
Vue3中的数据传递
父传子
- 是组件间传递数据的主流方式,尤其是在父子组件关系明确且数据流向单一的情况下。这种方式简单且直观,适合大多数场景。
- 子组件使用 props 属性接收父组件传递的数据;
- 语法:
<子组件 :属性名="父组件数据" :属性名="父组件数据"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<!-- 父组件 -->
<template>
<ChildComponent :message="parentMessage" />
</template>
<!-- 选项式 -->
<script>
import ChildComponent from './ChildComponent.vue';
export default {
data() {
return {
parentMessage: 'Hello from parent'
};
},
components: { ChildComponent }
/* 这行代码是关于组件注册的,必要的,但是如果使用<script setup>则可以省略
组件注册目的:
告诉 Vue 这个父组件要使用 ChildComponent 子组件
建立组件间的关联关系
为什么必要:
Vue 规定必须先注册组件才能在模板中使用
如果不注册就直接在模板中使用 <ChildComponent>,Vue 会报错
*/
};
</script>
<!-- 组合式API -->
<script setup>
import ChildComponent from './ChildComponent.vue';
const parentMessage = 'Hello, Child!';
</script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<!-- ChildComponent.vue -->
<template>
<p>{{ message }}</p>
</template>
<!-- 选项式API
<script >
export default {
props: ['message']
};
</script> -->
<!-- 组合式API -->
<script setup>
</script>
子传父
在子组件中触发事件
在父组件中监听事件
原理
- 事件触发
- 子组件通过 $emit() 触发自定义事件
- 可传递数据作为第二个参数
- 事件监听
- 父组件使用 @事件名 语法监听
- 通过回调函数接收数据
- 数据流向:子组件 -> 触发事件 -> 父组件接收 -> 更新数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28<!-- ChildComponent.vue -->
<template>
<button @click="sendMessage">发送消息到父组件</button>
</template>
<!-- <script>
选项式
export default {
methods: {
sendMessage() {
this.$emit('message-event', 'Hello from child');
}
}
};
</script> -->
<!-- 组合式 -->
<script setup>
import { defineEmits } from 'vue';
const emit = defineEmits(['message-event']);
const sendMessage = () => {
emit('message-event', 'Hello from child');
};
</script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<!-- ParentComponent.vue -->
<template>
<ChildComponent @message-event="handleMessageEvent" />
<p>{{ messageFromChild }}</p>
</template>
<!-- 选项式 -->
<!-- <script>
import ChildComponent from './ChildComponent.vue'
export default {
components: {
ChildComponent//注册组件
},
data() {
return {
messageFromChild: ''
};
},
methods: {
handleMessageEvent(message) {
this.messageFromChild = message;
}
}
};
</script> -->
<!-- 组合式 -->
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue'
// 必须遵循js的规范,任何变量使用前必须申明
let messageFromChild = ref('');
const handleMessageEvent = (message) => {
messageFromChild.value = message;
};
</script>- 直接通过useRoot获取全局数据,想要谁就直接通过options属性获取
- 全局状态管理:使用 Vuex 或者 Pinia 来管理全局状态,适用于复杂的应用场景。如下介绍vuex
- Provide/Inject API:适用于跨级组件间传递数据。
- 事件总线:使用事件总线(Event Bus)来传递数据,但这种方式在 Vue 3 中不推荐使用。
- Context API:在组合式 API 中,可以通过 context 来传递数据。
- 事件触发
vuex
安装:npm install vuex@next –save
核心概念
创建和定义 store
- state: 存储状态信息
- store/下定义状态信息
- 在组件中通过有三种方式获取state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//1内置函数computed (推荐)
/*
优点:
响应式: 状态变化时会自动更新视图
缓存: 计算结果会被缓存,只有依赖变化时才重新计算
*/
count: computed(() => store.state.count)
doubleCount: computed(() => store.getters.doubleCount),
//2直接访问 (不推荐)
/*
缺点:
非响应式: 状态变化时不会自动更新视图
需要手动触发更新
*/
count: store.state.count
//3使用 ref + watch (特殊场景)
/*
使用场景:
需要对状态变化做额外处理
需要本地维护状态副本
*/
const count = ref(store.state.count)
watch(() => store.state.count, (newVal) => {
count.value = newVal
})
- mutations: 同步修改状态
- store/下定义修改状态的方法
- 当需要修改状态时,在组件中通过commit相应的mutation和参数(见store/下定义修改状态的方法)触发重新处理&赋值给state中的变量来实现状态更新
- actions: 异步操作
- 异步请求是指发出请求后,不会立即得到结果,而是在未来某个时间点才会收到响应的操作。
- 为什么需要 Actions
- Mutations 必须是同步的
- Actions 可以包含异步操作
- Actions 可以组合多个 mutations
- 使用场景
- API 请求等延时操作
- 复杂的状态修改流程
- 需要组合多个 mutation
- 涉及异步操作的业务逻辑:比如定时器、延时操作、文件上传/下载等
- 特点:
- async 定义异步异步函数
- 通过 context 对象访问 store 实例的方法和属性
- 可以触发其他 actions (dispatch)
- 最终通过 commit 提交 mutation 修改状态
- 在组件中通过dispatch提交action
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37// store/index.js
const store = createStore({
state: {
userInfo: null,
loading: false
},
mutations: {
setUserInfo(state, info) {
state.userInfo = info
},
setLoading(state, status) {
state.loading = status
}
},
actions: {
// 1. 处理异步操作
async login({ commit }, credentials) {
try {
commit('setLoading', true)
const response = await api.login(credentials)
commit('setUserInfo', response.data)
} finally {
commit('setLoading', false)
}
},
// 2. 复杂业务逻辑
async checkout({ commit, state, dispatch }) {
// 检查库存
await dispatch('checkStock')
// 创建订单
await dispatch('createOrder')
// 清空购物车
commit('clearCart')
}
}
})
- getters: 计算属性
- store/下定义计算相关的方法
- 组件中通过store.getters调用相应的方法
- modules: 模块化管理状态
- 针对不同联动事件之间有不同的关联状态和操作,因此划分成多个模块,每个模块管理相关的一部分的共享状态会更方便操作;
- 比如Aside和headerNav之间的通信(联动)都是关于菜单的,于是将所有的菜单相关的状态(信息、操作等等)放在一个模块sotore/menu.js中
- 需要在/store/index.js中导入所需模块,将创建的store导出;供main.js挂载和其他组件使用;同时模块中只需要定义被其他组件需要的state、mutations、actions、getters并导出
1
2
3
4
5
6
7
8
9// src/sotre/index.js
import { createStore } from "vuex";
import menu from "./menu";
export default createStore({
modules: {
menu
}
})1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//menu.js
const state={
isCollapse:false,//是否折叠
selectMenu:[]//选中的菜单
}
const mutations={
collapseMenu(state){
state.isCollapse=!state.isCollapse//取反
}
}
//从此模块中导出此需要的数据
export default{
state,
mutations
}
- 在vuex中如果有模块的话,组件在调用 state 时需要加上模块名,而调用 mutations、actions、getters 时则不需要加模块名。(在vue3_study_basic中亲测)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24export default {
setup() {
const store = useStore()//获取store实例
const newUsername = ref('') // 新增响应式变量存储输入值
return {
// 读取状态
count: computed(() => store.state.Try.count),
doubleCount: computed(() => store.getters.doubleCount),
username: computed(() => store.state.Try.username),
newUsername,//这其实是newUsername:newUsername的简写;代表将newUsername暴露给模板即将输入框的值暴重新赋值给newUsername(不能忽略)
// 状态修改:mutations
increment: () => store.commit('increment'),//提交一个名为increment的变化
// 重置用户名
resetUsername: () => {
store.commit('setUsername', newUsername.value)//提交一个名为setUsername的变化
newUsername.value = '' // 清空输入框
},
//action:异步操作
login: (username) => store.dispatch('login', username),//分发一个名为login的action
}
}
}
- 针对不同联动事件之间有不同的关联状态和操作,因此划分成多个模块,每个模块管理相关的一部分的共享状态会更方便操作;
- state: 存储状态信息
在入口文件main.js中使用:
- 创建 store 实例并挂载
组件中通过 useStore() 访问和使用
- 如下是一个基础案例额
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// store/index.js
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
count: 0,
username: ''
}
},
mutations: {
// 修改状态的方法
increment(state) {
state.count++
},
setUsername(state, username) {
state.username = username
}
},
actions: {
// 异步操作
async login({ commit }, username) {
// 模拟API调用
//使用 new Promise 和 setTimeout 模拟了一个耗时1秒的API调用
//await 会暂停执行,直到Promise完成
await new Promise(resolve => setTimeout(resolve, 1000))
commit('setUsername', username)
}
},
getters: {
// 计算属性
doubleCount(state) {
return state.count * 2
}
}
})
export default store1
2
3
4
5
6
7
8<!-- main.js -->
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48<!-- 组件.vue -->
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<p>Username: {{ username || '未登录' }}</p>
<button @click="increment">+1</button>
<!-- 点击按钮触发login方法,传入参数admin到login action -->
<button @click="login('admin')">Login</button>
<!-- 添加重置按钮 -->
<input
v-model="newUsername"
placeholder="输入新用户名"
>
<button @click="resetUsername">Reset User</button>
</div>
</template>
<script>
import { useStore } from 'vuex'
import { computed } from 'vue'
export default {
setup() {
const store = useStore()//获取store实例
const newUsername = ref('') // 新增响应式变量存储输入值
return {
// 读取状态
count: computed(() => store.state.count),
doubleCount: computed(() => store.getters.doubleCount),
username: computed(() => store.state.username),
newUsername,//这其实是newUsername:newUsername的简写;代表将newUsername暴露给模板即将输入框的值暴重新赋值给newUsername(不能忽略)
// 状态修改:mutations
increment: () => store.commit('increment'),//提交一个名为increment的变化
// 重置用户名
resetUsername: () => {
store.commit('setUsername', newUsername.value)//提交一个名为setUsername的变化
newUsername.value = '' // 清空输入框
},
//action:异步操作
login: (username) => store.dispatch('login', username),//分发一个名为login的action
}
}
}
</script>
vuex持久化
vuex-persistedstate 将 store 存储到浏览器的时机如下:这种机制确保了在页面刷新或重新打开后能够恢复之前的状态。
主要存储时机
Store 发生变化时
- 当通过 mutation 修改 state 时
- 在每次 state 更新后自动触发
具体触发点
1
2
3
4
5// 监听 store 的变化
store.subscribe((mutation, state) => {
// 将数据持久化到 localStorage
localStorage.setItem('vuex', JSON.stringify(state))
})
- 存储行为特点
实时性
- 同步执行,立即存储
- 不需要手动触发
选择性存储
1
2
3
4
5
6
7
8
9import createPersistedState from 'vuex-persistedstate'
export default new Vuex.Store({
plugins: [
createPersistedState({
paths: ['需要持久化的state路径'] // 可以选择性存储
})
]
})存储位置
- 默认使用 localStorage
- 可配置使用 sessionStorage 或其他存储方式
vuex-persistedstate
插件在页面刚刚打开进行渲染时,不会立即将 store 存储到浏览器。相反,它会从浏览器的存储(如
localStorage或 sessionStorage
)中读取之前保存的状态,并将其恢复到 Vuex store 中。
- 工作流程:
页面加载时:
vuex-persistedstate
插件会从浏览器存储中读取之前保存的 Vuex 状态。- 将读取到的状态合并到当前的 Vuex store 中。
状态变化时:
- 当 Vuex store 中的状态发生变化时(通过 mutation),
vuex-persistedstate
会将新的状态存储到浏览器中。
- 当 Vuex store 中的状态发生变化时(通过 mutation),
总结:
页面加载时:
vuex-persistedstate
从浏览器存储中恢复状态到 Vuex store。状态变化时:
vuex-persistedstate
将新的状态存储到浏览器中。
因此,vuex-persistedstate
插件在页面刚刚打开进行渲染时,不会立即将 store 存储到浏览器,而是从浏览器中读取之前保存的状态并恢复。
异步操作
什么是异步请求?
Promise 是处理异步操作的一种方式,它代表一个异步操作的最终完成(或失败)及其结果值。
基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 1. 基础语法
const promise = new Promise((resolve, reject) => {
// 异步操作
if (成功) {
resolve(结果)
} else {
reject(错误)
}
})
// 2. 使用Promise
promise
.then(result => {
// 处理成功
})
.catch(error => {
// 处理失败
})实际示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 1. 封装API请求
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
axios.get(`/api/users/${userId}`)
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
// 2. 使用async/await简化
async function getUserData(userId) {
try {
const data = await fetchUserData(userId)
return data
} catch (error) {
console.error('获取用户数据失败:', error)
}
}
//3. 延时
async login({ commit }, username) {
//延时1秒
await new Promise(resolve => setTimeout(resolve, 1000))
commit('setUsername', username)//最终通过 commit 提交 mutation 修改状态
}
凡是内部调用多个函数且要讲究执行顺序的的函数要用异步async申明;异步操作内部要使用await调用已存在的方法,外部也要用async申明参数,每一环都是如此;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48//group/index.vue的提交弹窗逻辑
const localData=localStorage.getItem('pz_v3pz')
import { computed, toRaw } from 'vue';
const routerList=computed(()=>store.state.menu.routerList)
//内有异步操作
const confirm=async (formEl)=>{
if (!formEl) return
//内有异步操作
await formEl.validate( async(valid,fields)=>{
if (valid) {
//根据APi文档可知,其中一个参数为permissions:string,故要将选中的权限字符串化
const permissions = JSON.stringify(treeRef.value.getCheckedKeys())
// console.log(permissions)
try {
// 1. 先执行 userSetMenu请求重新设置菜单数据
await userSetMenu({
name: form.name,
permissions,//参数名与变量名同名时,可以简写:一个即可,不需要:
id: form.id
})
// 2. 更新列表数据
await getListData()
// 3. 关闭弹窗
beforeClose()
// 4. 再执行 menuPermissions
const { data } = await menuPermissions()
store.commit('dynamicMenu', data.data)
// 5. 添加路由
toRaw(routerList.value).forEach(item => {
router.addRoute('main', item)
})
} catch (error) {
console.error('操作失败:', error)
}
}else{
console.log('error submit!',fields)
}
})
}
transData
https://tolsz.me/2024/10/19/transData/