阅读 1351
React组件封装和逻辑复用新解

React组件封装和逻辑复用新解

如果你觉得可以,请多点赞,鼓励我写出更精彩的文章🙏。
如果你感觉有问题,也欢迎在评论区评论,三人行,必有我师焉


概要


这篇文章主要目的是和大家探讨一下,针对框架使用者(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的见解和看法。

好了,时间也不早了,马上又到凌晨2点了,实在肝不动了。今天是2020年的最后一天,希望大家都把不开心留在2020,把开心带到2021。

预祝大家,元旦快乐

记得双击,摸摸哒。

文章分类
前端
文章标签