介绍
工作流
React Components
:组件,相当于借书人。Action
:相当于 ‘我要借**书’,这句话。Store
:相当于 图书馆管理员。Reducer
:相当于 本子,记录所有图书的位置,让图书馆管理员知道那本书在哪里,找出来。
三大原则
- 单一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- State是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
- 使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers。
Redux设计理念
如果action
、reducer
、store
的关系已经应用不是很熟,可以先跳过这部分。
Redux 的 React 绑定库是基于 容器组件和展示组件相分离 的开发思想。
可以直接使用
store.subscribe()
来编写容器组件。但不建议这么做的原因是无法使用 React Redux 带来的性能优化。也因此,不要手写容器组件,而使用 React Redux 的connect()
方法来生成。
connect(
mapStateToProps,
mapDispatchToProps
)(关联到的组件)
使用 connect()
前,需要先定义:
mapStateToProps
这个函数来指定如何把当前 Redux storestate
映射到展示组件的props
中mapDispatchToProps()
方法接收dispatch()
方法并返回期望注入到展示组件的props
中的回调方法。
我的理解是 用来处理Redux store state和props中的回调。
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
基础
Action
Action
是把数据从应用传到 store
的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch()
将 action
传到 store
。
Action
本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type
字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
需要再添加一个 action index 来表示用户完成任务的动作序列号。因为数据是存放在数组中的,所以我们通过下标 index 来引用特定的任务。而实际项目中一般会在新建数据的时候生成唯一的 ID 作为数据的引用标识。
应该尽量减少在 action 中传递的数据。因此传index会比传整个对象好。
Action创建函数
Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。
在 Redux 中的 action 创建函数只是简单的返回一个 action:
// Action types
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from './actionTypes'
// Action creator
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
Reducer
Reducers
指定了应用状态的变化如何响应 actions
并发送到 store
的,记住 actions
只是描述了有事情发生了这一事实,并没有描述应用如何更新 state
。
永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random()。
只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from './actionTypes';
const defaultState = {
inputValue: '',
list: []
};
// state 是整个store的数据
// reducer 可以接受state,但绝对不能修改state
export default (state = defaultState, action) => {
switch (action.type) {
case CHANGE_INPUT_VALUE:
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState;
case ADD_TODO_ITEM:
const addState = JSON.parse(JSON.stringify(state));
addState.list.push(addState.inputValue);
addState.inputValue = '';
return addState;
case DELETE_TODO_ITEM:
const delState = JSON.parse(JSON.stringify(state));
delState.list.splice(action.index, 1);
return delState;
default:
return state;
}
}
注意:
- 不要修改state。(因此一般会克隆或者返回新对象)
- 在遇到未知action的时候一定要返回就的state。
当遇到大项目的时候,会把一个Reducer
分成很多个小的Reducer
。
注意每个 reducer
只负责管理全局 state
中它负责的一部分。每个 reducer
的 state
参数都不同,分别对应它管理的那部分 state
数据。
Redux
提供了 combineReducers()
工具类将多个reducer
合并成一个reducer
。
combineReducers()
所做的只是生成一个函数,这个函数来调用你的一系列reducer
,每个reducer
根据它们的key
来筛选出state
中的一部分数据并处理,然后这个生成的函数再将所有reducer
的结果合并成一个大的对象。
例:
import { combineReducers } from 'redux'
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
Store
使用 action
来描述“发生了什么”,和使用 reducers
来根据 action
更新 state
的用法。Store
就是把它们联系到一起的对象。Store
有以下职责:
- 维持应用的
state
; - 提供
getState()
方法获取state
; - 提供
dispatch(action)
方法更新state
; - 通过
subscribe(listener)
注册监听器; - 通过
subscribe(listener)
返回的函数注销监听器。
Redux
应用只有一个单一的 store
。当需要拆分数据处理逻辑时,你应该使用 reducer
组合而不是创建多个 store
。
使用createStore()
来创建 store
- 第一个参数是
reducers
; - 第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。
import {createStore} from "redux";
import reducer from "./reducer";
// 创建store 必须要有reducer
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store;
使用 chrome 插件
在 chrome 浏览器中安装 redux-devtools 插件。
然后回到你的项目里,在你创建store的时候传入第二个参数:
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
数据流
Redux 应用中数据的生命周期遵循下面 4 个步骤:
- 调用
store.dispatch(action)
。 - Redux
store
调用传入的reducer
函数。 - 根
reducer
应该把多个子reducer
输出合并成一个单一的state
树。Redux 原生提供combineReducers()
辅助函数,来把根reducer
拆分成多个函数,用于分别处理state
树的一个分支。 - Redux
store
保存了根reducer
返回的完整state
树。所有订阅store.subscribe(listener)
的监听器都将被调用;监听器里可以调用store.getState()
获得当前state
。
Redux中间件
简单来说Redux
中间件,指的是action
与store
的中间dispatch
,也可以说是dispatch
的升级封装。
-
redux中间件
redux-thunk
:把异步代码放到action
中处理,即action creator
返回是可以是一个函数。 -
redux中间件
redux-saga
:把异步代码或者复杂逻辑拆分放到一个文件里,当dispatch
一个action
的时候,不仅reducer
可以接收到action
,saga
文件也能接收到action
。saga
中使用put
来进行dispatch
一个action
。
个人理解:在页面上
dispatch
一个action
,然后另一个然后处理异步操作后,再重新dispatch
一个新的action
。
React Redux
- 安装:
npm i -S react-redux
- 使用:
Provider
包裹要渲染的组件,利用其store
属性 连接store
import store from "./store/index";
const App = (
// Provider 提供器,用于连接store,其内部组件就可以获取store数据(利用connect)
<Provider store={store}>
<TodoList/>
</Provider>
);
ReactDOM.render(App, document.getElementById('root'));
- 组件利用
connect
与store
连接,其中connect
接收两个参数:mapStateToProps
把 store 里的数据(即全局 state 的数据),映射为组件 props 的数据。mapDispatchToProps
把store.dispatch
挂载到props
。
// 把 store 里的state数据,映射为组件 props 的数据
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
};
// store.dispatch 挂载到 props
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = getInputChangeAction(e.target.value);
dispatch(action);
},
handleClick(e) {
const action = getAddItemAction();
dispatch(action);
},
handleDelete(index) {
const action = getDeleteItemAction(index);
dispatch(action);
}
}
};
// connect 用组件与 store 做连接, 实际 connect 返回导出的组件就为容器组件
export default connect(mapStateToProps, mapDispatchToProps)(TodoListUI);
两个小demo
两个都是实现简单的 todoList,只是实现的形式有少许不一样