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
            )
          )
        })
      )
    }
  }
}