中午吃饭的时候在跟同事讨论当年iOS的刚出江湖时的盛况。大家都在感慨当年为什么没有选择选择成为一名iOS程序员,否则早就财务自由买车买房了。
大三的时候iOS名声不显,可神奇的是我们居然开设了一门必修课Object-C,老师胖胖的爱吹牛,说学了我这门课,毕业随便找个8000以上的工作没有问题。
2012年的月薪8000,就是现在的月薪2万以上。还是在成都。
全班40多个人一群蠢蛋,只有一个人相信他。那个人当年实习工资6000+。我们才发现,那老师不是在吹牛,而是在谦虚!
所以我们几个围在一起在感叹,在风口的时候能抓住机遇,真的非常重要。
而IT行业未来几年的风口是什么?
一定是Deep Learning 深度学习!,通俗点来说,就是人工智能。
深度学习的前景甚至远超当年的iOS,这是一股更大的风。从资本投资的角度来看,目前所有的投资方向基本上来说都已经饱和,很难出现新的机会。所有资本家都在关注人工智能。也就意味着,人工智能的新项目,已经处于爆发式增长的档口,未来相关的人才缺口将会远超想象。
而世界上大多数做深度学习研究的团队,都是使用的python语言。
python有多火?
一件小事就可以表明。前不久,地产大佬潘石屹,突然发微博说,“今天我开始学习一门新的语言Python”
而对于程序员来说,python已经和Js一样,成为我们必备的技能之一。专业的Python程序员,在北京平均月薪都能达到2万。
好在Python也足够简单,通过一系列课程学习,基本上很快就能上手。
想要学Python的同学,给大家推荐一套免费课程。这套课程从基础知识点作为起点,帮助大家搭建一个完整的Python知识体系。并且准备了一整套编程实战演练课程。是入门Python的一个非常好的选择。
好了,说回正题。这篇文章的主要目的还是继续利用React hooks的思维重构antd pro。
而今天的关键,就是要进一步深入掌握组件化思维。
组件化思维所有人都在提,听上去似乎很简单,可是真正能够运用好的,却并不多。这里以antd pro的官方demo项目中的分析页为例,跟大家一步一步分享一下如何划分组件。
1
先看看页面。
从页面布局效果来看,这个页面的组件非常好划分,因为已经都用框框把每个单独的组件划分好了。
第一步,我们的组件可以划分如下:
// 总销售额 <TotalSales /> // 访问量 <VisitorCount /> // 支付笔数 <PayAmount /> // 运营活动效果 <ActivityEffect /> // 销售额访问量的图表 <SalesCard /> // 线上热门搜索 <TopSearch /> // 销售额类别占比 <ProportionSales /> // 离线数据图表 <OfflineData />
第二步我们发现,有几个模块长得差不多。
因此,这里可以使用同一个容器组件来处理这种有类似样式的模块。如果将这四个模块放在同一父组件IntroduceRow来处理,这个页面最顶层的父组件,也就是我通常说的页面组件就应该由如下几个组件合并而成。
// 四个长相类似的块合并成一个组件处理 <IntroduceRow /> // 销售额访问量的图表 <SalesCard /> // 线上热门搜索 <TopSearch /> // 销售额类别占比 <ProportionSales /> // 离线数据图表 <OfflineData />
第三步我们要思考的一个问题是,当这些组件在页面组件中使用时,需要往他们里面传入什么参数?
在确认传入参数时,我们需要思考几个重要的问题
•当前组件是否跟别的组件共享数据?
•如果共享数据,这个数据应该在哪里处理比较合适?
从页面中分析可以得出,只有一个组件的操作会影响到其他页面。
如图,SalesCard组件选择日期时,需要接口重新请求数据。这也是因为接口设计不合理导致,因为整个页面那么多数据只有一个接口。合理的设计是这个组件的数据应该由一个接口提供。
因此,如果基于props传参的思路,页面组件大概会是这个样子
// 四个长相类似的块合并成一个组件处理 <IntroduceRow visitData={visitData} /> // 销售额访问量的图表 <SalesCard salesData={salesData} selectDate={selectDate} /> // 线上热门搜索 <TopSearch searchData={searchData} /> // 销售额类别占比 <ProportionSales salesPieData={salesPieData} /> // 离线数据图表 <OfflineData offlineData={offlineData} />
官方demo中,并未思考这么多,而是将仅属于子组件的操作放在父组件处理,让父组件变得复杂,因此不合理。
这样分析之后,就将所有的组件隔离开,子组件内部的事情,就可以各自单独思考了。
2
组件化的第二个非常重要的思考,就是对于能够影响组件显示内容的数据如何处理。
经过前面的学习(useContext章节)我们知道,当不同的子组件共享了数据,那么就让该数据在他们共同的父组件中来维护。否则,就单独在子组件中自己维护。
由于整个页面中,所有的数据都是来自一个接口,因此,所有的组件共享一个数据来源,也就意味着,该数据成为了共享数据,应该在父级中维护。
在这里我们要站在更高一个层级来思考这份数据的处理。官方demo中,将页面数据维护在全局Store中。是否合理呢?
显然不合理。
从整个全局App的角度考虑,当前页面组件也是一个子组件。而该页面组件的内容,和其他页面相比,其实是独立的。
如果我们希望页面切换时,缓存当前页面的数据,当切换回来不需要重新请求接口,那么放在全局Store来维护是合理的,但是操作几个页面之后发现,官方Demo中的意图是希望当切换回来时,页面重新加载。
所以,当前页面独立的数据,不应该放在更顶层的父组件中去管理,而仅仅只在当前页面就可以了。
3
loading的处理
从整个页面来看,因为是一个接口请求过来了所有数据,因此对应的,也只能有一个loading来处理整个页面的加载状态。而不是将loading状态传入子组件中处理。
那么使用中间件dva-loading来统一处理全局的loading是否是好的解决方案呢?
当然不是。
dva-loading让整个项目全局共享一个loadingEffect对象
export interface LoadingEffect { effects: { [key: string]: boolean }, global: boolean, models: { [key: string]: boolean } }
也就是说,无论你的组件如何划分,loading都不可避免的成为了共享状态。可是在很多情况下,我们仅仅只希望loading成为当前页面或者某个子组件的私有状态。因此dva-loading虽然看上去简化了写法,却让组件化思维固化,失去了灵活性。
也正因为处于最顶层的共享数据,当loadingEffect状态改变时,会导致额外的冗余渲染。因此,dva-loading应该慎用。
4
分析到这里,分析页的重构方案就已经比较明确了。父组件维护共享数据,子组件维护仅属于自己的数据。
页面组件伪代码大概如下:
export function Analysis() { const [dashboardAnalysis, setDashboardAnalysis] = useState<AnalysisData>(); const [loading, setLoading] = useState(true); const {visitData, salesData, searchData, salesPieData, offlineData} = dashboardAnalysis; useEffect(() => { if (!loading) { return; } api().then(res => { setDashboardAnalysis(res.data); }) }, [loading]); return ( <GridContent> // 四个长相类似的块合并成一个组件处理 <IntroduceRow visitData={visitData} /> // 销售额访问量的图表 <SalesCard salesData={salesData} selectDate={() => setLoading(true)} /> // 线上热门搜索 <TopSearch searchData={searchData} /> // 销售额类别占比 <ProportionSales salesPieData={salesPieData} /> // 离线数据图表 <OfflineData offlineData={offlineData} /> </GridContent> ); }
日期选择时,设置loading为true,触发useEffect重新请求接口。这样处理是因为没考虑接口参数,如果考虑参数,则方式会不一样。
很显然,和官方Demo原有的方案相比,页面组件简单了许多,从最终结果看,这个复杂的页面,其实就和我们最简单的计数案例差不多。
合理的组件化思维运用,会省去非常多的额外概念,你看,优化到最后,甚至我们连redux的影子都看不到了。
这就是大道至简的真谛!