Skip to content

Instantly share code, notes, and snippets.

@yuretz
Last active May 18, 2018 19:30
Show Gist options
  • Save yuretz/cdba9cb6a0d74479da0570f5ee357cc0 to your computer and use it in GitHub Desktop.
Save yuretz/cdba9cb6a0d74479da0570f5ee357cc0 to your computer and use it in GitHub Desktop.
hyperHTML + Redux + TypeScript = Awesomeness
import hyper from 'hyperhtml/esm';
import { applyMiddleware, combineReducers, createStore, Store, Unsubscribe, Action, Dispatch } from 'redux';
import { Selector } from 'reselect';
export interface AppState {
// ...
// your app state interface here
}
export const appStateStore = createStore<AppState>(
combineReducers({
// ... some AppState reducers here
}),
applyMiddleware(/* ... some middleware here */)
);
/**
* Initializes the view instance
*
* @param view View instance to initialize
* @param selectors List of selectors to subscribe the view to
*/
export function initView<V extends View<any>, T = InferModelType<V>>(
view: V,
...selectors: Selector<AppState, Partial<T>>[]
): V {
view.setDispatch(appStateStore.dispatch);
selectors.forEach((item) => subscribeView(appStateStore, view, item));
return view;
}
/**
* Base class for all view classes
*
* @template T The view's model type
*/
export class View<T> extends hyper.Component<T> {
constructor(initialState?: T | undefined) {
super();
if(initialState) {
this.setState(initialState);
}
}
/**
* Returns the value indicating whether the view is subscribed
*/
get subscribed(): boolean {
return !!this._unsubscribes.length;
}
/**
* Passes the dispatch function to the view
*
* @param dispatch The dispatch function
* @memberof View
*/
setDispatch(dispatch: Dispatch<Action>) {
this.dispatch = dispatch;
}
/**
* Registers the view subscription
*
* @param {() => Unsubscribe} subscription The subscribe function
* @memberof View
*/
register(subscription: () => Unsubscribe) {
this._subscribes.push(subscription);
}
/**
* View disconnection event handler
*
* @protected
* @memberof View
*/
protected ondisconnected() {
// call all the unsubscribe() functions
this._unsubscribes.forEach(Function.call.bind(Function.call));
this._unsubscribes = [];
}
/**
* View connection event handler
*
* @protected
* @memberof View
*/
protected onconnected() {
// call all the subscribe() and save all unsubscribe() functions
this._unsubscribes = this._subscribes.map(Function.call.bind(Function.call));
}
/**
* Event dispatching function
*
* @memberof View
*/
protected dispatch: Dispatch<Action> = (_: any) => {throw new Error('dispatch missing'); };
private _subscribes: (() => Unsubscribe)[] = [];
private _unsubscribes: Unsubscribe[] = [];
}
// type inference helper
type InferModelType<V> = V extends View<infer M> ? M : never;
/**
* Subscribes the view to updates from the Redux store
*
* @export
* @template TAppState Application state type
* @template TModel View state type
* @param {Store<TAppState>} store The Redux store
* @param {EpokeView<TModel>} view The view instance
* @param {Selector<TAppState, Partial<TModel>>} selector The view state selector function
*/
function subscribeView<TAppState, TModel>(
store: Store<TAppState>,
view: View<TModel>,
selector: Selector<TAppState, Partial<TModel>>
) {
view.register(function subscribe() {
let current = selector(store.getState());
view.setState(current);
return store.subscribe(function listener() {
const state = selector(store.getState());
if (state !== current) {
current = state;
view.setState(state);
}
});
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment