目录
  • 1、redux
    • 1.1、store(图书馆管理员)
    • 1.2、state(书本)
    • 1.3、action(借书单)
    • 1.5、reducer(包装书本)

    前言:

    续上篇,没想到很多读者朋友们这么关注,感谢大家的支持和建议,我只是发表个人看法以及自己的一些思考也许不够全面,使用 vue 举例也仅仅只是作为引路且 vue 的关注度也是较高的。那有些朋友想听听除 vuex 的其他方案,今天将从 redux 入手逐渐拓展(如标题一样浅谈)。

    1、redux

    作为 react 全家桶的一员,redux 试图为 react 应用提供可预测化的状态管理机制。和大多数状态管理方案一样,redux 的思想也是发布订阅模式,我们还是以图书馆为例来简单了解一下 redux。

    redux 的基础操作大致为:

    • store(图书馆管理员)
    • state(书本)
    • action(借书单)
    • store.dispatch(提交借书单)
    • reducer(包装书本)
    • store.subscribe(接收书本)

    1.1、store(图书馆管理员)

    store 可以看作是一个容器,整个应用只有一个 store。就好比你想要借书只能找图书管理员。

    import { createstore } from 'redux'
    const store = createstore(reducer);
    
    

    1.2、state(书本)

    对于 state 来说他只能通过 action 来改变(既你借书只能提交借书单来借),不应该直接修改 state 里的值。

    使用store.getstate()可以得到state

    import { createstore } from 'redux'
    const store = createstore(reducer)
    store.getstate()
    
    

    1.3、action(借书单)

    你想借书咋办?那当然是向管理员提交借书单了。那用户是接触不到 state 的,只能通过 view (视图)去操作(如点击按钮等),也就是 state 的变化对应 view 的变化,就需要 view 提交一个 action 来通知 state 变化。(既通过提交借书单给管理员才会有接下来一系列的其他操作)

    action 是一个自定义对象,其中type属性是约定好将要执行的操作。

    const action = {
        type: 'click',
        info: '提交借书单'
    }
    
    

    1.4、store.dispatch (提交借书单)

    store.dispatch 是 view 发出 action 的唯一方法,他接受一个 action 对象(既提交借书单),只是把单的信息给了图书管理员,他在根据单子来搜索相应的书本。

    store.dispatch(action)
    
    

    1.5、reducer(包装书本)

    store 收到一个 action 后,必须给出一个新的 state ,这样 view 才会发生变化,而新的 state 的计算过程就是 reducer 来完成的。(既拿到单子将你的书本打包装袋等)

    reducer 是一个自定义函数,它接受 action 和当前的 state 作为参数,返回一个新的 state。

    const reducer = (state, action) => {
        return `action.info: ${state}` // => 提交借书单:红楼梦
    }
    
    store.subscribe(接收书本)
    当 state 一旦发生变化,那么 store.subscribe() 就会监听到自动执行更新 view。
    
    const unsubscribe = store.subscribe(() => {
      render() {
        // 更新view 
      }
    })
    // 也可以取消订阅(监听)
    unsubscribe()
    

    小结:

    相信刚接触 redux 的同学都会觉得 redux 比较繁琐,这也与他的思想有关:redux 里的一切应该都是确定的。

    尽管在 redux 里还是没办法做到一切都是确定的(如异步)但是应该保证大多数部分都是确定的包括:

    • 视图的渲染是可确定的
    • 状态的重建是可确定的

    至于为什么要这么做,上一篇我已有提及。他的重要之处在于:便于应用的测试,错误诊断和 bug 修复。

    2、状态管理的目的

    那其实大多数程序员使用 redux 的最多的场景无非是从 a 页面返回 b 页面 需要保存 b 页面的状态。

    倘若项目不大,用 redux 或 vuex 是不是会显得有些大?我们知道在 vue 中有提供 keep-alive 让我们缓存当前组件,这样就可以解决上述的场景。

    但是很遗憾在 react 中并没有像 vue 一样的 keep-alive。社区中的方案普遍是改造路由,但是这种改造对于项目入侵过大且不易维护,另外在 react-router v5 中也取消了路由钩子。于是,对小型项目来说自己封装一个函数也不失为良策。(当然你想用 redux 也没问题,咱们只是探索更多方式)

    还是用图书馆来举例子,现在有一个图书馆管理系统,你从列表页(list)跳入详情页(detail)需要保存列表页的状态(如搜索栏的状态等)。

    假设你使用的技术栈是(react + antd),来手写一个简单粗暴的(核心是利用context来进行跨组件数据传递):

    // keepalive.js
    export default function keepalivewrapper() {
      return function keepalive(wrappedcomponent) {
        return class keepalive extends wrappedcomponent { // ps
          constructor(props) {
            super(props)
            // do something ...
          }
    
          componentdidmount() {
            const {
              keepalive: { fieldsvalue },
            } = this.context
            // do something ...
            super.componentdidmount()
    
          }
    
          render() {
            // do something ...
            return super.render()
          }
        }
      }
    }
    
    

    这里提一下为什么要继承原组件(// ps)

    如果常规写法返回一个类组件(class keepalive extends react.component),那本质上就是父子组件嵌套,父子组件的生命周期都会按秩序执行,所以每当回到列表页获取状态时,会重复渲染两次,这是因为 hoc 返回的父组件调用了原组件的方法,到导致列表页请求两次,渲染两次。

    若使 hoc(高阶组件)继承自原组件,就不会生产两个生命周期交替执行,很好的解决这个问题。

    // main.jsx 根组件
    import react from 'react'
    
    const appcontext = react.createcontext()
    
    class app extends react.component {
        constructor(props) {
            super(props)
            this.state = {
              keepalive: {}, // 缓存对象
              iscache: false, // 是否缓存
              fieldsvalue: {} // 缓存表单值
            }
        }
        componentdidmount() {
            // 初始化
            const keepalive = {
              iscache: this.state.iscache,
              toggle: this.togglecache.bind(this),
              fieldsvalue: this.state.fieldsvalue,
            }
            this.setstate({ keepalive })
        }
        // 这里封装一个清除状态的方法 防止渲染警告(you can't set fields before render ...)
        // 比如 list1 => list1/detail => list2 需要将跳转放在以下回调中并清除状态
        togglecache(iscache = false, payload, callback) {
            const { fieldsvalue = null } = payload
            const keepalive = {
              iscache,
              fieldsvalue,
              toggle: this.togglecache.bind(this),
            }
            const fn = typeof callback === 'function' ? callback() : void 0
            this.setstate(
              {
                keepalive,
              },
              () => {
                fn
              }
            )
        }
        render() {
            const { keepalive } = this.state
            <appcontext.provider value={{ keepalive }}>
                // your routes...
            </appcontext.provider>
        }
    
    }
    
    

    至于为什么不直接使用 context,而多封装一层 keepalive,是为了统一处理 context,在组件头部中使用装饰器这种简洁的写法(@keepalive)你就立马知道这是一个有缓存的组件(方便阅读及维护)。

    // 在页面使用时
    import react from 'react'
    import keepalive from '../keepalive'
    
    // keepalive的位置需要放在原组件最近的地方 
    @keepalive()
    class app extends react.component {
        constructor(props){
            super(props)
            this.state = {
                // init something...
            }
        }
        componentdidmount() {
            // do something...
            if(this.context.keepalive.fieldsvalue) {
                const { tablelist } = this.context.keepalive.fieldsvalue
                console.log('缓存啦:',tablelist) // 缓存啦:['1', '2']
            }
        }
        // 查看详情
        detail = () => {
            this.context.keepalive.fieldsvalue = {
                tablelist: ['1', '2']
            }
            // jump...
        }
        // 当需要跨一级路由进行跳转时,如 list1 => list1/detail(下面这个方法应该在详情页里) => list2,此时需要处理一下警告
        tolist2 = () => {
            this.context.keepalive.toggle(false, {}, () => {
                // jump...
            })
        }
    }
    
    

    在上述使用了装饰器写法,简单说一下,需要先配置以下 babel 放可使用哦~

    npm install -d @babel/plugin-proposal-decorators

    jsconfig.json中(无则新建)配置一下:

    {
        "compileroptions": {
            "experimentaldecorators": true
        },
        "exclude": [
            "node_modules",
            "dist"
        ]
    }
    
    

    在 .babelrc 配置:

    {
        "plugins": [
            "@babel/plugin-proposal-decorators",
            {
                "legacy": true
            }
        ]
    }
    
    

    上面方法比较适用刚才说的场景(从 a 页面返回 b 页面 需要保存 b 页面的状态),有人的说,你这样还不如用 redux mobx 不就好了?跨路由跳转还得手动清除状态防止警告。。。仁者见仁,智者见智吧。自己封装了也说明自己有所研究,不论他易或难,编程本身不就该是不断探索吗,哈哈。尽管你写的可能不够好或是咋样,虚心接受批评就是了,毕竟厉害的人多着呢。

    总结:

    到此这篇关于前端的状态管理的文章就介绍到这了,更多相关前端的状态管理内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

    回顾上篇: