一.为什么要用Redux?
网页应用中需要管理的状态越来越多, 越来越复杂, redux作为数据的统一管理容器简化了应用的数据管理难度,按照既定的数据存取规则,降低了应用的维护难度。
二.redux的三大原则
- 单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中 - state是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。 - 使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。
三. 基础概念
3.1 Action
Action是一个JavaScript对象
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
type代表要执行的动作, 当应用规模比较大时,推荐将action单独提取到actionTypes文件中进行维护
3.2 Reducer
是一些纯函数来相应dispatch来的action, 函数内部进行对应动作类型的数据处理,返回新的state
reducer函数的switch的default分支要返回旧的state。
注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。
Redux 提供了 combineReducers()函数来生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理, 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树
combineReducers 接收一个对象,可以把所有顶级的 reducer 放到一个独立的文件中,通过 export 暴露出每个
reducer 函数,然后使用 import * as reducers 得到一个以它们名字作为 key 的 object
代码示例:
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
3.3 Store
action用来描述发生了什么, reducer函数用来根据action更新state, store将它们联系到一起
store职责如下:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store
在上面代码基础上创建store实例
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp, [state的初始状态,可选参数])
store派发action更新state示例:
import {
addTodo,
toggleTodo,
setVisibilityFilter,
VisibilityFilters
} from './actions'
// 打印初始状态
console.log(store.getState())
// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
const unsubscribe = store.subscribe(() => console.log(store.getState()))
// 发起一系列 action
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))
// 停止监听 state 更新
unsubscribe()
四. 与React整合
安装react-redux库, 该库用来将redux连接到react
一个简单实例
// store.js
import { createStore } from "redux";
const initialState = {
counter: 1,
};
function reducers(state, action) {
switch (action.type) {
case "increment":
return {
...state,
counter: state.counter + 1,
};
case "decrement":
return {
...state,
counter: state.counter - 1,
};
default:
return state;
}
}
export default createStore(reducers, initialState);
// index.js
import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(console.log);
// test.js
import React from "react";
import { connect } from "react-redux";
const mapStateToProps = (state) => ({
counter: state.counter,
});
const mapDispatchToProps = (dispatch) => ({
increment: () =>
dispatch({
type: "increment",
}),
decrement: () =>
dispatch({
type: "decrement",
}),
});
function Home({ counter, increment, decrement }) {
return (
<>
<span>现在的counter值为: {counter}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
五. 异步Action
安装redux-thunk, 也可以使用redux-saga构建构建更加复杂的异步Action
// actionCreators.js
import { INCREMENT_COUNTER, DECREMENT_COUNTER, LOGGER_COUNTER } from './actionTypes';
export const increment = () => ({
type: INCREMENT_COUNTER
});
export const decrement = () => ({
type: DECREMENT_COUNTER
});
export const logCounter = () => ({
type: LOGGER_COUNTER
});
/**
* 创建了一个异步函数
*/
export const delayLogCurrentCounterValue = () => {
return dispatch => {
setTimeout(() => {
// 这里可以继续派发同步或者异步action
dispatch(logCounter());
}, 2000);
}
};
// actionTypes.js
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
export const LOGGER_COUNTER = 'LOGGER_COUNTER';
// store.js
import { createStore, applyMiddleware } from "redux";
import { createLogger } from "redux-logger";
import thunkMiddleware from "redux-thunk";
import {
INCREMENT_COUNTER,
DECREMENT_COUNTER,
LOGGER_COUNTER,
} from "./actionTypes";
const initialState = {
counter: 1,
};
function reducers(state = initialState, action) {
switch (action.type) {
case INCREMENT_COUNTER:
return {
...state,
counter: state.counter + 1,
};
case DECREMENT_COUNTER:
return {
...state,
counter: state.counter - 1,
};
case LOGGER_COUNTER:
console.log("============延迟打印start===================");
console.log(`当前counter的值为: ${state.counter}`);
console.log("============延迟打印end=============");
return state;
default:
return state;
}
}
const loggerMiddleware = createLogger();
export default createStore(
reducers,
initialState,
applyMiddleware(
thunkMiddleware, // 允许我们dispatch函数
loggerMiddleware // 打印action日志
)
);
// Test.js
import React from "react";
import { connect } from "react-redux";
import * as actionCreators from "./store/actionCreators";
const mapStateToProps = (state) => ({
counter: state.counter,
});
function Home({ counter, increment, decrement, delayLogCurrentCounterValue }) {
return (
<>
<span>现在的counter值为: {counter}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={delayLogCurrentCounterValue}>触发redux-thunk延迟打印</button>
</>
);
}
export default connect(mapStateToProps, actionCreators)(Home);
我们可以使用redux-actions库来简化action的创建
// actionCreators.js
import { INCREMENT_COUNTER, DECREMENT_COUNTER, LOGGER_COUNTER } from './actionTypes';
import { createAction } from 'redux-actions';
export const increment = createAction(INCREMENT_COUNTER);
export const decrement = createAction(DECREMENT_COUNTER);
export const logCounter = createAction(LOGGER_COUNTER);
/**
* 创建了一个异步函数
*/
export const delayLogCurrentCounterValue = () => {
return dispatch => {
setTimeout(() => {
// 这里可以继续派发同步或者异步action
dispatch(logCounter());
}, 2000);
}
};
//store.js
import { createStore, applyMiddleware } from "redux";
import { createLogger } from "redux-logger";
import thunkMiddleware from "redux-thunk";
import { handleActions } from "redux-actions";
import {
INCREMENT_COUNTER,
DECREMENT_COUNTER,
LOGGER_COUNTER,
} from "./actionTypes";
const initialState = {
counter: 1,
};
const reducers = handleActions(
{
[INCREMENT_COUNTER]: (state, action) => ({
counter: state.counter + 1,
}),
[DECREMENT_COUNTER]: (state) => ({
counter: state.counter - 1,
}),
[LOGGER_COUNTER]: (state) => {
console.log("============延迟打印start===================");
console.log(`当前counter的值为: ${state.counter}`);
console.log("============延迟打印end=============");
return state;
},
},
initialState
);
const loggerMiddleware = createLogger();
export default createStore(
reducers,
initialState,
applyMiddleware(
thunkMiddleware, // 允许我们dispatch函数
loggerMiddleware // 打印action日志
)
);
使用redux的chrome插件进行调试
import { createStore, applyMiddleware, compose } from "redux";
import { createLogger } from "redux-logger";
import thunkMiddleware from "redux-thunk";
import { handleActions } from "redux-actions";
import {
INCREMENT_COUNTER,
DECREMENT_COUNTER,
LOGGER_COUNTER,
} from "./actionTypes";
const initialState = {
counter: 1,
};
const reducers = handleActions(
{
[INCREMENT_COUNTER]: (state) => ({
counter: state.counter + 1,
}),
[DECREMENT_COUNTER]: (state) => ({
counter: state.counter - 1,
}),
[LOGGER_COUNTER]: (state) => {
console.log("============延迟打印start===================");
console.log(`当前counter的值为: ${state.counter}`);
console.log("============延迟打印end=============");
},
},
initialState
);
const loggerMiddleware = createLogger();
// 判断chrome是否安装了redux调试插件
const reduxDevtools = window.devToolsExtension
? window.devToolsExtension({ trace: true })
: () => {};
export default createStore(
reducers,
compose(
applyMiddleware(
thunkMiddleware, // 允许我们dispatch函数
loggerMiddleware // 打印action日志
),
reduxDevtools
)
);
六. 中间件的使用和编写
redux中间件技术提供的是位于 action 被发起之后,到达 reducer 之前的扩展点, 我们可以基于此进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
简单示例
// 日志打印
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
// 崩溃报告
const crashReporter = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
Raven.captureException(err, {
extra: {
action,
state: store.getState()
}
})
throw err
}
}
然后是将它们引用到 Redux store 中:
import { createStore, combineReducers, applyMiddleware } from 'redux'
const todoApp = combineReducers(reducers)
const store = createStore(
todoApp,
// applyMiddleware() 告诉 createStore() 如何处理中间件
applyMiddleware(logger, crashReporter)
)
现在任何被发送到 store 的 action 都会经过 logger 和 crashReporter:
七. 使用reselect库计算衍生数据
-
该库用来对mapStateToProps中的state取值进行缓存, 如果store中该state值没有变化,则直接返回缓存值,不重新进行计算,提高了性能。
-
我们通过使用createSelector函数来创建带缓存的mapStateToProps
-
createSelector函数的2个参数分别为input-selectors数组和转换函数
-
如果 state tree 的改变会引起 input-selector 值变化,那么 selector 会调用转换函数,传入 input-selectors 作为参数,并返回结果。如果 input-selectors 的值和前一次的一样,它将会直接返回前一次计算的数据,而不会再调用一次转换函数。
简单使用示例:
// selectors.js
import { createSelector } from 'reselect'
const getVisibilityFilter = state => state.visibilityFilter
const getTodos = state => state.todos
export const getVisibleTodos = createSelector(
[getVisibilityFilter, getTodos],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
)
// VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { getVisibleTodos } from '../selectors'
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state)
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList