Concepts
Before creating a reobservable app, there are only two concepts you should konw.
Model
Same as dva, model defines a state machine which mixined initial state,sync actions, async actions(flows), reducers in one place.
import { Model } from '@reobservable'
const model: Model<State, UserState> = {
name: 'user',
state: {
list: [],
pagination: {
totolCount: 0,
page: 0,
pageSize: 10
}
},
reducers: {},
flows: {}
}
export default model
As shown above, a model consists of five parts:
name
Model name, it indicates the scope of a model in redux store. In this example, we could get the user state by calling store.getState().user
. Or in React component, we use react-redux:
const mapStateToPros = (state: IState) => {
return {
totalCount: state.user.totalCount
}
}
state
Initial state of a model.
reducers
Redux reducers. When a synchronous action dispatched, depends on its type, it may hit a reducer to return a new state. For example, if we dispatched an action:
{
type: 'user/fetchSuccess',
payload: {
list: [{nick: 'john', age: 21}, {nick: 'tom', age: 32}],
totalCount: 2
}
}
then reobservable will lookup user model to determine if there was a fetchSuccess
in the defined reducers. If user model has fetchSuccess
reducer, it will be called, and then, we would get a new state of the user model.
flows
Async flows. Every flow defined in model flows have four parameters, and return an ActionObservable
(An observable which emit action):
const model = {
name: 'user',
// ....
flows: {
fetch(flow$, action$, payload$, dependencies) {
return flow$.pipe(
mapTo({
type: 'user/fetchSuccess',
payload: {}
})
)
}
}
}
It's signature is very similar to the epic in redux-observable. The difference is the first parameter - flow$
, it is equivalent to action$.ofType('model/flow')
.
For example, if we dispatched an action:
{
type: 'user/fetch',
payload: {}
}
fetch
flow return in the user model will be responded.
Service
Service describe the communication between frontend and backend, in reobservable, you can define multiple services:
import { AxiosResponse, AxiosError } from 'axios'
import { ServiceConfig, ServiceFunc } from '@reobservable/core'
interface ApiService extends ServiceConfig<AxiosResponse<{data: any}>, AxiosError> {}
const api: ApiService = {
templates: {
success: (resp) => 'ok!',
error: (error) => 'error!'
}
}
export default api
In reobservable, service was a dependency injected into model flows, and could be used in frp way:
const model = {
// ....
flows: {
fetch(flow$, action$, payload$, dependencies) {
const { api } = dependencies.services
return flow$.pipe(
switchMap(action => {
const [success$, error$] = api(
'fetch',
fetch(action.payload.id)
)
return merge(
success$.pipe(
// success stream
),
error$.pipe(
// error stream
)
)
})
)
}
}
}