前言
以前聽過配置優先這個概念,但是從未在開發生產環節中運用這種思想。當然那個時候還是菜鳥,一心想著功能實現。最近的工作中接觸到了這個,而且在方方面面都運用了這個概念,本著學習總結的目的就有了這篇文章。
從路由配置開始
說起來我們前端開發最常接觸的配置其實就是各個框架的路由配置,原理我們也不深究,我們就以 react-router為例子。
<Router history={history}>
<Switch>
<Route path="/" component={App} />
</Switch>
</Router>
複製程式碼
其中 App
是之前 import 進來的。這就是簡單的配置。
我們只要關注一下這個配置還可以怎麼分離。以上這個已經是最簡模式了,我們考慮的是如何將其獨立出來。像 create-react-app
是這樣組織的:
const store = configureStore();
const history = createBrowserHistory();
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Switch>
<Route path="/" component={App} />
</Switch>
</Router>
</Provider>,
document.getElementById("root") as HTMLElement
);
registerServiceWorker();
複製程式碼
看上去沒什麼不對,但其實它將配置耦合進來了。
而分離的方式是建立新的 configSore
import routes from `../routes`;
import request from `../middlewares/request`;
import rootReducer from `../rootReducer`;
const middlewares = [thunk, request];
if (process.env.NODE_ENV !== `production`) {
middlewares.push(createLogger());
}
const finalCreateStore = compose(
applyMiddleware(...middlewares),
reduxReactRouter({
routes,
createHistory
})
)(createStore);
export default function configureStore(initialState) {
const store = finalCreateStore(rootReducer, initialState);
return store;
}
複製程式碼
然後在 routes
裡面寫路由配置
import AllListPage from `./pages/AllListPage`
。。。
export default(
<Route path="/">
<Route path="page/login" component={LoginPage} />
。。。
<Route path="*" component={NOTFOUND} />
</Route>
);
複製程式碼
可能看上去這個動作多此一舉,但這卻是配置必要做法。主要就是要將配置檔案給獨立出來。之後的修改都只通過修改配置檔案,然後模組通過遍歷配置載入配置,渲染對應的功能。
前端配置舉?
在前端方面,其實很多東西都能抽離成配置,通過寫生成器來完成一些抽象,減輕專案工作量。
比如我們常用的系統的左邊欄功能。像後臺系統普遍會有使用者管理、系統配置、文章列表等等選單功能。常規操作就是一個模組一個頁面,然後寫對應的路由配置,然後需要一一對應的效果(比如麵包屑),還需要額外再寫點程式碼。
但是如果我們將其抽離,寫成的用配置代替繁瑣重複的程式碼應該如何處理?
能夠立刻想到的解決方案肯定就是用元件化來實現。在一個 Menu 元件中傳入props,然後動態生成側邊。
但是如果開始分角色,發現不同角色之前要不同的側邊。配置就很管用了。
比如一開始我們可能要傳很多引數到Props裡去,但是如果通過一個簡單的函式傳入一個字串自動生成詳細的json配置,然後讓 Menu 元件自動能夠生成是不是會更簡單的?尤其在許可權判斷的時候就不需要一行行新增新的選單,直接去授權列表裡拿對應許可權的選單列表就可以了。
// 管理員
if (userInfo.role === `2`) {
// 生成側邊欄
menus.push(...tools.getSidebars(
[`TEMPLATE`,
`LIST`, `ALL_LIST`,
`CONFIG`]
));
}
<Sider
menus={menus}
/>
getSidebars:
getSidebars: (arr) => {
if (!Array.isArray(arr) || arr.length === 0) {
return [];
}
const result = [];
arr.map((item) => {
const temp = {
key: commonConstants[`SIDEBAR_${item.toUpperCase()}_KEY`],
icon: commonConstants[`SIDEBAR_${item.toUpperCase()}_ICON`],
title: commonConstants[`SIDEBAR_${item.toUpperCase()}_TITLE`]
};
result.push(temp);
});
return result;
}
複製程式碼
之後不管增加幾個角色還是幾個功能選單,只要在配置裡面填好對應資訊,就可以按照這種簡潔的新增方式給系統增加新的左邊欄。
當然這裡還可以考慮增加頁面元件的配置,最後一個左邊欄對應一個子頁面。
前端還有一個很重要的部分可以用配置搞定的事情我們其實以前都做過,甚至也在多個專案裡運用,那就是將一些第三方元件根據需求再次封裝,通過傳入引數,完成部分個性化配置。
比如常用的 Antd design
的表格元件,當我們想為利用其加一個搜尋功能,加一個篩選條件,加一個跳轉其他頁面的按鈕等等操作,根據業務需求我們可能在其上面加入一些特殊的需求,經過Biubiu的操作功能完成了,我們發現未來可能會需要類似的元件,我們就會想到將資料解耦,通過Props傳入,然後通過傳入的引數進行選擇性的渲染特殊的業務功能。
其實這就是配置優先的一些應用。
比如這裡我們再改一下需求,我們需要二級子選單。該如何操作?
我們需要以下形式:
一級選單
-- 二級選單
一級選單
-- 二級選單
-- 二級選單
複製程式碼
實現這一的功能我們需要做一些修改
const getItemTitle = item =>
item.icon ? (
<span><Icon type={item.icon} /><span>{item.title}</span></span>
) : item.title;
const getMenus = (menus) => {
let result = [];
result = menus.map(item => {
if (item.sub) {
return (
<SubMenu key={item.key} title={getItemTitle(item)}>
{getMenus(item.sub)}
</SubMenu>
);
} else {
return (
<Menu.Item
key={item.key}
disabled={item.disabled}
>
{getItemTitle(item)}
</Menu.Item>
);
}
});
return result;
};
複製程式碼
主要是做了一個 對 sub
存在的判斷。
這樣配置項從原來的純陣列轉為json
{
key: `NORMAL_USER`,
sub:[
{key: `TEMPLATE`},
{key: `LIST`},
]
},
複製程式碼
然後我們需要改動一下之前的生成器。
getSidebars: (arr) => {
if (!Array.isArray(arr) || arr.length === 0) {
return [];
}
const result = [];
arr.map((item) => {
const temp = {
key: commonConstants[`SIDEBAR_${item.toUpperCase()}_KEY`],
icon: commonConstants[`SIDEBAR_${item.toUpperCase()}_ICON`],
title: commonConstants[`SIDEBAR_${item.toUpperCase()}_TITLE`]
};
result.push(temp);
});
return result;
},
getAdvanceSidebars: (arr) => {
if (!Array.isArray(arr) || arr.length === 0) {
return [];
}
const result = [];
arr.map((item) => {
const arrResult = {
key: commonConstants[`SIDEBAR_${item.key.toUpperCase()}_KEY`],
icon: commonConstants[`SIDEBAR_${item.key.toUpperCase()}_ICON`],
title: commonConstants[`SIDEBAR_${item.key.toUpperCase()}_TITLE`]
};
// 如果有子選單
if (item.sub) {
const subResult = [];
item.sub.map((subItem) => {
subResult.push({
key: commonConstants[`SIDEBAR_${subItem.key.toUpperCase()}_KEY`],
icon: commonConstants[`SIDEBAR_${subItem.key.toUpperCase()}_ICON`],
title: commonConstants[`SIDEBAR_${subItem.key.toUpperCase()}_TITLE`]
});
});
arrResult.sub = subResult;
}
result.push(arrResult);
});
return result;
}
};
複製程式碼
對比一下增強版,也就實現了一個對子選單的遍歷。
如果都沒有二級選單,還是依舊像以前那樣傳遞個陣列就行。
這裡就是要說明我們設計的時候需要做為以後功能擴充套件做個相容處理。
寫到這裡,也突然想到了以前學的設計模式在這裡其實能應用很多種。比如介面卡模式等等。
深入
看到以上可能大家會覺得配置優先其實大概就是給自己元件加一個配置項,加一個生成器,然後通過載入約定好的配置檔案(json或者其它形式),就能夠達到縮小程式碼量的目的。
基於此我們還可以做的更加深入,配置的關鍵在於抽象,抽象的目的是為了將業務中頻繁用到的模組通過處理能夠和業務進行解耦,將其作為第三方模組,新函式生成目標物件。
這裡我們需要考慮的是將非常常用,在多個專案中都會用到的東西做一個技術層面和業務層面的思考,然後再將其抽象,整理成生成器。因為這類生成器需要很強的抽象,它可以應用在不同版本的框架中。當然這需要很強的開發功力,以及對細節的處理。
比如可以將react中 reducer和 action 做配置優先處理。
可以學習koa中中介軟體的做法,把很多東西的配置項抽離出來,當做中介軟體使用。
限於精力就不更加細細展開了。