一、定义创建store的函数
function createStore (reducer, preloadedState, enhancer) {
// reducer 类型判断
if (typeof reducer !== 'function') throw new Error('redcuer必须是函数');
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('enhancer必须是函数')
}
return enhancer(createStore)(reducer, preloadedState);
}
// 状态
var currentState = preloadedState;
// 订阅者
var currentListeners = [];
// 获取状态
function getState () {
return currentState;
}
// 用于触发action的方法
function dispatch (action) {
// 判断action是否是一个对象
if (!isPlainObject(action)) throw new Error('action必须是一个对象');
// 判断action中的type属性是否存在
if (typeof action.type === 'undefined') throw new Error('action对象中必须有type属性');
// 调用reducer函数 处理状态
currentState = reducer(currentState, action);
// 调用订阅者 通知订阅者状态发生了改变
for (var i = 0; i < currentListeners.length; i++) {
var listener = currentListeners[i];
listener();
}
}
// 订阅状态的改变
function subscribe (listener) {
currentListeners.push(listener);
}
// 默认调用一次dispatch方法 存储初始状态(通过reducer函数传递的默认状态)
dispatch({type: 'initAction'})
return {
getState,
dispatch,
subscribe
}
}
- 其中
preloadedState为state的初始值 - 订阅状态改变时,通过
subscribe函数将订阅者保存入数组currentListeners - 触发
action时,调用reducer函数修改state,同时遍历执行订阅者函数
二、参数类型的约束
reducer的参数类型必须是函数
if (typeof reducer !== 'function') throw new Error('redcuer必须是函数');
dispatch函数的参数action必须是对象,并且有type属性
// 判断action是否是一个对象
if (!isPlainObject(action)) throw new Error('action必须是一个对象');
// 判断action中的type属性是否存在
if (typeof action.type === 'undefined') throw new Error('action对象中必须有type属性');
- 定义判断是否为对象的函数(原理:判断对象的当前原型对象是否和顶层原型对象相同)
function isPlainObject (obj) {
// 排除基本类型和null
if (typeof obj !== 'object' || obj === null) return false;
// 区分数组和对象(采用原型对象对比的方式)
var proto = obj;
while (Object.getPrototypeOf(proto) != null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto;
}
- 使用
enhancer对store对象进行功能增强(可选参数)
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('enhancer必须是函数')
}
// 返回更加强大的store对象
return enhancer(createStore)(reducer, preloadedState);
}
enhancer进行功能增强(中间件用于覆盖原有dispatch方法)
function enhancer (createStore) {
return function (reducer, preloadedState) {
var store = createStore(reducer, preloadedState);
var dispatch = store.dispatch;
function _dispatch (action) {
if (typeof action === 'function') {
return action(dispatch)
}
// 正常的action
dispatch(action);
}
return {
...store,
dispatch: _dispatch
}
}
}
在视图中调用dispatch方法传入action,判断action的数据类型。如果是函数就将dispatch传给action函数。等action函数执行完异步逻辑之后,再去调用dispatch触发正常的action
三、enhancer演变为applyMiddleware函数
applyMiddleware规定中间件函数的写法
// 中间件外层的两个函数是用来接收参数的
function logger (store) {
// next函数实际就是下一个中间件函数(最后一个中间件中的next是dispatch方法)
return function (next) {
// 这才是中间件函数
return function (action) {
console.log('logger');
next(action)
}
}
}
applyMiddleware函数用于给中间件传参
2. applyMiddleware函数的实现
function applyMiddleware (...middlewares) {
return function (createStore) {
return function (reducer, preloadedState) {
// 创建 store
var store = createStore(reducer, preloadedState);
// 阉割版的 store
var middlewareAPI = {
getState: store.getState,
dispatch: store.dispatch
}
// 调用中间件的第一层函数 传递阉割版的store对象
var chain = middlewares.map(middleware => middleware(middlewareAPI));
var dispatch = compose(...chain)(store.dispatch);
console.log('dispatch: ', dispatch);
return {
...store,
dispatch
}
}
}
}
- 使用
compose函数串联中间件函数
function compose () {
var funcs = [...arguments];
return function (dispatch) {
for (var i = funcs.length - 1; i >= 0; i--) {
dispatch = funcs[i](dispatch);
}
return dispatch;
}
}
- 该函数从
applyMiddleware函数参数的最后一项执行循环,获取它的中间件函数并返回。返回值将作为下一个中间的next参数。最后执行var dispatch = compose(...chain)(store.dispatch);, 得到增强后的dispatch函数。 - 在执行
dispatch时,它先调用第一个中间件传入的next是下一个中间件,而最后一个中间件传入的next参数就是store.dispatch会执行store.dispatch触发action,在函数内部调用reducer函数处理状态(依据传入的action)
四、bindActionCreators函数的实现
bindActionCreators函数的写法 作用:将ActionCreator函数转换为能触发action的函数(返回对象)
var actions = bindActionCreators({increment, decrement}, store.dispatch);
function increment () { // `ActionCreator`函数
return {type: "increment"}
}
function decrement () { // `ActionCreator`函数
return {type: "decrement"};
}
bindActionCreators函数的实现
function bindActionCreators (actionCreators, dispatch) {
var boundActionCreators = {};
for (var key in actionCreators) {
// 采用自调用函数,传入参数,用于保存传入当前函数参数(闭包)
(function (key) {
boundActionCreators[key] = function () {
dispatch(actionCreators[key]())
}
})(key)
}
return boundActionCreators;
}
五、combineReducers函数的实现
combineReducers函数的用法 作用:Redux中允许我们将大的reducer拆分为一个个小的reducer,再通过combineReducers合并reducer。
function counterReducer(state, action) {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
return state;
}
}
var rootReducer = combineReducers({counter: counterReducer})
var store = createStore(rootReducer, {counter: 100}, applyMiddleware(logger, thunk));
combineReducers函数的写法
function combineReducers (reducers) {
// 1. 检查reducer类型 它必须是函数
var reducerKeys = Object.keys(reducers);
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] !== 'function') throw new Error('reducer必须是函数');
}
// 2. 调用一个一个的小的reducer 将每一个小的reducer中返回的状态存储在一个新的大的对象中
return function (state, action) {
var nextState = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
var reducer = reducers[key];
var previousStateForKey = state[key];
nextState[key] = reducer(previousStateForKey, action)
}
return nextState;
}
}
六、Redux Tookit应用
Redux Tookit是redux的工具集,对Redux进行二次封装,用于高效Redux开发,使Redux使用更简单- 创建状态切片
- 对于状态切片,我们可以认为它就是原本 Redux 中的那一个个的小的 Reducer 函数。在 Redux 中,原本 Reducer 函数和 Action 对象需要分别创建,现在通过状态切片替代,它会返回 Reducer 函数和 Action 对象.
import { createSlice } from "@reduxjs/toolkit"
export const TODOS_FEATURE_KEY = "todos"
const { reducer: TodosReducer, actions } = createSlice({
name: TODOS_FEATURE_KEY,
initialState: [],
reducers: {
addTodo: (state, action) => {
state.push(action.payload)
}
})
export const { addTodo } = actions
export default TodosReducer
- 创建
store
import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit"
import TodosReducer, { TODOS_FEATURE_KEY } from "./todos.slice"
import logger from "redux-logger"
export default configureStore({
reducer: {
[TODOS_FEATURE_KEY]: TodosReducer
},
devTools: process.env.NODE_ENV !== "production",
middleware: [...getDefaultMiddleware(), logger]
})
- 配置
Provider触发Action
import { Provider } from "react-redux"
import store from "./Store"
ReactDOM.render(
<Provider store={store}>
<Global styles={styles} />
<App />
</Provider>,
document.getElementById("root")
)
5.
Action预处理
当Action被触发后,可以通过prepare方法对Action进行预处理,处理完成后交给Reducer, prepare方法必须返回对象。
6. 执行异步操作
import { createAsyncThunk } from "@reduxjs/toolkit"
import axios from "axios"
export const loadTodos = createAsyncThunk("todos/loadTodos", payload =>
axios.get(payload).then(response => response.data)
)
创建接收异步操作结果的Reducer
createSlice({
...
extraReducers: {
[loadTodos.pending]: (state, action) => {
console.log("pending")
return state
},
[loadTodos.fulfilled]: todosAdapter.addMany
}
})
7、配置中间件
注意在引入自定义中间件前,需要导入toolkit中默认的中间件
import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit"
import TodosReducer, { TODOS_FEATURE_KEY } from "./todos.slice"
import logger from "redux-logger"
export default configureStore({
reducer: {
[TODOS_FEATURE_KEY]: TodosReducer
},
devTools: process.env.NODE_ENV !== "production",
middleware: [...getDefaultMiddleware(), logger]
})
- 实体适配器
实体:可以理解为一条数据
实体适配器:可以理解为一个放入数据的容器
将状态放入实体适配器中,因为实体适配器提供了操作状态的各种方法,简化增删改查的操作。
import { createEntityAdapter } from "@reduxjs/toolkit"
const todosAdapter = createEntityAdapter()
// 获取实体适配器默认的数据结构
todosAdapter.getInitialState()
// 向实体适配器中添加一条数据
todosAdapter.addOne(state, action.payload)
// 向实体适配器中添加多条数据
todosAdapter.addMany(state, action.payload)
9、将实体唯一标识指定为某个字段
实体适配器要求每一个实体必须拥有id属性作为唯一标识,如果实体中唯一标识的字段不叫id,则需要使用selectId进行声明。
const todosAdapter = createEntityAdapter({
selectId: todo => todo.cid
})
- 状态选择器 提供从实体适配器中获取状态的快捷途径
import { createSelector } from "@reduxjs/toolkit"
// 从实体适配器中获取一些便捷的状态选择器和我们自己的状态选择器一起使用
const { selectAll } = todosAdapter.getSelectors()
export const selectTodos = createSelector(
state => state[TODOS_FEATURE_KEY],
selectAll
)
在组件中通过const todos = useSelector(selectTodos)获取数据