業務程式碼配置化
寫業務中判斷邏輯無處不在,隨著功能迭代將越來越難以維護,我們該如何應對呢?
什麼是業務程式碼配置化?
根據業務場景使用配置化的 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}`);
}
每個狀態有多種屬性且引數定製化
- 屬性高度定製化,不同狀態需要適配介面不同的欄位
- 使用
Array
或Map
配置 - 通過配置函式並傳參注入介面資料可滿足定製化需求
// 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);
};