hbus

An event bus lib.

Usage no npm install needed!

<script type="module">
  import hbus from 'https://cdn.skypack.dev/hbus';
</script>

README

HBus

An event bus lib.

This lib helps you deal with the state of your application. Define an action processor, then publish actions anywhere and subscribe to the changes of state or even specified properties.

Example

// Use as an es module:
import { Bus } from "hbus";
// or a umd module:
const { Bus } = HBus;
// Define a processor:
function processor(state, action) {
    // ...
}
// Initialize state:
const defaultState = { foo: 'bar' };
// Create a bus:
const bus = new Bus(processor, defaultState);
// Subscribe to changes:
bus.subscribe(state => {
    // ...
}).subscribeProp('foo', prop => {
    // ...
});
// Publish an action:
bus.publish(
    new HBus.Action(
        'SOME_ACTION_TYPE',
        {
            // Custom payload... 
        }
    )
);

Usage

Install via npm:

npm i hbus

Load from unpkg.com:

<script crossorigin="anonymous" src="https://unpkg.com/hbus"></script>

APIs

Action

new HBus.Action(type, payload?);

A type is required for an action and custom data can be stored as payload.

An action object is just like this:

{
    type: 'SOME_ACTION_TYPE',
    payload: {
        foo: 'bar'
    }
}

So, you can also use a javascript object as an action, e.g. { type: 'FOO', payload: 'bar' }.

Bus

new HBus.Bus(processor, defaultState?);

This is the bus constructor. You need to pass an action processor to it and you can pass the default state.

processor

bus.processor(state, action): void | State;

The processor handles the actions and updates the state.

For instance:

function processor(state, action) {
    switch (action.type) {
        case 'INC_A':
            // You can change the properties
            // on the `state` directly: 
            // (In fact, it is a shallow
            // copy. So, `state.a++` works
            // correctly, but `state.b.c++`
            // doesn't because it will
            // change the original state
            // and leads to a bug.)
            state.a++;
            break;
        case 'INC_B':
            // If you want to change deep
            // properties or replace the
            // `state`, just return a new
            // `state` like this: (Do
            // not forget to copy other
            // properties if there're any.)
            return {
                // ...state,
                b: {
                    // ...state.b,
                    c: state.b.c + 1
                }
            };
        default:
            // ...
            break;
    }
}

comparer

bus.comparer(oldState, newState): boolean;

The comparer compares the two states and tells whether they are the same. If it returns true than bus will think that there's no changes and no subscribers will be called. By default, it is set to HBus.defaultComparer which returns whether the two state are deeply equal(e.g. { a: { b: NaN } } equals { a: { b: NaN } }, but it doesn't equal { a: { b: NaN, c: undefined } }). You can set a custom one if needed.

getState

bus.getState(): State;

This method returns the current state. Please note that the state are updated asynchronously, so use requestState if you need the state that may be changed next tick.

requestState

bus.requestState(callback): this;

The callback will be called next tick and receive newer state. To improve the performance, actions are processed asynchronously. So:

// An action is published here:
bus.publish({ type: 'FOO_BAR' });
// This doesn't work correctly because
// the action hasn't been processed
// and state hasn't been updated:
console.log(bus.getState());
// This will work correctly because
// the callback will be called at
// the right time:
bus.requestState(newerState => {
    console.log(newerState);
});

publish

bus.publish(action): this;

Call this method to publish an action.

subscribe

bus.subscribe(subscriber): this;

You can call this method to subscribe to all changes on the state.

unsubscribe

bus.unsubscribe(subscriber): this;

You can call this method to remove a subscriber.

clearSubscribers

bus.clearSubscribers(): this;

This method clears the subscribers registed by subscribe.

subscribeProp

bus.subscribeProp(propName, subscriber): this;

You can use this to subscribe to changes on a property of the state. The subscriber will only receive the specified property instead of the entire state.

unsubscribeProp

bus.unsubscribeProp(propName, subscriber): this;

Call this to remove a property subscriber.

clearPropSubscribers

bus.clearPropSubscribers(propName): this;

This method clears all the subscribers to a specified property.

clearAllPropSubscribers

bus.clearAllPropSubscribers(): this;

This method clears all the property subscribers.

clearAllSubscribers

bus.clearAllSubscribers(): this;

Call this method to clear all the subscribers.

createActionFactory

HBus.createActionFactory(type, defaultPayload?): ActionFactory;

This method returns an action factory which receives some data as payload and returns an action of a specified type. You can also pass the default payload to it.

// Create an action factory for action A:
const actionA = HBus.createActionFactory('A', {
    foo: 'foo',
    hello: 'world'
});
// Use it like this:
bus.publish(actionA({ foo: 'bar' }));
// (equals) bus.publish({ type: 'A', payload: { foo: 'bar', hello: 'world' } });

createProcessor

HBus.createProcessor(processorMap, defaultProcessor?): Processor;

This is a utility method that helps you create a processor. You need to pass an object to it. Each pair of the object stands for an action type and a processor for it. You can also pass a default processor which handles the actions whose types are not in processorMap.

For example:

const combinedProcessor = HBus.createProcessor({
    foo(state, action) {
        // Do sth. to `state` or return a new one.
    },
    bar(state, action) {
        // Do sth. to `state` or return a new one.
    }
}, (state, action) => {
    // Do sth. to `state` or return a new one.
});
// The above is similar to the following:
const combinedProcessor = (state, action) => {
    switch (action.type) {
        case 'foo':
            // Do sth. to `state` or return a new one.
            break;
        case 'bar':
            // Do sth. to `state` or return a new one.
            break;
        default:
            // Do sth. to `state` or return a new one.
            break;
    }
};

ticker

HBus.ticker

The ticker receives update requests from buses internally and handles them every tick.

tickMethod

HBus.ticker.tickMethod(callback): void;

This method handles the callback and is used internally. By default, it is set to HBus.defaultTickMethod which directly pass the callback to requestAnimationFrame. You can set your custom one if needed.

Changelog

See CHANGELOG.md