记一次taro项目中对redux模板代码的封装

Redux 是一个 JavaScript 应用状态管理的库,它帮助你编写行为一致,并易于测试的代码,而且它非常迷你,只有 2KB。

Redux 有一点和别的前端库或框架不同,它不单单是一套类库,它更是一套方法论,告诉你如何去构建一个状态可预测的应用。

但是同时在使用 redux 的过程中,需要写很多的模板代码,比如在接下来通过一个例子将会看到

首先先来温习一下 redux 吧~

redux 的三大原则
  • 单一数据源
    这使得来自服务端的 state 可以轻易地注入到客户端中;并且,由于是单一的 state 树,代码调试、以及“撤销/重做”这类功能的实现也变得轻而易举。
  • 只读的 state
    唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
  • 使用纯函数进行修改
    为每个 action 用纯函数编写 reducer 来描述如何修改 state 树

    纯函数即满足以下两点 1.函数在有相同的输入值时,产生相同的输出 2.函数中不包含任何会产生副作用的语句

reducer 要做到只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,只进行单纯执行计算。

通过一个 todolist 的例子来思考一下模板语法在何处以及如何才难减少
  • 入口文件
    index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'

const store = createStore(rootReducer)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
  • 创建 actino
    actions/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})

export const setVisibilityFilter = filter => ({
type: 'SET_VISIBILITY_FILTER',
filter
})

export const toggleTodo = id => ({
type: 'TOGGLE_TODO',
id
})
  • 编写 reducers

reducers/todos.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
)
default:
return state
}
}

export default todos

reducers/index.js

1
2
3
4
5
6
7
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
todos
})

分析例子并思考如何进行封装

对这段简短的 redux 代码进行分析,发现其实单独看每一步已经比较精简了,但是当步骤多了之后,尤其是创建 action 的过程中,基本都是繁琐的代码,改变的几乎只有 data,我们在具体组件调用的时候只会接触到 data,思路来了!封装一个函数,暴露出 data 即可,说干就干!下面来看下在小程序项目中我对其的封装

utils/createAction.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Taro from "@tarojs/taro";
import { HOST } from "./config";
const createActionSucess = (TYPE, data) => {
return {
type: TYPE,
payload: data
};
};
const createActionFail = data => {
return {
type: "GET_FAIL",
payload: data
};
};
export default function fetchData(option, TYPE) {
return async dispatch => {
Taro.showLoading({
title: "loading..."
});
const response = await Taro.request({
url: HOST + option.url,
method: option.method || "GET",
data: option.data || ""
});
const res = response.data;
if (res.code === 0) {
Taro.hideLoading();
dispatch(createActionSucess(TYPE, res.data));
} else {
Taro.hideLoading();
dispatch(createActionFail(res.data));
}
return res;
};
}

有了这个工具函数,再来看看 action 的创建
actions/classInfo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import Taro from "@tarojs/taro";
import {
GET_USER_CLASS,
GET_ALL_CLASS,
JOIN_CLASS,
GET_USER_CLASS_PLAN,
GET_CLASS_CMT,
GET_CLASS_FAIL,
GET_CLASS_MSG
} from "../constants/classInfo";
import fetchData from "../service/createAction";
import { getLoginInfo } from "../utils/getlocalInfo";

//处理错误情况
export const getClassFail = data => {
return {
type: GET_CLASS_FAIL,
payload: data
};
};

//获取用户已加入班级列表
export const ajaxGetUserClass = data => {
const option = {
url: "/getuserclazzlist",
method: "POST",
data
};
return fetchData(option, GET_USER_CLASS);
};

//获取全部班级列表
export const ajaxGetAllClass = data => {
const option = {
url: "/getclazzlist",
method: "GET",
data
};
return fetchData(option, GET_ALL_CLASS);
};

//加入班级
export const ajaxJoinClass = data => {
const option = {
url: "/joinclazz",
method: "POST",
data
};
return fetchData(option, JOIN_CLASS);
};

//获取用户班级学习计划
export const ajaxGetUserClassPlan = data => {
const option = {
url: "/getuserclazzstudyplan",
method: "POST",
data
};
return fetchData(option, GET_USER_CLASS_PLAN);
};

//获取班级评论
export const ajaxGetClassCmt = data => {
const option = {
url: "/getclazzcomment",
method: "POST",
data
};
return fetchData(option, GET_CLASS_CMT);
};

//查看班级基本信息
export const getClassMsg = data => {
const userId = getLoginInfo().userId || "";
const option = {
url: "/getclazzmessage",
method: "POST",
data: {
userId,
clazzId: data.clazzId
}
};
return fetchData(option, GET_CLASS_MSG);
};

这里就不贴使用封装之前的代码啦(因为被我删了,懒得再去找了 hhhh),但是我个人经过对比之后发现 action 的创建变得清凉多了。
这里虽然封装了 action,但其实在组件绑定 redux 状态树的时候还是比较繁琐的,如果项目比较简单,组件数据源单一,还是可以远离 redux 的~~