基于MVC理解React+Redux

我认为MVC模式虽然已经诞生了许多年,也有无数前端框架遵循了MVC模式,但我们在前端开发时,很多时候还是忽略了这个模式蕴含的思想。该思想的核心就是职责分离,这种分离又隐含了“信息专家模式”的意义,直白地说,就是“专业的事情应该交给专业的人去做”。

MVC(Model-View-Controller)的三个角色其实是各司其职:

  • model持有UI要展现的数据
  • View即UI的展现
  • Controller用于控制

以React来说,它就应该只专注于View的呈现,并将这些展现元素封装为Component。这些Component要展现的props可以视为Model所持有的数据。

那么,什么情况下会导致View产生变化呢?从表象上看,似乎引起变化的原因是由于客户端的某种请求或交互操作产生的事件。实则从业务上说,其实就是要改变Model的值,而UI的交互操作不过是对这种变化的界面展现罢了。换言之,View的变化其实应该通过Model的变化来传递。

当我们需要改变View时,一种做法是直接在View上做文章,通过编写针对UI元素的控制逻辑去改变View。另一种做法就是遵循MVC模式,应该通过Controller去改变Model的结构,然后通知View去改变自己(或者理解为View侦听到Model的变化,从而改变自己)。

React结合Redux框架做的正是这样的事情。在设计React Component时,我们需要通过UI的Layout来规划我们的Component,包括Component的分解与组合。呈现Component的过程就可以抽象为一个函数,这个函数接收一个输入对象model,返回一个包裹了HTML元素与Model的DOM结构。如以下伪代码:

const render = (model) => DOM

如果业务逻辑要求操作View的DOM,其实就是对DOM包裹的Model进行操作,例如添加或修改某个<li>,其本质是要添加或修改<li>元素中的值,这个值来自于Model。在Redux中,其实就是发起一个action。

执行action的目的虽然是修改Model,不过在Redux中,我们尽量希望遵循FP的思想设计出所谓的“纯函数”,于是Redux就引入了reducer函数,这个函数要做的事情其实就是对Model进行transform(可以考虑引入immutable.js来存储和操作Modle)。一旦Model对象发生了变化(并不是真正发生了变化,而是产生了一个新的Model),Redux就会通知React Component根据新获得的Model去重新Render。

显然,React扮演的是View的角色,Redux则是Controller,至于Model就是Redux Store中存储的State。我们要从MVC模式的角度去思考React+Redux开发,把代码需要做的每件事情想清楚,明确是谁的职责,如此才不至于在实现时走歪路,不讨好地去编写大量View的控制逻辑,尤其是那些牵涉到parent-child组件的递归关系时,可能会让前端代码炖成一锅粥。

举个实例。

我们要在前端编写一个过滤器,UI展现与控制逻辑类似Logiform,如下图所示:

△ Logiform的过滤器

这个过滤器可以理解为以Condition为根的一个递归嵌套树形结构,枝为Group,而叶为Expression。Group还可以嵌套Group或者Expression。可以添加、删除Group或Expression,也可以调整它们在树中所处的位置。

针对这样的需求,如果我们企图在React Component中直接去操控和管理这些逻辑,就需要考虑Component的父子关系,还需要考虑添加或删除Dom节点对整棵树的影响。

如果我们站在前述MVC模式的角度来考虑过滤器树的呈现与界面控制,其实不过就是针对Condition对象模型的操作罢了。这个时候,我们可以不用去操心DOM节点之间的关系,而是直接用React Component去render模型对象。对象的粗略结构如下所示:

{    "id": 1,    "operator": "and",    "conditions": [        {            "id": 2,            "type": "expression",            "operator": "=",            "fieldId": "11000",            "value": 3        },        {            "id": 3,            "type": "group",            "operator": "or",            "conditions": [                {                    "id": 4,                    "type": "expression",                    "operator": "<=",                    "fieldId": "11001",                    "value": 20                }            ]        }    ]}

由于render是一种只读的操作,要在React Component中去render这样的结构是非常容易的。如上,当我们要删除id为2的Expression时,其实就是去编写一个reducer,将其转换为如下的对象:

{    "id": 1,    "operator": "and",    "conditions": [      
  {            "id": 3,            
  "type": "group",           
   "operator": "or",            
   "conditions": [                {                
       "id": 4              
       "type": "expression",                    
       "operator": "<=",                    
       "fieldId": "11001",                    
       "value": 20                }            ]        }    ]}

render对UI的呈现与控制逻辑完全相同,并不需要再去控制复杂的DOM。

概括下来,React+Redux的主体流程为:

  • 通过action获得model,并将其作为state存储到Store中;
  • 传递给React Component,按照某种设计呈现model数据;
  • 调用action发起update请求,从而调用reducer生成新的state存储到Store中;
  • redux通知React Component重新Render。

这是MVC三种角色各司其职相互协作的结果。

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2016-08-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Material Design组件

Human Interface Guidelines — Text Fields

1525
来自专栏五毛程序员

五毛的cocos2d-x学习笔记06-处理用户交互

1452
来自专栏大前端开发

使用mpvue开发小程序教程(五)

在上一章节中,我们了解了组件的三个基本特性以及组件的基本使用方法。在实际的小程序开发中,我们应该以组件的思维去设计每个小程序的功能页面,对其进行合理的组件拆分,...

1862
来自专栏nice_每一天

一天带你入门到放弃vue.js(三)

自己新建的标签赋予特殊功能的是组件,而指定是在标签上使用类似于属性,以v-name开头,v-on,v-if...是系统指令! v-是表示这是vue的指令if,f...

1411
来自专栏HTML5学堂

JavaScript | 选中并获取多行文本框内容的效果

HTML5学堂(码匠):文本操作一直是开发中不可避免的存在,用户选中的文本内容,是否可以进行获取并处理到需要的位置当中?如果可以,这样的操作到底需要使用到哪些方...

4256
来自专栏自动化测试实战

Flask第37篇——模板项目实战(三)

我们先来分析上面这个页面,我们发现除了搜索栏下面的图片区域有些变化以外,其余都是base.html的布局,所以我们首先想到可以继承base.html,而图片显示...

1013
来自专栏智能算法

微信小程序,开发大起底

作者简介:张智超,北京微函工坊开发工程师,CSDN微信开发知识库特邀编辑。微信小程序爱好者。 感谢@翟东平 @qq_31383345 @nigelyq 等热情参...

50614
来自专栏java一日一条

Jsoup代码解读之六-parser(下)

读Jsoup源码并非无聊,目的其实是为了将webmagic做的更好一点,毕竟parser也是爬虫的重要组成部分之一。读了代码后,收获也不少,对HTML的知识也更...

762
来自专栏疯狂的小程序

实例分享微信小程序项目搭建(下)

首次 执行 wx.getLocation 小程序将自动调启如下 dialog:

2785
来自专栏Google Dart

AngularDart Material Design 日期选择器 顶

当用户键入日期时,将专门处理具有2位数年份的日期。 例如。7/7/77被解释为1977年7月7日,而不是77年7月7日。这个逻辑看起来是未来20年:现在(201...

1673

扫码关注云+社区