前言
本篇文章会结合React对Redux进行讲解介绍,所以你在阅读本文之前,最好能拥有一定的React知识储备,想要快速对React有良好的了解,可以参考React官方教程进行学习
如果你对文字阅读略显枯燥的话,可以切换到B站观看我费心录制的的视频进行学习——带你学懂Redux-上篇
本文所实现的源代码托管在 Github 上:mini-redux
认识Redux
Redux是什么
Redux 是 JavaScript 状态容器,它是一个纯JS写的公共库,提供可预测化的状态管理,你完全可以在React, Vue, Angular, jQuery中使用它。
我们先来看一下这张图,Redux的核心就跟这张图相关。希望阅读完本文后,每当你想起 Redux 时,脑海里就是下面这张图。
为什么需要redux
-
JavaScript
开发的应用程序, 已经变得非常复杂了:JavaScript
需要管理的状态越来越多, 越来越复杂了- 这些状态包括服务器返回的数据, 用户操作的数据等等, 也包括一些
UI
的状态
-
管理不断变化的
state
是非常困难的:- 状态之间相互存在依赖, 一个状态的变化会引起另一个状态的变化,
View
页面也有可能会引起状态的变化 - 当程序复杂时,
state
在什么时候, 因为什么原因发生了变化, 发生了怎样的变化, 会变得非常难以控制和追踪
- 状态之间相互存在依赖, 一个状态的变化会引起另一个状态的变化,
为此,我们可能需要Redux来帮助我们管理应用状态。下面,我们一起来看看Redux究竟如何使用吧。
redux在React中的使用
- store.js
我们需要创建一个store供App使用,且一个应用应该只有一个store,createStore
后会提供三个函数,分别是getState
、dispatch
和subscribe
。
createStore
:这个API接受reducer
函数作为参数,返回一个store
,主要功能都在这个store
上。
import { createStore } from "redux";
import { countReducer } from "../reducers/countReducer";
const store = createStore(countReducer);
export default store;
- countReducer.js
创建一个reducer,定义改变store state的规则,reducer是一个纯函数,接收一个旧state和action,根据定义的规则,返回一个新的state
export function countReducer(state = { count: 0 }, action) {
switch (action.type) {
case "INCREASE":
return { count: state.count + action.amount };
case "DECREASE":
return { count: state.count - action.amount };
default:
return state;
}
}
- App.js
创建一个React页面,在页面中引入刚刚我们刚刚创建的store,我们可以在初始化时通过store.getState
拿到redux store中的state初始值,将其作为我们React组件的state初始值。
同时,页面需要在渲染前通过store.subscribe
注册监听stote state值的变化,按钮点击后通过触发dispatch(action)
后经由reducer处理后返回一个新的store state状态值后,刚刚注册监听的函数会执行拿到最新的store state。
import React from "react";
import store from "../store";
class App extends React.Component {
constructor() {
super();
console.log(store.getState());
this.state = {
count: store.getState().count
};
}
componentWillMount() {
this.unsubscribe = store.subscribe(() => {
this.setState({
count: store.getState().count
});
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<h3>ReduxDemo</h3>
<p>{this.state.count}</p>
<button onClick={() => store.dispatch({ type: "INCREASE", amount: 2 })}>
+
</button>
<button onClick={() => store.dispatch({ type: "DECREASE", amount: 2 })}>
-
</button>
</div>
);
}
}
export default App;
以上就是Redux在React中的最简应用了,你学废了吗
这个例子虽然很简短,但是已经包含了Redux的核心功能了(是真的啦),有没有发现我们只有一个createStore
函数是来自于redux,那他究竟是个什么东东呢,在刚刚的例子中,他是怎么做到帮我们管理状态值的呢?所以接下来,我们一起来实现一个createStore
吧。
我们再来回忆store
上我们都用到了啥:
store.subscribe
: 订阅state
的变化,当state
变化的时候执行回调,可以有多个subscribe
,里面的回调会依次执行。
store.dispatch
: 发出action
的方法,每次dispatch(action)
都会执行reducer
生成新的state
,然后执行subscribe
注册的回调。
store.getState
:一个简单的方法,返回当前的state
。
看到subscribe
注册回调,dispatch
触发回调,想到了什么,这不就是发布订阅模式吗?不太清楚的童鞋可以点击链接学习一下。
谈谈观察者模式和发布订阅模式
下面我们看看createStore
内部是怎么实现的
实现createStore
- createStore.js
export default function createStore(reducer) {
let state;
let listeners = [];
function getState() {
return state;
}
function dispatch(action) {
state = reducer(state, action);
listeners.forEach((listener) => listener());
}
function subscribe(listener) {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
/** 为使得仓库有初始的状态值(reducer设定的初始值),需要自动dispatch(action)下 **/
dispatch({ type: "@@redux/INIT" });
return {
getState,
dispatch,
subscribe,
};
}
是不是并没有想象中那么复杂,不过如此嘛。
那么在实际工作中,我们的应用可能很复杂,需要管理的状态值很多,难道我们应用中所有页面用到的stote state需要update时,都通过dispatch(action)
经由同一个reducer处理后返回一个新的state吗?其实这样当然没问题,但是随着我们的应用越来越复杂,所有的规则逻辑都写在一个reducer
里,会特别的冗余,最终这个文件可能会有成千上万行,极其不便于维护以及团队协助。那么有什么办法呢?
当应用逻辑逐渐复杂的时候,我们就要考虑将巨大的 Reducer 函数拆分成一个个独立的单元。Redux 为我们提供了 combineReducers
API,用来组合多个小的reducer
,可以让我们为不同的模块写自己的reducer
,最终将他们组合起来,下面我们先来看一下redux中的combineReducers
怎么使用。
combineReducers
使用
- 添加numberReducer.js
export function numberReducer(state = 0, action) {
switch (action.type) {
case "ADD":
return state + 1;
case "DEDUCT":
return state - 1;
default:
return state;
}
}
- 修改App.js
import React from "react";
import store from "../store";
class App extends React.Component {
constructor() {
super();
console.log(store.getState());
this.state = {
count: store.getState().countState.count,
number: store.getState().numberState,
};
}
componentWillMount() {
this.unsubscribe = store.subscribe(() => {
// console.log(store.getState());
this.setState({
count: store.getState().countState.count,
number: store.getState().numberState,
});
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<h3>ReduxDemo</h3>
<p>{this.state.count}</p>
<p>{this.state.number}</p>
<button onClick={() => store.dispatch({ type: "INCREASE", amount: 2 })}>
+
</button>
<button onClick={() => store.dispatch({ type: "DECREASE", amount: 2 })}>
-
</button>
<button onClick={() => store.dispatch({ type: "ADD" })}>+</button>
</div>
);
}
}
export default App;
- 修改store.js
import { createStore, combineReducers } from "redux";
import { countReducer } from "../reducers/countReducer";
import { numberReducer } from "../reducers/numberReducer";
// 创建store
const store = createStore(
combineReducers({
countState: countReducer,
numberState: numberReducer,
})
);
export default store;
通过以上的例子,combineReducers 主要有两个作用:
- 组合所有 reducer 的 state
- countReducer 的 state 为:
state = {count: 0};
- numberReducer 的 state 为:
state = 0
- 那么通过
combineReducers
组合这两个reducer
的state
得到的最终结果为:
state = {
countState: {count: 0}
numberState: 0
};
这个通过 combineReducers
组合后的最终 state
就是存储在 Store 里面的对象状态树。
- 分发
dispatch
的 Action。
通过 combineReducers
组合countReducer和numberReducer之后,从 React 组件中 dispatch(action)
会遍历检查countReducer和numberReducer,判断是否存在响应对应 action.type
的 case
语句,如果存在,所有的这些 case
语句都会响应。
实现combineReducers
- combineReducers.js
const combineReducers = (reducers) => {
return (state = {}, action) => {
const newState = {};
for (let key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
};
};
export default combineReducers;
react-redux
react-redux
是什么
React-Redux
是Redux
的官方React
绑定库。它能够使你的React
组件从Redux store
中读取数据,并且向store
分发actions
以更新数据
为什么需要react-redux
React组件使用redux需要引入store,并且要手动调用store.subscript()
监听store state变化,使用起来比较麻烦。为了让React跟Redux结合的更优雅,react-redux
简化了这些步骤,使数据流管理使用起来更加方便。
react-redux
是运用Provider将组件和store对接,使在Provider里的所有组件都能共享store里的数据
在容器组件中通过react-redux
核心API connect
来连接UI组件和redux
,connect
是一个高阶函数,第一个参数接收的是两个回调函数,回调函数1:将接收一个state,然后返回一个对象对象中包含了UI组件想要的状态。回调函数2:接收一个dispatch,返回一个对象
react-redux使用
- index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
- App.js
import { Component } from "react";
import { connect } from "react-redux";
const App = (props) => {
const { count, dispatch, increaseHandler } = props;
return (
<div>
<h3>ReactReduxDemo</h3>
<button onClick={increaseHandler}>increaseHandler:{count}</button>
<button
onClick={() => {
dispatch({ type: "DECREASE", amount: 1 });
}}
>
dispatch: {count}
</button>
</div>
);
};
//mapStateToProps mapDispatchToProps
export default connect(
({ countState }) => {
return {
count: countState.count,
};
},
(dispatch) => {
return {
dispatch,
increaseHandler: () => dispatch({ type: "INCREASE", amount: 2 }),
decreaseHandler: () => dispatch({ type: "DECREASE", amount: 2 }),
};
}
)(ReactReduxDemo);
实现react-redux
- react-redux/index.js
import React, { useContext, useEffect, useLayoutEffect, useState } from "react";
const Context = React.createContext();
export function Provider({ store, children }) {
return <Context.Provider value={store}>{children}</Context.Provider>;
}
export const connect =
(mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (props) => {
const store = useContext(Context);
const { getState, dispatch, subscribe } = store;
const [state, setState] = useState(mapStateToProps(getState()));
let dispatchProps = { dispatch };
dispatchProps = mapDispatchToProps(dispatch);
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
const newState = mapStateToProps(getState());
setState(newState);
});
return () => {
unsubscribe();
};
}, []);
return (
<WrappedComponent
{...props}
dispatch={dispatch}
{...state}
{...dispatchProps}
/>
);
};
export function useSelector(selector) {
const store = useContext(Context);
const [state, setState] = useState(selector(store.getState()));
const { subscribe } = store;
useEffect(() => {
const unsubscribe = subscribe(() => {
setState(selector(store.getState()));
});
return () => {
unsubscribe();
};
}, []);
return state;
}
export function useDispatch() {
const store = useContext(Context);
const { dispatch } = store;
return dispatch;
}
React函数组件使用react-redux提供的Hooks
- ReactReduxHookDemo.js
import { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
const ReactReduxHookDemo = (props) => {
const dispatch = useDispatch();
const count = useSelector(({ countState }) => {
return countState.count;
});
const increaseHandler = useCallback(() => {
dispatch({ type: "INCREASE", amount: 2 });
}, []);
const decreaseHandler = useCallback(() => {
dispatch({ type: "DECREASE", amount: 2 });
}, []);
return (
<div>
<h3>ReactReduxHookDemo</h3>
<button onClick={increaseHandler}>点击+2 ---- {count}</button>
<button onClick={decreaseHandler}>点击-2 ---- {count}</button>
</div>
);
};
export default ReactReduxHookDemo;
最后
目前我们触发dispatch(action)
中action只能为一个形似{type: 'xxx', amount: ''}
的对象,因为目前触发dispatch(action)
都是直接经由reducer处理返回新的store state,实际开发场景中,我们可能点击一个按钮后需要通过发起一个Ajax的异步请求后,获取到结果后再更新store state,当然,我们可以在页面请求后再dispatch(action)
,那能否有更优雅的实现方式呢,答案是肯定的,这就引出一个新概念——redux
中间件。
所谓中间件,我们可以理解为拦截器,用于对某些过程进行拦截和处理,且中间件之间能够串联使用。在redux中,我们中间件拦截的是dispatch提交到reducer这个过程,从而增强dispatch的功能。下篇文章我们来分析实现稍困难一些的redux中间件,敬请期待......