redux-spring 是什么?

redux-spring 是一个专为 react + redux 应用程序进行的二次封装库, 解决了基于原生开发redux 遇到的各种问题。 同时提供新的开发理念。 具有以下四大特色:

  1. 模块化
  2. 面向对象
  3. 依赖注入
  4. 完美异步解决方案

模块化

我们知道 基于原生redux开发, 一个简单dispatch操作中至少需要定义 reducer, action, state, 还有常量,且分别定义在不同文件中, 并且在页面引入的时候 还需要定义mapStateToProps/mapActionToProps 等方法, 总之实现一个dispatch需要编写大量与业务无关的代码, 可读性也不友好, 并且经常要在需要在不同文件来回切换 非常繁琐,导致开发成本直线上升。

基于原生redux开发遇到的种种问题,redux-spring 引入模块概念, 把之前state,reducer,action 整合到一起,定义为一个model, 通过一系列封装,以及引入新的开发理念, 定义一个模块并不会因为整合了所有功能模块而导致代码量急剧上升导致可读性变差。

面向对象

面向对象带来的好处不言而喻,网上有大量介绍。 总的来说, 在数据层面上,更适合基于面向对象开发,展示层面用方法组件而不是类组件, 再配合react hooks新特性,二者可以完美融合。 我们可以把页面展示和数据流处理剥离开来, 甚至前端开发可以进一步拆分: “静态页面” 与 “数据处理” 一套数据流处理可以同时应用到多场合,比如pc/h5/小程序/react-native。 此外, 完美支持typescript,更方便提供api文档, 不用再为原生redux开发找不到对应action/reducer而烦恼。

依赖注入

react-spring 基本理念参考了后端java 中spring框架, DI(依赖注入)核心思想。 所有model都是单实例的,统一由框架创建与维护, 模块之间可以相互依赖, 由react-spring自动注入,用户只需通过注解标注类型即可,这样模块之间数据共享就变得特别简单。

异步操作

异步操作在开发过程中特别常见, 基本上所有主流库都有不错的支持, 为什么称完美, 肯定有自己的一套特殊的解决方案, 当遇到多个顺序异步操作, 而且异步操作之间有数据修改的情况下可以把修改的数据同步到页面中,而不需要做额外的操作,使得能够和面向对象思想完美融合。

基本原理介绍

  1. store 下面的state中保存了项目中所有子模块实例, 每个子模块代表所定义模块的一个实例;
  2. 每个子模块下有独立的原型对象, 意味着可以直接在模块中调用相应的原型方法;
  3. 原型方法对应着定义模块类中的类方法, 初始化的时候会对类方法进行进一步加工处理,劫持里面的this对象指向state下面的子模块实例。

开始使用redux-spring

安装

yarn add redux-spring # npm install --save redux-spring

从一个简单demo开始

实现一个简单小需求, 从后端接口获取一个随机数,展示在页面中, 页面有一个按钮,点击给获取的随机数+1

1. 初始化

  • 在项目初始化位置
import spring from 'redux-spring';
import store from './store'; // redux.createStore() 返回来的实例
spring(store);

  1. 此处目的是给redux-spring注入store,底层很多操作都是基于store这个实例进行操作的;
  2. 注意 初始化逻辑必须在导入模块之前, 否则导入模块的时候会报错;
  3. 如果要和老项目无缝集成可以传入第二个参数
spring(store, asycReudcers); // asyncReducers 是老项目中的所有reducer(数组类型) 集合

2. 定义模块类

import {service} from 'redux-spring';
function ajax() { // 模拟ajax请求return new Promise((resolve) => {setTimeout(() => {resolve(parseInt(Math.random() * 10, 10));}, 16);});
}@service('home')
class HomeModel extends Model {num = 0;* init() { // init 对外暴露的是一个promis方法this.num = yield ajax(); // yield 后面可以跟 promise}add() {this.num ++;}// created() { // 如果定义了created方法,此方法在模块加载的时候会自动执行////}
}
export default HomeModel;

  1. @service('home') 定义一个模块, 每个模块必须添加此注解, 其中home 是自己给模块取的名称, 如果不想取名,也可直接用module.id, 比如@service(module.id);
  2. redux-spring 大量依赖注解语法, 老版本babel需要配置相应插件;
  3. Model 是个接口, 主要是给model实例和类提供接口api和属性, Model定义可以参考API说明;
  4. init() 是一个异步方法,在redux-spring中异步方法都是基于 generator语法, 不推荐用async/await语法, generator和async/await使用方式一模一样;
  5. add() 是定义的普通类方法;
  6. num 类属性,所有类属性最终都会保存在redux的state中;
  7. 注意 不管是普通方法,还是异步方法, 都不能定义为箭头方法, 否则会由于找不到this中的属性而报错;
  8. 注意 保留字 setData,reset, ns,created不能用于自定义方法、属性名。

3. 在页面中使用 model

页面引入model同时支持类组件和方法组件,使用方法如下:

