resourcex v0.1.2
resourcex
resourcex - Utilities to wrap global variables into rxjs observables with reduced learning curve
This project provides a few handy utilities to make global variables slightly easier to work with, comparing to traditional Flux Architecture.
By using the utilities inside, plus some observable enhancement tools in your favorite MVVM framework, global store can be organized into a set of highly structured resources. Each resource exposes its value and corresponding mutating actions. In this way, frontend applications can integrate with backend RESTful API with less boilerplate.
Since this is a very light encapsulation on top of rxjs, anyone who doesn't mind delving into the design patterns of functional reactive programming can easily make something more elaborate from these basic tools.
Example
Both Vue and React examples can be found inside here.
// api.js
export async function getProfile() {
return { uid: 10086, name: 'ResouceX' };
}
export async function setName(name) {
return { success: true };
}
// resources.js
import { Resource } from 'resourcex';
import { getProfile, setName } from './api';
export const Profile = Resource(
{ uid: 0, name: '', version: 0 },
{
async get() {
const profile = await getProfile();
return { ...profile, version: 1 };
},
increaseVersion({ version }) {
return { version: version + 1 };
},
async setName(_, name) {
const { success } = await setName(name);
if (success) return { name };
else throw Error('update-failed');
},
},
);Vue
For integrating with Vue, it's recommended to install vue-rx to ensure observable unregistrations are done correctly on unmount.
import { Profile } from '../resources';
new Vue({
el: '#app',
template: `
<div>
<h1>Hello, {{ profile$.name }}!</h1>
<h2>{{ profile$.uid }} called {{ profile$.version }} times</h2>
<button @click="setName('New Name')">Set New Name</button>
<button @click="incProfileVersion">Bump Version</button>
</div>
`,
subscriptions() {
return { profile$: Profile };
},
methods: {
setName(name) {
Profile.setName(name); // <- discard original return
},
async incProfileVersion() {
const { version } = await Profile.increaseVersion();
console.log(version); // <- captured original return
},
},
created() {
Profile.get();
},
});React
For integrating with React, consider using resource-react-hook.
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import useResource from 'resource-react-hook';
import { Profile } from '../resources';
const App = () => {
const profile$ = useResource(Profile);
// on creation do a round of fetch
useEffect(() => {
Profile.get();
}, []);
return (
<div>
<h1>Hello, {profile$.name}!</h1>
<h2>
{profile$.uid} called {profile$.version} times
</h2>
<button onClick={() => Profile.setName('New Name')}>Set New Name</button>
<button onClick={Profile.increaseVersion}>Bump Version</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('app'));API
Resources
Resource(initial, actions?, middleware?, initHook?)Creates a resource (as an rxjs
BehaviorSubject) with (optionally)actionsdefining how to change its value. Each action is a function that- Takes in as first parameter
state, which is the current value of the subject - Returns a value which would be
Object.assigned into the originalstate
Therefore, the value
statecould be destructurally assigned. It could be left as_or skipped with a comma if there's no dependency on previous value. Return value ofResource.action(..args)respect the the original action function.document and examples for
middlewareandinitHookare under construction hohoho- Takes in as first parameter
NaiveResource(initial)- Almost the same as a
Resourceexcept that it only exposes asetaction
create a resource with an initial value, expose a
setcall to update its value- Almost the same as a
LocalStorageResource(localStorageKey, initial, actions?)- Almost the same as a
Resource, just that the value is persisted into local storage with key specified
- Almost the same as a
Operators
resourcex also exposes a few rxjs operators for easier use, esp in transforming DOM events:
catchMergeMap/catchFlatMapcatchConcatMapcatchSwitchMapcatchExhaustMap
The are analagous to the counterparts mergeMap / flatMap, concatMap, switchMap and exhaustMap in rxjs.
They respect the same flattening strategy and simply wraps over them for error handling,
so that on error thrown the source observables are preserved.
E.g. click$ is the click event stream on a button,
the following line triggers Profile.get call with exhaustive strategy:
click$.pipe(catchExhaustMap(Profile.get)).subscribe()
.subscribe()is an empty subscription to kick start the streaming of events- Here is a good article explaining what flattening strategies rxjs provides
Change Log
- 0.1.2: Fix
NaiveResourcenull returning bug (also the undocumented middleware API) - 0.1.1: Allow empty
actionsas pure, unmutatable global variable - 0.1.0: Major rewrite to return
ResourceasSubjectdirectly, and provide separate operators - 0.0.1 ~ 0.0.9: Pilot versions with
$as exposedSubjectand no access to current state; not recommended anymore
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago