From 32068e8b36cb7a23243829d5ef608deee70f2d13 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 3 Jan 2019 10:55:37 -0500 Subject: [PATCH] Data: Support subscribers per reducer key(s) --- packages/data/src/namespace-store.js | 2 +- packages/data/src/registry.js | 74 ++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/packages/data/src/namespace-store.js b/packages/data/src/namespace-store.js index dc0c0075fd6201..1f2dceb74201d5 100644 --- a/packages/data/src/namespace-store.js +++ b/packages/data/src/namespace-store.js @@ -54,7 +54,7 @@ export default function createNamespace( key, options, registry ) { lastState = state; if ( hasChanged ) { - listener(); + listener( key ); } } ); }; diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index acca73b93129a3..351fe72bfcc12c 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -2,6 +2,7 @@ * External dependencies */ import { + castArray, without, mapValues, } from 'lodash'; @@ -12,6 +13,16 @@ import { import createNamespace from './namespace-store.js'; import dataStore from './store'; +/** + * Given an array of functions, invokes each function in the array with an + * empty argument set. + * + * @param {Function[]} fns Functions to invoke. + */ +function invokeForEach( fns ) { + fns.forEach( ( fn ) => fn() ); +} + /** * An isolated orchestrator of store registrations. * @@ -40,27 +51,72 @@ import dataStore from './store'; */ export function createRegistry( storeConfigs = {} ) { const stores = {}; - let listeners = []; + let globalListeners = []; + const listenersByKey = {}; /** * Global listener called for each store's update. + * + * @param {?string} reducerKey Key of reducer which changed, if provided by + * the registry implementation. */ - function globalListener() { - listeners.forEach( ( listener ) => listener() ); + function onStoreChange( reducerKey ) { + invokeForEach( globalListeners ); + + if ( reducerKey ) { + if ( listenersByKey[ reducerKey ] ) { + invokeForEach( listenersByKey[ reducerKey ] ); + } + } else { + // For backwards compatibility with non-namespace-store, reducerKey + // is optional. If omitted, call every listenersByKey. + for ( const [ , listeners ] of Object.entries( listenersByKey ) ) { + invokeForEach( listeners ); + } + } } /** * Subscribe to changes to any data. * - * @param {Function} listener Listener function. + * @param {Function} listener Listener function. + * @param {?(string|Array)} reducerKeys Optional subset of reducer + * keys on which subscribe + * function should be called. * - * @return {Function} Unsubscribe function. + * @return {Function} Unsubscribe function. */ - const subscribe = ( listener ) => { - listeners.push( listener ); + const subscribe = ( listener, reducerKeys ) => { + if ( reducerKeys ) { + // Overload to support string argument of `reducerKeys`. + reducerKeys = castArray( reducerKeys ); + + reducerKeys.forEach( ( reducerKey ) => { + if ( ! listenersByKey[ reducerKey ] ) { + listenersByKey[ reducerKey ] = []; + } + + listenersByKey[ reducerKey ].push( listener ); + } ); + + return () => { + reducerKeys.forEach( ( reducerKey ) => { + listenersByKey[ reducerKey ] = without( + listenersByKey[ reducerKey ], + listener + ); + + if ( ! listenersByKey[ reducerKey ].length ) { + delete listenersByKey[ reducerKey ]; + } + } ); + }; + } + + globalListeners.push( listener ); return () => { - listeners = without( listeners, listener ); + globalListeners = without( globalListeners, listener ); }; }; @@ -122,7 +178,7 @@ export function createRegistry( storeConfigs = {} ) { throw new TypeError( 'config.subscribe must be a function' ); } stores[ key ] = config; - config.subscribe( globalListener ); + config.subscribe( onStoreChange ); } let registry = {