  • 使用react-hooks 写法
import React, {useEffect} from 'react';
import {useModel} from 'redux-spring';
import style from './style.less';
import HomeModel from '../../models/HomeModel';export default () => {const model = useModel(HomeModel);const {num,} = model;useEffect(() => {model.init();}, []);return (<div className={style.container}><div className={style.content}><div className={style.addOne} onClick={model.add}>+1</div><div className={style.txt}>{num}</div></div></div>);
};

  • 说明
  1. model 中包含模块中定义的数据和方法;
  2. 获取model 实例通过 useModel方法 ,传入Model类即可;
  3. model中所有方法已经绑定过this了, 可以单独拿出来直接调用;
  • 使用类组件
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import style from './style.less';import HomeModel from '../../models/HomeModel';class Home extends Component {render() {const {model,} = this.props;const {num, add} = model;return (<div className={style.container}><div className={style.content}><div className={style.addOne} onClick={add}>+1</div><div className={style.txt}>{num}</div></div></div>);}
}Home.propTypes = {model: PropTypes.instanceOf(HomeModel).isRequired,
};export default connect(state => ({model: state[HomeModel.ns]}))(Home);

  • 说明
  1. 所有model都挂载在store的根state下;
  2. HomeModel.ns 模块定义的名称, 等同于@service('home') 中的'home';
  3. 声明组件属性类型的时候建议用 PropTypes.instanceOf(类名);
  4. 类组件model注入需要依赖 react-redux模块的Provider,而hooks写法不需要;

高级用法

1. 依赖注入(DI)

以上案例基本上可以满足绝大部分业务需求, 但是有时候我们定义了多个model, model之间需要有数据共享, 在redux-spring 引入了依赖注入(Dependency Inject), 模块之间可以相互依赖, 我们不需要手动去注入, 框架会根据配置自动注入进来。举个例子,还是在上面的案例中, HomeModel 依赖另外 一个UserModel, UserModel 中定义了name 属性, HomeModel 初始化后拿到UserModel中的name,并展示在页面中

import {Model, service} from 'redux-spring';@service('usermodel')
class UserModel extends Model {name = 'hello user';
}
export default UserModel;

  • 此处定义了UserModel, 里面有name 属性
@service('home')
//@service(module.id) // 也可以直接使用模块标识
class HomeModel extends Model {num = 0;username;/*** 声明user类型* @type {UserModel}*/@inject(UserModel) user;* init() {this.num = yield ajax();this.username = this.user.name;}add() {this.num ++;}
}
export default HomeModel;

  • 说明
  1. @inject(UserModel),给属性注入UserModel 实例
  2. 注入的实例,类方法中可以获取实例属性, 也可以调用注入实例的方法, 但是不能直接修改实例的属性, 只能通过setData方法或者类方法去设置;
  3. 被注入的属性前面建议加上jsDoc注释,表明属性类型,方便后续使用实例属性和方法;
  4. 页面中尽量不要直接引用被注入的属性,否则可能出现数据不同步的情况。注入的属性主要为了解决类方法获取其他模块中数据功能。

最后在页面中展示数据

import React, {useEffect} from 'react';
import {useModel} from 'redux-spring';
import style from './style.less';
import HomeModel from '../../models/HomeModel';export default () => {const model = useModel(HomeModel);const {num,username} = model;useEffect(() => {model.init();}, []);// model.user.name 可以读取, 但是不建议这样使用,否则可能导致数据不同步。如果不了解底层原理, 建议不要使用,哈哈return (<div className={style.container}><div className={style.content}><div className={style.addOne} onClick={model.add}>+1</div><div className={style.txt}>{num}</div><div className={style.txt}>{username}</div></div></div>);
};

