Vue3实践和对vuex中mapState,mapGetters辅助函数封装

本文介绍了Vue.js中的高级使用技巧,包括如何使用readonly保护组件props不被子组件修改,避免响应式变量解构失去响应性,以及正确清理watchEffect的副作用。此外,还详细探讨了在setup函数中访问Vuex Store数据的方法,如使用mapState和mapGetters辅助函数,并进行了相关函数的封装,以支持模块化的状态管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. readonly API的使用

在我们传递给其他组件数据时,如果直接将响应式数据传递给子组件。子组件如果使用数据不规范,修改了父组件传进来的props值没有任何反馈。

// 父组件
// <ReadonlyChild :info="info" />

setup() {
    const info = reactive({
      name: "哇哈哈",
    });
    return {
      info,
    };
}
// 子组件
setup(props) {
    const onChangeInfo = () => {
      const info = props.info;
      // 修改父组件传来的props 没有任何反馈。
      info.name = "woowow";
    };
    return {
      onChangeInfo,
    };
}

开发中我们往往希望其他组件使用我们传递的内容,但是不允许它们修改时,就可以使用 readonly了。

// 父组件
// <ReadonlyChild :info="infoReadonly" />

setup() {
    const info = reactive({
      name: "哇哈哈",
    });
    const infoReadonly = readonly(info);
    const onChangeInfo = () => {
      // 在父组件中可修改info中的值,子组件依然可响应更新
      info.name = "父组件给你的值";
    };
    return {
      infoReadonly,
      onChangeInfo
    };
}
// 子组件
setup(props) {
    const onChangeInfo = () => {
      const info = props.info;
      // 此时修改props时,控制台会有一个警告:
      // Set operation on key "name" failed: target is readonly. 
      info.name = "woowow";
    };
    return {
      onChangeInfo,
    };
}

2. 响应式变量直接解构会失去响应性

将响应式变量直接解构会失去其响应性

const info = reactive({ age: 18 });
// 直接解构后 age 值失去响应性,当 onChangeAge 函数触发时,age值不在变,而ageRef 依然具有响应性
const { age } = info;
const { age: ageRef } = toRefs(info);
const onChangeAge = () => {
  info.age++;
};

3. watchEffect 清除副作用

watchEffect API 可自动收集依赖项,当依赖项改变时触发侦听器函数。当我们在侦听器函数执行额外的副作用函数,例如:发送网络请求时。每当依赖性项变更都会发起一个新的网络请求,那么上一次的网络请求应该被取消掉。这个时候我们就可以清除上一次的副作用了。

setup() {
    const count = ref(0);

    const onChangeCount = () => {
      count.value++;
    };

    watchEffect((onInvalidate) => {
      // 侦听器函数中需要发起网络请求,用setTimeout模拟
      const timer = setTimeout(() => {
        console.log("请求成功啦");
      }, 2000);

      // 在侦听器函数重新执行时触发onInvalidate函数
      onInvalidate(() => {
        // 在这个函数中清除请求
        clearTimeout(timer);
        console.log("onInvalidate 回调触发");
      });

      // 自动收集count的依赖
      console.log("count-在改变", count.value);
    });

    return {
      count,
      onChangeCount,
    };
  }

4. setup 函数访问Vuex中Store数据

4.1 使用mapState辅助函数

通常需要通computed函数来获取state中数据,并保存响应性。

setup() {
    const store = useStore();
    // 在setup中要获取store中的state。如果state非常多,无疑这样做很繁琐
    const uName = computed(() => store.state.name);
    const uAge = computed(() => store.state.age);
    const uHeight = computed(() => store.state.height);
    /**
     * 直接使用mapState辅助函数得不到想要的结果
     * 这样获取的storeState 是一个 { name:  function mappedState (){...}, age: function mappedState (){...}, height: function mappedState (){...} } 这样的对象
     */
    const storeState = mapState(["name", "age", "height"]);
    // 需要对返回值进行处理
    const resStoreState = {};
    Object.keys(storeState).forEach((fnKey) => {
      const fn = storeState[fnKey].bind({ $store: store });
      resStoreState[fnKey] = computed(fn);
    });

    return {
      uName,
      uAge,
      uHeight,
      ...resStoreState,
    };
  }

封装成hooks如下:

// useState.js
import { computed } from "vue";
import { useStore, mapState } from "vuex";

export default function useState(mapper) {
  const store = useStore();
  const storeStateFns = mapState(mapper);
  const storeState = {};
  Object.keys(storeStateFns).forEach((fnKey) => {
    const fn = storeStateFns[fnKey].bind({ $store: store });
    storeState[fnKey] = computed(fn);
  });
  return storeState;
}

在组件中使用时

import useState from "@/hooks/useState";


setup() {
    // 数组用法
    const state = useState(["name", "age", "height"]);

    // 对象用法,可使用别名
    const stateObj = useState({
      uName: (state) => state.name,
      uAge: (state) => state.age,
      uHeight: (state) => state.height,
    });
    return {
      ...state,
      ...stateObj,
    };
  }

4.2 mapGetters 辅助函数的封装

其原理与mapState 函数封装类似

// useGetters.js
import { computed } from "vue";
import { mapGetters, useStore } from "vuex";

export default function useGetters(mapper: any) {
  const store = useStore();
  const storeGettersFns = mapGetters(mapper);
  const storeGetters = {};
  Object.keys(storeGettersFns).forEach((fnKey) => {
    const fn = storeGettersFns[fnKey].bind({ $store: store });
    storeGetters[fnKey] = computed(fn);
  });
  return storeGetters;
}

useStateuseGetters两个函数相似度很高,在进一下封装

// useMapper.js
import { computed } from "vue";
import { useStore } from "vuex";

export default function useMapper(mapper, mapFn) {
  const store = useStore();
  const storeStateFns = mapFn(mapper);
  const storeState = {};
  Object.keys(storeStateFns).forEach((fnKey) => {
    const fn = storeStateFns[fnKey].bind({ $store: store });
    storeState[fnKey] = computed(fn);
  });
  return storeState;
}

// useState.js
import { mapState } from "vuex";
import useMapper from "./useMapper";

export default function useState(mapper) {
  return useMapper(mapper, mapState);
}

// useGetters.js
import { mapGetters } from "vuex";
import useMapper from "./useMapper";

export default function useGetters(mapper: any) {
  return useMapper(mapper, mapGetters);
}

4.3 对module的支持

useStateuseGetters 函数暂时还不支持传入命名空间,进一步封装。 useMapper的封装保持不变。

// useState.js
import { createNamespacedHelpers, mapState } from "vuex";
import useMapper from "./useMapper";

export default function useState(mapper, moduleName) {
  let mapperFn = mapState;
  if (typeof moduleName === "string" && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapState;
  }
  return useMapper(mapper, mapperFn);
}

// useGetters.js
import { createNamespacedHelpers, mapGetters } from "vuex";
import useMapper from "./useMapper";

export default function useGetters(mapper, moduleName) {
  let mapperFn = mapGetters;
  if (typeof moduleName === "string" && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapGetters;
  }
  return useMapper(mapper, mapperFn);
}

// 在组件中的使用
// Home.vue
setup() {
  const state = useState(["homeCounter"], "home");

  const stateGetter = useGetters(["doubleHomeCounter"], "home");

  return {
    ...state,
    ...stateGetter,
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值