为什么要有 ReactRedux ?
- Redux 是一个数据状态的管理仓库,它可以使数据按照预期进行可预测的变化。
- Redux 只是管理数据状态 State,它不管理视图 View !
- 在 React 中直接使用 Redux 是不能引起视图的 响应式更新 (这里的响应式指代数据驱动视图更新,不包括数据的劫持和代理),这是因为 Redux 的数据变化无法直接驱动 React 视图的变化。如果直接使用,需要手动订阅这样的更新:
jsx
import { useState, useEffect } from 'react';
import store from '@/store';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const { counter } = store.getState();
setCount(counter.count);
const unsubscribe = store.subscribe(() => {
const { counter } = store.getState();
setCount(counter.count);
});
return () => {
unsubscribe();
};
}, []);
function handleCountChange() {
store.dispatch('ADD_COUNT');
}
return (
<div className="counter">
<h1>{count}</h1>
<button onClick={ handleCountChange }></button>
</div>
);
}
- 每次更新 store 的某一个值,都需要在 useEffect 里面设置这样的订阅更新,十分的麻烦。
ReactRedux 解决了什么问题?
- ReactRedux 在 Redux 的基础上面提供了一套和 React 核心相关的、标准的数据状态 State 驱动视图 View 及时更新的解决方案,让用户直接使用对应的 API 方法(e.g
connect
、useSelector
、useDispatch
),无须手动订阅更新。 - 使用 ReactRedux 把整个 store 实例提供给了 React 的 Context 全局上下文,更方便调试。
- 使用 ReactRedux 更加符合 React 的编程范式 (函数式编程、hook 的调用),可读性和可维护性更强。
ReactRedux 的基本用法:
在使用 ReactRedux 之前,都需要把 store 全局提供给 App:
jsx
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
const WrapApp = () => (
<Provider store={store}>
<App />
</Provider>
);
createRoot(document.getElementById)
.render(
// <App />
<WrapApp />
);
根据编程范式的不同,ReactRedux 有以下两种不同的用法:
- connect 高阶组件包裹
- ReactRedux 提供的 hook
1. connect 高阶组件包裹
connect
函数是一个柯里化式调用的高阶组件
connect
在第一次调用的时候接收两个参数:mapStateToProps
函数和mapDispatchToProps
函数connect
在第二次调用的时候接收需要被提供 Redux state 和 Redux dispatch 的函数
jsx
import { connect } from 'react-redux';
function Counter({ count, addCount, initCount }) {
return (
<div>
<h1>{count}</h1>
<button onClick={() => addCount()}>ADD COUNT</button>
<button onClick={() => initCount()}>INIT COUNT</button>
</div>
);
}
export default connect(
function mapStateToProps({ counter }) {
return {
count: counter.count
};
},
function mapDispatchToProps(dispatch) {
return {
addCount() {
dispatch({
type: 'ADD_COUNT'
});
},
initCount() {
dispatch({
type: 'INIT_COUNT'
});
}
};
}
)(Counter);
当然,mapDispatchToProps
也可以直接配置一个 action 集合的对象
jsx
import { connect } from 'react-redux';
function Counter({ count, addCount, initCount }) {
return (
<div>
<h1>{count}</h1>
<button onClick={() => addCount()}>ADD COUNT</button>
<button onClick={() => initCount()}>INIT COUNT</button>
</div>
);
}
export default connect(
function mapStateToProps({ counter }) {
return {
count: counter.count
};
},
{
addCount: {
type: 'ADD_COUNT'
},
initCount: {
type: 'INIT_COUNT'
},
}
)(Counter);
2. ReactRedux 提供的 hook
useSelector()
概述:
useSelector()
是新版 ReactRedux 提出的获取 Redux State 的 hook 。
它接收一个携带 state 的回调函数,返回回调函数执行结果的 React 状态值。
基本用法:
使用 useSelector()
生成一个 Redux State 的返回值
js
const reactState = useSelector((state) => state.xxx);
useDispatch()
概述:
useDispatch()
是一个新版 ReactRedux 提出的获取 Redux Dispatch 的 hook 。
调用 useDispatch()
可以生成一个操作 Redux State 的 dispatch
方法,并且此 dispatch
方法可以及时更新视图。
基本用法:
使用 useDispatch()
生成一个操作 Redux State 和 视图更新的方法
js
const dispatch = useDispatch();
使用 useSelector()
和 useDispatch()
实现上面的功能:
jsx
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
// { count, addCount, initCount }
const count = useSelector(state => state.counter.count);
const dispatch = useDispatch();
const addCount = () => {
dispatch({
type: 'ADD_COUNT'
});
}
const initCount = () => {
dispatch({
type: 'INIT_COUNT'
});
}
return (
<div>
<h1>{count}</h1>
<button onClick={() => addCount()}>ADD COUNT</button>
<button onClick={() => initCount()}>INIT COUNT</button>
</div>
);
}
实现一下 ReactRedux 的基本功能:
注意
实现 ReactRedux 需要先有 react
、redux
依赖 !
概述:
本文和源码不同,直接使用函数组件的思路来实现:
目录结构:
perl
my-react-redux
|- adaptors.js # 适配器函数
|- connect.jsx # connect 高阶组件
|- context.js # my-react-redux 需要使用到的 context
|- hooks.jsx # 新版 my-react-redux 需要实现的 hook
|- index.js # my-react-redux 导出的 API
|- Provider.jsx # redux store 提供器
实现步骤:
按照程序从外向里设计,具体的实现步骤如下:
- 从 index.js 中导出需要给到外界使用的 API:
js
export { default as Provider } from './Provider';
export { default as connect } from './connect';
export * from './hooks';
- 在 context.js 创建一个 StoreContext, 表示提供 Redux Store 的 React 上下文对象:
js
import { createContext } from 'react';
/** @type {React.Context<any>} redux store context */
export const StoreContext = createContext({});
- 在
Provider.jsx
中实现一下 Provider 组件。这个组件比较简单,相当于把 Redux Store 提供给了全局:
jsx
import { StoreContext } from './context';
function Provider(props) {
const { store, children } = props;
return (
<StoreContext.Provider value={store}>
{children}
</StoreContext.Provider>
);
}
export default Provider;
-
实现 connect 高阶组件,这个高阶组件比较麻烦:
- 接收
mapStateToProps
和mapDispatchToProps
,生成一个createWrapComponent
高阶组件 mapDispatchToProps
可以是一个函数,也可以是一个 actions 的对象createWrapComponent
返回的是一个新的组件,这个组件里面需要收集所有的 props 传递给需要被提供 Redux 状态和方法的被包裹组件渲染
于是,我们采取这样的操作方案:
- 接收
js
/* 在 `adaptors.js`中写一个 `getMapDispatchToProps`方法,处理上述的第二点情况: */
export function getMapDispatchToProps({
mapDispatchToProps,
dispatch
}) {
if (typeof mapDispatchToProps === 'function') {
// ActionCreatorFunction
return mapDispatchToProps(dispatch) || {};
}
if (typeof mapDispatchToProps === 'object' && mapDispatchToProps !== null) {
// ActionNamespaceObject
return Object.keys(mapDispatchToProps).reduce((res, actionName) => {
res[actionName] = function (...args) {
dispatch(mapDispatchToProps[actionName].apply(null, args));
}
return res;
}, {});
}
return {};
}
jsx
/* 基于 `adaptors`编写 `connect` 高阶组件: */
import { useState, useContext } from 'react';
import { StoreContext } from './context';
import { getMapDispatchToProps } from './adaptors';
function connect(mapStateToProps, mapDispatchToProps) {
return (WrapComponent) => {
return (props) => {
const store = useContext(StoreContext);
const [storeState, setStoreState] = useState(store.getState());
const dispatch = (action) => {
store.dispatch(action);
setStoreState(store.getState());
}
const combineProps = {
...props,
...mapStateToProps(storeState),
...getMapDispatchToProps({
mapDispatchToProps,
dispatch
})
};
return (
<WrapComponent { ...combineProps } />
);
}
};
}
export default connect;
- 在
hooks.js
写一下外界需要使用到的自定义 hook:
js
import { useContext, useState, useCallback } from 'react';
import { StoreContext } from './context';
function useStore() {
return useContext(StoreContext);
}
export function useSelector(selector) {
const store = useStore();
try {
return selector(store.getState());
} catch (e) {
console.warn(e);
return null;
}
}
export function useDispatch() {
const store = useStore();
const [storeState, setStoreState] = useState(store.getState());
const dispatch = useCallback((action) => {
store.dispatch(action);
setStoreState(store.getState);
}, [store]);
return dispatch;
}
这样,ReactRedux 的基本功能就实现了!