动机

对于redux原生用法,不喜欢过于繁重的actions-types常量定义以及switch case的书写方式,redux就是一个工具,不喜欢在工具上面,浪费很多体力,这点刚好跟redux-actions动机一致,使用下来也觉得很是方便。

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
// actions/index.js
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})


// reducers/todos.js
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

极简使用

创建action/actionCreator.js

1
2
import { createAction } from 'redux-actions';
export const addnum = createAction('ADDNUM')

组件中引入使用

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
import React,{Component} from "react";
import store from "./store"
import {addnum} from "./action/actionCreator"

export default class App extends Component{
  constructor(){
    super()
    this.state = store.getState();
    store.subscribe(this.handleUpdate.bind(this))
  }
  render(){
    let {n} = this.state;
    return (
      <div>
        <h2>{n}</h2>
        <button onClick={this.handleClick.bind(this)}>点击</button>
      </div>
    )
  }
  handleClick(){
    store.dispatch(addnum());
  }
  handleUpdate(){
    this.setState(store.getState())
  }
}

reducer中使用

1
2
3
4
5
6
7
8
9
10
11
12
import {handleActions} from 'redux-actions';
const defaultState = {
n:10
}

export default handleActions({
ADDNUM: (state, action) => {
let newState = {...state};
newState.n++;
return newState;
},
}, defaultState)

入门使用

Redux传统用法

1
2
3
4
5
6
7
8
// actionTypes.js
const BOOK_LIST_GET = 'BOOK_LIST_GET';
export default {
/**
* 获取书籍列表
*/
BOOK_LIST_GET,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// actions.js
import actionTypes from './actionTypes';

const getBookList = () => {
const bookList = [{
id: '1',
title: '123',
description: '123',
}, {
id: '2',
title: '234',
description: '234',
}];
return {
type: actionTypes.BOOK_LIST_GET,
bookList,
};
};

export default {
getBookList,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// reducers.js
import actionTypes from './actionTypes';

const initialState = {
bookList: [],
};

const pageMainReducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.BOOK_LIST_GET:
return {
...state,
bookList: action.bookList,
};
default:
return state;
}
};
export default pageMainReducer;
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
// index.js
import React from 'react';
import { connect } from 'react-redux';
import Bookshelf from './components/bookshelf';
import actions from './actions';

/**
* 首页
*/
class PageMain extends React.Component {
componentDidMount() {
this.props.getBookList();
}

render() {
return (
<Bookshelf dataSource={this.props.bookList} />
);
}
}
export default connect((state) => {
return {
bookList: state.pageMain.bookList,
};
}, {
getBookList: actions.getBookList,
})(PageMain);

以上的代码中大致功能为:
PageMain 组件会在加载后,请求获取书籍信息,然后将获取到的值传给展示组件Bookshelf使用。

实现步骤:

  1. 首先我们创建了action类型BOOK_LIST_GET ,
  2. 然后根据BOOK_LIST_GET 创建了action工厂getBookList以及reducer。
  3. 最后用connect将store的值传映射到给PageMain的props,并且将dispath action的操作映射到props上。
  4. 当然在最外层我们已经根据reducer生成了一个store并传给了Provider,这里就不贴上去了。

使用redux-actions来处理action和reducer

修改actions.js为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createAction } from 'redux-actions';
import actionTypes from './actionTypes';

const getBookList = createAction(actionTypes.BOOK_LIST_GET, () => {
const bookList = [{
id: '1',
title: '123',
description: '123',
}, {
id: '2',
title: '234',
description: '234',
}];
return bookList;
});

export default {
getBookList,
};

修改reducer.js为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { handleAction } from 'redux-actions';
import actions from './actions';

const initialState = {
bookList: [],
};

const pageMainReducer = handleAction(actions.getBookList, (state, action) => {
return {
...state,
bookList: action.payload,
};
}, initialState);

export default pageMainReducer;

对比两种玩法之后,可以看到redux-actions实际上就是将我们之前做的操作封装了一道,让写法更简便。

  • createAction:创建action工厂的一个操作,返回一个action工厂。

    • 第一个参数:action类型
    • 第二个参数:生成action的函数。此函数的可以传递参数,参数值为实际调用action工厂函数时传递的参数。
  • handleAction:处理action的操作,返回一个reduce。

    • 第一个参数:action工厂
    • 第二个参数:改变store的state的函数。这里会根据store当前的state数据以及action返回的值返回一个新的state给store。
    • 第三个参数:当store的state啥也没有的时候给定一个初始的state。这里说的store的state,是针对这里的state.pageMain。

createAction 与 handleAction

顾名思义,相对于createAction和handleAction而言,就是创建和处理多个action。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
createAction(
type, // 动作名
payloadCreator = Identity, // 用来创建动作对象中的payload值,默认使用lodash的Identity
metaCreator // 用来创建动作对象中元数据
)

const increment = createAction(
'INCREMENT',
mount => mount,
() => ({ admin: true })
);
increment(20);
// {
// type: 'INCREMENT',
// payload: 20,
// meta: { admin: true },
// }

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
// type为动作类型, reducer为动作处理, defaultState为默认状态
handleAction(type, reducer, defaultState)

const reducer = (state = defaultState, action) {
switch(action.type) {
case 'INCREMENT':
return {
count: state.count + action.payload
}
default:
return state
}
}

// 上面的createAction效果就等同下面
const INCREMENT = 'INCREMENT'
const defaultState = { count: 1 }

