Modal.confirm是否违反了React模式分析

目录
  • 引言
  • 什么是“React模式”?

引言

如何评价 Ant Design 这个项目(一个设计语言)?

这是一篇临时起意的文章,我参与了一点上图中的讨论,正好有点时间,索性拉篇文章专门聊聊。

首先说结论:我不认为Modal.confirm以及类似的API是anti-pattern,尽管antd作者

@偏右悄悄地 也说Modal.confirm“并不符合React哲学”。

什么是“React模式”?

很多人都见过这个公式——

view = f(data)

React确实就是这么运作的。

然而React从来没有建议过,让你的整个应用都这么运作。很显然,一个view=f(data),是涵盖不了前端应用那么多可能性的。那么一个现实中有用的网页,大致应该怎么运作?

看图说话——

这张图我想所有人都能看懂,大部分给你科普前端架构的文章大致都是这么画的,这里只解释两点:

  • 第一,蓝箭头代表状态传递,红箭头代表行为传递。比如vdom,其实就是一堆描述某一时刻html状态的数据,但是经过diff,得到的指导react-dom进行操作的patch,其实描述的是“行为”,用现在时髦的说法叫“副作用”。
  • 红框里面为什么除了data还有timing?timing指的是时机,就是说你什么时候应该渲染,本质上是model那一层告诉你的,React组件不会自己没事儿渲染着玩(先不考虑state)。

图是很美好,然而如果一个网页就这样的话,它除了展示一下画面(还不能做单页),不会有任何用处。所以这个图要完善一下——

经过完善,图里面多了框,就是红框里那个actions,指的是当你点按钮的时候,发生的回调操作。有的框架里面管这个叫action,有的是store的成员函数,还有的比如redux,虽然它有action概念,但其实真正的“action”是在reducer里面实现的。

另外这里又出现了timing(时机),这其实是我最近很喜欢的一个概念。所谓的timing,就是告诉另一个模块“it’s time to do xxx”,trigger(event)是timing,dispatch(action)是timing,甚至直接调用成员函数,其实也是一种timing。

这张图,看起来能干一些事儿了,至少能点按钮刷新数据了,能做单页了。然而我们的网页,并非是只读的,除了展示数据,还要往后台提交数据,或者执行某个具体的功能(比如VScode里点ctrl+s保存,它需要往硬盘里面写文件)。所以还要完善一下——

相对完整的前端应用架构

哎,这个版本看起来有具体功能了,因为除了浏览器html之外,我们还多了个“IO等”,这样那些除了变动html之外的“副作用”,就可以实现了。基本上一个网页的架构,就是这样了。

注意,在这个版本中,Model也可以调用Actions,这是因为有些Model库是响应式的,比如rx,它本身就可以通过subscribe驱动副作用。

到这里,咱们终于可以进正题了。

我在上面的图中花了三个虚线框,中间那个是“React掌管的部分”,也就是建议view=f(data)那部分。我们看下箭头的颜色,Model到View,应该是状态数据(data),View到Actions,应该是行为指令(timing)。

其他两层,天生就不是React掌管的范围,业务模型主要负责维护数据以及数据之间的驱动关系,Side effect backend主要负责实现各种副作用,React的那套模型,在这里帮不上忙——注意,这不代表那两层就完全不能用React,事实上confirm完全可以理解为IO的一种(类比下c语言的scanf)。

也就是说,哪怕一个带有明显数据驱动特色的React项目,也存在很多部分不是数据驱动而是事件驱动的(我更喜欢成为“时机驱动”),这是天经地义的事情,不然你的程序根本写不出来。原因很简单,数据只能驱动出状态,只有时机才能驱动出行为——学过《数电》应该对这一点深有感触。

那么,对于“用户点击删除按钮,弹出确认框,点击确定删除条目,点击取消不做任何操作”这样一个功能——

  • 你觉得他是个状态还是个行为?
  • 你觉得他是时机驱动的还是数据驱动的?

答:它是个行为,时机驱动。

那么,对于一个时机驱动的行为,你非得把它硬坳成一个数据驱动的状态,你不觉得很奇怪吗?

一个comfirm,这么写有什么问题呢(伪代码,忽略一部分保护性代码)?

class FoobarComponent extends React.Component {
  // ...
  async onRemoveBtnClick (id) {
    const yes = await Modal.confirm('确认删除吗?')
    if (!yes) return
    dispatch(actions.remove({ id }))
  }
  render () {
    const { items } = this.props
    return <ul>
      {
        items.map(el => <li key={el.id}>
          <span>{el.name}</span>
          <button onClick={this.onRemoveBtnClick.bind(this, el.id)}>删除</button>
        </li>)
      }
    </ul>
  }
}

