状态管理的现状

很多前端开发者认为,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. Fabric基础架构原理(二)

    Fabric 的网络节点本质上是互相复制的状态机,节点之间需要保持相同的账本状态.为了实现这个目的,各个节点需要通过共识( consensus )过程,对账本状态的变化达成一致性的认同. Fabric ...

  2. Wowza 4.5 修改 manager 端口号

    //编辑下面的文件, 搜索8088 有两处,改为想要的端口号即可 vim /usr/local/WowzaStreamingEngine/manager/bin/startmgr.sh // 重启服务 ...

  3. (转)JNI参数传递|Surface && sign签名对应

    http://blog.csdn.net/stefzeus/article/details/6622011 char* Get_Surface(JNIEnv *env, jclass cls, job ...

  4. mysql语句练习50题

    为了练习sql语句,在网上找了一些题,自己做了一遍,收益颇多.很多地方换一种思路,有更好的写法,欢迎指正. 题目地址:https://blog.csdn.net/fashion2014/article ...

  5. 阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第6节 static静态_12_静态static关键字修饰成员变量

    创建一个学生类 定义成员变量,无参构造,全参构造.成员变量的getter和setter 所在教室必须是一样的,定义所在教室 下面来创建两个学生 只给one的room赋值了.two的教室并没有赋值.都输 ...

  6. Where we love is home, home that our feet may leave, but not our hearts.

    parcel.n. 包裹 endurance.n.耐力 rot.v.腐烂 ornament.n.装饰 pinch.v.捏 nationality.n.国家 sunshine.n.阳光 stagger. ...

  7. mysql部署-主从搭建

    一.安装数据库 yum -y install http://www.percona.com/downloads/percona-release/redhat/0.1-4/percona-release ...

  8. python字典使用总结

    作者:python技术人 博客:https://www.cnblogs.com/lpdeboke 字典是另一种可变容器模型,且可存储任意类型对象. 字典的每个键值 key=>value 对用冒号 ...

  9. POJ-1502 MPI Maelstrom 迪杰斯特拉+题解

    POJ-1502 MPI Maelstrom 迪杰斯特拉+题解 题意 题意:信息传输,总共有n个传输机,先要从1号传输机向其余n-1个传输机传输数据,传输需要时间,给出一个严格的下三角(其实就是对角线 ...

  10. UVA 10003 Cutting Sticks 区间DP+记忆化搜索

    UVA 10003 Cutting Sticks+区间DP 纵有疾风起 题目大意 有一个长为L的木棍,木棍中间有n个切点.每次切割的费用为当前木棍的长度.求切割木棍的最小费用 输入输出 第一行是木棍的 ...