37行代码构建无状态组件通信工具-让恼人的Vuex和Redux滚蛋吧!
状态管理的现状
很多前端开发者认为,Vuex和Redux是用来解决组件间状态通信问题的,所以大部分人仅仅是用于达到状态共享的目的。但是通常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不知道这么说老大会不会打我),所以只能招得起面试能拧螺丝的。但是我们有很多造火箭的需求。
如果你想寻求刺激,就来加入我们吧
37行代码构建无状态组件通信工具-让恼人的Vuex和Redux滚蛋吧!的更多相关文章
- React系列文章:无状态组件生成真实DOM结点
在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({name ...
- Flutter入门之无状态组件
Flutter核心理念 flutter组件采用函数式响应框架构建,它的灵感来自于React.它设计的核心思想是组件外构建UI,简单解释一下就是组件鉴于它当前的配置和状态来描述它的视图应该是怎样的,当组 ...
- React: 无状态组件生成真实DOM结点
在上一篇文章中,我们总结并模拟了 JSX 生成真实 DOM 结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({ ...
- React中的高阶组件,无状态组件,PureComponent
1. 高阶组件 React中的高阶组件是一个函数,不是一个组件. 函数的入参有一个React组件和一些参数,返回值是一个包装后的React组件.相当于将输入的React组件进行了一些增强.React的 ...
- 15. react UI组件和容器组件的拆分 及 无状态组件
1.组件的拆分 组件拆分的前提 当所有的逻辑都出现在一个组件内时 组件会变得非常复杂 不便与代码的维护 所以对组件进行拆分 IU组件 进行页面渲染 容器组件 进行逻辑操作 UI组件的拆分 新建一个 ...
- Blazor中的无状态组件
声明:本文将RenderFragment称之为组件DOM树或者是组件DOM节点,将*.razor称之为组件. 1. 什么是无状态组件 如果了解React,那就应该清楚,React中存在着一种组件,它只 ...
- React 中的 Component、PureComponent、无状态组件 之间的比较
React 中的 Component.PureComponent.无状态组件之间的比较 table th:first-of-type { width: 150px; } 组件类型 说明 React.c ...
- 37行代码实现一个简单的打游戏AI
不废话,直接上码,跟神经网络一点关系都没有,这37行代码只能保证电脑的对敌牺牲率是1:10左右,如果想手动操控,注释掉autopilot后边的代码即可. 哪个大神有兴趣可以用tensorflow或者s ...
- react的redux无状态组件
Provider功能主要为以下两点: 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件 接收Redux的store作为props,通过context对象传递给子孙组件上的connec ...
随机推荐
- 【洛谷P1383 高级打字机】
题目描述 早苗入手了最新的高级打字机.最新款自然有着与以往不同的功能,那就是它具备撤销功能,厉害吧. 请为这种高级打字机设计一个程序,支持如下3种操作: 1.T x:在文章末尾打下一个小写字母x.(t ...
- 二十三、python中的time和datetime模块
A.time模块 1. sleep():强制等待 import timeimport datetime print("start to sleep.....")time.sle ...
- maven(一) maven到底是什么
为了方便自己查找,这里转载他人文章,原文出处http://www.cnblogs.com/whgk/p/7112560.html 我记得在搞懂maven之前看了几次重复的maven的教学视频.不知道是 ...
- Bootstrap 学习笔记 项目实战 响应式轮播图
左右两个箭头可以随浏览器缩放进行移动 保持在图片中间 Html代码: <!DOCTYPE html> <html lang="zh-cn"> <hea ...
- mac 添加mysql的环境变量和删除mysql
添加环境变量 1.创建 .bash_profile,已创建过忽略这步 (1)启动终端 (2)进入当前用户的home目录(默认就是): cd ~ 或 cd /Users/YourMacU ...
- HDFS网络拓扑概念及机架感知(副本节点选择)
网络拓扑概念 在本地网络中,两个节点被称为“彼此近邻”是什么意思?在海量数据处理中,其主要限制因素是节点之间数据的传输速率——带宽很稀缺.这里将两个节点间的带宽作为距离的衡量标准. 节点距离:两个节点 ...
- 设计模式:单例模式(Singletion)
单例模式(Singletion):保证一个类仅有一个实例,并提供一个访问该实例的全局访问点. 单例模式主要作用是保证唯一的实例,可以严格地控制客户端怎样访问该实例以及何时访问它.可以简单的理解为对唯一 ...
- 在WebStorm中使用editorConfig插件
在webStorm中默认是支持editorConfig插件的,那么我们需要在webStorm中自定义editorConfig的配置怎么来做? 第一步:打开webStrome > File > ...
- Python学习-第一天-函数和模块的使用
目录 Python学习-第一天总结 print输出的一种简单格式 函数参数之可变参数 模块管理函数 if else语句的单行实现(简洁) 变量作用域 函数书写格式 Python学习-第一天总结 pri ...
- 洛谷 P1108 低价购买(LIS,统计方案数)
传送门 解题思路 看第一个要求,很显然是求最长下降子序列,和LIS几乎一样,很简单,再看第二个问号,求最长下降子序列的方案数??这怎么求? 注意:当二种方案“看起来一样”时(就是说它们构成的价格队列一 ...