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 ...
随机推荐
- win7x64 串口程序无法运行,提示:component 'MSCOMM32.OCX' or one of its dependencies not correctlu registered。。。
VB写的串口工具在win7 x64机器运行时遇到的问题, component 'MSCOMM32.OCX' or one of its dependencies not correctlu regis ...
- React-Native 之 GD (十九)TabBarItem 逻辑完善 / 关闭筛选菜单滑动手势 / Navigator 掉帧卡顿问题处理
1.TabBarItem 逻辑完善 那么为了更好的用户体验,我们这边还需要来处理一下点击 TabBarItem 的一下细节,那就是当用户点击 Item 时,可能只是单纯的想进行页面的 切换或者置顶操作 ...
- Laravel5.5执行 npm run dev时报错,提示cross-env找不到(not found)的解决办法
Laravel 5.4 Mix & Laravel5.5执行 npm run dev时报错,提示cross-env找不到(not found)的解决办法 首先进入package.json文 ...
- vue +ts 的一次踩坑日记
在vue的方法里面写事件的时候比如写一个路由跳转,方法大概如下: goBack1() { console.log(this); this.$router. ...
- docker 命令汇总2
docker version [root@cu-tmp-201 ~]# docker version Client: Version: 18.09.6 API version: 1.39 Go ver ...
- CSS3------box-shadow,即单边阴影效果设置
box-shadow修改元素的阴影效果要方便得多,因为box-shadow可以修改六个参数,得到不同的效果.下面结合一些简单的案例来对box-shadow属性进行演示说明. 1.单边阴影效果 定义元素 ...
- exceptions: django2.2/ mysql ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3
在centos部署python应用后访问页面,显示如下异常信息 报错环境 python=3.6,django=2.2……django.core.exceptions.ImproperlyConfigu ...
- Makefile之patsubst
经常要手写项目的Makefile,或者看其他项目的遗留项目的Makefile,有些makefile内置函数常用, 却用完就忘记了,最近项目中使用patsubst,感觉挺好用的 格式:$(pa ...
- Creat-React-Native-App 之StackNavigator之踩坑记录
Creat-React-Native-App简称CRNA. 在我开始入门RN时fb已经推出和Expo联合开发用于快速创建React Native应用的工具: Create-React-Native-A ...
- vue 点击任意地方防止冒泡
$('.mainL').mouseup(function(e){ let objLeader = $(obj.target); // 设置目标区域 if(!objLeader.is(e.target) ...