slot deposit pulsa slot mahjong slot gacor slot gacor slot gacor resmi slot gacor 2025 slot gacor terpercaya slot gacor 2025 slot gacor hari ini slot gacor hari ini slot gacor hari ini
想写好前端,先练好内功
17611538698
webmaster@21cto.com

想写好前端,先练好内功

前端 0 1816 2022-01-04 11:15:45

图片

前言

封不平听在耳里,暗叫:“到这地步,我再能隐藏甚么?”仰天一声清啸,斜行而前,长剑横削直击,迅捷无比,未到五六招,剑势中已发出隐隐风声。他出剑越来越快,风声也是渐响,剑锋上所发出的一股劲气渐渐扩展,旁观众人只觉寒气逼人,脸上、手上被疾风刮得隐隐生疼,不由自主的后退,围在相斗两人身周的圈子渐渐扩大,竟有四五丈方圆。泰山派的一个道士在旁说道:“气宗的徒儿剑法高,剑宗的师叔内力强,这到底怎么搞的?华山派的气宗、剑宗,这可不是颠倒来玩了么?

《笑傲江湖》中的“剑宗余孽”封不平本想仗着有嵩山派撑腰,一举夺了华山掌门宝座。可打了半天剑法上占不了便宜,最后只能使出“狂风快剑”,企图以内力取胜。可见,任何高明武功若无内功心法相辅,也是徒劳无功。

说回前端,如今的前端技术栈就如同武侠小说中的江湖一样,各门各派自成一体,可谓“百花齐放”、“百家争鸣”。

这边 React 、Vue 、AngularJS 、JQuery 谁还都谈不上能一统江湖。“武林新贵” Flux 、Redux 、Mobx 们已经忙着争夺谁是数据流框架老大。Native 端 RN 刚偃旗息鼓,Weex 就大有“ I'm the everywhere ”之势。连备受争议的 GraphQL 内部都还有 Apollo、Relay 掐来掐去。

常听到身边的前端工程师抱怨,上周刚发布的 XXX 新版本文档还没看,今天 YYY 公司又发布了新框架,到底先学哪个?其实,无论是哪种框架哪项技术都是解决实际业务需求的手段、方法,和武林中各门各派的武功招式是一样的,各有所长,各有各的独到之处。

我们学习技术,除了了解具体使用方法,还需要掌握技术背后的设计理念和工程思想,这些背后的东西是我们技术选型的依据,是架构设计的基础,是软件系统的灵魂。这就好比是的武功中“内功心法”催动拳脚刀枪,一招一式,虎虎生风,纵有大敌当前,亦是淡然自若。

接下来分别谈一下三种工程思想,分别是:“开闭原则”、“函数式编程”和“消息机制”,这三种工程思想在后端开发中均有广泛的使用,容易被大家忽略的是目前很多前端技术框架也应用了这三种思想,以下结合具体案例分析,希望能够帮助大家加深对技术本身的理解。

开闭原则

说到面向对象设计,大部分人脑海中闪过的恐怕都是“23种设计模式”。设计模式代表的是业务场景中总结出的最佳实现方式,属于实践的范畴,在其之上是更为重要的“SOLID”五大原则:

  • Single Responsibility Principle 单一责任原则
  • The Open Closed Principle 开放封闭原则
  • The Liskov Substitution Principle 里氏替换原则
  • The Dependency Inversion Principle 依赖倒置原则
  • The Interface Segregation Principle 接口分离原则

SOLID 五大原则的出发点也是软件工程的终极目标:“高内聚、低耦合”。在后端开发中运用最多的是“依赖倒置原则”,与其相关的设计模式大约有5-6个。如下图所示:

图片

上图也可以理解为从抽象概念到具体实践的逐步演进。

在前端技术框架中,运用最多的是“开放封闭原则”,我们先来看一下这条原则是怎么定义的:

A software artifact should be open for extension but closed for modification.

翻译过来就是:软件系统应当对扩展开放,对修改封闭(感觉像没说)。这里举一个简单的例子来说明开闭原则,先帮助大家理解概念:

public abstract class Shape
{
    public abstract double Area();    
}

public class RectangleShape
{
    public double Width { getset;}
    public double Height { getset;}
    public override double Area()
    {
         return Width*Height'
    }
}

public class Circle: Shape
{
    public double Radius { get; set}
    public override double Area()
    {
        return Radius*Radius*PI;
    }
}

public double Area(Shape [] shapes)
{
    doubel area = 0;
    foreach (var shape in shapes)
    {
        area += shape.Area();
    }
    return area;
}

上例中无论场景如何扩展,Area 函数都无需修改,每个 Shape 类通过继承接口和多态特性,各自实现面积计算。

总结一下开闭原则就是:软件系统的核心逻辑都不应该轻易改变,否则会破坏系统的稳定性和增加测试成本。我们应当建立合适的抽象并统一接口,当业务需要扩展时,我们可以通过增加实体类来完成。

接下来我们看一个“开闭原则”在前端框架中的应用:Ant Design 组件库中的 Form 表单组件。

和其它组件不同,Form 组件并没有具体的形态,它更像是一个容器,提供了接入的标准,并提供了校验、表单提交等功能。绘制表单中的一项如下所示:


  {getFieldDecorator('userName', {
     rules: [{ requiredtruemessage'Please input your username!' }],
     })(<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
        placeholder="Username" />

   )}
</FormItem>

Ant Design 组件库中已经提供个几乎所有的常见表单组件,如:Select 、Checkbox 、Radio 、Cascader 等,但在实际业务中,我们还是会需要设计业务相关的表单项,Form 表单通过统一组件接口的方式满足了这个技术需求,具体规约如下:

自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:

  1. 提供受控属性 value 或其它与 valuePropName 的值同名的属性。
  2. 提供 onChange 事件或 trigger 的值同名的事件。
  3. 不能是函数式组件。

具体例子

这正是“开闭原则”的一个典型实践案例,即表单核心逻辑(校验、提交等)保持不变并封装在 Form 组件中,自定义表单项只需要满足上述三条规约,就能平滑接入到 Form 组件中,和 Ant Design 原生组件契合在一起。

Ant Design 中的 Form 组件通过这样一个简洁的设计,完美提供了表单类型页面的统一解决方案。

函数式编程

随着人工智能、区块链、AR、VR、新零售等业务场景的出现,产品界面交互正在变得越来越复杂,这就对现代的前端开发者提出了更高的要求。如何快速、正确、高效地开发出高复杂度页面是目前前端技术最需要解决的问题。

函数式编程(以下简称 FP )凭借其高复用性、易测试性和与之带来的健壮性和简洁开始逐渐占据前端技术圈,我们发现越来越多的前端框架以 FP 为设计核心准则。

我们先简单介绍一下 FP,函数式编程的特征主要包括以下几个方面:

  • 函数为“一等公民”
  • 模块化、组合
  • 引用透明
  • 避免状态改变
  • 避免共享状态

JS 语言中的函数不同于 Java ,C/C++ 等语言, 可以被当做参数和返回值进行传递,因此天生具备“一等公民”特性。“模块化、组合”、“引用透明”、“避免状态改变”、“避免共享状态”这四个特征都需要通过特定代码模式实现。先举两个小例子:

找出字符串中率先出现的四个非数字字符?

非 FP 风格
var words = [], count = 0;
var text = str.split(''); 
for (var i = 0; couont < 4, i < text.length; i++) {   
  if(!text[i].match(/[0-9]/)) {
    words = words.concat(text[i]);
    count++;   
  }

FP 风格
var words = str.split('').filter(function(x){
  return (!x.match(/[1-9]+/))}).slice(0,4);

第二段代码中使用的 js 数组方法 filter 和 slice,去掉了 for 循环,代码更简洁流畅。在写具体业务代码的时候,“模块化、组合”是 FP 最常用的技术,也是最重要的实现功能的手段。

分别实现数组所有元素相加、相乘、相与?

非 FP 风格
function plus(array{
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res += array[i];   
  }
}

function mul(array{
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res *= array[i];
  }
}

function and (array{
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res = res & array[i];
  }
}

plus(array);
mul(array);
and(array);
FP 风格
var ops = { 
  "plus"(x,y)=>x+y,
  "mul" : (x,y)=>x*y,
  "and" : (x,y)=>x&y
}

function operation(op, array{
  return array.slice(1).reduce(ops[op], array[0]);


operation("plus", array);
operation("mul",  array);
operation("and",  array); 

后一段代码中,使用了 reduce 函数代替了 for 循环,并将数值计算部分作为模块提取出来,当有新的计算类型时,只需要在 ops 对象中定义计算过程。这里就体现了 FP 中“模块化、组合”的特性。在 FP 风格下,我们习惯将复杂逻辑切割成一个个小模块,通过组合这些模块实现新的业务功能,当有新的需求到来时,我们尽可能地复用已有模块达到目标。FP 代码在复用性方面相比 OOD 有明显的优势。

React 中的 FP 思想

React 框架中,当用户操作 UI 或者 API 的返回带来了数据的改变,React 随即进行 virtual dom diff 计算得到 dom 的修改指令,对 dom 元素应用修改指令便得到最新的 html 界面,如下图所示:

图片

不难发现,React 其实是应用数据对UI的一种映射,不同的数据会映射出不同样式的 UI 界面,我们可以得出如下的表达式:

UI=React(data)

没错,React 的本质其实是一种函数,并且还是符合 FP 要求的“引用透明”函数。所谓“引用透明”就是指函数的输出仅依赖函数参数,不受任何外部环境影响。这样的函数可测试性强,也非常容易进行组合。

在 React 的体系下,任何组件都可由一个个更小的组件构成,每个组件都只关心自己的输入,他们不断地接受新的数据并输出对应的新的UI界面。React 框架中常用的“高阶组件”可以看作引用透明”函数的组合模式。

在具体业务中我们通常还需要权衡 React 组件的复用性和开发体验,如果组件被拆分的过于细,固然复用性会提升,但文件数量会增加,对应的文档和沟通成本也会增加,这也是 FP 在实践过程中经常遭人诟病的点,即复用性提升后带来的额外开发成本。

消息机制

消息机制是软件工程中一个普遍运用的工程思想。“设计模式”中的观察者模式、Windows 操作系统底层、Spring 框架中的 ApplicationListener 模块、Objective-C 语言中的函数调用、都是通过消息机制驱动的。

使用消息机制最大的好处在于可以做到业务模块间安全解耦,模块间通过发送消息的方式进行协作,我们先举一个后端开发中的例子,下图是一个简单的预定系统的建模图,并没有使用消息机制:

图片

在没有消息机制的情况下,用户模块需要知道订单模块的存在,并向起进行接口调用,同理订单模块需要向支付模块进行接口调用。这种设计下模块间是耦合的。

我们再来看一下使用消息机制的情况:

图片

上图中,无论是客户下订单、支付还是预定都是通过消息的方式传递的,每个模块都是向一个消息处理器起发消息,同时也监听消息处理器发送回来的消息。在这种模式下,模块完全不知道其它模块的存在,彻底做到了解耦。

在前端业务开发中,我们经常也会用到 EventEmitter 库来进行消息传递。比如页面上有两块区域,一块用 React 框架渲染,一块用 D3 渲染的,当两块区域需要数据同步时,就可以使用消息机制进行通讯,保证页面数据整体一致。

如果你的业务中有不同生命周期的组件,建议采用消息机制进行管理,不仅消除了耦合,逻辑关系部分的代码也集中到了一个文件中,内聚性得到了提升

使用消息机制的一个附属产物就是中间件,我们可以为消息定制各种中间件,在中间中完成一些通用逻辑,让业务代码更精炼。

说到前端框架中消息机制的运用,当然首推 Redux 框架,在 Redux 框架中,任何数据交互都需要先转化为一个 action,由 action 去触发 reducer 和相关的 middleware 处理 action,改变数据,最终同步到页面 UI 上,如下图所示:

图片

关于使用 Redux 的种种利弊,在各大社区中都有很深入的讨论,本文不再赘述。

总结

“开闭原则”、“函数式编程”、“消息机制”这个三个软件工程中重要的思想方法好比三套内功口诀,掌握了他们,才能更深刻地理解技术框架本身,发挥出技术框架的最大威力。

写到这里突然又想起《天龙八部》中一段:

乔峰眼见旁人退开,蓦地心念一动,呼的一拳打出,一招“冲阵斩将”,也正是“太祖长拳”中的招数。这一招姿式既潇洒大方已极,劲力更是刚中有柔,柔中有刚,武林高手毕生所盼望达到的拳术完美之境,竟在这一招中表露无遗。

一套平平无奇的“太祖长拳”在乔峰手中尽能有如此气象!

多少年以后,每当人们聊起金庸,聊起那个武侠世界,想必都会津津有味地回味、谈论起聚贤庄中这石破天惊的一拳。

参考

  • Functional Programming in JavaScript — Dan Mantyla
  • Functional JavaScript: Introducing Functional Programming with Underscore.js — Michael Fogus
  • Clean Architecture — Robert C·Martin
  • https://reactjs.org
  • https://ant-design.gitee.io/d...
  • https://redux.js.org/
  • https://redux-saga.js.org/
  • 《笑傲江湖》— 金庸
  • 《天龙八部》— 金庸


原文:https://www.yuque.com/es2049/blog

评论