几个状态管理库的原理分析
Redux
Redux 的设计思想
单一数据源:Redux 鼓励整个应用的状态被存储在一个单一的 JavaScript 对象中。使得应用的状态变得可预测且易于理解。
不可变数据:应用状态是只读的,唯一改变状态的方式是通过发起一个 action,一个描述发生了什么事情的普通 JavaScript 对象。
使用纯函数进行状态修改:Redux 中的状态修改通过纯函数完成,这些函数被称为 "reducers"
通过单向数据流管理状态: Redux 强调了单向数据流的概念。数据在应用中的流动是单向的,从应用状态到用户界面,再到用户交互产生的 action,再到 reducer 修改状态。这种清晰的数据流方向使得状态的变化易于追踪和调试。
使用中间件处理副作用:Redux 提供了中间件的概念,允许在 action 发起到 reducer 处理之间插入额外的逻辑。因此可以借助中间件处理异步操作、日志记录、错误处理等副作用。
预测性和调试性:由于 Redux 遵循着单一数据源和纯函数的原则,应用的状态变化变得可预测,可以追踪到状态的每一个变化。
Redux 的用法
- 创建初始值
// initialState.js
const initialState = {
counter: 0,
todos: []
}
export default initialState
// initialState.js
const initialState = {
counter: 0,
todos: []
}
export default initialState
- 创建 action
每个 action 必须包含 type
字段
// actions.js
export const increment = () => ({
type: 'INCREMENT'
})
export const addTodo = text => ({
type: 'ADD_TODO',
payload: { text }
})
// actions.js
export const increment = () => ({
type: 'INCREMENT'
})
export const addTodo = text => ({
type: 'ADD_TODO',
payload: { text }
})
- 编写 reducer 和合并 reduceers
Reducers 是纯函数,接收当前状态和一个 action,返回一个新的状态。
// reducers.js
const counterReducer = (state = initialState.counter, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
default:
return state
}
}
const todosReducer = (state = initialState.todos, action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, { text: action.payload.text, completed: false }]
default:
return state
}
}
export { counterReducer, todosReducer }
// reducers.js
const counterReducer = (state = initialState.counter, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
default:
return state
}
}
const todosReducer = (state = initialState.todos, action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, { text: action.payload.text, completed: false }]
default:
return state
}
}
export { counterReducer, todosReducer }
// rootReducer.js
import { combineReducers } from 'redux'
import { counterReducer, todosReducer } from './reducers'
const rootReducer = combineReducers({
counter: counterReducer,
todos: todosReducer
})
export default rootReducer
// rootReducer.js
import { combineReducers } from 'redux'
import { counterReducer, todosReducer } from './reducers'
const rootReducer = combineReducers({
counter: counterReducer,
todos: todosReducer
})
export default rootReducer
- 创建 store
// store.js
import { createStore } from 'redux'
import rootReducer from './rootReducer'
import initialState from './initialState'
const store = createStore(rootReducer, initialState)
export default store
// store.js
import { createStore } from 'redux'
import rootReducer from './rootReducer'
import initialState from './initialState'
const store = createStore(rootReducer, initialState)
export default store
- 在应用中挂载
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import CounterComponent from './CounterComponent'
ReactDOM.render(
<Provider store={store}>
<CounterComponent />
</Provider>,
document.getElementById('root')
)
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import CounterComponent from './CounterComponent'
ReactDOM.render(
<Provider store={store}>
<CounterComponent />
</Provider>,
document.getElementById('root')
)
- 连接 React 组件
使用 React-Redux 提供的 connect 函数将 React 组件连接到 Redux store,并将状态和 action 创建函数映射到组件的 props 上。
// CounterComponent.js
import React from 'react'
import { connect } from 'react-redux'
import { increment } from './actions'
const CounterComponent = ({ counter, increment }) => (
<div>
<p>Counter: {counter}</p>
<button onClick={increment}>Increment</button>
</div>
)
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = {
increment
}
export default connect(mapStateToProps, mapDispatchToProps)(CounterComponent)
// CounterComponent.js
import React from 'react'
import { connect } from 'react-redux'
import { increment } from './actions'
const CounterComponent = ({ counter, increment }) => (
<div>
<p>Counter: {counter}</p>
<button onClick={increment}>Increment</button>
</div>
)
const mapStateToProps = state => ({
counter: state.counter
})
const mapDispatchToProps = {
increment
}
export default connect(mapStateToProps, mapDispatchToProps)(CounterComponent)
Mobx
Mobx 的设计思想
简单而可预测的状态树:使用简单的 js 对象来表示应用的状态,而不是深度嵌套的
可变状态:Mobx 认为可变状态是合理的,它不像 Redux 那样通过分发不可变的 action 来更新状态,Mobx 支持直接修改状态树的值,然后通过观察者模式自动通知订阅者更新。
响应式数据:Mobx 采用响应式编程的思想,通过依赖收集和派发更新,来构造依赖关系,无需开发者手动指定。(类似于 Vue)
通过计算属性派发状态:MobX 提供了 computed 属性,让开发者可以方便地创建派生状态。这些计算属性也会自动收集依赖,从而减少重复的计算开销。
最小化 UI:通过事务机制最小化 UI 重绘,只有事务结束时才会触发 UI 重绘。(微任务)
异步支持:Mobx 的 action 支持异步操作,并且在异步过程中处理响应的变化。
Mobx 的特性
观察者模式:
mobx-react
使用观察者模式来追踪状态的变化。当状态发生变化时,它会通知所有观察者(React 组件)执行相应的更新装饰器:
mobx-react
使用装饰器(Decorator)语法来简化状态管理的语法。装饰器是一种将额外行为附加到类或类的成员的语法糖。在mobx-react
中,@observer 装饰器用于将 React 组件转化为观察者,使其能够响应 Mobx 状态的变化。
import { observer, inject } from 'mobx-react'
@observer
@inject('appStore')
class MyComponent extends React.Component {
// ...
}
import { observer, inject } from 'mobx-react'
@observer
@inject('appStore')
class MyComponent extends React.Component {
// ...
}
- 上下文 API:
mobx-react
使用 React 的 Context API 来使 Mobx 的状态在整个应用中可访问。MobXProviderContext 提供了一个根组件,负责传递 Mobx store。
import { Provider } from 'mobx-react'
const Root = ({ store }) => (
<Provider store={store}>{/* Your app components go here */}</Provider>
)
import { Provider } from 'mobx-react'
const Root = ({ store }) => (
<Provider store={store}>{/* Your app components go here */}</Provider>
)
依赖收集和取消订阅:当一个组件通过 @observer 被转化为观察者时,它会自动订阅相关 Mobx store 中的状态。当组件被销毁时,它也会自动取消订阅,以避免内存泄漏。
强制更新:
mobx-react
使用 forceUpdate 来强制组件进行更新。这是因为 Mobx 不会改变 React 组件的状态,而是通过观察者模式触发更新。当 Mobx 收集到的依赖的状态发生变化时,forceUpdate 用于通知 React 组件重新渲染。性能优化:
mobx-react
使用了一些技巧来优化性能,比如将类成员包装成可观察对象,使用微任务(microtask)来异步触发更新,以及使用 computed 属性来缓存计算结果等。