• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

前端业务代码配置化处理条件判断逻辑

开发技术 开发技术 3个月前 (06-24) 141次浏览

业务代码配置化

写业务中判断逻辑无处不在,随着功能迭代将越来越难以维护,我们该如何应对呢?

什么是业务代码配置化?

根据业务场景使用配置化的 Object|Array|Map 处理条件判断逻辑,通常需要配置文件 CONFIG.js,若逻辑复杂需添加 getConfig 的处理函数 – tool.js

  • 本质上 if/else 逻辑是一种状态匹配

  • 表驱动法,使用表数据,存储对应的状态处理

  • 可读性好,减少了繁杂嵌套的 if-else,读取配置,逻辑更清晰

  • 可维护性高,逻辑分支的增删只是 CONFIG 的增删

不同业务场景的代码配置化

简单的状态映射

  • 按需使用 Object|Map 配置
单一条件
  • Object 形式:
// CONFIG.JS
  export const STATUS = {
    STUDENT: 0,
    TEACHER: 1,
    MA_NONG: 2,
  };
  export const WORK_MAP = {
    STATUS.STUDENT: '学生',
    STATUS.TEACHER: '老师',
    STATUS.MA_NONG: '码农',
  };

// index.js
  this.setData({
    work: WORK_MAP[status],
  });

  axios.post(url, { status: STATUS.MA_NONG });
  • Map 形式:
// CONFIG.JS
  export const WORK_MAP = new Map([[0, '学生'], [1, '老師'], [2, '码农']]);
// index.js
  this.setData({
    work: WORK_MAP.get(status),
  });


多重条件
  const config = new Map([
    [
      (condition0, condition1, condition2) =>
        condition0 && condition1 && condition2,
      () => {
        console.log('map0');
      },
    ],
    [
      (condition0, condition1, condition2) =>
        condition0 || condition1 || condition2,
      () => {
        console.log('map1');
      },
    ],
  ]);
  config.forEach((action, _if) => _if(0, 1, 0) && action());

每个状态有多种属性或行为

  • 多个属性或行为
  • 使用 Array 配置
// CONFIG.JS
  export const CONFIG = [
    {
      status: STATUS.STUDENT,
      name: '学生',
      action: '谈恋爱',
    },
    {
      status: STATUS.TEACHER,
      name: '老师',
      action: '教书',
    },
    {
      status: STATUS.MA_NONG,
      name: '码农',
      action: '写bug',
    },
  ];

// index.js
  <!-- 根据状态不同的行为 -->
  function action(status) {
    const { name, work } = CONFIG.find(i => i.status === status);
    console.log(`${name}在${action}`);
  }

每个状态有多种属性且参数定制化

  • 属性高度定制化,不同状态需要适配接口不同的字段
  • 使用 ArrayMap 配置
  • 通过配置函数并传参注入接口数据可满足定制化需求
// CONFIG.JS
  export const CONFIG = [
    {
      status: STATUS.STUDENT,
      name: '学生',
      action: () => {
        console.log('学生最喜欢谈恋爱了');
      },
    },
    {
      status: STATUS.TEACHER,
      name: '老师',
      action: (info) => {
        alert(`老师${info.age}岁,每天${info.action}`);
      },
    },
    {
      status: STATUS.MA_NONG,
      name: '码农',
      action: (info) => {
        toast(`码农工作${info.workTime}年了,头发仅剩${info.hair}根了`);
      },
    },
  ];

// index.js
  <!-- 根据接口状态action -->
  function action(res) {
    const { action, info } = CONFIG.find(i => i.status === res.status);
    action && action(info); // 传参定制化
  }

实例

大首页瀑布流 item 样式

  • 根据 list 接口下发的 item 的类型(type)&样式(layout)字段取 item 中的封面、标题、标签、头像…,字段各不相同
  • 十几种 item 类型,有的还有不同的 layout,item 数据下发方式不同
  • 公共组件,需要适配其他模块的接口数据作展示

CONFIG.JS

import { Layout, NewsType, Redirect } from 'src/constant';
import { formatImage, formatUser } from './tool';

/**
 * 配置项
 * @param {String} title 标题
 * @param {String} cover 封面
 * @param {String} tag 标签
 * @param {Object} user 用户信息
 * @param {Boolean} showFooter 是否显示footer
 * @param {Boolean} isAd 是否广告
 * @param {Function} itemWrap 兼容接口数据函数,数据可能以ref_xxx下发,比如帖子:ref_post
 * ......
 */

<!-- 默认配置项 -->
export const DEFAULT = {
  title: ({ title = '' }) => title,
  cover: ({ image_list = [], article_images = [] }) =>
    formatImage(image_list[0]) || formatImage(article_images[0]),
  showFooter: true,
  user: ({ user, user_account, article_source_tx = '' }) =>
    user
      ? formatUser(user)
      : user_account
      ? formatUser(user_account)
      : {
          icon: '',
          nickname: article_source_tx,
        },
};

export const CONFIG = [
  {
    type: NewsType.NEWS,
    ...DEFAULT,
    tag: '资讯',
    tagClass: 'news',
  },
  {
    type: NewsType.VIDEO,
    ...DEFAULT,
    tag: '',
    tagClass: 'video',
    cover: ({ image_gif_list = [], image_list = [] }) => formatImage(image_gif_list[0] || image_list[0]),
    video: ({ video_url = '', tencent_vid = '', image_gif_list = [] }) => ({
      hasVideo: true,
      src: tencent_vid || video_url,
      video_url,
      tencent_vid,
      gifCover: formatImage(image_gif_list[0]),
    }),
  },
  {
    type: Redirect.EVAL_DETAIL,
    layouts: [
      {
        layout: Layout.EVALUATION,
        ...DEFAULT,
        tag: '口碑',
        tagClass: 'praise',
      },
      {
        layout: Layout.PRAISE_COMPONENT,
        ...DEFAULT,
        tag: '口碑',
        tagClass: 'praise',
        itemWrap: ({ ref_car_score = {} }) => ref_car_score,
        title: ({ chosen_topic = '' }) => chosen_topic,
        commentCount: ({ comment_count = null }) => comment_count,
        cover: ({ images = [], recommend_images = [] }) =>
          formatImage(images[0]) ||
          formatImage(getCoverFromRecommendImages(recommend_images)),
      },
      {
        layout: Layout.NORMAL,
        ...DEFAULT,
      },
    ],
  },
];

tool.js

import { CONFIG, DEFAULT, AD_CONFIG } from './CONFIG';
// 获取瀑布流item数据
export const getPanelData = (item) => {
  const getConfigByTypeAndLayout = () => {
    let config = CONFIG.find((i) => i.type == item.type);
    if (item.isAd) {
      config = AD_CONFIG;
    }
    if (config && config.layouts) {
      config = config.layouts.find(
        (i) => i.layout === item.layout_type || i.layout === item.display_style
      );
    }
    if (!config) {
      config = DEFAULT;
      console.log('no-config', item.type, item.layout_type, item);
    }
    return config;
  };
  const getPanelDataByConfig = (c) => {
    const panel = {};
    let _item = item;
    if (c.itemWrap) {
      _item = c.itemWrap(item);
    }
    Object.keys(c).forEach((key) => {
      if (typeof c[key] === 'function') {
        panel[key] = c[key](_item);
      } else {
        panel[key] = c[key];
      }
    });
    return panel;
  };
  // 根据item的类型、样式获取配置
  const config = getConfigByTypeAndLayout(item);
  // 根据配置获取瀑布流item信息
  return getPanelDataByConfig(config);
};

喜欢 (0)