撸一个面向对象风格的redux
redux使用感受
各种优点就不说了,有两点觉得不爽的地方:
示例工程,文件夹太散。具体而言
action
,reduce
,store
,三者的对应关系很固定,分在三个文件夹下,实在太散。redux
的源码使用了大量的函数式
的pattern。本人不是很习惯(好像暴露了-_——!)
所以,我就撸了一个面向对象风格的简易版,代码数少到不行,代码在github上,(还附带了react-redux
的connect
的简易版,不过方面的代码注释掉了,因为这是为了配合react的功能,而redux的设计有独立于react的初衷。
在本人的一个demo项目中,使用下来,感觉还行。demo地址 样式上主要适配手机,pc上也能看,另外控制台有列表页加载数据的log,如下图。demo的代码暂时不开放,需要整理。
store的简单实现(无中间件功能,后面会有的)
action
,reduce
必须要使用者提供.同redux
一样,要求每次reduce
返回全新的state
对象
dispatch
的功能相对固定,另外提供一个subscribe
方法,用来注册监听器,dispatch
时,通知所有监听者。
根据这些要求,写一个BaseStore。用户需要继承BaseStore,提供reduce
函数
- export default class BaseStore{
-
- listeners=[]
-
- dispatch(action){
- this.state = this.reduce(action);
- this.listeners.forEach(listen=>{
- listen(action,this.getState())
- })
- }
-
- subscribe(listener){
- var index = this.listeners.length
- this.listeners.push(listener);
-
- return (index)=>{
- return ()=>{
- this.listeners.splice(index);
- }
- }(index)
- }
-
-
- reduce(action){
- throw new Error('subClass fo BaseStore should implement reducer function')
- }
-
- getState(){
- return this.state;
- }
-
- }
-
使用的时候,只需继承BaseStore
,提供reduce
函数就行
- import Immutable from 'immutable';
- import {BaseStore} from 'zlux'
-
- const ActionTypes={
- ADD:'ADD',
- DELETE_BY_ID:'DELETE_BY_ID',
- UPDATE:'UPDATE'
- }
-
- export default class SimpleStore extends BaseStore{
- __className ='PostListStore'
-
- state = new Immutable.List()
-
- reduce(action){
- if(action.type === ActionTypes.ADD){
- return state.push(action.payLoad)
- }
- if(action.type === ActionTypes.DELETE_BY_ID){
- let id = action.payLoad.id;
- return state.filter(item=>{return item.id !==id})
- }
- if(action.type == ActionTypes.UPDATE){
- var id = action.payLoad.id;
- var index = state.findIndex(item=>{return item.id == id});
- //if index == -1, 这里不考虑update时,没有相应item的情况
- return state.set(index,action.payLoad)
- }
-
- return state; //注意:默认返回原state
- }
-
- //提供方便外接调用的方法
- add(payLoad){
- this.dispatch({
- type:ActionTypes.ADD,
- payLoad
- })
- }
-
- deleteById(payLoad){
- this.dispatch({
- type:ActionTypes.DELETE_BY_ID,
- payLoad
- })
- }
-
- update(payLoad){
- this.dispatch({
- type:ActionTypes.UPDATE,
- payLoad
- })
- }
- }
然后像这样使用:
-
- var ss = new SimpleStore()
- ss.add({id:1,content:'hello'})
- ss.update({id:1,content:'world'})
- ss.deleteById({id:1})
每次调用dispatch,ss.getState()
都会返回最新的state。因为使用了immutable
的list,确保每次的state
都是全新的。
中间件
关于redux的中间件的来龙去脉,官方文档已经说得不能在详细,文档地址
这里实现中间件,故意做的和express
中的用法很像。如下:
- simpleStore.use(
- function(next,action,store){
- console.log('before')
- next()
- console.log('after')
- },
- function(next){
- if(isLogin){
- next()
- }
- else{
- goToLogin();
- }
- }
- )
稍微有些区别,
需要用中间件,要一次性的传个
use
函数,多次使用use
,后面的会覆盖前面的。中间件函数中,参数只有next是必须的。
action
和store
都是自动注入的。需要用就写上,不需要用,就不用管。-
暂时没使用error first的模式。个人认为
state
中完全可以体现错误信息。中间件实现的核心代码如下:
- use(...fns){
- this.middlewareFns = fns;
- var _this =this;
- this.wrappedDispatch= this.middlewareFns.reduceRight((a,b)=>{
- return ()=>{
- b(a,_this.__curAction,_this)
- }
- },this.__dispatch)//__dispatch是原始的dispatch实现。
- }
- //改写上面的dispatch实现。
- dispatch(action){
- this.__curAction = action
- this.wrappedDispatch();
- }
简易connect
redux
的实现都是不依赖于react,只要合适,任何环境下都能使用。为了更好的和react
配合使用,redux官方还提供了 react-redux
.
react-redux
里的connect
,帮助Redux store
(由Provider
传入的合并过的store)的中状态、方法和container
进行绑定。
react-redux
要求整个应用只有一个redux store
,是由多个单纯store(使用单纯store
来区分redux store
)是合并而成。container
可以对应多个单纯store。
使用者(也就是你)选取Redux store
中的需要的state
,dispatch
, 交由connect
去绑定到react组件的props中。
指定了哪些 Store/State
属性被映射到 React Component
的 props
,这个过程被称为 selector
.
如果非常在意性能,避免不必要的计算,还需要通过reselect
这样的库。
而我这里要求单纯的store和container是一对多的关系。如果一个container
需要多个store,那么通过拆分container,而不是合并store。
这样就要求container
是最小的页面构成单位,应该做到原子化。
这样,container是否需要通过setState()
来render组件,只要比较对应
的单纯store
的state,是不是同一个(还记得吗,任何的状态改变,都返回全新的state,所以这个判断非常快)
只所以这样要求,是因为好实现(找了那么多借口,最后暴露了-_——!)。当然我好实现,使用者就得多写点代码,但是结构上,我个人觉得更清晰点。
不过因为我还没写个特别大型的项目,不知道拆分container
而不是合并store,是不是能满足复杂应用的开发。
- import {Component} from 'react';
- import getStoreShape from './getStoreShape.js'
-
- export default (WrappedContainer,store) => {
- return class extends Component{
-
- state={}
-
- constructor(props,context){
- super(props,context)
-
- this.state.props = store.getState();
-
- this.unsubscribe = store.subscribe(()=>{
- //直接判断state的引用是否变化,shouldComponentUpdate都不需要了
- if(this.state.props == store.getState()){return ;}
- //这里的props随便取的名字,没有意义
- //setState只是用于通知重新渲染。
- this.setState({
- props:store.getState()
- })
- })
- }
-
- componentWillUnmount(){
- this.unsubscribe();
- }
-
- //context机制,TODO test case
- static childContextTypes={
- store:getStoreShape
- }
-
- getChildContext() {
- return { store: this.store };
- }
-
- getWrappedInstance() {
- return this.refs.wrappedInstance;
- }
-
- render(){
- return(
- <WrappedContainer ref='wrappedInstance' {...this.props} store={store} />
- )
- }
- }
- }
整体是,就是一个 high order component
在constructor
里,注册了对store
的监听。这是一个单纯store,直接比较state是否变化,就知道要不要从新render。
使用的话,贴点代码:
- //postListStore 是已经实例化的单纯store,可以被多个container公用。
- const PostListElement = enhanceWithStore(PostListContainer,postListStore)
- const PostDetailElement = enhanceWithStore(PostDetailContainer,postDetailStore)
- //...
- //使用react-router
- React.render(
- (
- <Router>
- <Route path="/" component={App}>
- <IndexRoute component={PostListElement}
- onEnter={()=>{ utils.Scroll.restoreScroll('PostList') }}
- onLeave={()=>{ utils.Scroll.saveScroll('PostList') }} />
-
- <Route path="posts/:postId" component={PostDetailElement} />
- </Route>
- </Router>
- ),
- document.getElementById('mount-dom')
- )
其他
核心的功能就这些。
真正serious的实际项目,大家还是用用redux
吧,配套齐全。
自撸的项目,自己先踩踩坑~。