API Reference
Ship<Effect, Commit, State, A>
Snapshot<Effect, Commit>
all
call
commit
getState
map
middleware
run
simulate
snap
Ship<Effect, Commit, State, A>
The type of a ship. A ship is a generator and can be defined using the function*
syntax.
Effect
the type of the side effects the ship can call. We often use a singleEffect
type for a whole programCommit
the type of the commits the ship can commitState
the type of the state as visible from the shipA
the type of the value returned by the ship (oftenvoid
)
Example
A controller to load a random gif:
export function* control(action: Action): Ship.Ship
switch (action.type) {
case 'Load': {
yield* Ship.commit({
type: 'LoadStart',
});
const result = yield* Effect.httpRequest(
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${action.tag}`
);
const gifUrl: string = JSON.parse(result).data.image_url;
yield* Ship.commit({
type: 'LoadSuccess',
gifUrl,
});
return;
}
default:
return;
}
}
Snapshot<Effect, Commit>
The type of the snapshot of an execution of a ship. A snapshot includes the side effects ran by a ship, as well as their execution order (sequential or concurrent). You can take a snapshot with the redux-ship-logger dev tools or with the snap
function.
Effect
the type of effects in the snapshotCommit
the type of commits in the snapshot
Example
The snapshot of a controller loading a random gif:
[
{
"type": "Commit",
"commit": {
"type": "LoadStart"
}
},
{
"type": "Effect",
"effect": {
"type": "HttpRequest",
"url": "http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=minion"
},
"result": [...]
},
{
"type": "Commit",
"commit": {
"type": "LoadSuccess",
"gifUrl": "http://media3.giphy.com/media/HyanD1KpfzPiw/giphy.gif"
}
}
]
all
all<Effect, Commit, State, A>(
ships: Ship<Effect, Commit, State, A>[]
): Ship<Effect, Commit, State, A[]>
Returns the array of results of the ships
by running them in parallel. This is the equivalent of Promise.all
in Ship.
ships
an array of ships to execute concurrently
If you have a fixed number of tasks with different types of result to run in parallel, use:
all2(ship1, ship2)
all3(ship1, ship2, ship3)
...
all7(ship1, ship2, ship3, ship4, ship5, ship6, ship7)
The result type is a tuple with the same types as the arguments:
all3<Effect, Commit, State, A1, A2, A3>(
ship1: Ship<Effect, Commit, State, A1>,
ship2: Ship<Effect, Commit, State, A2>,
ship3: Ship<Effect, Commit, State, A3>
): Ship<Effect, Commit, State, [A1, A2, A3]>
Example
To concurrently get three random gifs:
const gifUrls = yield* Ship.all(['cat', 'minion', 'dog'].map(function* (tag) {
const result = yield* Effect.httpRequest(
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${tag}`
);
return JSON.parse(result).data.image_url;
}));
call
call<Effect, Commit, State>(
effect: Effect
): Ship<Effect, Commit, State, any>
Calls the serialized effect effect
. The type of the result is any
because it depends on the value of the effect, which is only known at runtime.
effect
the effect to call
Example
To prevent type errors, we recommend to wrap your calls to call
with one wrapper per kind of effect. For example, if you have an effect HttpRequest
which always returns a string
:
export function httpRequest<Commit, State>(url: string): Ship<Effect, Commit, State, string> {
return Ship.call({
type: 'HttpRequest',
url,
});
}
commit
commit<Effect, Commit, State>(
commit: Commit
): Ship<Effect, Commit, State, void>
Commits a commit of type Commit
and waits for its termination.
commit
the commit to apply
Example
To commit the result of a successful HTTP request:
yield* Ship.commit({
type: 'LoadSuccess',
gifUrl,
});
getState
getState<Effect, Commit, State, A>(
selector: (state: State) => A
): Ship<Effect, Commit, State, A>
Returns a part of the current state by applying a selector.
selector
a selector to extract the useful part of the current state
Example
To get the current gif in the store:
const currentGif = yield* Ship.getState(state => state.randomGif.gifUrl);
map
map<Effect, SubCommit, SubState, Commit, State, A>(
liftCommit: (subCommit: SubCommit) => Commit,
extractState: (state: State) => SubState,
ship: Ship<Effect, SubCommit, SubState, A>
): Ship<Effect, Commit, State, A>
A function useful to compose nested components. Lifts a ship
with access to "small set" of commits SubCommit
and a "small set" of states SubState
to a ship with access to the "larger sets" Commit
and State
.
liftCommit
lifts a sub-commit to a commitextractState
extract a sub-state from a stateship
the ship to map
Example
To embed a controller retrieving one random gif into a controller retrieving two random gifs:
return yield* Ship.map(
commit => ({type: 'First', commit}),
state => state.first,
RandomGifController.control(action.action)
);
middleware
middleware<Action, Effect, Commit, State>(
runEffect: (effect: Effect) => any | Promise<any>,
control: (action: Action) => Ship<Effect, Commit, State, void>
): ReduxMiddleware
Returns a Redux middleware to connect Redux Ship to Redux.
runEffect
the function to evaluate a serialized side effect (may return a promise for asynchronous effects)control
the function to evaluate anaction
with a ship
The Ship middleware eats all actions: it does not let actions propagate to the next middleware. If you want an action to propagate use Ship.commit
explicitly.
When a Ship.commit
occurs, the commit is dispatched to the next middleware rather than to the whole middleware chain. This is so to prevent loops of commits. Thus, this is not possible to call another ship with Ship.commit
. Instead, use the yield*
keyword to directly call the corresponding ship.
Example
import {applyMiddleware, createStore} from 'redux';
const middlewares = [
Ship.middleware(runEffect, control),
otherMiddleware
];
const store = createStore(reduce, initialState, applyMiddleware(...middlewares));
run
run<Effect, Commit, State, A>(
runEffect: (effect: Effect) => any | Promise<any>,
store: {
dispatch: (commit: Commit) => void | Promise<void>,
getState: () => State
},
ship: Ship<Effect, Commit, State, A>
): Promise<A>
Runs a ship and its side effects by evaluating each primitive effect with runEffect
and interpreting each call to Redux with the store
.
runEffect
the function to evaluate a serialized side effect (may return a promise for asynchronous effects)store
the current Redux storeship
the ship to execute
Example
To run Redux Ship with a Redux store store
, you can do:
Ship.run(runEffect, store, ship);
where runEffect
is your function to evaluate your side effects:
export type Effect = {
type: 'HttpRequest',
url: string,
};
export async function runEffect(effect: Effect): Promise<any> {
switch (effect.type) {
case 'HttpRequest': {
const response = await fetch(effect.url);
return await response.text();
}
default:
return;
}
}
simulate
simulate<Effect, Commit, State, A>(
ship: Ship<Effect, Commit, State, A>,
snapshot: Snapshot<Effect, Commit>
): Snapshot<Effect, Commit>
Simulates a ship
in the context of a snapshot
and returns the snapshot of the simulation. A simulation is a purely functional (with no side effects) execution of a ship. Useful for regression testing.
ship
the ship to simulatesnapshot
a snapshot of a previous execution of the ship
To simulate a ship, we need a snapshot of a previous live execution because there are many ways to execute a ship. For example, if the ship starts an API request, we do not know which API answer to provide. In this case, we use a snapshot which already contains an API answer. Since the simulation always gives the same API answers as in the snapshot, the ship
should behave the same as when its snapshot was taken. In particular, the result of simulate
should be equal to snapshot
, unless the implementation of ship
had a regression since its snapshot was taken.
Example
In a unit test of a controller:
expect(Ship.simulate(control(action), snapshot)).toEqual(snapshot);
snap
snap<Effect, Commit, State, A>(
ship: Ship<Effect, Commit, State, A>
): Ship<Effect, Commit, State, {
result: A,
snapshot: Snapshot<Effect, Commit>
}>
Returns a ship behaving as ship
but taking the snapshot of its own execution.
ship
the ship to take in picture
Example
To take the snapshot of a ship:
const {result, snapshot} = yield* Ship.snap(ship);
To take the snapshot of a controller (the result
should always be undefined
):
const {snapshot} = yield* Ship.snap(control(action));