那么,为什么有人会觉的这样写是反模式呢?

我觉得可能是因为我们在编写网页的时候,一般都会比较注重纵向分层,却常常忽视横向切分。由于react-router的盛行,基本上我们是在组件这个层面配合前端路由来做横向切分的,但实际上这只是一种特殊情况,如果我们的应用更复杂,我们可能需要这样的结构——

上图中每个模块,都和模块A具有大致相同的结构,各个模块之间的时机和状态,可以通过总线一类的工具来中转。

如果我们写网页的“起手式”就是这样的,那么Modal,可能会被设计成一个单独的模块M,Modal.comfirm,可能会被设计成这样:

const yes = await globalBus.dispachAndWait(Actions.comfirm('确认删除吗?'))

这样,你还会觉得“反模式”吗?

PS:为什么为这件小事儿啰嗦这么一大篇文章呢?其实这本来也是我2018年前端技术总结的一部分想法,只是临时借着这个讨论说出来了。2018年上半年,参与了一个临时前端团队,堆了不少业务表单页面,期间,我跟人强调了无数次“不要无脑数据驱动”,还是有有人非得闪转腾挪用数据驱动和render表达一切,留了一大堆屎给我重构,我这一股无名火憋到现在。

以上就是Modal.confirm是否违反了React模式分析的详细内容,更多关于React模式剖析Modal.confirm的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java Reactor反应器模式使用方法详解

    Reactor反应器模式 到目前为止,高性能网络编程都绕不开反应器模式.很多著名的服务器软件或者中间件都是基于反应器模式实现的,如Nginx.Redis.Netty. 反应器模式是高性能网络编程的必知.必会的模式. Reactor简介 反应器模式由Reactor反应器线程.Handlers处理器两大角色组成: (1)Reactor反应器线程的职责:负责响应IO事件,并且分发到Handlers处理器. (2)Handlers处理器的职责:非阻塞的执行业务处理逻辑. 从上面的反应器模式定义,看不出这

  • Java中多线程Reactor模式的实现

    目录 1. 主服务器 2.IO请求handler+线程池 3.客户端 多线程Reactor模式旨在分配多个reactor每一个reactor独立拥有一个selector,在网络通信中大体设计为负责连接的主Reactor,其中在主Reactor的run函数中若selector检测到了连接事件的发生则dispatch该事件. 让负责管理连接的Handler处理连接,其中在这个负责连接的Handler处理器中创建子Handler用以处理IO请求.这样一来连接请求与IO请求分开执行提高通道的并发量.同时

  • 详解React的回调渲染模式

    一.一个简单的小例子 1.父组件 <Twitter username='tylermcginnis33'> {(user) => user === null ? <Loading /> : <Badge info={user} />} </Twitter> 2.子组件框架 import React, { Component, PropTypes } from 'react' import fetchUser from 'twitter' // fetc

  • React router动态加载组件之适配器模式的应用详解

    前言 本文讲述怎么实现动态加载组件,并借此阐述适配器模式. 一.普通路由例子 import Center from 'page/center'; import Data from 'page/data'; function App(){ return ( <Router> <Switch> <Route exact path="/" render={() => (<Redirect to="/center" />)}

  • React 并发功能体验(前端的并发模式)

    React 是一个开源 JavaScript 库,开发人员使用它来创建基于 Web 和移动的应用程序,并且支持构建交互式用户界面和 UI 组件.React 是由 Facebook 软件工程师 Jordan Walke 创建,React 的第一个版本在七年前问世,现在,Facebook 负责维护.React框架自首次发布以来,React 的受欢迎程度直线飙升,热度不减. 2020 年 10 月,React 17 发布了,但令人惊讶的是--"零新功能".当然,这并不是真的表示没有任何新添加

  • 谈谈React中的Render Props模式

    概述 Render Props模式是一种非常灵活复用性非常高的模式,它可以把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件拥有这样的能力,接下来我们一步一步来看React组件中如何实现这样的功能. 简要介绍:分离UI与业务的方法一直在演进,从早期的mixins,到HOC,再到Render Prop,本文主要对比HOC,谈谈Render Props 1 . 早期的mixins 早期复用业务通过mixins来实现,比如组件A和组件B中,有一些公用函数,通过mixins剥离这些公用部分,并

  • Modal.confirm是否违反了React模式分析

    目录 引言 什么是“React模式”? 引言 如何评价 Ant Design 这个项目(一个设计语言)? 这是一篇临时起意的文章,我参与了一点上图中的讨论,正好有点时间,索性拉篇文章专门聊聊. 首先说结论:我不认为Modal.confirm以及类似的API是anti-pattern,尽管antd作者 @偏右悄悄地 也说Modal.confirm“并不符合React哲学”. 什么是“React模式”? 很多人都见过这个公式—— view = f(data) React确实就是这么运作的. 然而Re

  • 基于React.js实现原生js拖拽效果引发的思考

    一.起因&思路 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖拽效果. 首先,其实拖拽效果的思路是很简单的.主要就是三个步骤: 1.onmousedown的时候,启动可拖拽事件,记录被拖拽元素的原始坐标参数. 2.onmousemove的时候,实时记录鼠标移动的距离,结合被拖拽元素第一阶段的坐标参数,计算并设置新的坐标值. 3.onmouseup的时候,关闭可拖拽事件,记录新的坐标值. 注意:这里主要是通过绝对定位的top和left来确定元素的位置

  • React Native学习教程之Modal控件自定义弹出View详解

    前言 最近在学习RN,好多知识都懒得写,趁今天有空,来一发吧,Modal控件的一个小demo:下面话不多说了,来一起看看详细的介绍吧. 参考文章地址:http://reactnative.cn/docs/0.27/modal.html#content Modal组件可以用来覆盖包含React Native根视图的原生视图(如UIViewController,Activity). 在嵌入React Native的混合应用中可以使用Modal.Modal可以使你应用中RN编写的那部分内容覆盖在原生视

  • React Native Modal 的封装与使用实例详解

    目录 背景 Android FullScreenModal 的封装使用 Android 原生实现全屏 Dialog 封装给 RN 进行相关的调用 Android 原生部分实现 JS 部分实现 使用 RootSiblings 封装 Modal 实现界面 Render 相关 实现 Modal 展示动画相关 使用 View 封装 Modal 整体 Modal 控件的封装 其他 Android Back 键的注意 View 封装 Modal 时候的注意 最后 背景 在使用 React Native(以下

  • 关于React状态管理的三个规则总结

    目录 前言 No.1 一个关注点 No.2 提取复杂的状态逻辑 No.3 提取多个状态操作 总结 前言 React 组件内部的状态是在渲染过程之间保持不变的封装数据.useState() 是 React hook,负责管理功能组件内部的状态. 我喜欢 useState() ,它确实使状态处理变得非常容易.但是我经常遇到类似的问题: 我应该将组件的状态划分为小状态,还是保持复合状态? 如果状态管理变得复杂,我应该从组件中提取它吗?该怎么做? 如果 useState() 的用法是如此简单,那么什么时

  • react中braft-editor的基本使用方式

    目录 braft-editor的基本使用 项目需求 使用braft-editor踩坑记,引用 braft-utils有错误 遇到的问题 解决方式 braft-editor的基本使用 项目需求 实现照片上传,富文本为空时的提示,官网详见Braft Editor import React, { PureComponent, Fragment } from 'react'; import { connect } from 'dva'; import BraftEditor from 'braft-ed

  • React-Native 组件之 Modal的使用详解

    Modal组件可以用来覆盖包含React Native根视图的原生视图(如UIViewController,Activity),用它可以实现遮罩的效果. 属性 Modal提供的属性有: animationType(动画类型) PropTypes.oneOf(['none', 'slide', 'fade'] none:没有动画 slide:从底部滑入 fade:淡入视野 onRequestClose(被销毁时会调用此函数) 在 'Android' 平台,必需调用此函数 onShow(模态显示的时

  • React 实现车牌键盘的示例代码

    vehicle-plate-keyboard React 实现的车牌键盘. https://github.com/LiuuY/vehicle-plate-keyboard

  • react中代码块输出,代码高亮显示,带行号,能复制的问题

    目录 react 代码块输出,代码高亮显示,带行号,能复制 以modal组件为例 infoModal.less样式 react 代码块插件 代码块插件 react 代码块输出,代码高亮显示,带行号,能复制 以modal组件为例 import React, { useState, useEffect } from 'react'; import { Modal, Button, message } from 'antd'; import Highlight from 'react-highligh

  • 在Vue.js中使用Mixins的方法

    一个很常见的场景: 有两个非常相似的组件, 它们拥有非常相似的基本功能, 但是它们之间又有足够的不同的地方, 该如何选择呢? 我们是应该将它们分成两个完全不同的组件呢? 还是创建一个基础组件, 然后定义足够多的props以方便区分使用场景? 这两种方式都不是完美的: 如果你将它们分成两个完全不同的组件, 在需求变化(功能变化)时, 可能会增加需要同时修改两个组件的风险, 这违反了"DRY"的前提. 另一方面, 太多的props很快会让人变得凌乱, 并且, 迫使维护人员, 甚至是你自己,

随机推荐