平时会维护一些多年迭代的项目,里面最大的通病在于数据流非常的混乱, 那么如何借助MVC 这样的设计模式来进行优化呢
实际案例
我先举一些,实际项目中,存在的非优化前的代码,具体设计业务我会有所删减,重点是里面暴露的问题
const NewValatilityTable = (() => {
const valatilityData = useSelector((s: any) => s.ovs.valatilityData);
const [ifEquity, setIfEquity] = useState < number > ();
const selectValue = localStorage.getItem('selectValue');
const All = localStorage.getItem('All');
const All2 = localStorage.getItem('All2')
const [tableSource, settableSource] = useState([]);
const [columnsSource, setcolumnsSource] = useState < Array < any >> ([]);
const { windCode } = useSelector((s: any) => s.ovs.underlyingCode);
const { isAbroad, ovsReduxSelectedArr, noDataFlag } = useSelector((s: any) => s.ovs);
const {
volatilityType,
priceType,
endTradeDateType,
varietyType,
showRightPrice,
showFutures,
showFutureDate,
calTime
} = useSelector((s: any) => s.ovs.toolbarParams);
useEffect(() => {
const a = JSON.parse(All);
const isAbroadList = JSON.parse(All2);
if (!isAbroad) {
a?.forEach(v => {
v.varietys.map(f => {
if (f.windCode === selectValue) {
setIfEquity(f.ifEquity);
return f.ifEquity;
}
});
});
} else {
isAbroadList?.forEach((item) => {
for (let key in item?.varietys) {
if (item.varietys[key]) {
for (let value of item.varietys[key]) {
if (value.underlyingCode === selectValue) {
const securityType = value['securityType']
const isFutures = convertScientificToNumber(securityType);
if (isFutures) {
setIfEquity(0)
} else {
setIfEquity(1)
}
}
}
}
}
})
}
}, [selectValue, All, isAbroad]);
const xIndex = getIndexByType(volatilityType);
// 数据流转处理-1
const pipLine = (data) => {
let res = data;
const termArr = ovsReduxSelectedArr['term'].map(item => item.key);
const deltaArr = ovsReduxSelectedArr['delta'].map(item => item.key);
const valueArr = ovsReduxSelectedArr['value'].map(item => Number(item.key?.split('%')?.[0]));
if ((data?.length > 0)) {
if (xIndex === 'pricePercent') {
res = data
?.map(item => {
return {
...item,
volatilityDetail: item?.volatilityDetail?.filter(item =>
valueArr.includes(item?.pricePercent),
),
};
});
} else if (xIndex === 'deltaName') {
res = data
?.map(item => {
return {
...item,
volatilityDetail: item?.volatilityDetail?.filter(item =>
deltaArr.includes(item?.deltaName),
),
};
});
} else if (xIndex === 'strikePrice') {
res = data
}
if (endTradeDateType === 1) {
res = res
?.filter(item => termArr.includes(item?.contractName))
}
// 行权价进行筛选
if (volatilityType === 0 && isAbroad) {
const basisLineValue = res?.[0]?.underlyingPrice;
res = getValueRange(res, basisLineValue)
}
if (endTradeDateType === 1) {
res = res.filter((item) => ovsReduxSelectedArr['term'].map((item) => item.key).includes(item.contractName))
}
} else {
res = res
}
return res
}
useEffect(() => {
let tableData = {
data: valatilityData?.data?.filter(v => v.deltaType !== '3'),
underlying: valatilityData?.underlying,
};
if (
((!isAbroad &&
valatilityData.underlying === windCode &&
varietyType !== undefined &&
valatilityData?.volatilityType === volatilityType &&
valatilityData?.endTradeDateType === endTradeDateType &&
valatilityData?.calTime?.calTime === calTime
&& ifEquity !== undefined
) ||
((isAbroad &&
valatilityData?.underlying === windCode &&
valatilityData?.volatilityType === volatilityType
&& valatilityData?.endTradeDateType === endTradeDateType && ifEquity !== undefined)
))
) {
if (judgeIRIB(windCode, isAbroad)) {
const res = tableData?.data
const table = getTableDataSource({
valatilityData: res || [],
xIndex,
priceType,
volatilityType,
showRightPrice,
varietyType,
});
let columns = getTableColumns({
ifEquity: ifEquity,
showFutureDate,
endTradeDateType,
showFutures,
valatilityData: res,
xIndex,
varietyType,
windCode,
isAbroad,
volatilityType
});
settableSource(table);
setcolumnsSource(columns);
} else {
const endsWithHK = str => str?.endsWith('HK');
const endsWithHI = str => str?.endsWith('HI');
if (endsWithHK(windCode) || endsWithHI(windCode)) {
judgeEmpty(tableData)
const table = getTableDataSource({
valatilityData: tableData?.data || [],
xIndex,
priceType,
volatilityType,
showRightPrice,
varietyType,
});
let columns = getTableColumns({
ifEquity: ifEquity,
showFutureDate,
endTradeDateType,
showFutures,
valatilityData: valatilityData?.data,
xIndex,
varietyType,
windCode,
isAbroad,
volatilityType
});
settableSource(table);
setcolumnsSource(columns);
} else {
if ((tableData?.data?.length > 0)) {
const table = getTableDataSource({
valatilityData: pipLine(tableData?.data) || [],
xIndex,
priceType,
volatilityType,
showRightPrice,
varietyType,
});
let columns = getTableColumns({
ifEquity: ifEquity,
showFutureDate,
endTradeDateType,
showFutures,
valatilityData: pipLine(tableData?.data),
xIndex,
varietyType,
windCode,
isAbroad,
volatilityType
});
settableSource(table);
setcolumnsSource(columns);
} else {
judgeEmpty(tableData)
const res = pipLine(tableData?.data)
let columns = getTableColumns({
ifEquity: ifEquity,
showFutureDate,
endTradeDateType,
showFutures,
valatilityData: res,
xIndex,
varietyType,
windCode,
isAbroad,
volatilityType
});
settableSource([]);
setcolumnsSource(columns);
}
}
}
} else if (varietyType === undefined && !isAbroad) {
settableSource([]);
setcolumnsSource([]);
}
}, [
ifEquity,
showFutureDate,
endTradeDateType,
showFutures,
valatilityData,
xIndex,
priceType,
volatilityType,
showRightPrice,
varietyType,
windCode,
isAbroad,
ovsReduxSelectedArr,
calTime,
noDataFlag
]);
return (
<div>
{tableSource.length > 0 && (
<div className='ovc-table'>
<Table
className={classNames({ 'scroll-x': volatilityType === 0 }, 'ovc-table')}
columns={columnsSource}
size='middle'
theme={'dark'}
bordered={'vertical-horizontal'}
dataSource={tableSource}
striped={false}
pagination={false}
data-uc-id='IzrXxu4LzK'
data-uc-ct='table' />
</div>
)}
</div>
);
});
export default NewValatilityTable;
我们来分析一下这段代码有什么问题.
- 整个业务组件,将数据层,视图层,控制层,也就是操作逻辑等,全部都杂糅到了,一起,这样会导致一个问题 视图层出现问题后难以定位问题,各种逻辑全部杂糅,其实这个对纯业务项目非常常见,但是这个是违反设计原则,并且只会让迭代,越来越困难.
- 那既然明确了问题,我们来一步步分析一下.
数据层拆分
首先我整合了当前数据,到对应模块store中集中管理,统一导出,其次对于数据获取,格式化等逻辑,进行整合.导出
const useVolatilityModel = () => {
const valatilityData = useSelector((s: any) => s.ovs.valatilityData);
const { isEmpty, RRBFColumn, RRBFTableSource, columnsSource, tableSource, ifEquity } = useSelector((s: any) => s.ovs);
const selectValue = localStorage.getItem('selectValue');
const { windCode } = useSelector((s: any) => s.ovs.underlyingCode);
const { isAbroad, ovsReduxSelectedArr, noDataFlag } = useSelector((s: any) => s.ovs);
const {
volatilityType,
endTradeDateType,
varietyType,
calTime
} = useSelector((s: any) => s.ovs.toolbarParams);
const toolbarParams = useSelector((s: any) => s.ovs.toolbarParams);
let tableData = {
data: valatilityData?.data?.filter(v => v.deltaType !== '3'),
underlying: valatilityData?.underlying,
};
// 收集缓存
const derivedState = useMemo(() => {
return {
selectValue: localStorage.getItem('selectValue'),
All: localStorage.getItem('All'),
All2: localStorage.getItem('All2'),
calType: localStorage.getItem('calType'),
exChangeCode: localStorage.getItem('exChangeCode'),
};
}, []);
// 获取IFEqity
const getSecurityType = () => {
let ifQuity: number | undefined;
const a = JSON.parse(derivedState.All);
const isAbroadList = JSON.parse(derivedState.All2);
if (!isAbroad) {
a?.forEach(v => {
v.varietys.map(f => {
if (f.windCode === selectValue) {
ifQuity = f.ifEquity
return f.ifEquity;
}
});
});
} else {
isAbroadList?.forEach((item) => {
for (let key in item?.varietys) {
if (item.varietys[key]) {
for (let value of item.varietys[key]) {
if (value.underlyingCode === selectValue) {
const securityType = value['securityType']
const isFutures = convertScientificToNumber(securityType);
if (isFutures) {
ifQuity = 0
} else {
ifQuity = 1
}
}
}
}
}
})
}
return ifQuity
}
// 数据流转处理
const pipLine = (data, volatilityType, ovsReduxSelectedArr) => {
let res = data;
const xIndex = getIndexByType(volatilityType);
const termArr = ovsReduxSelectedArr['term'].map(item => item.key);
const deltaArr = ovsReduxSelectedArr['delta'].map(item => item.key);
const valueArr = ovsReduxSelectedArr['value'].map(item => Number(item.key?.split('%')?.[0]));
if ((data?.length > 0)) {
if (xIndex === 'pricePercent') {
res = data
?.map(item => {
return {
...item,
volatilityDetail: item?.volatilityDetail?.filter(item =>
valueArr.includes(item?.pricePercent),
),
};
});
} else if (xIndex === 'deltaName') {
res = data
?.map(item => {
return {
...item,
volatilityDetail: item?.volatilityDetail?.filter(item =>
deltaArr.includes(item?.deltaName),
),
};
});
} else if (xIndex === 'strikePrice') {
res = data
}
if (endTradeDateType === 1) {
res = res
?.filter(item => termArr.includes(item?.contractName))
}
// 行权价进行筛选
if (volatilityType === 0 && isAbroad) {
const basisLineValue = res?.[0]?.underlyingPrice;
res = getValueRange(res, basisLineValue)
}
if (endTradeDateType === 1) {
res = res.filter((item) => ovsReduxSelectedArr['term'].map((item) => item.key).includes(item.contractName))
}
} else {
res = res
}
return res
}
// 检查数据是否为空
const judgeEmpty = (tableData) => {
return (tableData?.data?.length === 0)
}
// RRBFConditions
const validateRRBFCondition = () => {
const data = valatilityData?.data?.filter(v => v.deltaType === '3')
return varietyType === 10002 && data?.length > 0 && valatilityData.underlying === windCode && varietyType !== undefined
}
const validateDataConditions = () => {
if (!isAbroad) {
return (!isAbroad &&
valatilityData.underlying === windCode &&
varietyType !== undefined &&
valatilityData?.volatilityType === volatilityType &&
valatilityData?.endTradeDateType === endTradeDateType &&
valatilityData?.calTime?.calTime === calTime
&& ifEquity !== undefined
)
} else {
return ((isAbroad &&
valatilityData?.underlying === windCode &&
valatilityData?.volatilityType === volatilityType
&& valatilityData?.endTradeDateType === endTradeDateType && ifEquity !== undefined)
)
}
}
// 验证数据是否有效
return {
// 状态
isEmpty,
tableSource,
columnsSource,
RRBFColumn,
RRBFTableSource,
valatilityData,
windCode,
isAbroad,
ovsReduxSelectedArr,
noDataFlag,
toolbarParams,
derivedState,
ifEquity,
tableData,
// 业务逻辑方法
getSecurityType,
pipLine,
validateRRBFCondition,
judgeEmpty,
validateDataConditions,
}
};
模型层拆分
这里主要是把对状态的操作,设置更新等,进行整合,将视图层所需要的最终数据返回
export const useVolatilityController = () => {
const dispatch = useDispatch()
const model = useVolatilityModel() ;
// ifEquity状态设置
useEffect(() => {
const modleType = model.getSecurityType();
dispatch({
type: 'SET_IFEQUITY',
payload: modleType
})
}, [model.isAbroad, model.derivedState.All, model.derivedState.selectValue]);
const handleIRIBCode = ({
priceType,
volatilityType,
showRightPrice,
varietyType,
tableData,
showFutureDate,
ifEquity,
endTradeDateType,
showFutures,
windCode,
isAbroad,
}) => {
const xIndex = getIndexByType(volatilityType);
dispatch({
type: 'SET_ISEMPTY',
payload: model.judgeEmpty(model.tableSource)
});
const table = getTableDataSource({
valatilityData: tableData?.data || [],
xIndex,
priceType,
volatilityType,
showRightPrice,
varietyType,
});
let columns = getTableColumns({
ifEquity,
showFutureDate,
endTradeDateType,
showFutures,
valatilityData: tableData?.data,
xIndex,
varietyType,
windCode,
isAbroad,
volatilityType,
});
dispatch({
type: 'SET_TABLESOURCE',
payload: table
})
dispatch({
type: 'SET_COLUMNSSOURCE',
payload: columns
});
};
const xIndex = getIndexByType(volatilityType);
const handleHkCode = ({
tableData,
priceType,
volatilityType,
showRightPrice,
varietyType,
ifEquity,
showFutureDate,
endTradeDateType,
showFutures,
valatilityData,
windCode,
isAbroad,
}) => {
dispatch({
type: 'SET_ISEMPTY',
payload: model.judgeEmpty(model.tableSource)
})
const table = getTableDataSource({
valatilityData: tableData?.data || [],
xIndex,
priceType,
volatilityType,
showRightPrice,
varietyType,
});
let columns = getTableColumns({
ifEquity: ifEquity,
showFutureDate,
endTradeDateType,
showFutures,
valatilityData: valatilityData?.data,
xIndex,
varietyType,
windCode,
isAbroad,
volatilityType,
});
dispatch({
type: 'SET_TABLESOURCE',
payload: table
});
dispatch({
type: 'SET_COLUMNSSOURCE',
payload: columns
});
};
useEffect(() => {
if (model.validateDataConditions()) {
if (judgeIRIB(model.windCode, model.isAbroad)) {
handleIRIBCode({
ifEquity: model.ifEquity,
showFutureDate: model.toolbarParams.showFutureDate,
endTradeDateType: model.toolbarParams.endTradeDateType,
showFutures: model.toolbarParams.showFutures,
priceType: model.toolbarParams.priceType,
volatilityType: model.toolbarParams.volatilityType,
showRightPrice: model.toolbarParams.showRightPrice,
varietyType: model.toolbarParams.varietyType,
windCode: model.windCode,
isAbroad: model.isAbroad,
tableData: model.tableData,
});
} else {
if (endsWithHK(model.windCode) || endsWithHI(model.windCode)) {
handleHkCode({
ifEquity: model.ifEquity,
showFutureDate: model.toolbarParams.showFutureDate,
endTradeDateType: model.toolbarParams.endTradeDateType,
showFutures: model.toolbarParams.showFutures,
priceType: model.toolbarParams.priceType,
volatilityType: model.toolbarParams.volatilityType,
showRightPrice: model.toolbarParams.showRightPrice,
varietyType: model.toolbarParams.varietyType,
windCode: model.windCode,
isAbroad: model.isAbroad,
tableData: model.tableData,
valatilityData: model.valatilityData,
});
} else {
if (model.tableData.data.length > 0) {
handleNormalCode({
ifEquity: model.ifEquity,
showFutureDate: model.toolbarParams.showFutureDate,
endTradeDateType: model.toolbarParams.endTradeDateType,
showFutures: model.toolbarParams.showFutures,
priceType: model.toolbarParams.priceType,
volatilityType: model.toolbarParams.volatilityType,
showRightPrice: model.toolbarParams.showRightPrice,
varietyType: model.toolbarParams.varietyType,
windCode: model.windCode,
isAbroad: model.isAbroad,
tableData: model.tableData,
ovsReduxSelectedArr: model.ovsReduxSelectedArr,
});
} else {
dispatch({
type: 'SET_ISEMPTY',
payload: model.judgeEmpty(model.tableSource)
});
const res = model.pipLine(
model.tableData?.data,
model.toolbarParams.volatilityType,
model.ovsReduxSelectedArr,
);
let columns = getTableColumns({
ifEquity: model.ifEquity,
showFutureDate: model.toolbarParams.showFutureDate,
endTradeDateType: model.toolbarParams.endTradeDateType,
showFutures: model.toolbarParams.showFutures,
volatilityType: model.toolbarParams.volatilityType,
xIndex: getIndexByType(model.toolbarParams.volatilityType),
varietyType: model.toolbarParams.varietyType,
windCode: model.windCode,
isAbroad: model.isAbroad,
valatilityData: res,
});
dispatch({
type: 'SET_TABLESOURCE',
payload: []
});
dispatch({
type: 'SET_COLUMNSSOURCE',
payload: columns
});
}
}
}
}
}, [
model.ifEquity,
model.toolbarParams.showFutureDate,
model.toolbarParams.endTradeDateType,
model.toolbarParams.showFutures,
model.valatilityData,
model.toolbarParams.priceType,
model.toolbarParams.volatilityType,
model.toolbarParams.showRightPrice,
model.toolbarParams.varietyType,
model.windCode,
model.isAbroad,
model.ovsReduxSelectedArr,
model.toolbarParams.calTime,
model.noDataFlag,
]);
}
视图层的拆分
最终视图层就只包含对数据的承接,这样的好处在于,在一开始就有情绪的数据流向,以及模块拆分,非常方便,单独测试,重构和优化,避免每次修改都牵一发动全身,因为之前逻辑全部都杂糅到了一起
// 表格内容视图组件
const TableContentView = () => {
const model = useVolatilityModel()
const {
tableSource,
columnsSource,
RRBFColumn,
RRBFTableSource,
toolbarParams
} = model ;
return (
<div>
{/* 主表格 */}
{tableSource.length > 0 && (
<MainTableView
columns={columnsSource}
dataSource={tableSource}
volatilityType={toolbarParams.volatilityType}
/>
)}
</div>
);
};