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路径'] // 可以选择性存储
    })
    ]
    })
  3. 存储位置

    • 默认使用 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.me/2024/10/19/transData/
作者
wbj_Lsz
发布于
2024年10月19日
许可协议