  • 注意: 页面中可以直接使用model注入的user,绝大多数情况下没问题, 如果遇到其他模块修改UserModel中的数据, 会导致当前组件中的数据不能及时同步。

2. 初始化方法

有时候会遇到这种场景, 模块加载的时候进行一些初始化操作, 初始化操作可以定义created方法来实现

@service(module.id)
class CreatedModel extends Model {num = 0;constructor() { // 构造方法只能初始化变量this.num = 1;// this.ajaxGet()// 不能调用模块中的方法}ajaxGet() {}   created() { // 如果定义了created方法,此方法在模块加载的时候会自动执行thia.ajaxGet() // 此方法中可以调用模块中的方法进行初始化}
}
export default CreatedModel;

  • 最佳实践是在模块类中定义init方法,然后放入组件的 React.useEffect方法中调用。

3. 快捷的操作model数据

有时候页面中需要修改model中的数据, 如果只是修改少量数据,新定义一个方法会大大增加业务代码量, 可以使用 model.setData(params)方法 params是一个普通对象, key是要修改的属性名, value是修改后的值。

export default () => {const model = useModel(HomeModel);const {num,username} = model;useEffect(() => {model.init();}, []);// model.user.name 可以读取, 但是不建议这样使用,否则可能导致数据不同步。return (<div className={style.container}><div className={style.content}><div className={style.addOne} onClick={() => model.setData({num: num + 1})}>+1</div><div className={style.txt}>{num}</div><div className={style.txt}>{username}</div></div></div>);
};

