当前位置:   article > 正文

#自撸一个面向对象风格的redux

自撸

撸一个面向对象风格的redux

redux使用感受

各种优点就不说了,有两点觉得不爽的地方:

  1. 示例工程,文件夹太散。具体而言action,reduce,store,三者的对应关系很固定,分在三个文件夹下,实在太散。

  2. redux的源码使用了大量的函数式的pattern。本人不是很习惯(好像暴露了-_——!)

所以,我就撸了一个面向对象风格的简易版,代码数少到不行,代码在github上,(还附带了react-reduxconnect的简易版,不过方面的代码注释掉了,因为这是为了配合react的功能,而redux的设计有独立于react的初衷。

在本人的一个demo项目中,使用下来,感觉还行。demo地址 样式上主要适配手机,pc上也能看,另外控制台有列表页加载数据的log,如下图。demo的代码暂时不开放,需要整理。
screen for console

store的简单实现(无中间件功能,后面会有的)

actionreduce必须要使用者提供.同redux一样,要求每次reduce返回全新的state对象

dispatch的功能相对固定,另外提供一个subscribe方法,用来注册监听器,dispatch时,通知所有监听者。

根据这些要求,写一个BaseStore。用户需要继承BaseStore,提供reduce函数

  1. export default class BaseStore{
  2. listeners=[]
  3. dispatch(action){
  4. this.state = this.reduce(action);
  5. this.listeners.forEach(listen=>{
  6. listen(action,this.getState())
  7. })
  8. }
  9. subscribe(listener){
  10. var index = this.listeners.length
  11. this.listeners.push(listener);
  12. return (index)=>{
  13. return ()=>{
  14. this.listeners.splice(index);
  15. }
  16. }(index)
  17. }
  18. reduce(action){
  19. throw new Error('subClass fo BaseStore should implement reducer function')
  20. }
  21. getState(){
  22. return this.state;
  23. }
  24. }

使用的时候,只需继承BaseStore,提供reduce函数就行

  1. import Immutable from 'immutable';
  2. import {BaseStore} from 'zlux'
  3. const ActionTypes={
  4. ADD:'ADD',
  5. DELETE_BY_ID:'DELETE_BY_ID',
  6. UPDATE:'UPDATE'
  7. }
  8. export default class SimpleStore extends BaseStore{
  9. __className ='PostListStore'
  10. state = new Immutable.List()
  11. reduce(action){
  12. if(action.type === ActionTypes.ADD){
  13. return state.push(action.payLoad)
  14. }
  15. if(action.type === ActionTypes.DELETE_BY_ID){
  16. let id = action.payLoad.id;
  17. return state.filter(item=>{return item.id !==id})
  18. }
  19. if(action.type == ActionTypes.UPDATE){
  20. var id = action.payLoad.id;
  21. var index = state.findIndex(item=>{return item.id == id});
  22. //if index == -1, 这里不考虑update时,没有相应item的情况
  23. return state.set(index,action.payLoad)
  24. }
  25. return state; //注意:默认返回原state
  26. }
  27. //提供方便外接调用的方法
  28. add(payLoad){
  29. this.dispatch({
  30. type:ActionTypes.ADD,
  31. payLoad
  32. })
  33. }
  34. deleteById(payLoad){
  35. this.dispatch({
  36. type:ActionTypes.DELETE_BY_ID,
  37. payLoad
  38. })
  39. }
  40. update(payLoad){
  41. this.dispatch({
  42. type:ActionTypes.UPDATE,
  43. payLoad
  44. })
  45. }
  46. }

然后像这样使用:

  1. var ss = new SimpleStore()
  2. ss.add({id:1,content:'hello'})
  3. ss.update({id:1,content:'world'})
  4. ss.deleteById({id:1})

每次调用dispatch,ss.getState()都会返回最新的state。因为使用了immutable的list,确保每次的state都是全新的。

中间件

关于redux的中间件的来龙去脉,官方文档已经说得不能在详细,文档地址

这里实现中间件,故意做的和express中的用法很像。如下:

  1. simpleStore.use(
  2. function(next,action,store){
  3. console.log('before')
  4. next()
  5. console.log('after')
  6. },
  7. function(next){
  8. if(isLogin){
  9. next()
  10. }
  11. else{
  12. goToLogin();
  13. }
  14. }
  15. )

稍微有些区别,

  1. 需要用中间件,要一次性的传个use函数,多次使用use,后面的会覆盖前面的。

  2. 中间件函数中,参数只有next是必须的。actionstore 都是自动注入的。需要用就写上,不需要用,就不用管。

  3. 暂时没使用error first的模式。个人认为state中完全可以体现错误信息。

    中间件实现的核心代码如下:

    1. use(...fns){
    2. this.middlewareFns = fns;
    3. var _this =this;
    4. this.wrappedDispatch= this.middlewareFns.reduceRight((a,b)=>{
    5. return ()=>{
    6. b(a,_this.__curAction,_this)
    7. }
    8. },this.__dispatch)//__dispatch是原始的dispatch实现。
    9. }
    10. //改写上面的dispatch实现。
    11. dispatch(action){
    12. this.__curAction = action
    13. this.wrappedDispatch();
    14. }

简易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中的需要的statedispatch, 交由connect去绑定到react组件的props中。
指定了哪些 Store/State 属性被映射到 React Componentprops,这个过程被称为 selector.
如果非常在意性能,避免不必要的计算,还需要通过reselect这样的库。

而我这里要求单纯的store和container是一对多的关系。如果一个container需要多个store,那么通过拆分container,而不是合并store。
这样就要求container是最小的页面构成单位,应该做到原子化。
这样,container是否需要通过setState()来render组件,只要比较对应单纯store的state,是不是同一个(还记得吗,任何的状态改变,都返回全新的state,所以这个判断非常快)
只所以这样要求,是因为好实现(找了那么多借口,最后暴露了-_——!)。当然我好实现,使用者就得多写点代码,但是结构上,我个人觉得更清晰点。

不过因为我还没写个特别大型的项目,不知道拆分container而不是合并store,是不是能满足复杂应用的开发。

  1. import {Component} from 'react';
  2. import getStoreShape from './getStoreShape.js'
  3. export default (WrappedContainer,store) => {
  4. return class extends Component{
  5. state={}
  6. constructor(props,context){
  7. super(props,context)
  8. this.state.props = store.getState();
  9. this.unsubscribe = store.subscribe(()=>{
  10. //直接判断state的引用是否变化,shouldComponentUpdate都不需要了
  11. if(this.state.props == store.getState()){return ;}
  12. //这里的props随便取的名字,没有意义
  13. //setState只是用于通知重新渲染。
  14. this.setState({
  15. props:store.getState()
  16. })
  17. })
  18. }
  19. componentWillUnmount(){
  20. this.unsubscribe();
  21. }
  22. //context机制,TODO test case
  23. static childContextTypes={
  24. store:getStoreShape
  25. }
  26. getChildContext() {
  27. return { store: this.store };
  28. }
  29. getWrappedInstance() {
  30. return this.refs.wrappedInstance;
  31. }
  32. render(){
  33. return(
  34. <WrappedContainer ref='wrappedInstance' {...this.props} store={store} />
  35. )
  36. }
  37. }
  38. }

整体是,就是一个 high order component

constructor里,注册了对store的监听。这是一个单纯store,直接比较state是否变化,就知道要不要从新render。

使用的话,贴点代码:

  1. //postListStore 是已经实例化的单纯store,可以被多个container公用。
  2. const PostListElement = enhanceWithStore(PostListContainer,postListStore)
  3. const PostDetailElement = enhanceWithStore(PostDetailContainer,postDetailStore)
  4. //...
  5. //使用react-router
  6. React.render(
  7. (
  8. <Router>
  9. <Route path="/" component={App}>
  10. <IndexRoute component={PostListElement}
  11. onEnter={()=>{ utils.Scroll.restoreScroll('PostList') }}
  12. onLeave={()=>{ utils.Scroll.saveScroll('PostList') }} />
  13. <Route path="posts/:postId" component={PostDetailElement} />
  14. </Route>
  15. </Router>
  16. ),
  17. document.getElementById('mount-dom')
  18. )

其他

核心的功能就这些。
真正serious的实际项目,大家还是用用redux吧,配套齐全。
自撸的项目,自己先踩踩坑~。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/306379
推荐阅读
相关标签
  

闽ICP备14008679号