查询模块数据流优化

简介

上一篇介绍的查询模块数据流发现了一些问题:https://www.cnblogs.com/cjc-0313/p/16810460.html

  • 当用户没有操作视图,而是通过路由前后跳转时,并不会触发视图层数据的更新,因此需要调整部分数据流向
  • 路由参数和 API 参数的生成过程存在重复,需要合并;这是由一开始对生成过程的权责分配不清而导致的逻辑混乱

优化版

先上图:

与旧版相比,本此优化主要调整了“视图层 —— 路由 —— 本地数据 —— API 参数”四者之间的数据流向,不影响 API 层与服务器之间的交互。虚线部分仍是不需要处理路由参数变化时的数据流向

这是旧版的数据流向:

上图与示例代码其实有些区别,代码中是从视图层获取数据后保存到本地的同时重新生成路由参数,并不是从本地数据获取更新并生成路由参数。

而新版中改为:用户操作使得参数发生变化时,在事件处理其中只将更新值传给路由对象,而本地数据变量是从路由对象获取更新后的值,以便更新视图上的数据;同时,仍是通过监听路由的变化,自动触发生成新的 API 参数并调用相关接口。API 参数这里可以根据需要,更换成自动从路由对象获取新的值,或者像这里介绍的一样,在路由对象的侦听器中更新。这里采用在侦听器中更新的主要的目的是通过同步模式确保 API 参数能在发送请求前完成,因为 vue 的计算属性生成新的 API 参数并不能保证在侦听器之前执行完毕(注意是完毕,也就是生成了完整的新参数,而不是开始执行生成过程)。

示例代码

由于调整了数据流向,因此涉及到上述四部分的代码都需要调整。同时,将公共方法提取到外部,通过 mixins 导入方便复用。

searchParamsMixins.js(核心)

import isObjEmpty from '@/utils/utils/isObjEmpty';

function searchParamsMixins() {
    return {
        methods: {
            //#region 工具 --
            // params 或 query 对象可能有属性,但值为空,所以还需要额外判断
            isEmpty(obj) {
                return isObjEmpty(obj);
            },
            //#endregion --

            //#region 路由参数处理 --
            // 用户操作视图后,调用事件处理器方法,获取更新
            // 将更新的内容以特定格式传入处理方法,转化为新的路由参数对象,并 push 到路由器对象
            // watch 侦听路由对象,一旦改变,立即获取路由中的参数,自动生成 API 参数并请求服务器

            /* 参数示例:
            * 全保留 { queryChanges: 'allReserve', paramsChanges: 'allReserve' }
            * 只保留 query 或 只保留 params,保留的给一个 'allReserve',清空的给 undefined 或不给
            * 全清空 {}
            * 对 query 或 params 单独增、删、改,保留的参数给一个 'allReserve',要增加的参数的属性直接给值,删除的给 undefined,修改的也是直接给值
            */

            /**
             * 
             * @param {从路由对象获取旧的参数} oldParams
             * @param {根据用户输入,生成参数的更新值(不是完整的新参数)} paramsChanges
             * @param {参数的默认值,可选} defaultParams
             * @returns 返回更新后的完整 query 或 params 对象,用于路由显示参数
             */
            handleUpdateParams(oldParams, paramsChanges, defaultParams = {}) {
                let newParams = {}; // paramsChanges === undefined
                if (paramsChanges === 'allReserve') {
                    newParams = oldParams;
                } else if (!this.isEmpty(paramsChanges)) {
                    newParams = Object.assign({}, oldParams);
                    for (const key in paramsChanges) {
                        // 将同名参数重置为初始值
                        if (Object.hasOwnProperty.call(paramsChanges, key)) {
                            if (
                                Object.hasOwnProperty.call(oldParams, key) &&
                                paramsChanges[key] === undefined
                            ) {
                                newParams[key] = defaultParams?.[key]
                                    ? defaultParams[key]
                                    : undefined;
                            }
                            else {
                                newParams[key] = paramsChanges[key];
                            }
                            // 假性值统一转化为 undefined ,避免将假性值传递给路由对象而导致跳转失败
                            if (!newParams[key]) newParams[key] = undefined;
                        }
                    }
                }
                return newParams;
            },
            /**
             * 
             * @param {查询组件在 router 中定义的 name} SearchRouterCompoName 
             * @param {$route 对象中 query 和 params 对象的属性的变化} changes 
             */
            handleUpdateRouteParams(SearchRouterCompoName, { queryChanges, paramsChanges }) {
                if (SearchRouterCompoName) {
                    let location = { name: SearchRouterCompoName };
                    let newQuery = this.handleUpdateParams(this.$route.query, queryChanges),
                        newParams = this.handleUpdateParams(this.$route.params, paramsChanges);
                    console.log('newParams', newParams)
                    if (!this.isEmpty(newQuery)) location.query = newQuery;
                    if (!this.isEmpty(newParams)) location.params = newParams;
                    this.$router.push(location);
                }
            },
            //#endregion --

            //#region 生成请求参数 --
            // 为了确保在请求之前获取到最新的路由参数,此处通过方法在请求函数前同步执行,而不是通过计算属性获取
            setupSearchParams(route) {
                return Object.assign({}, route.params, route.query);
            },
            //#endregion --
        }
    }
}

