真正以数据为核心,提升前端开发效率

当前前端的开发框架在技术选型及流行程度上基本形成了三足鼎立的局面,他们分别是:React、Vue以及Angular。React是专注于View层解决方案的框架,其他两个则是MVVM框架。 今天我们主要讲基于React的开发模式。

React的单项数据流

React是专注于View层解决方案的框架,在数据管理上官方推荐使用单项数据流管理方式,并且提出了Flux的架构模式,流程图如下:

Flux 架构

很容易可以看出,数据流从Actions到Dispatcher到Store再到React Component,是单向流动的。有以下几个好处:

视图组件变得很薄,只包含了渲染逻辑和触发 action 这两个职责,即所谓 “dumb components”;
要理解一个 store 可能发生的状态变化,只需要看它所注册的 actions 回调就可以;
任何状态的变化都必须通过 action 触发,而 action 又必须通过 dispatcher 走,所以整个应用的每一次状态变化都会从同一个地方流过。 其实 Flux 和传统 MVC 最不一样的就在这里了。 React 在宣传的时候一直强调的一点就是 “理解你的应用的状态变化是很困难的 (managing state changing over time is hard)”, Flux 的意义就在于强制让所有的状态变化都必须留下一笔记录,这样就可以利用这个来做各种 debug 工具、历史回滚等等。

Redux可以说是Flux的一种实现,但又不完全遵循Flux的思想而实现。它最大的一点不同就是模型中建议只有一个store,Flux 里面会有多个 store 存储应用数据,并在 store 里面执行更新逻辑,当 store 变化的时候再通知 controller-view 更新自己的数据;而Redux 将各个 store 整合成一个完整的 store,并且可以根据这个 store 通过reducers推导出应用完整的 state。同时 Redux 中更新的逻辑也不在 store 中执行而是放在 reducer 中。
基于Redux,根据各自不同的需求,又有很多的其他框架或中间件的实现。比如:连接React与Redux的react-redux;操作action的redux-actions;用于异步操作的redux-thunk、redux-saga、redux-promise;logger系统redux-logger;连接react-router与Redux等react-router-redux;Redux的selector辅助库reselector等等,加上React相关的技术栈、Redux的各种概念及操作API,使得我们在实际的使用及技术选型过程中往往会有很多的困扰。那能不能基于这些技术选型并结合开发中的最佳实践,封装一个框架封边我们使用呢?答案是肯定的!

使用dva

dva是如何来的可以参考其文档:支付宝前端应用架构的发展与选择。我们主要说下他的使用思想,以及如何遵循我理解的最佳实践。

dva的API其实是受elm的API启发而来的。它主要包含以下几个概念:

  1. model:主要用于数据维护;
  2. model的state:状态数据,用于React组件里的渲染数据;
  3. model的reducers:同步的修改state的纯函数;
  4. model的effects:异步请求数据,使用redux-saga实现,所以写法上是Generator的形式;
  5. model的subscriptions:用于model被添加后的钩子函数执行;
  6. connect:使用react-redux的connect方法,连接store与React Component;
  7. Router:来自于react-router;
  8. Route Component:来自于react-router,用于路由与组件的连接;
    具体可以参见其API文档:dva的几个概念。

最佳实践

dva的具体使用我们不再做详细的介绍,具体可以参考其官网。我们从以下几个维度来阐述下最佳实践。

组件划分

我们一般会将组件划分为:Container Component与UI Component,即容器组件与视图组件。容器组件是与dva(Redux)通过react-redux的connect API进行连接的组件,其实是赋予了他两个能力:

组件的props属性里可以获取到model state的数据;
具备dispatch能力。
UI组件是容器组件的子组件,UI组件一般不会与dva进行connect,有以下两个考虑:

  1. 减少性能消耗;
  2. 组件可复用。

那如何让UI组件也具备dispatch能力呢?答案就是将所有可能的dispatch的封装在容器组件的实例方法里面,这样做的另外两个个好处就是:

  1. 避免dispatch满天飞;
  2. 代码更容易维护,数据状态更改也更方便做管理。

而UI组件是与dva(Redux)解耦的,所以也可以用在非Redux体系(如Flux、reflux等)的系统里。
基础能力封装

我们的UI组件会有非常多的通用操作,比如基于classnames模块对className的操作,xss安全,常见数据处理,pure render性能优化等等,这时,我们可以将这些通用的操作封装在一个基础类中,然后提供出来供UI Component来继承使用。

组件中尽量不要再维护数据

这个是最关键的问题。按理说,如果一个组件的数据只在其内部使用,基本是没必要的放在dva model的state里去维护的,但是,通常我们的代码的变化是不可预测的,谁能保证哪天我们会将其暴露出去,让其他组件能控制这个组件呢?

另一个就是对于刚入门的开发者特别头疼的一个问题:组件受控与非受控,从意思也可以看出,就是说该组件是否能被外面控制,其实其本质上也是上面的问题。一个非受控的组件在初始化接收一个初始数据后,后面再将数据做更改,该组件也不会再接收新更改后的数据;如果一个组件是受控的,我们往往会通过组件内setState()的方式来维护数据状态,传递给组件的值是被时时接受的。

如果将一个组件设置为非受控的,而该组件的数据是来自于外层的model state,当我们将外面数据进行更改后,该组件的值就不会改变,显示到Input这种组件就是无论你怎么输入内容,输入框的显示都不变;如果我们将组件设置为受控的,而且我们组件内维护Input组件的状态数据的时候,因为输入框的数据最后是要提交服务器的,而提交服务器的异步操作肯定是在model中操作的,所以需要我们将数据实时同步到model的state。所以这里就需要做两份操作:setState,dispatch reducer,如果受控组件的渲染数据直接来自于model而不是组件内部state,我们其实就不需要维护组件内的state数据了。况且,setState出于性能考虑不会实时更新状态,容易出现其他问题,这一点可以参考文章:解密setState。

综上所述,尽量把所有状态数据(如:组件loading状态控制、Dialog是否显示状态控制等)、表单数据等全部放到model的state里维护,不再在React Component里单独维护state。

通过以上的模式我们就达到了所有的数据维护(同步改state、异步请求、状态控制等)都放在model里,用户操作要触发UI重新渲染只需要在组件内调用本身(Container Component)或父组件传递过来的封装了dispatch的方法(UI Component)即可,做到了UI渲染与数据的完全分离的同时,也做到了数据维护与管理也绝对的集中。

可能有些同学会好奇,dispatch数据更改state后,UI是如何同步更改渲染的呢?其实奥秘就在react-redux。react-redux将数据与组件做了连接,数据更改时会触发整个组件树的重新渲染,因为牵扯整个组件树的重新渲染,在性能上肯定是个问题,所以这也就是React使用Virtual DOM来做性能提升的原因。在组件树重新渲染的时候,会将最新的状态数据传递给各个组件,所以各个组件的渲染数据也就成了最新的数据。

吐血推荐上面提到的最佳实践,在开发效率大大提高的同时,代码的可维护性也会大大提高,同时,还能避免很多潜在的坑。

接下来可能会有很多同学对dva的实现比较好奇,后面我会再开一篇博客,一步步引导大家开发一个类dva的数据流管理框架,只是异步部分我们不再采用redux-saga,而是自己开发一个redux中间件来实现。

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

2 3 收藏 1 评论

关于作者:abell123

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部