基于源码解析 Flux 架构

2021-03-14  本文已影响0人  犯迷糊的小羊

Flux

Flux 是 Facebook 在 2014 年提出的 Web 应用架构,其核心思想是将应用划分为 action 层、dispatcher 层、store 层和 view 层。

整个应用的运行方式是,用户页面交互行为触发特定类型的 action,dispatcher 会携带特定的 action type 和数据到 store 层,store 层会处理不同 action 类型下的数据变更逻辑,再把变动更新到 view 层。

flux framework

源码分析

Actions

Actions 就是一个描述操作类型和携带数据的对象,可以设计为一个 Action Creator;

var todoDispatcher = new Dispatcher();

var Actions = {
    addTodo(text) {
        todoDispatcher.dispatch({
            type: 'ADD_TODO',
            text
        });
    }
}

Dispatcher

Dispatcher 在 Flux 架构中扮演的角色是连接 Actions 和 Stores,Dispatcher 提供两个方法,dispatcher.register 用于注册处理 state 变更和 view 更新的逻辑,dispatcher.dispatch 用于触发 dispatcher 注册的各个回调;

Dispatcher 在 Flux 架构中是一个单例,所有 stores 都绑定在一个 dispatcher;

var _prefix = 'ID_';

class Dispatcher {
    constructor() {
        this._isDispatching = false;
        this.isPending = {};
        this._isHandled = {};
        this._pendingPayload = null;
        this._lastID = 1;
        this._callbacks = {};
    }

    register(callback) {
        var id = _prefix + this._lastID++;
        this._callbacks[id] = callback;
        return id;
    }

    // 同一个 payload 将会作为所有 callback 的 payload
    dispatch(payload) {
        this._startDispatching(payload);
        try {
            for (var id in this._callbacks) {
                if (this._isPending[id]) continue;
                this._invokeCallback(id);
            }
        } finally {
            this._stopDispatching();
        }
    }
    _startDispatching(payload) {
        for (var id in this._callbacks) {
            this._isPending[id] = false;
            this._isHandled[id] = false;
        }
        this._pendingPayload = payload;
        this._isDispatching = true;
    }
    _stopDispatching() {
        delete this._pendingPayload;
        this._isDispatching = false;
    }
    _invokeCallack(id) {
        this._isPending[id] = true;
        this._callbacks[id](this._pendingPayload);
    }
}

class TodoStore extends ReduceStore {
    constructor() {
        super(todoDispatcher);
    }

    getInitialState() {
        // todo
        return null;
    }

    reduce(state, action) {
        switch(action.type) {
            case 'ADD_TODO':
                if (!action.text) return state;
                const id = uuid.v4();
                return state.set(id, {
                    id,
                    text: action.text,
                });
            default:
                return state;
        }
    }
}

export default new TodoStore();

Store

Flux 架构在创建业务 Store 的时候,将全局单例 dispatcher 和 store 进行绑定,并在 dispatcher 中注册回调函数;

dispatcher.dispatch 调用时,store 中注册的回调函数会触发,遍历和调用 dispatcher._callbacks 的所有回调;

回调函数会调用 store.reduce 方法,如果产生新的 state,则会发送变更事件;

注意,这里的 reduce 是一个纯函数,当 state 发生变化时,会返回一个新的 Immutable 对象,如果没有发生变化则返回原来的 Immutable 对象,根据 Immutable 对象是否发生变化触发后续渲染更新;

Store 可以根据业务分割不同的特定 Store

// ReduceStore
class FlexReduceStore extends FluxStore {
    constructor(dispatch) {
        super(dispatch);
        this._state = this.getInitialState();
    }
    getState() {
        return this._state;
    }
    /**
   * Checks if two versions of state are the same. You do not need to override
   * this if your state is immutable.
   */
  areEqual(one: TState, two: TState): boolean {
    return one === two;
  }
    // 抽象方法
    getInitialState() {}
    // 抽象方法
    reduce() {}
    __invokeOnDispatch() {
        this.__changed = false;
        const startingState = this._state;
        const endingState = this.reduce(startingState, action);

        if (!this.artEqual(startingState, endingState)) {
            this._state = endingState;
            this.__emitChanged();
        }

        if (this.__changed) {
            this.__emitter.emit(this.__changedEvent);
        }
    }
}

