如果你觉得可以,请多点赞,鼓励我写出更精彩的文章🙏。
如果你感觉有问题,也欢迎在评论区评论,三人行,必有我师焉
概要
这篇文章主要目的是和大家探讨一下,针对框架使用者(React
/VUE
),如何合理的进行 组件封装和 逻辑复用。这里有一个前提条件--在平时以 业务驱动的开发流程中,我们如何思考或者如何处理才能做到又快又好。 同时,由于我的 开发技能包为React
,所以更多的我会从React
的角度来思考和解答问题。
时间不早了,该干点正事了,咱们书归正转。 (郭德纲语音包)
👈 点击触发
组件封装
作为一名合格的Reacter来说,组件封装这个技能点就和相声演员口中的说学逗唱一样,是需要刻在骨子里的基本功。
而React官方为我们提供了一些常规的操作方式,其中不乏HOC
,RenderProps
。如果对这些方式的如何使用还无法做到信手拈来。那可以参考我早些时候,写的React组件封装思路拓展(可能当时文笔和技术不是很好,如有不对的地方,轻喷,我怕疼。)当然,现在技术也很渣{手动狗头}
但是对于封装这个事来讲,There are a thousand Hamlets in a thousand people's eyes
。 只所以造成这种,群魔乱舞
,鬼魅丛生
的现状,我认为最关键的一点,就是React它所选用的构建UI的JSX
,本身就是一种JS的扩展。而JS最大的特点就是万物皆可对象,而对象的灵活性可以说是 无孔不入,防不胜防。
RenderProps增强版
在React官网中给出了一种关于组件封装的一种方案。
组件属性中有一个属性,而该属性是一个函数,函数返回了一个组件。
<RenderProps renderRegion={(value)=><CommonConponenet value={value}/>} 复制代码
这种情况是可以满足很大部分的组件封装。但是鄙人认为,这样处理是可以将一部分可以抽离的功能和主UI进行分割。实现了一种叫做 面向切片编程的编程思路。
用RenderProps进行可复用组件的抽离,固然好,但是,但是,但是,这里是一个转折,也不知道大家有没有维护过老代码或者是存在一个需求。一个主UI中,存在很多零散的操作区域,而这些零散的操作区域又可以进行封装。我们来做一个简单的描述。
假如现在我们接手了如上图的需求,但是由于其每个操作区域(Area
),都可能存在很多的情况,而此时为了践行 职责单一的设计模式。当然,也不是非要按照书本上的观点或者条条框框去做事,而我描述这个,只是为了师出有名(朱棣夺取皇位还想出了,清君侧呢。最近在拜读<<明朝那些事>>,敲击好看)
。我们尝试将其每个部位都封装了组件。
其实这种处理方式没有啥不好的地方,如果硬要是鸡蛋里挑骨头的话,就是在实例化父 组件的时候,看不出这些被抽离的组件是显示的什么逻辑。如果页面效果简单或者是需要抽离的部位不是很多,还好。
但是,如果存在某些极端情况,一个组件内需要被抽离的东西过于零散,并且没法进行合并处理,这就在后期维护产生了隐患。
同时,其实有些页面的功能是和页面结构强耦合的。有些同学有时候,陷入了一种 为了封装组件而封装组件的陷阱
。将本来隶属于同一页面单独使用的逻辑,抽离成小组件,然后用各种props或者其他的状态管理去维护回调和参数。
在利用RenderProps进行组件封装的时候,1. 被抽离组件过于零散,看不出UI是如何构建和组装的。2. 陷入为了将UI分割,而将逻辑分割的错误泥沼
所以,为了解决这种让人感觉无关痛痒的问题,在RenderProps
这种方案的基础上做了一个变种或者升级
。
我们再次将上述情况,精细化描述,假如RightBottomArea
是一组button
的集合,并且在项目开发中,多个页面存在与Parent
组件相似的功能,但是展示的数量和操作都存在差异。
我们在设计Parent
的时候,就将UI分块处理,让它能够有可配置的能力。在常规开发中,我们遇到上面的需求,第一想法,就是构建一个专门维护Button的组件(就像第一个例子中LeftTopArea
等),在调用Parent的时候通过传递不同的参数 pageSoucre
= pageA
或者pageB
。来区分不同页面,并且在组件内部进行三元或者多元判断。
下面,我们来简单说一下,我对这个问题是如何处理。
我们先来看一下组件是如何调用的。
看到这里,细心的朋友,脑子可能存在一些疑问。
- 1 .button的click事件如何绑定,并且该回调函数还是一个异步处理
- 2 . 如果存在每个button的样式需要定制化需要如何处理
客官别急,菜已经在锅里了,马上来。
这里简单的解释一下,这里面用的API的思路。
- React.Children.map 用于遍历组件的children数据和数组map是一个用法.
- React.cloneElement 用于复制出一个新组件,该组件的type,props都和源组件一样。并且,还可以在复制的时候,对新组件的属性(
props
)进行个性化的处理。
总结:不知道,大家在开发过程中,用没用过这些API,并且在这些API现有功能基础上,做对自己开发有力的应用。其实,其中用这个点的来源,主要来源于 React.createElement()。它是JSX的背后的男人,并且它返回的是一个对象。这就和 JS一切皆对象的宗旨很契合。既然是对象了,只要能拿到对象的实例,是不是我们就可以 为所欲为了。
Web Component
该技术是一种全新的组件封装的方式或者思路。他利用浏览器API来赋予你封装组件的能力。
Web Components is a suite of different technologies allowing you to create
reusable
custom elements — with their functionality
encapsulated away from the rest of your code — and utilize them in your web apps.
上面描述是摘录至 MDN_Web_Components。
从如上的描述中,我们可以得出几点
- 1 构建可复用自定义元素
- 2 将构建的元素和现有代码进行分离,使用了 Shadow DOM
因为,这个组件封装的技术方案,我在实际项目中,只简单运用了下,但是感觉该技术方案是以后的趋势(无论你使用React还是Vue的框架),所以有必要,舔着脸来给大家讲述一下。(如果有不对的地方,欢迎指出)。
我们在项目开发中,存在某种展示效果,就是文案超出指定宽度,需要显示...
或者某个文案需要多行展示。
常规的处理,我们是在一个div
或者span
中定义某个class
然后我们用JSX
来简单展示一下如何展示使用。
虽然我们通过CSS自定义属性减低了代码量,但是像此类的功能在一个系统里,是很常见的。每次使用都写对应的样式(当然,也可以将此类样式维护到全局),然后再指定的组件或者元素中引用。无形中增加了维护成本。当然,有些同学,可能会想到,将此类属于 辅助
或者展示
功能并且与业务耦合度不是很高的片段,构建成组件。
这种思路是对的,但是每次在需要使用此类功能片段,就需要将组件 import
到目标文件。如果类似的组件很多,在组件的头部,很有很多 相关组件的import
。这样存在潜在的问题,
- 1. 如果功能片段组件路径变更,那引入的地方也需要跟着变动(维护成本偏大 )
- 2. 有些功能片段代码量很大,并且没有做
按需加载
,那页面首次加载会很慢(加载速度 ) - 3. 在调用功能组件的头部,代码臃肿。(不美观 )
- 4. 抽离的组件,可能和原有组件样式冲突 (存在冲突)
- 5. 无法跨框架(Vue/React)(框架局限)
- 6. ......
那如果我们按照 Web_Components
来重构一下刚才的代码。 通过extends
HTMLElement
然后继承相关属性。
我们可以和引用组件一样来讲自定义元素实例化。
这不失为一种好方法,但是我们是不是可以换一种思路,在我们项目的稍微顶级目录或者组件中,将元素实例化,这样就是一次实例化,处处调用。省去了很多功夫。
虽然,在实际项目中,没有更多的尝试,但是现在无论Vue还是React都尝试向Web Components的方向靠拢。
配置化(LCD)
低代码开发(Low-Code Development,LCD),是一种很早被提出(2011)的开发模式,开发者主要通过图形化用户界面和
配置来创建应用软件,而不是像传统模式那样主要依靠手写代码。其中就有一种基于编写
JSON
的低代码开发。
而我们所讲的这种方式,严格意义上算不上LCD,但是我们可以借助这个思想来进行组件的封装。
做过ToB业务的同学可以知道,我们在进行表单查询的时候,总是会出现奇奇怪怪的筛选条件,而这些筛选项有时候就是一个简单的Input
或者是Select
,稍微复杂点,就会在筛选的时候,加入一些自己封装的自定义组件。
我们按Antd的组件来简单规划下如上的筛选部分。 我们通过常规的方式来处理这段逻辑,在没有完全写完的情况下,对应的事件回调就存在3个以上(基本上每个组件一个回调函数),然后一些类似的功能也是重复率很高。这样一味的去堆砌代码,那么就是一个查询相关的代码就会变的又臭又长。不管是对自己还是以后接手维护这段代码的人来讲,都是一段煎熬,
那么如何才能更加优雅或者更加让代码看起来简洁呢,我这里模仿LCD的实现方式。简单描述一下。 虽然大家看到用了LCD的这种方式来处理代码,表面上代码量增加了,但是这其实有一个前提条件的。就如我们上图说描述的,一个筛选框存在8个以上的条件,你就需要写对应的回调和配置信息,而这些配置信息其实都是类似的。并且代码还不好追踪。我们来简单的换算一下,我们用最原始的方式来构建筛选项,我们构建了5个组件,代码有50多行,这还是在有些组件没有配置必要的参数和回调的情况下,如果存在10个以上或者更多,按照现在的换算,保守估计,实现一个10个以上的筛选项并且存在多个自定义的组件,代码量至少300-400行起步。
而我们利用LCD的思路来处理代码,这段代码最多200行搞定。代码量上直接节省 50%,这还是保守计算。
大家可以看看平时写此类代码,可以对比一下代码量。就会一目了然了。
这里再给大家唠叨一下,关于我是如何对这段代码进行抽离和封装的。
- 1 利用了React本事支持的事件代理,将处理事件挂载到父级元素。(React其实也是这么做的)
- 2 针对自定义组件我们通过配置信息中的
instance
来指定,这是利用React中的JSX本身就是JS语法的扩展 - 一切皆对象 - 3 利用了自定义属性,进行数据的传递
逻辑复用
其实组件封装就属于逻辑复用的一种,但是我还是想单独想聊一下除了组件封装以外的数据/逻辑处理,因为有些思路和方法是从另外的一个角度来描述和思考的。
中间件(MiddleWare)
大家接触过Node的experss的开发,里面充斥着各种中间件。我认为中间件的作用就是面前切片编程的一种前端实现。
而我们此次聊的不是聊Node,我们还是将目光关注于前端页面的处理。如果大家用React同时由于项目比较臃肿,单纯的利用React提供的context
,state
来维护页面直接的数据,就会变的杂乱无章。所以Redux就横空出世了。
我们不讲如何配置和使用Redux,我相信市面上有很多关于这些的教材。我们要讨论的是,如何利用Redux来进行逻辑抽离和复用。
案例一 改造Redux-thunk
在项目开发中,我们希望在触发接口发送的时候,有一个前端Loading的效果处理,然后接口请求成功或者失败的时候,将Loading进行移出。(我们利用axios也是可以拦截的)
这里还需要唠叨一下Redux-thunk,其他代码就几行,作为Redux的异步处理的中间件,就是为了处理异步action。实现控制反转的功能。
既然是处理异步,我们是不是可以在这里搞点事情做。如果异步action是一个promise
,我们就将处理一些loading的逻辑。
这里有一个前提条件,就是在发起接口处理,是需要利用dispatch来进行包装的。
- 1 我们在异步操作触发的时候,将store中的loading置为ture
- 2 在接口有结果(失败/成功)以后, 再将store中的loading置为false
- 3 同时配合一个全局的Loading组件来监听store中的loading来显示对应的操作
案例二 监听history
在实际业务中,有些操作是需要判断指定的URL
变化或者出现就需要做一些额外的操作。比方说,针对某些特定的页面,需要为这页面的资源进行加锁处理。而针对SPA的场景中,基本上都是一个URL对应一个资源。
所以,我们可以通过监听History的变化来做指定的操作。
我们可以通过,在某个位置,监听store中的objectId和isTriggerLock来做一些和后台交互的处理。 这样做的好处是
- 1 通过中间件的方式,将逻辑抽离到框架层面,和业务代码耦合度不那么高
- 2 如果以后某个页面或者某个模块需要此类的功能,迁移效果好(新增配置项就可以)
其实这还有一个很重要的点,这块是需要接收一个history, 而这个history是通过
history
这个库createBrowserHistory
来实例化的。所以才可以通过监听@@router/LOCATION_CHANGE
进行处理。 (这是我通过读源码偶然发现的一个小玩意)
stateComponment
stateComponment
这个词语是我自己创造的,他的含义或者用法-就是将组件存储到组件的state变量中。通过setState来切换组件的指向,从而实现页面的变换。
我们简单描述下,只提供简单的思路。
假如我们页面中存在很多操作,而这些操作都对应着一个modal。我们在平时所写代码中,基本上都是一个button -> visible -> modal
其实在我看来,visible变量就是一个累赘
,同时如果存在多个modal的情况下,也是需要一些额外的参数去维护。那么我们化繁为简,通过将组件存储于state中,是不是会变的更加方便。
这里举了一个很常见的栗子,我们完全可以把Modal置换为其他自定义组件或者元素。其实,我想通过这个例子来说明下,针对于React项目开发中,组件其实就是一个对象,而JS中一切皆对象,并且它的灵活性是很高的。我们不必拘泥于常规的组件使用方式。我们就按照写JS变量的方式来处理组件就OK
其实,关于逻辑复用这一块,有一点是绕不开的,那就是自定义Hooks。但是如果简单讲一下,总感觉讲不透彻的感觉,所以,我打算专门写一篇,关于自定义Hook的见解和看法。