redux 源码分析一:常用API介绍
# 0.前言
本篇博客主要的目的是快速介绍 redux
中的基本概念和API
。
# 1.基本概念和API
# 1.1 store
Store
就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store
。
Redux
提供createStore
这个函数,用来生成 Store
对象。
import { createStore } from 'redux';
const store = createStore(fn);
2
store
提供了三个方法:
store.getState()
:获取当前时刻的state
。store.dispatch()
:View
发出Action
的唯一方法。store.dispatch({ type: 'ADD_TODO', payload: 'Learn Redux' });
1
2
3
4store.subscribe()
:设置监听函数,一旦state
发生变化,就自动执行这个函数。常规的搭配:
let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();
1
2
3
4
# 1.2 Action
以及 Redux
的工作流程
在 redux
中对状态的管理如上:
state
存放在store
中,View
对应着React Components
。View
可以通过store.getState()
获取到state
。但是不允许直接修改store
中的state
,按照redux
规范需要通过ActionCreators
对store
中的值进行操作。官方推荐修改
store
中的state
最佳实践,如下:ActionTypes
:Magic string
const ADD_TODO = '添加 TODO';
1ActionCreators
export const addTodo = payload => { return { type: ADD_TODO, payload } }
1
2
3
4
5
6生成一个
Action
const action = addTodo('Learn Redux');
1调用
store.dispatch()
,此方法是View
发出Action
的唯一方法。store.dispatch(action);
1
上述4个步骤看似复杂,实则等价下面这种写法。
store.dispatch({type: ADD_TODO,'Learn Redux'});
1
# 1.3 Reducer
文件
store
收到 Action
以后,还有一步,即需要告诉计算机ADD_TODO
和操作的是state
中的value
变量,这样 对View
的状态更新形成闭环。并且Redux
规定接受的必须是一个newState
,而计算 newState
的过程就叫做 Reducer
。
ps. 从形式来说,看上去是
store
内部对接受到action
的一个响应,即对不同的actionType
进行相应的state
的修改,实则reducer
的本质是createNewState
函数,是dispatch
的底层实现,这一点在分析源码时可以发现这一点。
Reducer.js
文件一般如下:
// 1. 包含存入 store 中的 initialState
const defaultState = 0;
// 2. 接受 state,action => newState (switch case语法)
export const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload;
default:
return state;
}
};
/*
通过 reducer 函数可以生成一个新的state,以下函数非文件的一部分
const newstate = reducer(1, {ActionType:'ADD',payload:2});
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1.4 Redux
项目:
以下,是一个非常简单的计数器redux
项目:完整代码见github (opens new window)
// 1.reducer.js
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1; // 2.省略 ActionTypes文件
case 'DECREMENT': return state - 1;
default: return state;
}
};
// 3.Components文件夹/Container文件夹
const Counter = ({ value, onIncrement, onDecrement }) => (
<div>
<h1>{value}</h1>
<button onClick={onIncrement}>+</button>
<button onClick={onDecrement}>-</button>
</div>
);
// 4.App.js
const store = createStore(reducer);
const render = () => {
ReactDOM.render(
<Counter
value={store.getState()}
// 5.这里直接传递 {type: 'INCREMENT'} 而省略 ActionCreactor 文件
onIncrement={() => store.dispatch({type: 'INCREMENT'})}
onDecrement={() => store.dispatch({type: 'DECREMENT'})}
/>,
document.getElementById('root')
);
};
render();
store.subscribe(render);
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
官方推荐的最佳实践项目的拓扑结构 (opens new window),如下所示:
├── actions # ActionCreators文件 │ └── index.js ├── api # 公共api │ ├── products.json │ └── shop.js ├── components # 组件文件(小组件) │ ├── Cart.js │ ├── Cart.spec.js │ ├── Product.js │ ├── Product.spec.js │ ├── ProductItem.js │ ├── ProductItem.spec.js │ ├── ProductsList.js │ └── ProductsList.spec.js ├── constants │ └── ActionTypes.js # ActionTypes ├── containers # 容器文件(大组件) │ ├── App.js │ ├── CartContainer.js │ └── ProductsContainer.js ├── index.js # 存放状态,即store ├── reducers # reducer文件 │ ├── cart.js │ ├── cart.spec.js │ ├── index.js │ ├── index.spec.js │ ├── products.js │ └── products.spec.js └── setupTests.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
# 2.React-redux
为了方便使用,Redux
的作者封装了一个 React
专用的库 React-Redux (opens new window)。
核心只有一个函数 connect
,用于连接全局状态GlobalContext
。
具体使用方法如下:
使用
Provider
创建全局环境。import { Provider } from 'react-redux' render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
1
2
3
4
5
6
7
8使用
connect
创建一个HOC
组件,去连接这个Provider
提供的全局环境。类似的函数:
withRouter
高阶组件import { connect } from 'react-redux' const HOC_Component = connect( mapStateToProps, mapDispatchToProps )(Component)
1
2
3
4
5
其中,mapStateToProps
和mapDispatchToProps
是两个selector
,前者用于筛选props
,后者用于筛选dispatch
。
举例说明:
// 全局仓库有:
state = {
count : 0
}
// 现有一个Action,用于修改 count
const increaseAction = {type: "increase"}
// 如果不传 `mapStateToProps` 和 `mapDispatchToProps`
// 可以直接从 props 中获取 state 和 dispatch
const HOC_Component = connect(null,null)({state,dispatch}=>{
console.log(state.count) // 获取 count
dispatch(increaseAction) // 修改 count
})
2
3
4
5
6
7
8
9
10
11
12
13
14
筛选后,对count
的获取和修改则更为简单:
const mapStateToProps = (state) => value: state.count
const mapDispatchToProps = dispatc => {
return {
onIncreaseClick: () => dispatch(increaseAction)
}
}
// 使用 selector 后,可直接获取到深层的状态(如state.b.c.d->d)以及修改`d`属性的方法。
const HOC_Component = connect(mapStateToProps,mapDispatchToProps)({count,onIncreaseClick}=>{
console.log(count) // 获取 count
onIncreaseClick() // 修改 count
})
2
3
4
5
6
7
8
9
10
11
12
13
计数器的 react-redux
版本项目:这里 (opens new window)
# 3.总结
本篇博客一共学习到了两个仓库redux
和react-redux
。
在redux
中我们需要了解:
store
中存放着,视图层所需的state
- 读:
store.getState()
- 写:
store.dispatch(action)
- 视图更新:
store.subscribe()
- 读:
- 了解
redux
中的项目结构,官方推荐的最佳实践,需要额外创建ActionTypes
、ActionCreators
等文件,使用时需要手动构建大量的模板代码,这也是redux
诟病的地方。
在react-redux
中核心的知识点就是:connect
的使用
connect
是一个高阶的组件,可以快速于全局state
绑定。- 两个
selector
的函数的使用,目的除了起到筛选props
和dispatch
,简化使用的目的外,实际上还起到精确渲染
的作用,通过后续的源码讲解可以对这一点更为清晰的认识。
# 4.参考
本篇博客主要参考了阮一峰的 redux
教程: