状态管理的现状

很多前端开发者认为,VuexRedux是用来解决组件间状态通信问题的,所以大部分人仅仅是用于达到状态共享的目的。但是通常Redux是用于解决工程性问题的,用于分离业务与视图,让结构更加清晰,从而达到易于维护的目的。也就是 Flux(这里我之前翻译的Flux深度解读)架构所解决的问题。但是绝大多数时候,大家只是想解决的问题是组件嵌套过深的时候,如何将子组件的状态直接传递给父组件。那么此时Vuex也好Redux也好,对于我们的诉求就过于繁琐。每次通信后,我们还需要清理掉Store中的状态。更加恼人的是,我们该如何选择哪些状态应该放入Store,那些状态应该放在组建内的state一直困扰着大家,甚至于社区也是没有一个定论。因此很多年轻前端工程师所开发的项目,状态管理极其混乱。以至于不久后就难以维护。

无状态组件间通信的由来

针对以上诉求,我们能不能开发一个简单的组件间通信工具来解决目前前端状态管理的痛点呢?因此我实现了一个无状态组件通信工具,这也就是这篇文章的由来。

无状态,也就是它并不关注数据内容,它只是起到一个管道的作用,在组件间建立管道,组件可以通过该管道向管道另一头的组件说:“hello world!This is your message。”。

巧用设计模式

设计模式,大家都很熟悉,现代前端框架已经使用非常多的设计模式,大家都能耳熟能详的就是观察者模式装饰器模式,以及发布订阅模式(一种将观察者和通知者融合的设计模式)。

设计模式,是用于解决特定问题而被大家公认为最佳实践的模式。一般最被大家熟知的为23种设计模式 - 这里是我用ES2015实现的面向对象方式的设计模式例子

那么我们该如何利用设计模式解决我们的问题呢?上代码:

    const listener = {}; // 用于保存订阅者

    // 注册订阅者
function subscribe (event, handle) {
// 订阅者订阅的信息
if (typeof event !== 'string') {
throw new Error('event must be String!');
}
// 订阅者的callback函数
if (typeof handle !== 'function') {
throw new Error('handle must be function!');
}
// 将订阅者添加到订阅者容器中保存起来
if (!listener[event]) {
listener[event] = [];
listener[event].push(handle);
} else {
var index = listener[event].indexOf(handle);
if (index < 0) {
listener[event].push(handle);
}
}
// 返回用于取消订阅的接口,这里是一个高阶函数
return function unSubscribe() {
var index = listener[event].indexOf(handle);
if (index > -1) {
listener[event].splice(index, 1);
}
}
}
// 为通知者提供的发起通知的接口
function dispatch (event, payload) {
if (listener[event]) {
listener[event].forEach(function serviceFunc(handle) {
handle(payload);
})
} else {
throw new Error('No subscriber be registried for serviceName!');
}
} export {
subscribe,
dispatch
}

这里主要使用了一下几种JS语言常用的设计模式以及技术知识点:

  • 沙盒模式 在之前一篇文章如何构建一个不到100行的小程序端mini版本redux中介绍了如何通过沙盒模式构建一个mini小程序版的redux。如果对于沙盒模式还不了解可以参看这篇文章,这里用沙盒模式对用于存储订阅者的变量进行封装和保护。
  • 发布订阅模式 发布(dispatch)订阅(subscribe)模式是一种混合模式,它包含了观察者模式和通知者模式。
  • 高阶函数 这是JS一种常见的知识点,在面试的时候经常会有面试官提问这个技术,但是真正用于实战的并不多,大多都是构建基础架构的高级工程师才有机会使用。

以上,我们利用沙盒模式,发布订阅模式实现了一个基本的无状态组件间通信工具。那么我们如何使用它呢?

使用无状态工具实现组件间数据通信

下面是我们要实现的一个例子:


组件结构是 爷爷包含儿子,儿子包含孙子,儿子和孙子可以和爷爷直接对话。

在根组件(爷爷组件)注册订阅者用来订阅儿子和孙子发来的信息:

    import Son from './Son';

    import { subscribe } from './utils';

    class App extends Component {
constructor(props) {
super(props);
this.state = {
messageFromSon: '',
messageFromGrandson: ''
}
// 在这里订阅了儿子的会话和孙子的会话,记得bind(this)这样才能访问组件的上下文
this.listenSonHandle = this.listenSonHandle.bind(this);
this.listenGrandsonHandle = this.listenGrandsonHandle.bind(this);
// 我们需要保留订阅会话,在不需要的时候取消注册
this.listenHandle = [
subscribe('son', this.listenSonHandle),
subscribe('grandson', this.listenGrandsonHandle)
]
} listenSonHandle(payload) {
this.setState({
messageFromSon: payload
});
} listenGrandsonHandle(payload) {
this.setState({
messageFromGrandson: payload
})
} componentWillUnmount() {
this.listenHandle.forEach((unSubscribe) => {
unSubscribe();
})
} render() {
return (
<div style={{background: 'red'}}>
<Son />
<div>
儿子来电:{this.state.messageFromSon}
</div>
<div>
孙子来电:{this.state.messageFromGrandson}
</div>
</div>
);
}
}

儿子组件需要和爷爷组件直接对话,那么就需要和爷爷组件建立相同的通信管道:

    import React from 'react';
import { dispatch } from './utils';
import Grandson from './Grandson'; export default class Son extends React.Component {
constructor(props) {
super(props);
this.state = {
message: ''
}
} render() {
return <div style={{background: 'green'}}>
这里是儿子:
<input value={this.state.message} onChange={(e) => {
this.setState({
message: e.target.value
})
}}></input>
<button onClick={() => {
// 利用通知者接口,向爷爷组件发送信息
dispatch('son', this.state.message);
}}>告诉老子</button>
<Grandson/>
</div>
}
}

孙子组件想要向爷爷组件发送信息,如果不使用redux的话就要一层一层的传递props。先告诉爸爸,然后爸爸告诉爷爷,但是有了我们现在构建的无状态组件通信工具。就不需要那么麻烦了:

    import React from 'react';
import { dispatch } from './utils'; export default class Son extends React.Component {
constructor(props) {
super(props);
this.state = {
message: ''
}
} render() {
return <div style={{background: 'yellow'}}>
这里是孙子:
<input value={this.state.message} onChange={(e) => {
this.setState({
message: e.target.value
})
}}></input>
<button onClick={() => {
dispatch('grandson', this.state.message);
}}>告诉爷爷</button>
</div>
}
}

例子源码

甚至我们可以很容易再剥离出一层业务层,实现业务与视图的隔离。起到和Vuex,Redux同样的目的。

最后

由于设计模式是语言无关的,因此这个utils/index.js下的代码是可以用于任何前端框架的。

这就是设计模式的强大之处。是不是你们可以扔掉那恼人的Vuex和Redux了呢?

广告:我们团队招人,途家网,地点在国家会议中心,我们的团队很年前,才组件1年多。有很多机会。

别人面试造火箭,进去拧螺丝。我们面试拧螺丝,进来造火箭。我们招不起能面试造火箭的人(T_T不知道这么说老大会不会打我),所以只能招得起面试能拧螺丝的。但是我们有很多造火箭的需求。

如果你想寻求刺激,就来加入我们吧

yahuil_1@tujia.com

37行代码构建无状态组件通信工具-让恼人的Vuex和Redux滚蛋吧!的更多相关文章

  1. React系列文章:无状态组件生成真实DOM结点

    在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({name ...

  2. Flutter入门之无状态组件

    Flutter核心理念 flutter组件采用函数式响应框架构建,它的灵感来自于React.它设计的核心思想是组件外构建UI,简单解释一下就是组件鉴于它当前的配置和状态来描述它的视图应该是怎样的,当组 ...

  3. React: 无状态组件生成真实DOM结点

    在上一篇文章中,我们总结并模拟了 JSX 生成真实 DOM 结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({ ...

  4. React中的高阶组件,无状态组件,PureComponent

    1. 高阶组件 React中的高阶组件是一个函数,不是一个组件. 函数的入参有一个React组件和一些参数,返回值是一个包装后的React组件.相当于将输入的React组件进行了一些增强.React的 ...

  5. 15. react UI组件和容器组件的拆分 及 无状态组件

    1.组件的拆分 组件拆分的前提 当所有的逻辑都出现在一个组件内时 组件会变得非常复杂 不便与代码的维护 所以对组件进行拆分 IU组件 进行页面渲染 容器组件  进行逻辑操作 UI组件的拆分 新建一个 ...

  6. Blazor中的无状态组件

    声明:本文将RenderFragment称之为组件DOM树或者是组件DOM节点,将*.razor称之为组件. 1. 什么是无状态组件 如果了解React,那就应该清楚,React中存在着一种组件,它只 ...

  7. React 中的 Component、PureComponent、无状态组件 之间的比较

    React 中的 Component.PureComponent.无状态组件之间的比较 table th:first-of-type { width: 150px; } 组件类型 说明 React.c ...

  8. 37行代码实现一个简单的打游戏AI

    不废话,直接上码,跟神经网络一点关系都没有,这37行代码只能保证电脑的对敌牺牲率是1:10左右,如果想手动操控,注释掉autopilot后边的代码即可. 哪个大神有兴趣可以用tensorflow或者s ...

  9. react的redux无状态组件

    Provider功能主要为以下两点: 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件 接收Redux的store作为props,通过context对象传递给子孙组件上的connec ...

随机推荐

  1. 如何为我们的程序编写开发文档——Java文档注释

    Java文档注释是用于生成Java API文档的注释,通过在程序中的类.属性.方法部分加上注释,就可以用javadoc命令生成漂亮的API文档,是程序员进阶的必备技能. 注意,文档注释只说明紧跟其后的 ...

  2. optistruct线性求解一次二次单元应力位移比较

    通过分析比较10mm.5mm.3mm.1mm的网格模型, 网格越细密: 位移与应力均趋于恒定值(收敛): 一次与二次单元的应力区域一致: 一次与二次单元的位移相差11.3%,一次单元的位移小. 所用的 ...

  3. kafka 生产者发送消息

    KafkaProducer 创建一个 KafkaThread 来运行 Sender.run 方法. 1. 发送消息的入口在 KafkaProducer#doSend 中,但其实是把消息加入到 batc ...

  4. CDN分发

    CDN 是Content Delivery Network,即内容分发网络. 未完待续..

  5. Android在WindowManagerService和ActivityManagerService中的Token

    https://upload-images.jianshu.io/upload_images/5688445-6cf0575bb52ccb45.png 1. ActivityRecord中的token ...

  6. 【HANA系列】SAP HANA快捷键大全

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA快捷键大全   ...

  7. 【FICO系列】SAP 关于SAP中的记账码的解释

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[FICO系列]SAP 关于SAP中的记账码的解 ...

  8. 应用安全 - 代码审计 -Java

    Java %c0%ae 安全模式绕过漏洞 原理 在Java端"%c0%ae"解析为"\uC0AE",最后转义为ASCCII低字符-".".通 ...

  9. 精灵图和base64如何选择

    Css Sprites: 介绍: Css Sprites(雪碧图或css精灵),是网页图片处理的一种方式,它允许你将一个页面涉及到的所有零星图片都包含到一张大图中去,这样一来,当访问该页面时,载入的图 ...

  10. 通过FSDataOutputStream向HDFS上写数据

    FSDataOutputStream,这个类重载了很多write方法,用于写入很多类型的数据:比如字节数组,long,int,char等等. 像FSDataInputStream一样,要获得FSDat ...