如何借助MVC 模式拆分代码

60 阅读5分钟

平时会维护一些多年迭代的项目,里面最大的通病在于数据流非常的混乱, 那么如何借助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;

我们来分析一下这段代码有什么问题.

  1. 整个业务组件,将数据层,视图层,控制层,也就是操作逻辑等,全部都杂糅到了,一起,这样会导致一个问题 视图层出现问题后难以定位问题,各种逻辑全部杂糅,其实这个对纯业务项目非常常见,但是这个是违反设计原则,并且只会让迭代,越来越困难.
  2. 那既然明确了问题,我们来一步步分析一下.

数据层拆分

首先我整合了当前数据,到对应模块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>
  );
};