silent-tan / blog Goto Github PK
View Code? Open in Web Editor NEW个人博客备忘
个人博客备忘
前言:在前端应用日渐复杂的环境下,前端页面状态可控制和可跟踪成为解决开发和调试的重要手段,显然我们有必要了解状态管理方案可以解决的是什么问题,解决问题的核心方式是什么,目前前沿流行的解决方案有哪些?这些方案针对的背景是哪些?使用这些方案的技巧有哪些?这些问题本次分享都会有所涉及
页面的状态复杂与否决定是否需要数据状态管理。那么什么样的页面状态可以说是复杂呢?
从交互和数据方面来说:
组件来说,非组件状态的共享,包含多方面,相互影响,依赖关系等
更具体来说,因为react dom的树形结构,我们考虑组件的关系,以及其状态的维护与交流来看状态维护与管理的困难程度
自身与子组件的状态维护
简单
简单
一般
复杂或者说麻烦
②context 简单
,以下面例子说明# 旧版react的context存在缺陷,当中间父组件shouldComponentUpdate做优化处理返回false时,子孙组件无法获取更新的context。解决方案是传递的context作为一个事件的监听系统
const AppContext = React.createContext({
value: 'ceshi',
changeValue: () => {}
})
class Parent extends React.Component {
render() {
return (
<div>
{this.props.children}
</div>
)
}
}
class ChildOne extends React.Component {
render() {
return (
<AppContext.Consumer>
{({changeValue}) => (
<input onChange={changeValue} />
)}
</AppContext.Consumer>
)
}
}
class ChildTwo extends React.Component {
render() {
return (
<AppContext.Consumer>
{({value}) => (
<div>{value}</div>
)}
</AppContext.Consumer>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
this.changeValue = (e) => {
this.setState({
value: e.target.value
})
}
this.state = {
value: '',
changeValue: this.changeValue
}
}
render() {
return (
<AppContext.Provider value={this.state}>
<Parent>
<ChildOne />
</Parent>
<Parent>
<ChildTwo />
</Parent>
</AppContext.Provider>
);
}
}
// ========================================
ReactDOM.render(
<App />,
document.getElementById('root')
);
自身和兄弟组件的状态管理:
一般
一般
复杂
自身和祖先状态
一般
复杂
复杂
总体上来说,这些状态的关联可以用下面图标表示
面对以上复杂问题,单凭react自身的state和props维护与工作方式,会让页面重构以及问题定位都陷入放飞自我的过程。
以上问题如果说明了我们是需要状态管理,那么我们需要怎样的状态管理,状态管理怎么为我们解决这些状态复杂的问题。这个需要根据我们时候的UI渲染方案的特点,本文就以我们目前使用的React进行探究
react是用以创建有相互影响的UI的一个库,在react应用中,表现层响应每一个状态,react保证高效地正确更新状态改变后的组件和让代码更加可预测和调试
每个React元素都是Immutable的,所以一旦创建React Element后就不可以直接改变它了。React DOM创建了一个树形结构的虚拟树,其实根据React的Component互相组合的特点也是可以想象出这个树形结构的虚拟树的。
这里顺便说一下React Component的几种形式:
- React.Component
- React.PureComponent
- 纯函数Component
可以根据组件特点来进行定义可以进一步加快ReactDom的比较和构建
还有就是关于contructor构造函数,contructor(props)是否要写?如果contructor里面有用到props可以加props,没有就没有必要
react Component提供一些钩子函数,叫做生命周期函数,这些生命周期函数反应了React组件的创建过程,包括初始化,装载,更新,卸载的过程。
对象存储,对象即是View的状态,存储了共享的状态
对象状态的查询,View可以从存储状态读取View所需要的数据和状态
对象状态改变导致对象存储的改变的描述,当数据状态因为页面的交互发生变化时,对应来自对象存储中的数据和状态怎么变化
对象存储变化的时候,怎么通知页面,或者说页面引用的状态怎么保证与对象存储中的状态同步
React只是一个UI渲染的库,它不是一个架构,它没有明确地说明页面数据必须怎么样流动,但是其state和props的以及render机制说明单向数据流有利于其机制运行
Flux是最早的一个状态管理方案(架构),它的主要核心是Dispatcher、Store、View。主要流程是当用户进行操作的时候,会从组件发出一个 action,这个 action 流到 store 里面,触发 store 对状态进行改动,然后 store 又触发组件基于新的状态重新渲染。其实质就是强制每个数据状态的改变都通过store进行记录。
架构设计思路:
①实例化模块状态类(比如TodoStore),该类继承FluxReduceStore,FluxReduceStore继承FluxStore类。所以必须要实现getInitialState方法和reduce方法。当状态类实例化后该类会向Dispatcher中注册一个回调,这个回调会调用状态类中的reduce方法,然后比较reduce方法返回的state和之前的state做比较,如果不同,会调用该Store中响应__changeEvent
的事件
②创建顶层Container,作用是结合React的渲染机制,使得数据进行单向流动,即使得如果状态集合中如果有状态发生改变,子组件能够进行更新机制。
创建这个Container一般使用createFunctional方法,它接受四个参数,第一个参数是一个纯函数React组件,这个组件是我们代码中的顶层AppContainer; 第二个参数是Store的数组形式;第三个参数是一个函数,这个函数返回我们Container中的state, 这个state会传给我们的AppContainer; 第四个参数是额外的选项,选项包括是否渲染成PureContainer和传递props和context
function createFunctional<Props, State, A, B>(
viewFn: (props: State) => React.Element<State>,
getStores: (props?: ?Props, context?: any) => Array<FluxStore>,
calculateState: (
prevState?: ?State,
props?: ?Props,
context?: any
) => State,
options?: Options
): ReactClass
```
Container实例化的时候会实例化一个FluxContainerSubscriptions类
会为这个Subscriptions实例的`_callbacks`属性添加回调函数, 这个回调函数会调用Container组件的`setState`方法,新的state会是`calculateState`返回的state
然后会调用Subscriptions实例的setStore方法,setStore方法有两个作用:第一是为每一个实例的状态类监听`__changeEvent`的一个回调函数,这个回调函数作用是设置Subscriptions实例的change为true或者打印一下有状态改变的状态类实例。***第二是创建一个状态集合FluxStoreGroup类的实例`new FluxStoreGroup(stores, setStateCallback)`。Group类初始化的时候会向Dispatcher 注册一个回调。该回调会执行注册中心Dispatcher的`waitFor()`方法并且`执行setStateCallback`, waitFor方法会执行dispatch中心注册的非处理中回调函数(这些函数包括每个状态store实例初始化时候注册的回调函数)***
③经过上面两个步骤,flux为应用做了响应数据状态改变的准备。
④当用户触发一些action,比如点击按钮之类的UI操作或者其他触发action的操作。也就是调用了类似这类的函数
const Actions = {
addTodo(text) {
TodoDispatcher.dispatch({
type: TodoActionTypes.ADD_TODO,
text,
});
}
}
实质就是转发中心实例调用dispatch(action: Object)
方法。该方法会调用所有转发中心里面注册过的函数,我们回忆一下向转发中心里面注册的回调是:所有store初始化注册的函数,Group实例初始化时注册的函数。即是先执行FluxReduceStore
实例的__invokeOnDispatch()
,该方法会调用状态类中的reduce方法,然后比较reduce方法返回的state和之前的state做比较,如果不同把新的state赋值给状态实例的_state
属性,然后执行Group类初始化的时候会向Dispatcher 注册的回调(waitfor + setStateCallback)。
state发生改变就会触发react的渲染机制了,这就形成了flux的单数据流解决方案,用下图表示
根据上面的思路和图表,更加细致地看一下这四个核心部分的源码:
action描述用户行为,比如添加一个Todo的描述,值得注意的是,这个描述是会发给所有Dispatcher注册的回调的
const Actions = {
addTodo(text) {
TodoDispatcher.dispatch({
type: TodoActionTypes.ADD_TODO,
text,
});
}
}
转发中心,作用将Action转发到store处理
在分析Dispatcher前,先说一个工具函数invariant, invariant(condition: boolean, format: string, a: any, b: any, c: any, d: any, e: any, f: any)
。它接受一个返回true或者false的条件,如果false就抛出错误报告,abcdef是打印的参数,format字符串中的的%s字符会被这些参数填充填充
然后我们快速看下Dispatcher的源码
var invariant = require('invariant');
export type DispatchToken = string;
var _prefix = 'ID_';
class Dispatcher<TPayload> {
_callbacks: {[key: DispatchToken]: (payload: TPayload) => void};
_isDispatching: boolean;
_isHandled: {[key: DispatchToken]: boolean};
_isPending: {[key: DispatchToken]: boolean};
_lastID: number;
_pendingPayload: TPayload;
constructor() {
this._callbacks = {};
this._isDispatching = false;
this._isHandled = {};
this._isPending = {};
this._lastID = 1;
}
/**
* 注册一个可以被分发的函数,返回一个回调ID,这个ID可以被waitfor函数使用,这个注册函数默认未处理未完成
* 往实例变量_callbacks添加key-value
*/
register(callback: (payload: TPayload) => void): DispatchToken {
var id = _prefix + this._lastID++;
this._callbacks[id] = callback;
return id;
}
/**
* 根据回调ID删除注册函数
* 删除_callbacks中的key
*/
unregister(id: DispatchToken): void {
invariant(
this._callbacks[id],
'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
id
);
delete this._callbacks[id];
}
/**
* 在执行当前回调之前等待指定执行的回调完成
* 执行所有非isPending状态的回调函数
*/
waitFor(ids: Array<DispatchToken>): void {
invariant(
this._isDispatching,
'Dispatcher.waitFor(...): Must be invoked while dispatching.'
);
for (var ii = 0; ii < ids.length; ii++) {
var id = ids[ii];
if (this._isPending[id]) {
invariant(
this._isHandled[id],
'Dispatcher.waitFor(...): Circular dependency detected while ' +
'waiting for `%s`.',
id
);
continue;
}
invariant(
this._callbacks[id],
'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',
id
);
this._invokeCallback(id);
}
}
/**
* 转发参数给所有注册的回调函数,不允许转发中心在转发状态下进行转发
*/
dispatch(payload: TPayload): void {
invariant(
!this._isDispatching,
'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'
);
this._startDispatching(payload);
try {
for (var id in this._callbacks) {
if (this._isPending[id]) {
continue;
}
this._invokeCallback(id);
}
} finally {
this._stopDispatching();
}
}
/**
* 转发中心是否处于转发状态
*/
isDispatching(): boolean {
return this._isDispatching;
}
/**
* 根据回调函数的ID和使用参数pendingPayload去调用函数
* 同时改变回调函数的状态 isPending=true
* 运行后,修改该回调的运行状态 isHandled=true
*/
_invokeCallback(id: DispatchToken): void {
this._isPending[id] = true;
this._callbacks[id](this._pendingPayload);
this._isHandled[id] = true;
}
/**
* 开始分发的时候将所有注册回调的pending状态和handle状态清空,即是设置为false
* 然后赋值转发回调函数需要的参数pendingPayload
* 并改变转发状态isDispatching=true
*/
_startDispatching(payload: TPayload): void {
for (var id in this._callbacks) {
this._isPending[id] = false;
this._isHandled[id] = false;
}
this._pendingPayload = payload;
this._isDispatching = true;
}
/**
* 清空转发状态的标记
* 删除_pendingPayload和设置isDispatching=false
*/
_stopDispatching(): void {
delete this._pendingPayload;
this._isDispatching = false;
}
}
module.exports = Dispatcher;
store的作用有如下作用:
我们看一下Flux的store设计
import type Dispatcher from 'Dispatcher';
const {EventEmitter} = require('fbemitter');
// 这个类是内部基类,不直接继承
class FluxStore {
// 私有
_dispatchToken: string;
// 保护属性,可以子类去使用
__changed: boolean;
__changeEvent: string;
__className: any;
__dispatcher: Dispatcher<any>;
__emitter: EventEmitter;
// 对象初始化
constructor(dispatcher: Dispatcher<any>): void {
this.__className = this.constructor.name;
this.__changed = false;
this.__changeEvent = 'change';
this.__dispatcher = dispatcher;
this.__emitter = new EventEmitter();
// store 初始化会默认注册一个回调
this._dispatchToken = dispatcher.register((payload) => {
this.__invokeOnDispatch(payload);
});
}
// 添加监听
addListener(callback: (eventType?: string) => void): {remove: () => void}
// 获取转发中心
getDispatcher(): Dispatcher<any>
// 获取转发中心注册回调的ID,也就是每个store注册的回调
getDispatchToken(): string
// 最近的调度中,store是否发生改变
hasChanged(): boolean
// 触发store改变后,修改__changed属性为true
__emitChange(): void
// 继承该类的类需要重写该方案,否则__onDispatch会抛出错误
// 封装了所有__onDispatch的逻辑,该函数在store的注册回调中被调用
__invokeOnDispatch(payload: Object): void
// 这个回调会在实例化期间被Dispatcher注册
__onDispatch(payload: Object): void
}
class FluxReduceStore<TState> extends FluxStore {
// 私有
_state: TState;
constructor(dispatcher: Dispatcher<Object>) {
super(dispatcher);
this._state = this.getInitialState();
}
// 如果store不是Immutable类型,这个方法是要重写的
// 返回_state
getState(): TState
// 需要重写
// 初始化_state的具体函数
getInitialState(): TState
// 需要重写
// 根据动作状态描述,返回新的状态
reduce(state: TState, action: Object): TState
// 如果Tstate是Immutable类型的话不需要重写
// 比较前后state是否相同
areEqual(one: TState, two: TState): boolean
// 重写FluxStore的__invokeOnDispatch
__invokeOnDispatch(action: Object): void
}
// 检测所有的store初始化dispatcher对象为同一个对象
function _getUniformDispatcher(stores: Array<FluxStore>): Dispatcher<any>
/**
* 初始化接受两个参数
* FluxStore[], callback
*/
class FluxStoreGroup {
// 私有
_dispatcher: Dispatcher<any>;
_dispatchToken: DispatchToken;
constructor(stores: Array<FluxStore>, callback: Function): void {
this._dispatcher = _getUniformDispatcher(stores);
// Precompute store tokens.
var storeTokens = stores.map(store => store.getDispatchToken());
// Register with the dispatcher.
this._dispatchToken = this._dispatcher.register(payload => {
this._dispatcher.waitFor(storeTokens);
callback();
});
}
// 取消回调的注册
release(): void
}
有两种方式去创建顶层View
function create<DefaultProps, Props, State>(
Base: ReactClass<Props>,
options?: ?Options
): ReactClass<Props>
function createFunctional<Props, State, A, B>(
viewFn: (props: State) => React.Element<State>,
getStores: (props?: ?Props, context?: any) => Array<FluxStore>,
calculateState: (
prevState?: ?State,
props?: ?Props,
context?: any
) => State,
options?: Options
): ReactClass<Props>
下图是完整的flux架构示意图:
Redux是基于观察模式来设计的。
回顾一下观察者模式的实现思路:
subscribers
subscribers
数组中去,比如addSubscriber
subscribers
删除观察者, 比如removeSubscriber
subscribers
中的所有观察者方法,即一个遍历函数调度subscribers
, 比如dispatch
一种方案运行都有它自身的约定原则,redux的几个原则是:
同上面flux介绍,根据一个简单的例子我们先来了解一下
①我们一般会先整理好我们的store集合组成,这里先不关注UI,尽管我们的store的初始化是根据UI上面的状态来进行设置的,如下面两个模块的reducer
,然后我们会使用combineReducers
方法来合并reducer,这是因为redux方案需要的是一个store
const todos = (state = [], action) => {
switch(action.type) {
case 'ADD_TODO':
return [
...state, {
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo => {
if (todo.id !== action.id) {
return todo;
}
return {
...todo,
completed: !todo.completed
}
});
default:
return state;
}
};
const visibilityFilter = (state = 'SHOW_ALL', action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
};
const { combineReducers } = Redux;
const todoApp = combineReducers({
todos,
visibilityFilter
});
②使用createStore构建状态树
const { combineReducers, createStore } = Redux;
const store = createStore(todoApp);
③向store添加监听函数,当dispatch函数调用的时候都会重新render
store.subscribe(render)
以上就是最简单的redux demo,而在实际开发中,我们一般不会每次都重新render的和直接把所有props传递给组件,也不会手动去添加监听函数,一般引入react-redux
帮我们处理这些问题,react-redux
就不在这里介绍了
redux主要向外暴露五个api:
先介绍下combineReducers
combineReducers的用法如下,作用是合并reducer为一个,是一个curry化的函数
const authReducer = (state = initState, action) => {}
const bindCardReducer = (state = initState, action) => {}
const appReducer = combineReducer({ authReducer, bindCardReducer })
回到他的源码我们快速过一下:
export default function combineReducers(reducers) {
// 筛选符合的reducer,reducer为function
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 遍历finalReducers中的reducer,检查传入reducer的state是否合法
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
// 传入的reducer的state如果不合法,就抛出错误
if (shapeAssertionError) {
throw shapeAssertionError
}
// 如果不是正式环境就抛出警告
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {}
// 遍历所有的key和reducer,分别将reducer对应的key所代表的state,代入到reducer中进行函数调用
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// combineReducers黑魔法--要求传入的Object参数中,reducer function的名称和要和state同名的原因
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
**
redux的观者者createStore
实现
/**
*@param reducer 是一个函数,返回下一个store的的状态
*@param preloadedState是一个初始态的状态
*@param enhancer函数 是redux store的中间件
*@returns store
*/
export default function createStore(reducer, preloadedState, enhancer) {
// 检测preloadState和enhancer是否传反
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// enhancer中间件
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
// 如果nextListeners和currentListeners指向同一个栈,浅复制一份
// 目的是修改nextListeners不会影响到currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* 通过方法获取state. 在中间件一般都会用到这个函数来获取state
*/
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
/**
* 注册listener,同时返回一个取消事件注册的方法。当调用store.dispatch的时候调用listener
* 每次调度动作时都会调用它
* 而如果状态树的某些部分可能已经发生了变化那么你可以调用getState()来读取回调中的当前状态树
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
/*
* 返回删除该listener的函数
* 使用:
* const unsubscribe = store.subscribe(() => { // dosomethin })
* unsubscribe()
*/
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
/**
* 分发一个action.这是唯一改变state的方法
* reducer函数可以被当前状态树和给以的action对象调用以创建状态集合。它的返回值会作为下一个状态数,会被所有listeners监听
* 基本的实现仅仅支持普通对象的分发,如果你Promise,Observable,thunk或其他东西,那么就要使用中间件去实现了,比如redux-thunk
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 防止多个dipatch同时进行
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 分发的时候,nextListeners作为新的listeners
const listeners = (currentListeners = nextListeners)
// 遍历调用listener
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
/**
* 替换reducer
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
// 实例化store的时候初始化分发action
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer
}
}
将多个函数组合在一起,从从右到左运行
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
回顾下我们中间件在开发中的用法
import thunkMiddleware from 'redux-thunk'
const store = createStore(reducers, state, applyMiddleware(thunkMiddleware))
为什么要用到thunkMiddleware?
Action 发出以后,Reducer 立即算出 State,这叫做同步。那么Action发出一段时间后,再执行Reducer,就是异步了。
而thunkMiddleware可以解决这个问题。
什么是中间件?
我们把对观察者的扩展叫做中间件
根据Redux的核心部分,我们看哪个地方适合我们扩展
(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
所以相对来说,只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。举例来说,要添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch进行如下改造
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action);
next(action);
console.log('next state', store.getState());
}
code:
export default function applyMiddleware(...middlewares) {
// 最终返回一个以createStore为参数的匿名函数
// 这个函数返回另一个以reducer, initialState, enhancer为参数的匿名函数
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 每个 middleware 都以 middlewareAPI 作为参数进行注入,返回一个新的链。
// 此时的返回值相当于调用 thunkMiddleware 返回的函数: (next) => (action) => {} ,接收一个next作为其参数
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 并将链代入进 compose 组成一个函数的调用链
// compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函数对象。
// 在目前只有 thunkMiddleware 作为 middlewares 参数的情况下,将返回 (next) => (action) => {}
// 之后以 store.dispatch 作为参数进行注入
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
// bindActionCreators期待一个Object作为actionCreators传入,里面是 key: action
export default function bindActionCreators(actionCreators, dispatch) {
// 如果只是传入一个action,则通过bindActionCreator返回被绑定到dispatch的函数
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
// 遍历并通过bindActionCreator分发绑定至dispatch
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
Mobx的源码比较复杂和多,所以这里主要分析它的原理和主要api
transaction 事务。表示一组原子性的操作,mobx正是因为此避免不必要的重新计算。主要涉及到startBatch和endBatch两个函数用于开始事务和结束事务
export function startBatch() {
globalState.inBatch++
}
export function endBatch() {
if (--globalState.inBatch === 0) {
// 执行所有 Reaction
runReactions()
// 处理不再被观察的 Observable
const list = globalState.pendingUnobservations
for (let i = 0; i < list.length; i++) {
const observable = list[i]
observable.isPendingUnobservation = false
if (observable.observers.size === 0) {
if (observable.isBeingObserved) {
observable.isBeingObserved = false
observable.onBecomeUnobserved()
}
if (observable instanceof ComputedValue) {
observable.suspend()
}
}
}
globalState.pendingUnobservations = []
}
}
atom。任何能用于存储状态的值在mobx中被称为Atom,它会在被观察时
和自身发生改变时
发出通知
export class Atom implements IAtom {
// 标志属性,不再被观察时为 true
isPendingUnobservation = false
isBeingObserved = false
// 观察者集合
observers = new Set()
diffValue = 0
// 上一次被使用时,Derivation 的 runId
lastAccessedBy = 0
// 状态最新的观察者所处的状态
lowestObserverState = IDerivationState.NOT_TRACKING
constructor(public name = "Atom@" + getNextId()) {}
public onBecomeUnobserved() {
// noop
}
public onBecomeObserved() {
/* noop */
}
// 被使用时触发
public reportObserved() : boolean {return reportObserved(this)}
// 发生变化时触发
public reportChanged() {
startBatch()
propagateChanged(this)
endBatch()
}
toString() {
return this.name
}
}
ObservableValue 正是继承自 Atom。可以看到,reportObserverd 和 reportChanged 分别调用了 reportObserved 和 propagateChanged 两个方法,这正是 Observable 用于「通知被观察」和「通知自身变化」的两个函数
可以让用户能够基于它定制一些可观察的数据类型
Derivation。任何 源自状态并且不会再有任何进一步的相互作用的东西就是衍生。 衍生以多种形式存在
例子:https://codepen.io/farzer/pen/QBaeWB?editors=0011
① 定义状态并使其可观察
import {observable} from 'mobx';
var appState = observable({
timer: 0
});
② 跟踪变化
mobx.autorun(() => {
console.log(appState.timer)
})
③ 创建视图以响应状态的变化,以react为例
import {observer} from 'mobx-react';
@observer
class TimerView extends React.Component {
render() {
return (<button onClick={this.onReset.bind(this)}>
Seconds passed: {this.props.appState.timer}
</button>);
}
onReset () {
this.props.appState.resetTimer();
}
};
ReactDOM.render(<TimerView appState={appState} />, document.body);
observer 函数/装饰器可以用来将 React 组件转变成响应式组件。 它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件
④ 更改状态
appState.resetTimer = action(function reset() {
appState.timer = 0;
});
setInterval(action(function tick() {
appState.timer += 1;
}), 1000);
数据流复杂就使用redux,不复杂使用mobx。
我们在实际react开发中使用mobx方案都是会使用到mobx-react方法结合使用,mobx-react提供了Provider和inject,observer等方法。
而mobx-state-tree的出现就包含了mobx,mobx-react的功能,并且弥补了mobx的跟踪调试差的问题以及快照等功能
rxjs是一种响应式的编程方式,对异步问题的解决非常优雅的,它把一个函数的运行看做一个流,然后通过api对这个流进行阶段操作或者更多细致化的操作,比如一个例子
Immutable解决的是什么问题呢?首先是改变的object对象的时候,层次深的时候复制原来object开销大,其次PureComponent浅比较的时候作比较不优雅。当然其恶心的api却是让人默默继续用回原来的方法
let newRate = Object.assign({}, state.list[0].roomInfo.rateList[0])
newRate.score = 90;
新一代的Immutable Data方案,和Immuatable.js的区别是使用原生的数据结构,这样的话就减少了toJS之类的操作开销
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.