transData

讲述Vue3中数据传输的方法以及异步操作

Vue3中的数据传递

父传子

  1. 是组件间传递数据的主流方式,尤其是在父子组件关系明确且数据流向单一的情况下。这种方式简单且直观,适合大多数场景。
  2. 子组件使用 props 属性接收父组件传递的数据;
  3. 语法:<子组件 :属性名="父组件数据" :属性名="父组件数据"/>
    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>
    //编译器宏:defineProps 是由 Vue 的编译器在编译时处理的特殊语法糖。它并非普通的 JavaScript 函数,因此不需要通过 import 从 'vue' 中导入。自动可用:在 <script setup> 块内,defineProps 会被自动识别并处理。
    // 使用 defineProps 声明 props
    const props = defineProps({
    message: {
    type: String,
    required: true
    }
    });
    </script>

子传父

  1. 在子组件中触发事件

  2. 在父组件中监听事件

  3. 原理

    1. 事件触发
      1. 子组件通过 $emit() 触发自定义事件
      2. 可传递数据作为第二个参数
    2. 事件监听
      1. 父组件使用 @事件名 语法监听
      2. 通过回调函数接收数据
    3. 数据流向:子组件 -> 触发事件 -> 父组件接收 -> 更新数据
    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>
    1. 直接通过useRoot获取全局数据,想要谁就直接通过options属性获取
    2. 全局状态管理:使用 Vuex 或者 Pinia 来管理全局状态,适用于复杂的应用场景。如下介绍vuex
    3. Provide/Inject API:适用于跨级组件间传递数据。
    4. 事件总线:使用事件总线(Event Bus)来传递数据,但这种方式在 Vue 3 中不推荐使用。
    5. Context API:在组合式 API 中,可以通过 context 来传递数据。

vuex

  • 安装:npm install vuex@next –save

  • 核心概念

    1. 创建和定义 store

      1. state: 存储状态信息
        1. store/下定义状态信息
        2. 在组件中通过有三种方式获取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
          })
      2. mutations: 同步修改状态
        1. store/下定义修改状态的方法
        2. 当需要修改状态时,在组件中通过commit相应的mutation和参数(见store/下定义修改状态的方法)触发重新处理&赋值给state中的变量来实现状态更新
      3. actions: 异步操作
        1. 异步请求是指发出请求后,不会立即得到结果,而是在未来某个时间点才会收到响应的操作。
        2. 为什么需要 Actions
          1. Mutations 必须是同步的
          2. Actions 可以包含异步操作
          3. Actions 可以组合多个 mutations
        3. 使用场景
          • API 请求等延时操作
          • 复杂的状态修改流程
          • 需要组合多个 mutation
          • 涉及异步操作的业务逻辑:比如定时器、延时操作、文件上传/下载等
        4. 特点:
          1. async 定义异步异步函数
          2. 通过 context 对象访问 store 实例的方法和属性
          3. 可以触发其他 actions (dispatch)
          4. 最终通过 commit 提交 mutation 修改状态
        5. 在组件中通过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')
        }
        }
        })
      4. getters: 计算属性
        1. store/下定义计算相关的方法
        2. 组件中通过store.getters调用相应的方法
      5. modules: 模块化管理状态
        1. 针对不同联动事件之间有不同的关联状态和操作,因此划分成多个模块,每个模块管理相关的一部分的共享状态会更方便操作;
          1. 比如Aside和headerNav之间的通信(联动)都是关于菜单的,于是将所有的菜单相关的状态(信息、操作等等)放在一个模块sotore/menu.js中
          2. 需要在/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
          }



        2. 在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
          24
          export 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

          }
          }
          }
    2. 在入口文件main.js中使用:

      1. 创建 store 实例并挂载
    3. 组件中通过 useStore() 访问和使用

      1. 如下是一个基础案例额
    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 store

    1
    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 存储到浏览器的时机如下:这种机制确保了在页面刷新或重新打开后能够恢复之前的状态。

  • 主要存储时机

  1. Store 发生变化时

    • 当通过 mutation 修改 state 时
    • 在每次 state 更新后自动触发
  2. 具体触发点

1
2
3
4
5
// 监听 store 的变化
store.subscribe((mutation, state) => {
// 将数据持久化到 localStorage
localStorage.setItem('vuex', JSON.stringify(state))
})
  • 存储行为特点
  1. 实时性

    • 同步执行,立即存储
    • 不需要手动触发
  2. 选择性存储

1
2
3
4
5
6
7
8
9
import createPersistedState from 'vuex-persistedstate'

export default new Vuex.Store({
plugins: [
createPersistedState({
paths: ['需要持久化的state路径'] // 可以选择性存储
})
]
})
  1. 存储位置
    • 默认使用 localStorage
    • 可配置使用 sessionStorage 或其他存储方式

vuex-persistedstate 插件在页面刚刚打开进行渲染时,不会立即将 store 存储到浏览器。相反,它会从浏览器的存储(如
localStorage或 sessionStorage)中读取之前保存的状态,并将其恢复到 Vuex store 中。

  • 工作流程:
  1. 页面加载时

    • vuex-persistedstate 插件会从浏览器存储中读取之前保存的 Vuex 状态。
    • 将读取到的状态合并到当前的 Vuex store 中。
  2. 状态变化时

    • 当 Vuex store 中的状态发生变化时(通过 mutation),vuex-persistedstate 会将新的状态存储到浏览器中。
  • 总结:

  • 页面加载时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.site/2024/10/19/transData/
作者
wbj_Lsz
发布于
2024年10月19日
许可协议