  • 用 model.setData({num: num + 1}) 取代 model.add 方法, 可以减少许多代码量, 但是每次页面渲染都会生成一个新方法, 可能对性能优化不是很友好, 具体取舍看业务场景吧!

4. 重置model中的所有数据到初始值

组件销毁的时候, 我们要清空现有的数据, 我们可以调用 model.reset;

export default () => {const model = useModel(HomeModel);const {num,username} = model;useEffect(() => {model.init();return model.reset; // 当前组件销毁的时候会调用 model.reset() 方法}, []);// model.user.name 可以读取, 但是不建议这样使用,否则可能导致数据不同步。return (<div className={style.container}><div className={style.content}><div className={style.addOne} onClick={() => model.setData({num: num + 1})}>+1</div><div className={style.txt}>{num}</div><div className={style.txt}>{username}</div></div></div>);
};

5. 异步方法回调不能直接通过this 修改类属性

有两种解决方案, 一种是直接通过setData 方法修改属性, 另外一种是通过初始化promise实例方案, 建议采用后者;

  • 直接修改
@service(module.id)
class HomeModel extends Model {num = 0;getDataByJQuery() {$.ajax({url: 'xxx',method: 'get',success: (resp) => {// this.num = resp; 直接修改num属性不生效this.setData({num: resp}) // 可以通过直接调用setData方法来更新数据}})}add() {this.num ++;}
}
export default HomeModel;

  • 初始化promise实例
@service(module.id)
class HomeModel extends Model {num = 0;* getDataByJQuery() {this.num = yield new Promise((resolve) => {$.ajax({url: 'xxx',method: 'get',success: (resp) => {resolve(resp);}});});}add() {this.num ++;}
}
export default HomeModel;

最佳实践

1. 应用场景

redux-spring 非常适用于具有复杂交互逻辑的页面/组件, 或者页面之间有数据交互/共享等场景 不适用于循环列表项中的复杂组件

2. ui展示层与数据分离

页面展示和数据可以进一步拆分, 页面中不包含任何逻辑处理, 数据层完全基于model 以面向对象的方式进行开发, 对外提供api接口和数据文档,并且一份model可以适配多平台,比如同时适配移动端h5 和pc端页面, 多人协作的时候, 可以ui 和数据完全交给不同人负责,高效完成需求, 同时可以保证代码风格统一。

自己构建

如果需要定制api GitHub 上克隆代码并自己构建。

git clone https://github.com/sampsonli/redux-spring node_modules/redux-spring cd node_modules/redux-spring npm install npm run build

参考项目

一个整合最新react17+webpack5通用模板项目react_template_project

spring 数组中随机取几个_最新redux-spring前端模块化框架相关推荐

  1. spring 数组中随机取几个_游戏编程中需要掌握哪些数学物理知识

    一. 相似三角形知识的应用 在摇杆控制物体运动的游戏中,摇杆的手柄(下图黄色圆饼),不能移出摇杆所在的套(下图灰色圆环),也就是说摇杆偏离中心点的最大距离为max_R.一旦触摸移动过程中移动的点超出此 ...

  2. spring 数组中随机取几个_准备几个月,面试阿里耗时两周,最终凭借这些知识拿下阿里offer...

    朋友去阿里进行的技术面,历经了二个多小时,面试的是Java开发工程师,出来后立马拿手机记录了面试问的知识点,知识点包括Java基础和高级.spring.多线程.网络.数据库.算法.Redis.分布式. ...

  3. spring 数组中随机取几个_美团Java研发三面(3年经验):MySQL+Spring源码+分布式+算法+线程...

    虽然自己记性不太好,但还是记录了一下,热乎乎的面经啊,也有一些问题没能记住.三面技术面经如下: 美团一面: 中间省略掉大概几个问题,因为我不记得了,下面记得的基本都是我没怎么答好的. 了解SOA,微服 ...

  4. spring 数组中随机取几个_别给孩子取这三种名字!截止年末,名字中的这几个字已经烂大街了...

    随着时代的进步,科技的发展,人们的生活水平不断,提高,许多的家庭都在拥有了不错的生活条件之后选择培养自己的下一代,随着宝宝的到来让整个家庭变得更加幸福,然而,许多家长却在给宝宝起名字这件事上放了愁,对 ...

  5. 从数组中随机选择一条

    /**从数组中随机取一个数据出来*/ function getRandom(arr) {var len = arr.length;var i = Math.ceil(Math.random() * ( ...

  6. python权重是什么意思_在python带权重的列表中随机取值的方法

    1 random.choice python random模块的choice方法随机选择某个元素 foo = ['a', 'b', 'c', 'd', 'e'] from random import ...

  7. 汇编语言-016(SCASB 、STOSB 、LODSD 、数组中的取值、二维数组操作、冒泡排序和二分查找、CMPSW )

    1: SCASB : (字节)将AL的值与EDI寻址的一个字比较.进行在一个字符串检索特定的字符 .386 .model flat,stdcall.stack 4096 ExitProcess PRO ...

  8. PHP取出数组中随机一条字符串

    PHP取出数组中随机一条字符串 在接微信支付的时候用到的,随机返回一条字符串作为支付的标题,防止微信支付安全检测. 只是自己简单记录一下,方便下次使用. /*** 取数组中随机一条为支付标题*/fun ...

  9. js 从一个数组中随机抽取元素

    需求 从长度为20的数组中随机取出3个元素,且不能重复 如果使用生成随机数取值的方法 可能会取到重复值 因为随机数可能重复 如果每次生成随机数都记录下来,再取的时候判断是否重复又太过麻烦 可以使用随机 ...

  10. php判断数组不重复的元素,php从数组中随机选择若干不重复元素

    php从数组中随机选择若干唯一元素 /* * $array = the array to be filtered * $total = the maximum number of items to r ...

最新文章

  1. OpenCV中的霍夫线变换、概率霍夫线变换
  2. mybatis trim标签_MyBatis学习笔记
  3. 《Netty 实战》Netty In Action中文版 第2章——你的第一款Netty应用程序(一)
  4. 云服务器网站不能够上传视频,网站的视频要存到云服务器上吗
  5. Kruskal Prim模板
  6. Koa / Co / Bluebird or Q / Generators / Promises / Thunks 的相互关系
  7. treeview调用数据库成树
  8. 【LeetCode笔记】112 113. 路径总和 I II(Java、递归、DFS)
  9. HDU 5400 Arithmetic Sequence
  10. 【Java_Spring】控制反转IOC(Inversion of Control)
  11. idea中 Application Server not specified
  12. 2021美赛MCM选题
  13. B-树、B+树、B*树
  14. 装逼神器,逼真黑客范儿
  15. 百度大脑营业执照识别使用攻略
  16. 教你怎么召唤百度贴吧繁体字ID
  17. 【mysql】 踩坑记录之derived(派生表)
  18. 【PHP】 解决报错:Error: php71w-common conflicts with php-common-5.4.16-43.el7_4.x86_64
  19. 线性代数笔记3.2向量间的线性关系(二)
  20. 安装office2007安装程序找不到office.zh-cn\*三种详细解决方法

热门文章

  1. hibernate CascadeType属性说明
  2. 32、[源码]-AOP原理-创建AOP代理
  3. DRF基类APIView的子类GenericAPIView
  4. c#:无限极树形结构
  5. WebFrom 【文件上传】
  6. 浅谈CDN技术的性能与优势
  7. 4_2 刽子手游戏(UVa489)自顶向下逐步求精法
  8. Jmeter参数化HTTP request中Send Files With The Request的文件路径和文件名
  9. petshop4.0 详解之五(PetShop之业务逻辑层设计)(转帖)
  10. Security+ 学习笔记25 硬件与数据安全