antd picker 使用 如何_使用hooks重新定义antd pro想象力(一)

5d7ba5c428da9e6cd14b6d1f03ddf6f7.png

本来没计划马上写antd pro,但是有三位大佬打赏了巨额赏金,说能不能讲讲如何在antd pro中使用react hooks。

当然没有问题!

没办法,金钱的力量真的伟大[手动狗头]。

1

react生态中,antd pro占据重要的位置。非常多的团队使用其来完成自己的中后台应用。它的核心数据处理方案dva聚合了react-redux, redux-saga,极大的降低了redux使用的复杂度。因此使用antd pro无疑是一个非常好的方案。

但是!

在长达一年多的时间里,由于官方并没有针对React hooks提出任何解决方案和推荐方案,因此目前来说,react hooks的开发福利,极少有团队享受到了。

(其实他们内部早就已经在悄悄咪咪的使用了,并且封装了大量简单好用的自定义hooks)

幸运的是,我的团队,早在半年多以前就已经使用react hooks重构了antd pro。开发效率直接飞速提升,简直美滋滋。

不过大家也不用羡慕,后续几篇文章的目的,就是给大家提供一个思路,利用react hooks,去重构antd-pro的demo。

antd pro并非一个入门项目,因此阅读本系列文章,需要有以下基本功底

1.对ant design和antd pro的组件有一定的了解2.对dva有一定的了解,知道dva的运行机制3.掌握react的基础知识4.掌握react hooks基础知识5.掌握简单的Typescript使用

本系列文章要有最好的阅读效果,建议下载官方的demo,并手动练习更改。当然也可以关注我的项目antd-pro-with-hooks,我重构之后的源码就在这个项目里

2

首先在重构之前,我们需要达成一个共识。

官方提供的demo,在许多实现上,并非最佳方式,因此如果要运用于实践,不可盲从,需要根据实际情况进行调整。

例如:

接口请求的异常处理被封装成公共逻辑,未做差异化处理

/** * 配置request请求时的默认参数 */const request = extend({  errorHandler, // 默认错误处理  credentials: 'include', // 默认请求是否带上cookie});

工作台页面,某个接口的请求函数。

*fetchUserCurrent(_, { call, put }) {  const response = yield call(queryCurrent);  yield put({    type: 'save',    payload: {      currentUser: response,    },  });},

一些地方官方也没有找到合适的实践方案,例如在redux-saga中的类型推导。

e6739027a9e544f20dfe94948be1718f.png

这里因为无法推导出来,返回结果只能显示any。因此实践中我们只能手动指定response的值。

OK,总结一下就是,官方提供的demo中,需要改进的地方还很多,因此,官方demo只能作为参考,切勿作为标准。

接下来,我们就开始来操刀重构!

3

dva中,新的hooks API,useDispatch与useSelector

useDispatchuseSelector是react-redux提供的api。但是集成react-redux的dva一直没有支持他们俩。因此想要使用他们,需要从react-redux中引入

import { useSelector, useDispatch } from 'react-redux';

dva@2.6.0[1]的beta版本也已经支持了这两个api,不过在正式版发布之前,需要指定版本下载依赖。

> yarn add dva@2.6.0-beta.19

useDispatch的作用很简单,就是获取事件触发方法dispatch。它与redux中的dispatch一模一样。

在以往的class语法中,通过connect高阶方法注入组件props的方式获取dispatch方法

@connectclass Center extends PureComponent<CenterProps, CenterState> {}

react hooks直接使用useDispatch获得。hooks的方式来源清清楚楚,更加通俗易懂。

const dispatch = useDispatch();// 使用时,与之前一样dispatch({ type: 'dashboardAnalysis/fetch'});

useSelector的使用也非常简单,就是从全局维护的Store状态中,获取当前组件需要的数据。

例如在Demo项目中的分析页,需要获取当前页面对应model维护的状态,直接用useSelector获取即可。

const dashboardAnalysis = useSelector(state => state.dashboardAnalysis);

那么重构的第一步,就是使用useDispatch + useSelector替换connect。

终于摆脱了connect这种谜一样的写法,浑身都轻松了。

fc1b2028e89dc10766b5b2b7a0623c2e.png

重构前后对比。

6a2f7a8f8e48f5a8d7976787cf951cb1.png

Dashboard的三个页面,分析页,监控页,工作台,都非常简单。以分析页为例,所有的数据都来源于一个接口,只需要在页面组件渲染时请求一次即可。

useEffect(() => {  dispatch({ type: 'dashboardAnalysis/fetch'});}, []);

然后相对应的,将组件内部状态改为使用useState维护

const [salesType, setSalesType] = useState('all');const [currentTabKey, setCurrentTabKey] = useState('');const [rangePickerValue, setRangePickerValue] = useState(getTimeDistance('year'));

通过useSelector从model中维护的数据获取到。

const dashboardAnalysis = useSelector(state => state.dashboardAnalysis);const loadingEffect = useSelector(state => state.loading);const loading = loadingEffect.effects['dashboardAnalysis/fetch'];

到这里,重构的第一步就已经完成了。

4

下一步要思考的问题就是,组件拆分的合理性。

在前面几篇文章里,专门有跟大家分享过,面对一个复杂页面,如何划分组件,如何去确定一个状态所处的位置,那么在官方demo的案例中,使用的方式是否合理?

留下一个思考,下一篇文章分享。

分析页第一步重构之后完整代码,留个备份。

import { Col, Dropdown, Icon, Menu, Row } from 'antd';import React, { Suspense, useEffect, useState } from 'react';import { GridContent } from '@ant-design/pro-layout';import { useSelector, useDispatch } from 'dva';import PageLoading from './components/PageLoading';import { getTimeDistance } from './utils/utils';import { AnalysisData } from './data.d';import { RangePickerValue } from 'antd/es/date-picker/interface';import styles from './style.less';const IntroduceRow = React.lazy(() => import('./components/IntroduceRow'));const SalesCard = React.lazy(() => import('./components/SalesCard'));const TopSearch = React.lazy(() => import('./components/TopSearch'));const ProportionSales = React.lazy(() => import('./components/ProportionSales'));const OfflineData = React.lazy(() => import('./components/OfflineData'));export interface LoadingEffect {  effects: {    [key: string]: boolean  },  global: boolean,  models: {    [key: string]: boolean  }}export type SalesType = 'all' | 'online' | 'stores';export type DateType = 'today' | 'week' | 'month' | 'year';export default function AnalysisFC() {  const dashboardAnalysis = useSelector(state => state.dashboardAnalysis);  const loadingEffect = useSelector(state => state.loading);  const loading = loadingEffect.effects['dashboardAnalysis/fetch'];  const dispatch = useDispatch();  const [salesType, setSalesType] = useState('all');  const [currentTabKey, setCurrentTabKey] = useState('');  const [rangePickerValue, setRangePickerValue] = useState(getTimeDistance('year'));  const {    visitData, visitData2, salesData, searchData, offlineData, offlineChartData, salesTypeData, salesTypeDataOnline, salesTypeDataOffline,  } = dashboardAnalysis;  useEffect(() => {    dispatch({ type: 'dashboardAnalysis/fetch'});  }, []);  const isActive = (type: DateType) => {    const value = getTimeDistance(type);    if (!rangePickerValue[0] || !rangePickerValue[1]) {      return '';    }    if (rangePickerValue[0].isSame(value[0], 'day') && rangePickerValue[1].isSame(value[1], 'day')) {      return styles.currentDate;    }    return '';  }  const handleRangePickerChange = (rangePickerValue: RangePickerValue) => {    setRangePickerValue(rangePickerValue)    dispatch({      type: 'dashboardAnalysis/fetchSalesData',    });  };  const selectDate = (type: DateType) => {    setRangePickerValue(getTimeDistance(type));    dispatch({      type: 'dashboardAnalysis/fetchSalesData',    });  };  const dropdownGroup = (                  overlay={          
操作一 操作二 } placement="bottomRight" > ); const salesPieData = { all: salesTypeData, online: salesTypeDataOnline, stores: salesTypeDataOffline }[salesType]; const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name); return ( }> rangePickerValue={rangePickerValue} salesData={salesData} isActive={isActive} handleRangePickerChange={handleRangePickerChange} loading={loading} selectDate={selectDate} /> loading={loading} visitData2={visitData2} searchData={searchData} dropdownGroup={dropdownGroup} /> dropdownGroup={dropdownGroup} salesType={salesType} loading={loading} salesPieData={salesPieData} handleChangeSalesType={(e) => setSalesType(e.target.value)} /> activeKey={activeKey} loading={loading} offlineData={offlineData} offlineChartData={offlineChartData} handleTabChange={setCurrentTabKey} /> )}

本系列文章为原创,欢迎私信我添加白名单转载

2e567fef72ac477f3d68d95dde1d471c.png

关于如何学好JavaScript,我写了一本书,感兴趣的同学可点击阅读原文查看详情。

References

[1] dva@2.6.0: mailto:dva@2.6.0

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值