状态管理入门:说清 Redux 和 mbox

Redux

理解 Redux 的三个基本概念 、Action 和 Reducer。

  • State 即 Store,一般就是一个纯 JavaScript Object。
  • Action 也是一个 Object,用于描述发生的动作。
  • Reducer 则是一个函数,接收 Action 和 State 并作为参数,通过计算得到新的 Store。

比如说要实现"加一"和"减一"这两个功能,对于 Redux 来说,我们需要如下代码:

js 复制代码
import { createStore } from 'redux'

// 定义 Store 的初始值
const initialState = { value: 0 }

// Reducer,处理 Action 返回新的 State
function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

// 利用 Redux API 创建一个 Store,参数就是 Reducer
const store = createStore(counterReducer)

// Store 提供了 subscribe 用于监听数据变化
store.subscribe(() => console.log(store.getState()))

// 计数器加 1,用 Store 的 dispatch 方法分发一个 Action,由 Reducer 处理
const incrementAction = { type: 'counter/incremented' };
store.dispatch(incrementAction);
// 监听函数输出:{value: 1}

// 计数器减 1
const decrementAction = { type: 'counter/decremented' };
store.dispatch(decrementAction)
// 监听函数输出:{value: 0}

通过这段代码,我们就用三个步骤完成了一个完整的 Redux 的逻辑:

  • 先创建 Store;
  • 再利用 Action 和 Reducer 修改 Store;
  • 最后利用 subscribe 监听 Store 的变化。

在 react-redux 的实现中,为了确保需要绑定的组件能够访问到全局唯一的 Redux Store,利用了 React 的 Context 机制去存放 Store 的信息。通常我们会将这个 Context 作为整个 React 应用程序的根节点

js 复制代码
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
const rootElement = document.getElementById('root')
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)

仍然以官方给的计数器例子为例,在 React 中使用 Redux:

js 复制代码
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'

export function Counter() {
  // 从 state 中获取当前的计数值
  const count = useSelector(state => state.value)

  // 获得当前 store 的 dispatch 方法
  const dispatch = useDispatch()

  // 在按钮的 click 时间中去分发 action 来修改 store
  return (
    <div>
      <button
        onClick={() => dispatch({ type: 'counter/incremented' })}
      >+</button>
      <span>{count}</span>
      <button
        onClick={() => dispatch({ type: 'counter/decremented' })}
      >-</button>
    </div>
  )
}

middleware 可以让你提供一个拦截器在 reducer 处理 action 之前被调用

假设我们在创建 Redux Store 时指定了 redux-thunk 这个中间件

js 复制代码
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from './reducer'

const composedEnhancer = applyMiddleware(thunkMiddleware)
const store = createStore(rootReducer, composedEnhancer)

那么在我们 dispatch action 时就可以 dispatch 一个函数用于来发送请求,通常,我们会写成如下的结构:

js 复制代码
function fetchData() {
  return dispatch => {
    dispatch({ type: 'FETCH_DATA_BEGIN' });
    fetch('/some-url').then(res => {
      dispatch({ type: 'FETCH_DATA_SUCCESS', data: res });
    }).catch(err => {
      dispatch({ type: 'FETCH_DATA_FAILURE', error: err });
    })
  }
}

那么在我们 dispatch action 时就可以 dispatch 一个函数用于来发送请求,通常,我们会写成如下的结构:

js 复制代码
import fetchData from './fetchData';

function DataList() {
  const dispatch = useDispatch();
  // dispatch 了一个函数由 redux-thunk 中间件去执行
  dispatch(fetchData());
}

Mbox

mobx是一个简单、可扩展状态工具,相比redux,具有以下特点

  • 简洁、无模板代码(redux需要写大量模板代码)
  • 响应式数据,可直接修改(redux需要保证不可变)
  • 可直接处理异步(redux需要中间件处理异步)
  • 适合简单、规模不大的应用(redux约束强,更适合大型多人协作开发)

注: mobx6默认不开启修饰器语法

  • observable定义一个存储state的可追踪字段(Proxy)
  • action将一个方法标记为可以修改state的action
  • computed标记一个可以由state派生出新值并且缓存其输出的计算属性

以一个计数器为例,创建文件store/Counter.ts,新建一个Counter类,使用makeObservable方法将类的属性和方法变成响应式,并导出实例
:mobx中的每一个store都应该只初始化一次

js 复制代码
// store/Counter.ts
import {action, makeObservable, observable} from 'mobx'
class Counter {
  constructor(){
    // 参数1:target,把谁变成响应式(可观察)
    // 参数2:指定哪些属性或者方法变成可观察
    makeObservable(this, {
       count: observable,
       increment: action,
       decrement: action,
       reset: action,
     })
  }
  count = 0
  increment(){
    this.count++
  }
  decrement(){
    this.count--
  }
  reset(){
    this.count = 0
  }
}
const counter = new Counter()
export default counter 

