状态管理入门:说清 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()
...
相关推荐
我叫czc13 分钟前
【Python高级366】静态Web服务器开发
服务器·前端·python
温轻舟20 分钟前
前端开发 -- 自动回复机器人【附完整源码】
前端·javascript·css·机器人·html·交互·温轻舟
赵大仁20 分钟前
深入解析 Vue 3 的核心原理
前端·javascript·vue.js·react.js·ecmascript
csstmg32 分钟前
记录一次前端绘画海报的过程及遇到的几个问题
前端
bidepanm33 分钟前
Vue.use()和Vue.component()
前端·javascript·vue.js
顾平安1 小时前
手写 PromiseA+ 实现,轻松通过 872 条用例
前端
胡西风_foxww1 小时前
【ES6复习笔记】对象方法扩展(17)
前端·笔记·es6·对象·方法·扩展·对象方法扩展
bin91531 小时前
npm报错
前端·npm·node.js
一指流沙q2 小时前
Chrome被360导航篡改了怎么改回来?
前端·chrome
范文杰2 小时前
Stop using barrel files, now!
前端