redux团队先后推出了redux、react-redux、@reduxjs/toolkit,这三个库的api各有不同。本篇文章就来梳理一下当我们需要在项目中集成redux,从直接使用redux,到使用react-redux,再到react-redux和@reduxjs/toolkit配合使用,分别需要调用到哪些api。
一、在react中集成redux
redux中通过createStore可以创建出store实例,在该实例上存在三个实例方法。
通过store.subscribe可以监听到state的改变,引起组件的重新渲染。
通过dispatch可以分发action,引起redux内部state的更新。
通过getState可以获取到redux内部的state。
以一个简单的计数器程序作为示例。
首先在App组件中引入Count组件,为其传入store实例作为props。
import React from "react";
import Count from "./components/Count";
import store from "./store";
export default function App() {
return <Count store={store} />;
}
在Count组件中,我们在组件挂载后通过store.subscribe监听redux的state,通过store,getState获取state,为按钮绑定点击事件,回调调用dispatch,传入action的返回值作为参数。
import React, { useEffect, useState } from "react";
import store from "../store";
import { createAddActions } from "../store/actions/count";
export default function Count() {
const [_, rerender] = useState({});
useEffect(() => {
store.subscribe(() => rerender({}));
}, []);
return (
<div>
<div>sum: {store.getState()}</div>
<button onClick={() => store.dispatch(createAddActions(1))}>
点我+1
</button>
</div>
);
}
接下来是store的编写。
// store/index.js
import { countReducer } from "./reducers/count";
import { createStore } from "redux";
export default createStore(countReducer);
reducers必须是一个纯函数,纯函数的概念是需要返回一个新的state替换原来的state,而不是直接在原来的state上修改。
// store/reducers/count.js
export function countReducer(prevState, action) {
switch (action.type) {
case "add":
return prevState + action.value;
default:
return 0;
};
};
action的实现。
// store/actions/count.js
export function createAddActions(value) {
return {
type: "add",
value,
};
};
二、在react中集成react-redux
react-redux提出了容器组件的概念,要求redux只能和容器组件进行通信,至于ui组件,只能通过props来获取容器组件传入的state和引起redux内部state改变的callback。
引入容器组件的目的在于尽量让ui组件内部看起来与其他不需要使用redux的组件无异。
如何理解无异呢?就是不要直接去调用store的实例方法,而是调用父组件提供的方法。当我们调用父组件提供的方法,而react-redux会再调用store的实例方法。
store.getState() -> props.state
store.dispatch() -> props.callback()
store.subscribe不再手动调用,而是由react-redux执行。
Count.js修改如下。
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { createAddActions } from "../store/actions/count";
function Count(props) {
return (
<div>
<div>sum: {props.state}</div>
<button onClick={() => props.add(1)}>点我+1</button>
</div>
);
}
export default connect(
(state) => ({ state }),
(dispatch) => ({
add: (number) => dispatch(createAddActions(number)),
})
)(Count);
connect的第二个参数还可以简写为对象形式。
export default connect((state) => ({ state }), { add: createAddActions })(Count);
我们使用redux的目的是在多个组件之间共享state,所以我们直接引用redux时,需要在多个组件中传入store作为props,react-redux针对此也做了优化。
我们只需引入Provider包裹App组件,在Provider中传入store作为props,我们就可以在容器组件中获取到props。其实底层就是调用了react的SomeContext.Provider,内部组件只要使用useContext就可以获取到store。
// index.js
import React from "react";
import ReactDOM from 'react-dom/client';
import { Provider } from "react-redux";
import store from "./store";
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
在Count组件中,我们不再需要手动传入store。
import React from "react";
import Count from "./components/Count";
export default function App() {
return <Count />;
}
在现在的react-redux中,更推荐使用useSelector和useDispatch来代替connect。当我们使用useSelector和useDispatch获取state和分发action时,不再存在container组件,取而代之的是selector hook和dispatch hook。
对于connect而言,会优先从props中获取store,不存在的情况下再用React.useContext获取props,所以store有两种传入方式。
对于useSelector和useDispatch而言,他们只是Hook而不是组件,没有props,只能从React.useContext中获取store,所以如果外层没有Provider组件的话,项目是无法运行的。
此外,connect和Provider都实现了对store的监听。
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { createAddActions } from "../store/actions/count";
export default function Count() {
const state = useSelector((state) => state);
const dispatch = useDispatch();
return (
<div>
<div>sum: {state}</div>
<button onClick={() => dispatch(createAddActions(1))}>点我+1</button>
</div>
);
}
三、在React中集成@reduxjs/toolkit和react-redux
@reduxjs/toolkit的作用如下:
1. 将initialState、reducers、actions整合在一起形成切片
2. 自动生成action,{name: sliceName/reducerName, payload: value}
3. 代替redux/thunk实现异步action功能
// store/slices/count.js
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
add: (state, action) => {
return state + action.payload;
},
},
});
export const { add } = counterSlice.actions;
export default counterSlice.reducer;
// store/index.js
import countReducer from "./slices/count";
import { configureStore } from "@reduxjs/toolkit";
export default configureStore({
reducer: countReducer // reducer的结构对应state的结构
});