目录
通过纯函数(pure function)改变数据源(state)
react-redux的基本使用
安装redux和react-redux
yarn add redux react-redux
编写reducer并创建store
import { createStore } from 'redux'
const countReducer = (state = 0, action) => {
console.log('state', state);
switch (action.type) {
case 'ADD':
return state + 1
case 'MINUS':
return state - 1
default:
return state
}
}
const store = createStore(countReducer)
注意:现在官方推荐用@reduxjs/toolkit的configureStore方法代替createStore
最外层组件提供store
import { Provider } from 'react-redux'
import store from './store';
import CountClass from './count-class';
import CountFn from './count-fn';
import './App.css';
function App() {
return (
<Provider store={store}>
<div className="App">
<CountClass />
<CountFn />
</div>
</Provider>
);
}
export default App;
内层子组件使用
类组件:
import { Component } from 'react'
import { connect } from 'react-redux'
class Count extends Component {
render () {
const {value, onAddClick, onMinusClick } = this.props
return (
<div>
<h1>count in Class: { value}</h1>
<button onClick={onAddClick}>ADD in Class</button>
<button onClick={onMinusClick}>MINUS in Class</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
value: state
}
}
const mapDispatchToProps = (dispatch) =>{
return {
onAddClick: () => dispatch({ type: 'ADD' }),
onMinusClick: () => dispatch({ type: 'MINUS' })
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Count)
函数式组件:
import {useSelector, useDispatch} from 'react-redux'
function Count() {
const value = useSelector(state => state)
const dispatch = useDispatch()
const onAddClick = () => {
dispatch({ type: 'ADD' })
}
const onMinusClick = () => {
dispatch({ type: 'MINUS' })
}
return (
<div>
<h1>count in Fn: { value}</h1>
<button onClick={onAddClick}>ADD in Fn</button>
<button onClick={onMinusClick}>MINUS in Fn</button>
</div>
);
}
export default Count;
redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
中文官网:
中文文档:
基本原理
视图层获取state的数据,订阅action的执行,然后更新state。
核心概念
state
state 的形式取决于你,可以是基本类型、数组、对象、甚至是 Immutable.js 生成的数据结构。唯一的要点是当 state 变化时需要返回全新的对象,而不是修改传入的参数。
action
action 就是一个普通 JavaScript 对象,用来描述发生了什么。
reducer
reducer 是一个接收 state 和 action,并返回新的 state 的函数。
请记住:reducer 函数必须总是通过复制来不可变地创建新的状态值! 调用诸如
Array.push()
之类的变异函数或修改 Redux Toolkit 中的createSlice()
中的诸如state.someField = someValue
之类的对象字段是安全的,因为它使用 Immer 库在内部将这些突变转换为安全的不可变更新,但不要尝试在createSlice
之外更改任何数据!
三大原则
唯一数据源(state)
所有视图的数据应该都是直接或间接来自于state,否则UI视图是无法因state改变而更新的。
数据源(state)只读
不应该直接修改state数据。
通过纯函数(pure function)改变数据源(state)
preState => newState,reducer就是一个纯函数
createStore的实现
createStore是redux的核心方法之一,它接收reducer和一个可选的中间件函数执行结果为参数,返回一个store对象,store对象里主要包含获取状态(getState)、订阅(subscribe)、发布(dispatch)三个方法,其实现如下:
function createStore(reducer, enhancer) {
if (enhancer) {
// 返回一个加强版的store
return enhancer(createStore)(reducer);
}
let currentState = undefined;
let currentListeners = [];
let isDispatching = false;
function getState() {
return currentState;
}
let nextListeners = currentListeners;
// 每次添加订阅和取消订阅时需要拷贝一个新的完全隔离的数组进行操作,防止在dispatch时发生bug
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
const subscribe = (listener) => {
let isSubscribed = true;
// 新生成一个listener数组,以免push时影响 currentListener
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
// 如果已经移除了就直接返回
if (!isSubscribed) {
return;
}
isSubscribed = false;
ensureCanMutateNextListeners();
// 没有移除的话,先找到位置,通过splice移除
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
};
combineReducers的实现
combineReducers是redux提供的用于组合多个reducer的方法,其实现如下:
function applyMiddleware(...middlewares) {
// 返回enhancer
return createStore => (...args) => {
// 先得到基础版store
let store = createStore(...args)
let { getState, dispatch } = store
const middleApi = {
getState,
// dispatch
dispatch: (...args) => newDispatch(...args) // 为什么要这么写?继续看后文thunk的实现中就明白了
}
// 给中间件传递store的相关api参数
const middlewaresChain = middlewares.map(middleware => middleware(middleApi))
// 利用聚合函数执行middlewaresChain里的函数,得到新的dispatch
const newDispatch = compose(...middlewaresChain)(dispatch)
return {
...store,
// 覆盖store的dispatch
dispatch: newDispatch
}
}
}
// 聚合函数,参数数组中的方法从后向前调用,并将返回值作为参数产传递给前一个
function compose(...funs) {
if(funs.length === 0) {
return arg => arg;
}
if(funs.length === 1) {
return funs[0]
}
return funs.reduce((previousValue, currentValue) => {
return (...args) => {
return previousValue(currentValue(...args))
}
})
}
// 以上compose(a,b,c)(...args) 等价于 a(b(c(...args)))
中间件的实现
通过applyMiddleware源码中对中间件的调用的学习我们可以知道,中间件接收middleApi参数,返回一个三层柯里化的函数,其实现如下:
function middleware(middleApi) {
// 这一层会在applyMiddleware里自动执行,middlewares.map(middleware => middleware(middlewareAPI))
return function (next) {
// 这一层在compose函数里执行,compose(a,b,c)(...arg),next是右边中间件函数的返回值,或者原始store的dispatch(如果是最后中间件(最右边))
return function (action) {
// 这一层在业务代码中执行(如果这是第一个中间件(最左边),dispatch(对象或者函数),或者被前一个(左边)中间件当作next调用
// 返回结果
const result = next(action);
return result
}
}
}
react-thunk中间件可实现异步调用dispatch,其实现如下:
function thunk(middleApi) {
return function (next) {
// 以下返回的函数会传递给前一个(左边)中间件作为参数或者业务中的newDispatch(action)(如果是第一个(最左边)的情况下)
return function (action) {
// 从middleApi中解构出dispatch, getState
const { dispatch, getState } = middleApi;
// 如果action是函数,将它拿出来运行,参数就是dispatch和getState
if (typeof action === 'function') {
return action(dispatch, getState);
// 这里把原始的dispatch传给业务代码,然后直接return了
}
// 否则按照普通action处理
const result = next(action);
return result
}
}
}
// 从上面看来thunk中间件只能放在最后一个(最右边)的原因,因为这里没法再调用next了。
// 然而事实并不是这样的,真实使用中thunk并没有限制,这是为什么?
// 原因是middleApi中的dispatch并不是store.dispatch,而是dispatch: (...args) => newDispatch(...args)
// 即上文applyMiddleware的实现中的:
const middleApi = {
getState,
dispatch
}
// 应该是:
const middleApi = {
getState,
dispatch: (...args) => newDispatch(...args)
}
// 这样从middleApi拿到的dispatch在执行的时候就可以拿到compose返回的newDispatch了
react-redux
Provider的实现
Provider就是提供了一个store的上下文,其实现如下:
const Valuecontext = React.createContext()
class Provider extends Component {
render() {
return (
<Valuecontext.Provider value={this.props.store}>
<div>
{this.props.children}
</div>
</Valuecontext.Provider>
);
}
}
connect的实现
connect返回一个高阶组件,利用上下文可以获取store,添加监听,然后将state和dispatch传给类组件,其实现如下:
const connect =
(
mapStateToProps = (state) => ({ state }),
mapDispatchToProps
// mergePros // 没有实现
) =>
(WrapComponent) => {
// 最新源码返回的是函数组件,最终调用react的useSyncExternalStore hooks实现订阅和更新数据,这里用类组件说明原理
return class extends Component {
static contextType = Valuecontext;
constructor() {
super();
this.state = {
props: {},
};
}
componentDidMount() {
const { subscribe } = this.context;
this.update();
// 订阅store更新
this.unsubscribe = subscribe(() => {
// 源码中利用快照和最新的value比较,判断state是否变化,防止重复更新
this.update();
});
}
componentWillUnmount() {
this.unsubscribe();
}
update() {
const { dispatch, getState } = this.context;
let dispatchProps;
let stateProps = mapStateToProps(getState());
if (typeof mapDispatchToProps === "object") {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
} else if (typeof mapDispatchToProps === "function") {
dispatchProps = mapDispatchToProps(dispatch);
} else {
// 默认
dispatchProps = { dispatch };
}
this.setState({
props: {
...dispatchProps,
...stateProps,
},
});
}
render() {
return <WrapComponent {...this.state.props} />;
}
};
};
useSelector和useDispatch的实现
useSelector和useDispatch是利用useContext获取上下文store,添加监听,然后将state和dispatch传给函数组件使用,其实现如下:
function useSelector(fn = (state) => ({ state })) {
// 源码中是通过调用react的useSyncExternalStoreWithSelector hooks包装selector,并最终调用useSyncExternalStore实现订阅和更新数据,和connect一致
const { getState, subscribe } = useContext(Valuecontext);
const [, setstate] = useState(0);
useEffect(() => {
const unsubscribe = subscribe(() => {
// 源码中利用快照和最新的value比较,判断state是否变化,防止重复更新
setstate((v) => v + 1);
});
return () => {
unsubscribe();
};
}, [subscribe]);
return fn(getState());
};
function useDispatch () {
const { dispatch } = useContext(Valuecontext);
return dispatch;
};