在组件中使用,需要在App.tsx文件中引入store,即可使用其属性方法

js 复制代码
// App.tsx
import counter from './store/Counter';
// observer是一个高阶组件函数,需要包裹一个组件,这样组件才会更新
import { observer } from 'mobx-react'

function App() {
  const {cart, counter} = useStore()
  return (
    <div className="App">
      <h3>计数器案例</h3>
      <div>点击次数:{counter.count}</div>
      <button onClick={()c=> ounter.increment()}>加1</button>
      <button onClick={()c=> ounter.decrement()}>减1</button>
      <button onClick={() => counter.reset()}>重置</button>
    </div>
  );
}
export default observer(App);

默认class中的方法不会绑定this,this指向取决于如何调用。Counter里面的方法的this没有绑定,因此需要通过箭头函数的形式使用

js 复制代码
<button onClick={()=> counter.increment()}>加1</button>

要想直接使用,需要在Counter里面的makeObservable的使用通过action.bound绑定this的指向

js 复制代码
makeObservable(this, {
   count: observable,
   increment: action.bound,
   reset: action.bound,
 })

此时组件中即可直接使用store的方法

js 复制代码
<button onClick={counter.increment}>加1</button>

mobx的computed可以用来从其他可观察对象中派生信息,具有以下特点:

  • 采用惰性求值,会缓存其输出,并且只有当其依赖的可观察对象被改变是才会重新计算
  • 其前面必须使用get进行修饰
  • 还需要通过makeObservable方法指定
    以double为例
js 复制代码
...
    makeObservable(this, {
       count: observable,
       increment: action.bound,
       reset: action.bound,
       double: computed,
     })
...
get double(){
  return this.count * 2
}

makeAutoObservable 是加强版的 makeObservable,在默认情况下它将推断所有属性。推断规格如下:

  • 所有属性都成为 observable
  • 所有方法都成为 action
  • 所有的个体都成为 computed
    可以通过第二个参数 overrides 排除不需要被观察的属性和方法,第三个参数 autoBind 可以绑定 this 指向
js 复制代码
// 参数1:target,把谁变成响应式(可观察)
// 参数2:排除属性和方法
// 参数3:指定自动绑定this
makeAutoObservable(this, {}, {autoBind: true})

mobx中有两个监听方法autorun和reaction,其中:

  • autorun函数接受一个函数作为参数,在创建以及每当该函数所观察的值发生变化时,它都应该运行;mobx会自动收集并订阅所有可观察属性,一旦有改变发生,autorun将会再次触发
js 复制代码
autorun(() => {
   console.log('counter', counter.count);
})
  • reaction类似autorun,但在初始化时不会自动运行,且可以让你更加精细地控制要跟踪的可观察对象,其接受两个函数作为参数,参数1为data函数,其返回值将会作为第二个函数输入,参数2为回调函数
js 复制代码
reaction(
  () => counter.count,
  (newValue, oldValue) => {
    console.log('counter.count变化了');
  }
)

异步进程在mobx中不需要任何特殊处理,因为不论是何时引发的所有reaction都将会自动更新,这是因为可观察对象是可变的,在action执行过程中保持对它们的引用一般是安全的。

如果可观察对象的修改不是在action函数中,控制台会报警告,这是可以通过 runInAction 保证所有异步更新可观察对象步骤都标识为action

js 复制代码
  incrementAsync(){
    setTimeout(() => {
      runInAction(() => {
        this.count++
      })
    }, 2000)
  }

mobx模块化管理即通过一个根store统一管理所有store

新建store/index.ts文件,导入所有store,使用useContext机制,自定义useStore hook,统一导出store

js 复制代码
import { useContext, createContext } from 'react'
import cart from './Cart'
import counter from './Counter'

class RootStore {
  cart = cart
  counter = counter
}
const store = new RootStore()
const Context = createContext(store)
export const useStore = () => {
  return useContext(Context)
}

在App.tsx中统一导入,解构得到相应store

js 复制代码
import {useStore} from './store'
...
	const {cart, counter} = useStore()
...
相关推荐
Boilermaker19921 小时前
【Java EE】SpringIoC
前端·数据库·spring
中微子1 小时前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上10241 小时前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y2 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁2 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry2 小时前
Fetch 笔记
前端·javascript
拾光拾趣录2 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟2 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan2 小时前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson2 小时前
青苔漫染待客迟
前端·设计模式·架构