一、redux的使用
1.redux的工作流程:
- 通过createStore创建一个store仓库,用来存储状态;
- store接收的第一个参数reducer,初始化state并定义state修改规则;
- 页面中通过dispatch⼀个action来提交对数据的修改;
- action提交到reducer函数⾥,根据传⼊的action的type,返回新的 state。
2.相关代码:
创建项目,安装redux
npx create-react-app write-redux
npm i --save redux
创建./src/store/index.js来创建store:
import { createStore } from 'redux';
// 创建reducer
function reducer(state = 0, action) {
switch(action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
// 创建store仓库
const store = createStore(reducer);
export default store;
创建./src/views/ReduxPage.js使用redux:
import React, {Component} from 'react';
import store from '../store';
class ReduxPage extends Component {
add = () => {
// dispatch分发action
store.dispatch({ type: 'ADD' })
}
minus = () => {
store.dispatch({ type: 'MINUS' })
}
render(){
return (
<div>
<h1>{store.getState()}</h1>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
</div>
)
}
}
export default ReduxPage;
在App.js中引入组件:
import logo from './logo.svg';
import './App.css';
import ReduxPage from './views/ReduxPage'
function App() {
return (
<div className="App">
<ReduxPage />
</div>
);
}
export default App;
在index.js中订阅更新:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './store';
const render = () => {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
}
render();
store.subscribe(render);
reportWebVitals();
界面:
二、手写redux源码
1. 根据上面例子,我们可以发现createStore方法返回常用的几个方法dispatch、subscribe、getState。
-
getState:直接返回当前state。
-
dispatch:接受一个action,执行reducer返回新的state,再遍历执行监听数组。
-
subscribe:将订阅的监听添加到监听数组中, 返回一个取消订阅的方法。
相关代码:
创建./src/redux/index.js:
export function createStore(reducer) {
// 创建state
let currentState;
// 创建监听数组
let currentListeners = [];
// getState直接返回当前state
function getState() {
return currentState;
}
// dispatch接受一个action,执行reducer返回新的state,再遍历执行监听数组
function dispatch(action) {
currentState = reducer(currentState, action);
currentListeners.forEach(listener => listener())
}
// subscribe:将订阅的监听添加到监听数组中, 返回一个取消订阅的方法
function subscribe(listener) {
currentListeners.push(listener);
return () => {
// 取消订阅:删除监听数组中对应的监听
const index = currentListeners.indexOf(listener);
currentListeners.splice(index, 1);
}
}
// 处理reducer中的默认值
// 由于用户对于找不到的type都会做默认处理,
// 所以redux中通过dispatch自执行一次reducer来返回默认值,
// 注意执行的type不能与用户定义的type相同
dispatch({type: new Date()})
return {
getState,
dispatch,
subscribe
}
}
三、redux中间件的使用
Redux只是个纯粹的状态管理器,默认只⽀持同步,实现异步任务 ⽐如延迟,⽹络请求,需要中间件的⽀持,⽐如我们使⽤最简单的redux-thunk和 redux-logger 。 l
中间件就是⼀个函数,对 store.dispatch ⽅法进⾏改造,在发出 Action 和执⾏ Reducer 这两步之间,添加了其他功能。
安装中间件redux-thunk和 redux-logger:
npm i --save redux-thunk redux-logger
在 ./src/store/index.js使用中间件:
// import { createStore } from '../redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
// 创建reducer
function reducer(state = 0, action) {
switch(action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
// 创建store仓库
const store = createStore(reducer, applyMiddleware(thunk, logger));
export default store;
在./src/views/ReduxPage.js中添加异步方法:
// 异步任务
addSync = () => {
store.dispatch((dispatch, getState) => {
setTimeout(() => {
store.dispatch({ type: 'ADD'})
}, 1000)
})
}
四、手写中间件
1.applyMiddleware:给只能处理普通对象的dispatch添加传入的中间件的功能。
如上面例子中给dispatch添加thunk来处理函数,添加logger来打印日志。
过程:
- 执行时传入中间件作为参数,返回一个函数(enhancer),作为createStore的第二个参数,在createStore中传入createStore和reducer并执行;
- 这个函数中创建一个store,将store中的getState和dispatch传递给中间件并执行,每个中间件执行后返回的方法组成一个数组,按顺序执行这个数组中的每个函数;
- 由于applyMiddleware是用来加强dispatch,所以enhancer执行后最终返回新的store和dispatch。
./src/redux/applyMiddleware.js:
export function applyMiddleware(...middlewares) {
// 返回一个函数作为createStore的第二个参数,在createStore中传入createStore和reducer并执行
return (createStore) => (reducer) => {
let store = createStore(reducer);
let dispatch = store.dispatch;
// 将store和dispatch传给中间件,执行中间件
const midApi = {
getState: store.getState,
dispatch: (action, ...arg) => dispatch(action, ...arg)
}
// 将midApi传递给中间件并执行,每个中间件执行后返回一个函数,所有返回的函数组成数组middlewareChain
let middlewareChain = middlewares.map(middleware => {
return middleware(midApi);
});
console.log(middlewareChain);
// 合成函数:按顺序执行中间件
// 获取到功能更强大的dispatch,如thunk返回的dispatch能执行函数形式的action
// dispatch当做参数传给中间件,中间件中对应的参数是next
dispatch = compose(...middlewareChain)(store.dispatch);
// 最终返回最新的store和dispatch
return {...store, dispatch}
}
}
// 合成函数
function compose(...funcs) {
console.log(funcs);
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
./src/redux/index.js添加createStore的第二个参数enhancer:
export function createStore(reducer, enhancer) {
if(enhancer) {
return enhancer(createStore)(reducer)
}
// 创建state
let currentState;
// 创建监听数组
let currentListeners = [];
// getState直接返回当前state
function getState() {
return currentState;
}
// dispatch接受一个action,执行reducer返回新的state,再遍历执行监听数组
function dispatch(action) {
currentState = reducer(currentState, action);
currentListeners.forEach(listener => listener())
}
// subscribe:将订阅的监听添加到监听数组中, 返回一个取消订阅的方法
function subscribe(listener) {
currentListeners.push(listener);
return () => {
// 取消订阅:删除监听数组中对应的监听
const index = currentListeners.indexOf(listener);
currentListeners.splice(index, 1);
}
}
// 用户会在reducer中设置默认state值,由于用户对于找不到的type都会做默认处理,
// 所以redux中通过dispatch自执行一次reducer来返回默认值,
// 注意执行的type不能与用户定义的type相同
dispatch({type: new Date()})
return {
getState,
dispatch,
subscribe
}
}
2.redux-thunk
作用:判断用户传入的action是否为函数,若为函数则执行函数,否则使用原本的dispatch执行action。
实现:在applyMiddleware中执行时传入getState和dispatch,返回一个函数,跟其他中间件组成返回的函数组成数组,根据传入的dispatch链式执行后返回新的dispatch。
./src/redux/thunk.js:
export function thunk({getState, dispatch}) {
return (next) => {
console.log(next); // dispatch
return (action) => {
console.log(action);
if(typeof action === 'function') {
return action(dispatch, getState);
}
return next(action)
}
}
}
3.redux-logger
作用:打印日志。
注意:logger中间件应该放在最后,因为前面的中间件处理完dispatch之后再进行打印,否则当logger遇到特殊的action时将无法打印。
./src/redux/logger.js:
export function logger({getState, dispatch}) {
return (next) => {
return (action) => {
console.log("----------------");
console.log("prev state", getState());
const rerutnValue = next(action);
console.log("next state", getState());
console.log("rerutnValue", rerutnValue);
console.log("----------------");
return rerutnValue;
}
}
}
4.redux-promise
作用:解决dispatch传入的参数为promise。
在./src/views/ReduxPage.js中添加promise方法:
addPromise = () => {
store.dispatch(Promise.resolve({ type: 'ADD'}))
}
安装依赖 is-promise:
npm i --save is-promise
创建./src/redux/promiseMiddleware.js:
import isPromise from 'is-promise';
export function promiseMiddleware({getState, dispatch}) {
return (next) => (action) =>
isPromise(action) ? action.then(dispatch) : next(action);
}
五、combineReducers的使用
当有多个reducer模块时,可以使用combineReducer将多个reducer组合成一个reducer,再传给createStore。
在./src/redux/index.js中使用combineReducer合并reducer和reducer2:
// import { createStore, applyMiddleware } from 'redux';
// import thunk from 'redux-thunk'
// import logger from 'redux-logger'
// import promiseMiddleware from 'redux-promise'
import { combineReducers } from 'redux';
import { createStore} from '../redux';
import { applyMiddleware } from '../redux/applyMiddleware';
import { thunk } from '../redux/thunk';
import { logger } from '../redux/logger';
import { promiseMiddleware } from '../redux/promiseMiddleware';
// 创建reducer
function reducer(state = 0, action) {
switch(action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
function reducer2(state = 100, action) {
switch(action.type) {
case "ADD":
return state + 100;
default:
return state;
}
}
// 创建store仓库
const store = createStore(
// reducer,
combineReducers({count: reducer, count2: reducer2}),
applyMiddleware(thunk, promiseMiddleware, logger)
);
export default store;
./src/views/ReduxPage.js中获取state:
<h1>{store.getState().count}</h1>
<h1>{store.getState().count2}</h1>
六、combineReducers源码
思路:传入的多个reducer组合成的对象reducers,返回一个新的reducer函数等待执行,当这个函数被执行时,遍历并执行之前传进来的reducers对象,将返回的新的state放到nextState中,最终返回nextState对象。
./src/redux/combineReducers.js:
export function combineReducers(reducers) {
// 返回新的reducer,接收参数初始值和action
return (state = {}, action) => {
// 将多个reducer综合成一个nextState,最后返回nextState
// 遍历所有reducer,将所有reducer放到nextState,
let nextState = {};
let hasChanged = false;
for(let key in reducers) {
const reducer = reducers[key];
console.log(reducer);
nextState[key] = reducer(state[key], action);
console.log(nextState);
// 判断nextState是否有改变
hasChanged = hasChanged || nextState[key] !== state[key];
}
hasChanged = Object.keys(nextState).length !== Object.keys(state);
return hasChanged ? nextState : state;
}
}