const reducer = handleAction(
INCREMENT,
(state, action) => ({
count: state.count + action.payload
}),
defaultState
)

针对上面的代码我们加入一个删除操作,那么修改actionTypes.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const BOOK_LIST_GET = 'BOOK_LIST_GET';
const BOOK_DELETE = 'BOOK_DELETE';

export default {
/**
* 获取书籍列表
*/
BOOK_LIST_GET,
/**
* 删除书籍
*/
BOOK_DELETE,
};

用createActions修改actions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { createActions } from 'redux-actions';
import actionTypes from './actionTypes';

export default createActions({
[actionTypes.BOOK_LIST_GET]: () => {
const bookList = [{
id: '1',
title: '123',
description: '123',
}, {
id: '2',
title: '234',
description: '234',
}];
return bookList;
},
[actionTypes.BOOK_DELETE]: (id) => {
console.info(`删除id为${id}的Book`);
return { id };
},
});

用handleActions修改reducers.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
import { handleActions } from 'redux-actions';
import actionTypes from './actionTypes';


const initialState = {
bookList: [],
};

const pageMainReducer = handleActions({
[actionTypes.BOOK_LIST_GET]: (state, action) => {
return {
...state,
bookList: action.payload,
};
},
[actionTypes.BOOK_DELETE]: (state, action) => {
return {
...state,
bookList: state.bookList.filter(l => l.id !== action.payload.id),
};
},
}, initialState);

export default pageMainReducer;

修改index.js部分代码

1
2
3
4
5
6
7
8
9
10
import actions from './actions';

export default connect((state) => {
return {
bookList: state.pageMain.bookList,
};
}, {
bookDelete:actions.bookDelete ,
bookListGet:actions.bookListGet,
})(PageMain);

createActions会返回一个对象,对象针对每个action类型都有一个值为action工厂的属性,属性名为action类型的值去掉下划线后的驼峰命名。

handleActions仍然返回一个reducer。
除了上面那种写法,handleActions还可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
[actionTypes.BOOK_LIST_GET]:
{
next(state, action) {
return {
...state,
bookList: action.payload,
};
},
throw(state) {
return state;
},
},

实际上多出来的功能就是加上了对异常的处理。

CreateAction源码

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
/**
2 * 参数不是function调用此函数
3 */
4 function identity(t) {
5 return t;
6 }
7
8 /**
9 * 创建action
10 * @param type action的类型
11 * @param actionCreator 需要创建的action,函数
12 * @param metaCreator action的属性
13 * @returns {Function}
14 */
15 export default function createAction(type, actionCreator, metaCreator) {
16 /**
17 * finalActionCreator最终创建的action,
18 * 判断传进来的参数是不是function,true返回这个函数,false调用identity函数
19 */
20 const finalActionCreator = typeof actionCreator === 'function'
21 ? actionCreator
22 : identity;
23 /**
24 * 返回一个匿名函数
25 */
26 return (...args) => {
27 /**
28 *创建的action,只有两个属性
29 */
30 const action = {
31 type,
32 payload: finalActionCreator(...args)
33 };
34 /**
35 * 如果给匿名函数传递参数的长度为1个,或者第一个参数元素的类型为Error,那么这么action的error属性为true
36 */
37 if (args.length === 1 && args[0] instanceof Error) {
38 // Handle FSA errors where the payload is an Error object. Set error.
39 action.error = true;
40 }
41 /**
42 * 传递到action里面的函数
43 */
44 if (typeof metaCreator === 'function') {
45 action.meta = metaCreator(...args);
46 }
47
48 return action;
49 };
50 }

createActions和handleActions

createActions具体用法就是createActions(actionMap)

actionMap就是一个对象,

  • key值为动作类型,
  • value可以是payloadCreator函数、一个数组[payloadCreator, metaCreator]、嵌套的actionMap。
1
2
3
4
5
6
7
8
9
10
11
12
13
cosnt {add, remove} = createActions({
ADD_TODO: todo => ({ todo }), // payload creator
REMOVE_TODO: [
todo => ({ todo }), // payload creator
(todo, warn) => ({ todo, warn }) // meta
]
})

// {type: 'ADD_TODO', payload: {todo: 'redux-actions'}}
add('redux-actions')

// {type: 'ADD_TODO', payload: {todo: 'redux-actions'}, meta: {todo: 'redux-actions', warn: 'warn'}}
remove('redux-actions', 'warn')

handleActions函数是用来处理多个动作的。

reducerMap中key就是动作名, value就是reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
handleActions(reducerMap, defaultState)

const reducer = handleActions(
{
INCREMENT: (state, action) => ({
counter: state.counter + action.payload
}),
DECREMENT: (state, action) => ({
counter: state.counter - action.payload
})
},
{ counter: 0 }
);

combineActions

合并多个action和actionCreator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { increment, decrement } = createActions({
INCREMENT: amount => ({ amount }),
DECREMENT: amount => ({ amount: -amount }),
})
const reducer = handleAction(
combineActions(increment, decrement), {
next: (state, { payload }) => ({ ...state, count: state.count + payload }),
throw: state => ({ ...state, count: 0 }),
},
{ count: 10 }
)

const reduce2 = handleActions(
{
[combineActions(increment, decrement)] (state, {payload}) {
return { ...state, count: state.count + payload }
}
},
{ count: 10 }
)