// FluxStore
class FluxStore {
    constructor(dispatcher) {
        this.__changed = false;
        this.__changeEvent = 'change';
        this.__dispatcher = dispatcher;
        this.__emitter = new EventEmitter;
        this._dispatchToken = dispatcher.register((payload) => {
            this.__invokeOnDispatch(payload);
        });
    }
    getDispatchToken() {
        return this._dispatchToken;
    }
    addListener(callback) {
        return this.__emitter.addListener(this.__changedEvent, callback);
    }
    __invokeOnDispatch(payload) {
        this.__changed = false;
        this.__onDispatch(payload);
        if (this.__changed) {
            this.__emitter.emit(this.__changedEvent);
        }
    }
    // 抽象方法
    __onDispatch(action) {

    }
    __emitChange() {
        this.__changed = true;
    }
}

Container

绑定 View 和 Stores 的 controller-view,使得经过 dispatcher.dispatch 后变化的数据能够传递给 view 并进行更新

function getStores() {
    return [
        todoStore,
    ];
}

function getState() {
    return {
        todos: todoStore.getState(),
        onAdd: todoActions.addToDo,
    }
}

Container.createFunctional(AppView, getStores, getState);

// createFunctional
function createFunctional(viewFn, getStores, calculateState) {
    class FunctionalContainer extends React.Component {
        state = null;
        static getStores(props, context) { 
            return getStores(props, context);
        }
        static calculateState(prevState, props, context) {
            return calculateState(prevState, props, context);
        }
        render() {
            return viewFn(this.state)
        }
    }

    return create(FunctionalContainer);
}

// create
function create(Base) {
    const getState = (state, maybeProps, maybeContext) => {
        return Base.calculateState(state, maybeProps, maybeContext);
    }

    const getStores = (maybeProps, maybeContext) => {
        return Base.getStores(maybeProps, maybeContext);
    }

    class ContainerClass extends Base {
        _fluxContainerSubscriptions: null;
        constructor(props, context) {
            super(props, context);
            this._fluxContainerSubscriptions = new FluxContainerSubscriptions();
            this._fluxContainerSubscriptions.setStores(getStores(props, context));
            // *核心*
            this._fluxContainerSubscriptions.addListener(() => {
                this.setState((prevState, currentProps) => getState(
                    prevState,
                    currentProps,
                    context,
                ));
            });
            const calculatedState = getState(undefined, props, context);
            this.state = {
                ...(this.state || {}),
                ...calculatedState,
            }
        }

        componentWillUnmount() {
            if (super.componentWillUnmount) {
                super.componenetWillUnmount();
            }
            this._fluxContainerSubscriptions.reset();
        }
    }

    const container = ContainerClass;
    return container;
}

// FluxContainerSubscriptions
class FluxContainerSubscriptions {
    _callbacks;
    _stores;
    _tokens;
    _storeGroup;

    constructor() {
        this._callbacks = [];
    }
    setStores(stores) {
        this._stores = stores;
        this._resetTokens();
        this._resetStoreGroup();

        let changed = false;
        let changedStores = [];
        const setChanged = () => { changed = true; }
        this._tokens = store.map(store => store.addListener(setChanged));
        const callCallbacks = () => {
            if (changed) {
                this._callbacks.forEach(fn => fn());
                changed = false;
            }
        }
        this._storeGroup = new FluxStoreGroup(stores, callCallbacks);
    }
    addListener(fn) {
        this._callbacks.push(fn);
    }
    reset() {
        this._resetTokens();
        this._resetStoreGroup();
        this._resetCallbacks();
        this._resetStores();
    }
    _resetTokens() {
        if (this._tokens) {
            this._tokens.forEach(token => token.remove());
            this._tokens = null;
        }
    }
    _resetStoreGroup() {
        if (this._storeGroup) {
            this._storeGroup.release();
            this._storeGroup = null;
        }
    }
    _resetCallbacks() {
        this._callbacks = [];
    }
    _resetStores() {
        this._stores = null;
    }
}

// FluxStoreGroup
class FluxStoreGroup {
    constructor(stores, callback) {
        this._dispatcher = _getUniformDispatcher(stores);
        var storeTokens = stores.map(store => store.getDispatchToken());
        this._dispatchToken = this._dispatcher.register(payload => {
            this._dispatcher.waitFor(storeTokens);
            callback();
        });
    }
    release() {
        this._dispatcher.unregister(this._dispatchToken);
    }
}

function _getUniformDispatcher() {
    var dispatcher = stores[0].getDispatcher();
    return dispatcher;
}

Flux 运行机制

Dispatcher

Dispatcher 是全局单例,多个 stores 和单一 dispatcher 绑定,每实例化一个 store,都会向 dispatcher 注册对应的回调函数;

dispatcher.dispatch

new Store()

store.__invokeOnDispatch(payload)

Container

function getStores() {
  return [store]
}

function getState() {
  return {
    todos: store.getState(),
  }
}

FluxContainerSubscriptions (sub)

FluxStoreGroup

总的来看,Flux 架构通过 Container 将 stores 和 view 绑定在一起;每个 stores 在实例化的时候,都会向全局单例 dispatcher 注册自身的 reduce,store.reduce 定义了针对不同 actions ,state 所作出的改变;此外,Container 在初始化时,还会额外将更新视图的逻辑 component.setState 注册到 dispatcher 中,将所有的 stores 注册 change 事件的回调函数,用于后续再 state 发生变化时,设置 changed 这一状态锁,这一状态锁用于控制是否进行视图渲染;当 view 视图发生交互,通过 dispatcher.dispatch 提交特定 action 时,dispatcher 会遍历先前注册好的回调列表,回调列表中的函数会调用 store.reduce 计算出 new state,然后会比较前后 state,如果 state 发生变化,store 则会发送 change 事件;change 事件会使得 changed 设置为 true,因此在遍历到更新视图的回调函数时,会开启视图渲染;

Immutable 对象

Flux 架构中,采用 Immutable 对象进行状态前后的比较。

之所以采用 Immutable 对象,是因为在比较前后状态变化的时候,需要快速识别出状态是否产生变化,最快的比较方式是采用浅比较(引用比较);

Immutable 对象的特点就是 Immutable 对象一旦创建就无法修改,如果数据发生变化会产生一个新的 Immutable 对象,如果没有发生变化则会返回原来的 Immutable 对象的引用,因此非常好的契合上述场景。此外,Immutable 对象修改时所创建的新的对象采用优化算法,开销较小,并且有利于实现变化追踪技术。

Immutable 对象被视为值,所以 Immutable 对象在进行 === 比较时是值比较;

// 如果 Immutable 对象未发生改变,则 Immutable 对象是相等的
const map1 = Map({a: 1, b: 2, c: {d: 1}});
const map2 = Map({a: 1, b: 2, c: {d: 1}});
const map3 = map1.set('a', 1);
map1.equals(map2); // true
map1 === map2; // true
map3 === map1; // true  由于值为发生变化

// 如果值发生变化,则 Immutable 对象返回一个新的 Immutable 对象
const map4 = map1.set('a', 2);
map4 === map1; // false

map1.get('a'); // 1
map1.set('a', 1);
map1.getIn(['a', 'c', 'd']); // 1
map1.setIn(['a', 'c', 'd'], 2); // {a: 1, b: 2, c: {d: 2}}
map1.updateIn(['a', 'c', 'd'], value => value + 1); // {a: 1, b: 2, c: {d: 2}}

上一篇下一篇

猜你喜欢

热点阅读