export default searchParamsMixins;

isObjEmpty.js

import { isEmpty } from 'lodash'

function isObjEmpty(obj) {
    if (isEmpty(obj)) return true;
    // lodash.isEmpty 无法排除对象有属性,但属性值为空的情况
    for (const key in obj) {
        if (Object.hasOwnProperty.call(obj, key)) {
            return false;
        }
    }
    return true;
}

export default isObjEmpty;

业务组件中使用

将定义好的方法作为业务组件的 mixins,然后在本地重新包装一个传入查询组件 name 值的方法。

对于用户操作后从视图层返回的数据,在本地的事件处理方法中,仅将更新作为参数传递给 handleSearchParamsChaned 方法。

import searchParamsMixins from '@/utils/mixins/searchParams';

...
  mixins: [searchParamsMixins],
...

  methods: {
    // 从外部混入,本地只包装一个传入路由组件名称的方法
    handleUpdateRoute(changes) {
      this.handleUpdateRouteParams('Search', changes);
    },
    // 增加或修改 query 参数;params 同理
    handleUpdateCurrentName(name) {
      this.handleUpdateRoute({
        queryChanges: { name: name},
        paramsChanges: 'allReserve', // 由于只有 query 的变化,params 的属性全部保留
      });
    },
    // 更新数组类型的参数
    handleUpdateCurrentTags(tagId, tagValue) {
      const oldTags = this.$route.query.tags;
      let newTags = oldTags instanceof Array ? [].concat(oldTags ) : [];
      const findIdx = this.oldTags.findIndex(oldTagVal => oldTagVal === tagValue);
      if (findIdx > -1) {
        newTags [findIdx ] = tagValue;
      } else {
        newTags .push(tagValue);
      }
      this.handleUpdateRoute({
        queryChanges: { newTags },
        paramsChanges: 'allReserve',
      });
    },
    // 显示全部结果(无参查询)
    handleGetAllResult() {
      this.handleUpdateRoute({});
    },
  },
...

考虑到参数的变化规则不可穷举,因此将变化的处理过程放在通用方法之外,由业务组件负责。通常会放在事件的 handler 中,或者多处 handler 存在相同变化的,可以抽象为单独的函数,但仍然是在业务组件之中。

总结

本次升级主要是解决了开头提到的两个问题,让数据流向的规则更加简化,通过降低模块的重复与耦合来提升模块协作能力,提高程序的复用性和稳定性,更加方便开发和调试,略微加深了对于软件分层、逻辑复用、权责分配等方面的理解。

posted @ 2022-10-28 21:44  CJc_3103  阅读(23)  评论(0编辑  收藏  举报