SugarTurboS/rc-redux-model

rc-redux-model 介绍

PDKSophia opened this issue · 0 comments

Why rc-redux-model ?

相信大家都了解 redux,并且也认同这种数据流的方式(毕竟不认同,你也不会用嘛~),然,世间万物,皆有利弊。

以我为例,每次起一个项目,我都需要 :

  • 脚手架 create-react-app 快速生成一个应用框架,进行开发
  • 安装 redux 进行数据状态管理
  • 安装 react-redux ,调用 Provider 提供者模式,使得子组件都能取到 store 值
  • 如果想要解决异步请求,我也许还需要安装一个 redux-saga / redux-thunk
  • 如果想看到日志,那么我还会安装 redux-logger
  • ...

看似一顿操作猛如虎,其实心中已经 MMP,我会想,这么多前置工作,是不是让我的开发成本更高了呢?

为了解决异步 Action,我需要按照 redux-saga 或者 redux-thunk,从而处理异步问题,以 redux-saga 为例 :

在使用中,我发现 redux + redux-saga 让我的 [重复性] 工作变多(逐步晋升 CV 工程师),因为它存在啰嗦的样板代码。

举个 🌰 : 异步请求,获取用户信息,我需要创建 sagas/user.jsreducers/user.jsactions/user.js,为了统一管理 const,我还会有一个 const/user.js,然后在这些文件之间来回切换。

分文件应该是一种默认的规范吧?反正我实习的时候,是分文件的;现在组里,是分文件的;看一些优秀库(star 多),也是分文件的;包括看 dva 的介绍,它也提到了问题

// const/user.js
const FETCH_USER_INFO = 'FETCH_USER_INFO'
const FETCH_USER_INFO_SUCCESS = 'FETCH_USER_INFO_SUCCESS'
// actions/user.js
export function fetchUserInfo(params, callback) {
  return {
    type: FETCH_USER_INFO,
    params,
    callback,
  }
}
// sagas/user.js
function* fetchUserInfoSaga({ params, callback }) {
  const res = yield call(fetch.callAPI, {
    actionName: FETCH_USER_INFO,
    params,
  })
  if (res.code === 0) {
    yield put({
      type: FETCH_USER_INFO_SUCCESS,
      data: res.data,
    })
    callback && callback()
  } else {
    throw res.msg
  }
}
// reducers/user.js
function userReducer(state, action) {
  switch (action.type) {
    case FETCH_USER_INFO_SUCCESS:
      return Immutable.set(state, 'userInfo', action.data)
  }
}

没错, 这种样板代码,简直就是 CV 操作,只需要 copy 一份,修改一下名称,对我个人而言,这会让我不够专注,分散管理 const、action、saga、reducer 一套流程,需要不断的跳跃思路。

而且文件数量会变多,我是真的不喜欢如此繁琐的流程,有没有好的框架能帮我把这些事都做完呢?

dva

世间万物存在,必然有它自身的价值和意义。dva 的出现,肯定是解决了一些问题。我们看看 dva 官网怎么说的 ~~

dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。

有意思,但是因为 dva 身负重任,对我而言,使用它太过于“笨重”,我只是想取其精华,去其内置,我就只想用它写状态管理的方式: 在 model 里边,写完 reducer, state, action

再一次与 JPL 同学交流的过程中,发现他也有这种想法,同时他已经写了一个简单的中间件,在他们组里用了起来,出于学习以及如何写一个中间件,在参考它的代码之后,我也开始尝试写一个 redux 的中间件,并且内部支持默认的 action,让开发更加简洁,释放键盘上的 C 与 V ,同时对于 Immutable 的支持以及错误类型的检测,让你的 state 更加“合法”

于是 rc-redux-model 就这样出现了...

What's rc-redux-model ?

rc-redux-model 是一个中间件,提供一种更为简洁和方便的数据状态管理[书写方式]。参考了 dva 的数据流方案,在一个 model 文件中写所有的 actionreducerstate,解读了 redux-thunk 的源码,内部实现了一个中间价,同时提供默认行为 action,调用此 action 可以直接修改任意值的 state,例如 :

只需要定义一个 model

export default {
  namespace: 'reduxModel',
  state: {
    testA: '',
    testB: [],
  },
}

那么 rc-redux-model 会自动帮你注册 action 及 reducers,等价于 action 和 reducers 不用你自己写了,如下 :

export default {
  namespace: 'reduxModel',
  state: {
    testA: '',
    testB: [],
  },
  action: {
    settestA: ({ commit, currentAction }) => {
      commit({
        type: 'SET_REDUXMODEL_TESTA',
        payload: currentAction.payload,
      })
    },
    settestB: ({ commit, currentAction }) => {
      commit({
        type: 'SET_REDUXMODEL_TESTB',
        payload: currentAction.payload,
      })
    },
    // 推荐使用此action进行修改reducers值
    setStore: ({ dispatch, currentAction }) => {
      dispatch({
        type: `reduxModel/change${currentAction.payload.key}`,
        payload: currentAction.payload.values,
      })
    },
  },
  reducers: {
    ['SET_REDUXMODEL_TESTA'](state, payload) {
      return {
        ...state,
        ...payload,
      }
    },
    ['SET_REDUXMODEL_TESTB'](state, payload) {
      return {
        ...state,
        ...payload,
      }
    },
  },
}

那么你只需要在组件中,调用的默认 Action 即可

class MyComponent extends React.Component {
  componentDidMount() {
    this.props.dispatch({
      type: 'reduxModel/setStore',
      payload: {
        key: 'testA',
        values: '666',
      },
    })
  }
}

hooks ?

hooks 的出现,让我们看到了处理复杂且重复逻辑的曙光,那么问题来了,在 hooks 中能不能用 rc-redux-model ,我想说 : “想啥呢,一个是 react 的特性,一个是 redux 的中间件, 冲突吗?”

// Usage with React Redux: Typing the useSelector hook & Typing the useDispatch hook
// https://redux.js.org/recipes/usage-with-typescript#usage-with-react-redux
import { useDispatch } from 'react-redux'

export function useFetchUserInfo() {
  const dispatch = useDispatch()
  return async (userId: string) => {
    // 这里我选择自己处理异步,异步请求完后,再把数据传到 reducer 中
    const res = await callAPI(userId)
    if (res.code === 200) {
      dispatch({
        type: 'userModel/setStore',
        payload: {
          key: 'userInfo',
          values: res.data,
        },
      })
    }
  }
}

强调说明

rc-redux-model 出发点在于解决我繁琐重复的工作,store 文件分散,state 类型和赋值错误的问题,为此,对于跟我一样的用户,提供了一个写状态管理较为[舒服]的书写方式,大部分情况下兼容原先项目~

  • 为了解决[store 文件分散],参考借鉴了 dva 写状态管理的方式,一个 model 中写所有的 action、state、reducers
  • 为了解决[繁琐重复的工作],提供默认的 action,用户不需要自己写修改 state 的 action,只需要调用默认提供的 [model.namespace/setStore] 即可,从而将一些重复的代码从 model 文件中剔除
  • 为了解决[state 类型和赋值错误],在每次修改 state 值时候,都会进行检测,如果不通过则报错提示

How to use

FAQ

可在现有的项目中兼容使用,具体使用方式,可参考完整例子

其他文章