前端業務代碼中的配置化

業務代碼中的配置化

工作中有許多邏輯冗雜、迭代頻繁的業務代碼,隨着迭代將越來越難以維護,一些場景適合通過配置化的方式來處理便於維護。

一、什麼是業務代碼配置化?

根據業務場景使用配置化的 Object|Array|Map 處理條件判斷邏輯,通常需要配置文件 CONFIG.js,若邏輯複雜需添加 getConfig 的處理函數 – tool.js

  • 本質上 if/else 邏輯是一種狀態匹配

  • 表驅動法,使用表數據,存儲對應的狀態處理

  • 可讀性好,減少了繁雜嵌套的 if-else,讀取配置,邏輯更清晰

  • 可維護性高,邏輯分支的增刪只是 CONFIG 的增刪

二、如何在業務場景中進行代碼配置化?

1. 簡單的狀態映射

  • 按需使用 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());

2. 每個狀態有多種屬性

  • 多個屬性
  • 使用 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}`);
  }

3. 每個狀態有多種屬性且參數定製化

  • 參數高度定製化,不同狀態需要適配接口不同的字段
  • 使用 Array 配置
  • 通過配置函數並傳參注入接口數據可滿足定製化需求
// 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 數據下發方式不同
  • 公共組件,需要適配其他模塊的接口數據作展示

index.xml

  • 數據驅動,減少模板中的判斷邏輯
<view class="panel" bind:tap="goDetail">
  <!-- 封面 -->
  <image  wx:if="{{panel.cover}}" class="panel__cover" src="{{panel.cover.image}}">
      <view class="panel__tag {{panel.tagClass}}" wx:if="{{panel.tagClass}}">{{panel.tag}}</view>
  </image>
  <!-- 標題 -->
  <view class="panel__titl" wx:if="{{panel.title}}">{{panel.title}}</view>
  <!-- footer -->
  <view class="panel__footer" wx:if="{{panel.showFooter}}">
    <image class="panel__footer-icon" wx:if="{{panel.user.icon}}" src="{{panel.user.icon}}"></image>
    <text class="panel__footer-name" wx:if="{{panel.user.nickname}}">{{panel.user.nickname}}</text>
    <text class="panel__footer-comment" wx:if="{{panel.commentCount}}">{{panel.commentCount}}評論</text>
  </view>
</view>

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);
};

四、結語

所以,業務代碼配置化很簡單,大家也都一直在用,只是如果在一些業務場景中都形成配置化的習慣或者共識,可能更好維護吧。

鄙人拙見,大佬賜教~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

您可能也會喜歡…