前端状态管理的Redux怎么理解,状态管理的目的是什么
Admin 2022-06-28 群英技术资讯 377 次浏览
作为 React
全家桶的一员,Redux
试图为 React
应用提供可预测化的状态管理机制。和大多数状态管理方案一样,Redux 的思想也是发布订阅模式,我们还是以图书馆为例来简单了解一下 Redux。
Redux 的基础操作大致为:
Store
(图书馆管理员)State
(书本)Action
(借书单)store.dispatch
(提交借书单)Reducer
(包装书本)store.subscribe
(接收书本)Store
可以看作是一个容器,整个应用只有一个 Store
。就好比你想要借书只能找图书管理员。
import { createStore } from 'redux' const store = createStore(reducer);
对于 State
来说他只能通过 Action
来改变(既你借书只能提交借书单来借),不应该直接修改 State
里的值。
使用store.getState()
可以得到state
。
import { createStore } from 'redux' const store = createStore(reducer) store.getState()
你想借书咋办?那当然是向管理员提交借书单了。那用户是接触不到 State
的,只能通过 View (视图)去操作(如点击按钮等),也就是 State
的变化对应 View
的变化,就需要 View 提交一个 Action
来通知 State
变化。(既通过提交借书单给管理员才会有接下来一系列的其他操作)
Action
是一个自定义对象,其中type属性是约定好将要执行的操作。
const action = { type: 'click', info: '提交借书单' }
store.dispatch
是 View 发出 Action 的唯一方法,他接受一个 Action
对象(既提交借书单),只是把单的信息给了图书管理员,他在根据单子来搜索相应的书本。
store.dispatch(action)
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
修复。
那其实大多数程序员使用 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
不就好了?跨路由跳转还得手动清除状态防止警告。。。仁者见仁,智者见智吧。自己封装了也说明自己有所研究,不论他易或难,编程本身不就该是不断探索吗,哈哈。尽管你写的可能不够好或是咋样,虚心接受批评就是了,毕竟厉害的人多着呢。
总结:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这些年来,ES6 将js的可用性提升到一个新的水平时: 箭头函数、类等等,这些都很棒。箭头函数是最有价值的新功能之一,有很多好文章描述了它的上下文透明性和简短的语法。
目录前言只读函数参数如何更改函数只读类属性只读索引总结前言在Typescript 2.0中,引入了readonly关键字,可以对类中的属性进行修饰,作用是:该属性被readonly修饰之后无法修改**(如需修改,在构造函数中可以对只读属性进行修改)**。我们可以直接在interface和type中直接使用readonl
很多时候我们需要用图表来制作我们统计的数据直观的分析,所以我们可以用Echarts来制作图表,这篇文章主要给大家介绍了关于Echats图表大屏自适应的实现方法,需要的朋友可以参考下
这篇文章主要为大家介绍了JavaScript之instanceof方法手写示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
js不会保护hasOwnProperty被非法占用,如果一个对象碰巧存在这个属性, 就需要使用外部的hasOwnProperty 函数来获取正确的结果。当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008