React 下,谈谈编程范式
但却很少有文章结合 React 特殊语境,对这两个范式进行详细说明,因此,写这篇文章,帮助大家形成 共同语言
首先,编程范式除了实现方式不同以外,其区别的根源在于 ——
关注点不同
- 函数的关注点在于 —— 变化
- 面向对象的关注点在于 —— 结构
对于函数,因为结构方便于处理变化,即输入输出是天然关注点,所以 ——
管理状态和副作用很重要
为何如此说:
var a = 1
function test(c){
var b = 2
a = 2
var a = 3
c = 1
return {a,b,c}
}
这里故意用 var 来声明变量,让大家又更深的体会
在函数中变更函数外的变量 ——
破坏了函数的封装性
这种破坏极其危险,比如上例,如果其他函数修改了 a,在 重新 赋值之前,你知道 a 是多少么?如果函数很长,你如何确定本地变量 a 是否覆盖外部变量?
会有大量意外发生
无封装的函数,不可能有’可装配性‘和’可调试性‘
所以,使用函数封装逻辑,不能引入任何副作用!注意,这个是强制的,在任何多人协作,多模块多资源的项目中 ——
封装是第一要务,更是基本要求!
所以,你必须将数据(或者说状态)全部包裹在函数内部,不可以在函数内修改任何函数以外的数据!
所以,函数天然存在一个缺点 —— 封装性需要人为保证(即你需要自己要求自己,写出无副作用函数)
当然,还存在很多优点 —— 只需要针对输入输出测试,更加符合物体实际运行情况(变化是哲学基础)
这部分没有加重点符号,是因为它不重要 —— 对一个思想方法提优缺点,只有指导意义,因为思想方法可以综合运用,不受限制
再来看看面相对象,来看看类结构:
class Test{
a = 1
b = 2
c = 3
constructor(){
this.changeA()
}
changeA(){
this.a = 2
}
}
这个结构一眼看去就具有 —— 自解释性,自封装性
还有一个涉及应用领域的优势 —— 对观念系统的模拟 —— 这个词不打着重符,不需要太关心,翻译过来就是,可以直译人脑中的观念(动物,人,车等等)
但它也有非常严重的问题 —— 初始化,自解耦麻烦,组合麻烦
需要运用到大量的’构建’,’运行’设计模式!
对的,设计模式的那些名字就是怎么来的
其实,你仔细一想,就能明白为什么会这样 ——
- 如果你关注变化,想要对真实世界直接模拟,你就需要处理静态数据,需要自己对一个领域进行人为解释
- 如果你关注结构,想要对人的观念进行模拟,你就需要处理运行时问题,需要自己处理一个运行时对象的生成问题
鱼与熊掌,不可兼得,按住了这头,那头就会翘起来,你按住了那头,这头就会翘起来
想要只通过一个编程范式解决所有问题,就像用手去抓沙子,最后你什么都得不到
极限的函数式,面向对象
通过函数和对象(注意是对象,类是抽象的,观念中的对象)的分析,很容易发现他们的优势
函数 —— 测试简单,模拟真实(效率高)
对象 —— 自封装性,模拟观念(继承多态)
将两者发扬光大,更加极限地使用,你会得到以下衍生范式:
管道/流
既然函数只需要对输入输出进行测试,那么,我将无数函数用函数串联起来,就形成了只有统一输入输出的结构
听不懂?换个说法 ——
只需要 e2e 测试,不需要单元测试!
如果我加上类型校验,就可以构造出 ——
理想无bug系统
这是将函数这一结构推广到极致的产物,也是各个函数式开发语言的主打卖点:
</p>
<p>而且,还有模式识别,异步亲和性等很多好处,甚至可以自建设计语言(比如麻省老教材《如何设计计算机语言》就是以lisp作为标准)</p>
<p>在 js 中, Cycle.js 和 Rxjs 就是极限的管道风格函数式,还有大家熟悉并且讨厌的 Node.js 的 Stream 也是如此,即便他是用 类 实现的,骨子里也是浓浓的函数式</p>
<p>分析一下这样的系统,你会发现 ——</p>
<p>它首先关注底层逻辑 —— 也就是 or/c , is-a,and/c,not/c 这样的函数,最后再组装</p>
<p>按照范畴学的语言(就是函数式的数学解释,不想看这个可以不看,只是补充说明):</p>
<figure data-size=)
</p>
<p>所谓领域,就是一系列相同目的,相同功能的资源的集合</p>
<p>比如,学校,公司,这两个类,如果分别封装了大量的其他类以及相关资源,共同构成一个整体,自行管理,自行测试,甚至自行构建发布,对外提供统一的接口,那这就是领域</p>
<p>听不懂?</p>
<p>这么说,如果实现了一个类和其相关资源的自行管理,自行测试,这就是 ——</p>
<p><b>DDD</b></p>
<p>如果实现了对其的自行构建发布,这就是 ——</p>
<p><b>微服务</b></p>
<p>听到这个词是不是非常开心?</p>
<p>这种模型给了应用规模化的能力 —— 横向,纵向扩展能力</p>
<p>还有高可用,即类的组合间的松散耦合范式</p>
<p>对于这样的范式,你首先思考的是 —— 你要做什么!</p>
<p>这就是 ——</p>
<p><b>自顶向下</b></p>
<ul>
<li>我要做什么应用?</li>
<li>这个应用有哪些功能?</li>
<li>我该怎么组织我的资源和代码?</li>
<li>该怎么和其他职能合作?</li>
<li>工期需要多久?</li>
</ul>
<p>这是企业应用的基础</p>
<hr>
<h2>现实告诉你,单用任何一种都不行</h2>
<p>开发过程中,不止有自底向上封装的工具,还有自顶向下设计的结构</p>
<p>产品经理不会把要用多少个 isObject 判断告诉你,他只会告诉你应用有哪些功能</p>
<p>同理,再丰富细致的功能划分,没有底层一个个工具函数,也完成不了任何工作</p>
<p>这个世界的确处在变化之中,世界的本质就是变化,就是函数,但是软件是交给人用,交给人开发的</p>
<h2>观念系统和实际运行,缺一不可!</h2>
<p>凡是动不动就跟你说某某框架函数式,某某应用要用函数式开发的人,大多都学艺不精,根本没有理解这些概念的本质</p>
<p>人类编程历史如此久远,真正的面向用户的纯粹函数式无bug系统,还没有出现过……</p>
<p>当然,其在人工智能,科研等领域有无可替代的作用。不过,是人,就有组织,有公司,进而有职能划分,大家只会通过观念系统进行交流 —— 你所说的每一个词汇,都是观念,都是类!</p>
<hr>
<p>好了,我们再来谈谈 React ,React 提倡函数式么?提倡!</p>
<p>不过说实话,Angular,Vue,React,都提倡函数式,三大框架的 template 解析,都是函数嵌套的“单向数据流”—— 就是管道!</p>
<p>这个结构上,大家没有本质区别,区别可能是 ng 为了 配合面向对象,用的是工厂函数</p>
<p>但是,工厂函数和函数,只是一个返回的是对象,一个返回的不止是对象而已:</p>
<div>
<pre><code><span>class</span> <span>OOStyle</span><span>{</span>
<span>name</span><span>:</span> <span>string</span>
<span>password</span><span>:</span> <span>string</span>
<span>constructor</span><span>(){}</span>
<span>get</span> <span>nameStr</span><span>(){}</span>
<span>changePassword</span><span>(){}</span>
<span>}</span>
<span>function</span> <span>OOStyleFactory</span><span>(){</span>
<span>return</span> <span>new</span> <span>OOStyle</span><span>(</span><span>/* ... */</span><span>)</span>
<span>}</span>
</code></pre>
</div>
<p>这是面向对象风格的写法(注意,<b>只是风格</b>,不是指只有这个是面向对象)</p>
<div>
<pre><code><span>function</span> <span>funcStyle</span><span>(</span><span>name</span><span>,</span><span>password</span><span>){</span>
<span>return</span> <span>{</span>
<span>name</span><span>,</span>
<span>password</span><span>,</span>
<span>getName</span><span>(){},</span>
<span>changePassword</span><span>(){}</span>
<span>}</span>
<span>}</span>
</code></pre>
</div>
<p>这个是函数风格的写法(注意,这只是风格,这同时也是面向对象)</p>
<p>这两种风格的逻辑是一样的,唯一的区别,只在于<b>可读性</b></p>
<blockquote><p>不要理解错,这里的可读性,还包括对于程序而言的可读性,即:<br />自动生成文档,自动生成代码结构<br />或者由产品设计直接导出代码框架等功能</p></blockquote>
<p>但是函数风格牺牲了可读性,得到了灵活性这一点,也是值得考虑的</p>
<p>编程其实是个权衡过程,对于我来说,我愿意</p>
<ul>
<li>在处理<b>复杂结构</b>时使用 面向对象 风格</li>
<li>在处理<b>复杂逻辑</b>时,使用 函数 风格</li>
</ul>
<p>各取所长,才是最佳方案!</p>
<hr>
<p>注意到上面那个函数风格的面向对象实现么?</p>
<p>恭喜你,你好像发现了什么不得了的东西:</p>
<div>
<pre><code><span>// Vue
</span><span></span><span>data</span><span>(){</span>
<span>return</span> <span>{</span>
<span>// ...
</span><span></span> <span>}</span>
<span>}</span>
<span>// redux reducer
</span><span></span><span>function</span> <span>todoApp</span><span>(</span><span>state</span><span>,</span> <span>action</span><span>)</span> <span>{</span>
<span>if</span> <span>(</span><span>typeof</span> <span>state</span> <span>===</span> <span>'undefined'</span><span>)</span> <span>{</span>
<span>return</span> <span>initialState</span>
<span>}</span>
<span>// 这里暂不处理任何 action,
</span><span></span> <span>// 仅返回传入的 state。
</span><span></span> <span>return</span> <span>state</span>
<span>}</span>
</code></pre>
</div>
<p>这其实就是用函数风格实现的 <b>面向对象</b> 封装,没有这一步,你无法进行顶层设计!</p>
<p>用类的写法来转换一下 redux 的写法:</p>
<div>
<pre><code><span>class</span> <span>MonitRedux</span><span>{</span>
<span>// initial,想要不变性可以将 name,password 组合为 state
</span><span></span> <span>name</span> <span>=</span> <span>''</span>
<span>password</span> <span>=</span> <span>''</span>
<span>// 惰性初始化(配合工厂)
</span><span></span> <span>constructor</span><span>(){</span>
<span>this</span><span>.</span><span>name</span> <span>=</span> <span>''</span>
<span>this</span><span>.</span><span>password</span> <span>=</span> <span>''</span>
<span>}</span>
<span>// action
</span><span></span> <span>changeName</span><span>(){}</span>
<span>}</span>
</code></pre>
</div>
<p>不要纠结所谓 class 中的 纯度问题,因为 class 的封装性是 class 本身带来的</p>
<h2>只有 函数 的封装性才受副作用限制</h2>
<p>注意这一点,React 程序员非常容易犯的错误,就是到了 class 里面还在想纯度的问题,恨不得将每个成员函数都变成纯函数</p>
<p>没必要以词害意,需要融汇贯通</p>
<p>同样,以上例子也说明,如果你的技术栈提供直接生成对象的方案 ——</p>
<h2>你可以只用函数直接完成面向对象和函数式的设计</h2>
<div>
<pre><code><span>function</span> <span>ImAClass</span><span>(){</span>
<span>return</span> <span>{</span>
<span>// ...
</span><span></span> <span>}</span>
<span>}</span>
</code></pre>
</div>
<p>我就说这个是类!为什么不行?</p>
<p>他要成员变量有成员变量,要成员函数有成员函数,封装,多态,哪个特性没有?</p>
<p>什么?继承?这年头还有搞面向对象的提继承?组合优于继承是常识!</p>
<p>抛弃了继承,你需要this么?你不需要,本来你就不需要this(除了装饰器等附加逻辑,但是函数本身就能够实现附加逻辑 —— 高阶函数)</p>
<p>同样,你也可以综合面向对象和函数式的特点,各取所长,对你的项目进行顶层构建和底层实现</p>
<p>这也是很方便的</p>
<hr>
<h2>Hooks,Composition,ngModule</h2>
<p>我们来看看上面的那个函数风格的类</p>
<p>像不像什么东西?</p>
<div>
<pre><code><span>function</span> <span>useThisClass</span><span>(){</span>
<span>const</span> <span>[</span><span>val1</span><span>,</span><span>setVal1</span><span>]</span> <span>=</span> <span>useState</span><span>(</span><span>0</span><span>)</span>
<span>const</span> <span>[</span><span>val2</span><span>,</span><span>setVal2</span><span>]</span> <span>=</span> <span>useState</span><span>(</span><span>0</span><span>)</span>
<span>useEffect</span><span>(()=>{},[])</span>
<span>const</span> <span>otherObject</span> <span>=</span> <span>useOtherClass</span><span>()</span>
<span>reutrn</span> <span>{</span><span>val1</span><span>,</span><span>setVal1</span><span>,</span><span>val2</span><span>,</span><span>setVal2</span><span>,</span><span>otherObject</span><span>}</span>
<span>}</span>
</code></pre>
</div>
<p>Hooks 恭喜各位,用得开心!</p>
<p>在 15 年 9 月的时候,Angular 首先发布了服务(React 叫做 状态逻辑复用),并自动帮你实现 module 封装</p>
<p>在那个时候, <b>React,Vue 都完全没有办法实现 领域模块 </b></p>
<p>大家只能将状态,副作用,逻辑拆开,然后在顶层处理,为什么?</p>
<p>以 React 为例,老一代的 React 在 组件结构上是管道,也就是单向数据流,但是对于我们这些使用者来说,我们写的逻辑,基本上是放养状态,根本没有接入 React 的体系,完全游离在函数式风格以外:</p>
<figure data-size=)

</p>
<p>为什么要有 state,action?</p>
<p>为什么要在每个组件里用 s/a ?</p>
<p>action 其实是用命令模式,将逻辑复写为状态,以便 Context 传递,为何?</p>
<p>因为生命周期在组件里,setState在组件的this上</p>
<p>换句话说,框架没有提供给你,将用户代码附加于框架之上的能力!</p>
<p>这个能力,叫做<b> IOC 控制反转</b>,即 框架将功能控制权移交到你的手上 </p>
<p>不要把这个类Redux开发模式作为最自然的开发方式,否则你会非常痛苦!</p>
<h2>只有集成度不高的系统,才需要中介模式,才需要 MVC</h2>
<p>之前的 React/Vue 集成度不高,没有 Redux 作为中介者 Controller,你无法将用户态代码在架构层级和 React/Vue 产生联系,并且这个层级天然应该用领域模块的思想方法来处理问题</p>
<p>因为<b>框架没有这个能力</b>,所以你才需要这些工具</p>
<p>所谓的状态管理,所谓的单一Store,都是没有IOC的妥协之举,并且是在完全抛弃面向对象思想的基础上强行用函数式语言解释的后果,是一种畸形的技术产物,是框架未达到最终形态之前的临时方案,不要作为核心技术去学习,这些东西不是核心技术!</p>
<p>还是上文所说,理想的规模化纯管道无bug系统,人类没有做出来过</p>
<p>你想要的为这个没有做出来过的东西,奋斗努力么?</p>
<p>15年9月,Angular 首先提供了IOC,且更近一步,将领域模块的概念也提了出来,并且强制执行!</p>
<p>回头看看 React 那些暧昧的话语,有些值得玩味:</p>
<ul>
<li>Hook 使你在无需修改组件结构的情况下复用<b>状态逻辑</b> (注意!是状态逻辑,不是状态,是状态逻辑一起复用,不是状态复用)</li>
<li>我们推荐用 自定义hooks 探索更多可能</li>
<li>提供渐进式策略,提供 useReducer 实现大对象操作(好的领域封装哪来的操作大对象?)</li>
</ul>
<p>他决口不提 面向对象,领域驱动,和之前的设计失误,是因为他需要顾及影响和社区生态,但是使用者不要被这些欺骗!</p>
<p>当你有了 hooks,Composition ,Service/Module 的时候,你应该主动抛弃所有类似</p>
<ul>
<li>状态管理</li>
<li>一定要写纯函数</li>
<li>副作用要一起处理</li>
<li>一定要保证不变形</li>
</ul>
<p>这之类的所有言论,因为在你手上,不仅仅只有函数式一件武器</p>
<p>你还有面向对象和领域驱动</p>
<hr>
<p>用领域驱动解决高层级问题,用函数式解决低层级问题,才是最佳开发范式</p>
<p>也就是说,函数式和面向对象,没有好坏,他们只是两个关注点不同的思想方法而已</p>
<p>你要是被这种思想方法影响,眼里只有对错 ——</p>
<p>实际上是被忽悠了</p>
</div>
<!-- Start Tags -->
<div class=)