Redux
Redux
是一个小型的独立JS
库,Redux
可以与任何 UI
框架集成,常与React
一起使用,因此Redux
官方提供了React-Redux
的包,来让React
组件通过读取状态与调度操作(更新Store
)与React Store
交互。
Redux
设计思想:
- 视图与状态是一一对应的
- 所有的状态,保存在一个对象里面
Redux
的核心:
-
createStore
:创建一个Redux Store
存储 -
combineReducers
:将多个不同的Reducer
函数,包装成一个Reducer
,它将调用每个child Reducer
,然后把它们的结果收集到一个状态对象中。 -
applyMiddleware
:将中间件应用到Store
增强器,比如异步状态获取。 -
compose
:将多个Store
增强器合并为一个Store
增强器
React Store
就是保存数据的地方,可以认为它是一个容器,整个应用只能有一个Store
///计数器组件
const Counter = (params) => {
const [count, setCount] = useState(0)
return (
当前计数为:{count}
)
}
计数器组件采用redux
进行状态管理
///第1步,创建Store对象
import { createStore } from "redux";
///函数`createStore`的参数之一,初始状态
const initValues = {
count: 12,
}
///函数`createStore`的参数之一,Reducer函数(两参数,一个状态,一个更新状态的函数,like 高阶函数reduce)
const CounterReduce = (state, action) => {
switch (action.type) {
case 'incrementOne':
return {...state,count: state.count + 1}
case 'incrementTwo':
return {...state,count: state.count + 2}
default:
return state
}
}
///创建完毕
const store = createStore(counterReduce,initValues)
export default store;
///第2步,使用`Redux Store`,实现全局状态的多组件共享状态
import store from './Store.js'
///函数组件
const Counter = (params) => {
// state 的值 {count: 0}
const state = store.getState()
///2.1 取出默认值,设置状态
const [count, setCount] = useState(state.count)
///2.2 组件挂载后,订阅数据对象
useEffect(()=>{
const unsubscribe = store.subscribe(()=>{
//2.3 状态改变时会回调,需重新读取
setCount(store.getState().count)
})
return unsubscribe
},[state])
//2.4 返回UI的操作函数,用于发送`action`对象
const dispatch = store.dispatch
return (
当前计数为:{count}
)
}
export default Counter
Rudux
的基本数据流:UI
—>Action
—>Store
—>Reducer
—>State
—>UI
Redux ToolKit
Redux ToolKit
简称RTK
是官方推荐的编写Redux
状态逻辑的方式,使用它可以规避许多错误,使得使用Redux
更简单,更规范。
If you are writing any Redux logic today, you should be using Redux Toolkit to write that code!
RTK
包含有助于简化许多常见用例工具,包括存储设置、创建 reducer
和编写不可变更新的逻辑,甚至一次创建整个状态切片的逻辑。
RTK
的核心API
:
-
configureStore
:是对Redux
的createStore()
函数的友好抽象。 -
createSlice
: 该函数接收一个初始状态,一个Reducer
函数对象,一个name
,然后自动生成reducer
函数可以响应的action
,简单说action
不需要手写,自动生成
RTK
的安装:
# 独立安装
npm install @reduxjs/toolkit
# 创建`React`应用执行模板安装
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
采用Redux ToolKit
改造上述示例:
///第1步,创建Store对象
import { configureStore } from '@reduxjs/toolkit'
///导入函数`createStore`的参数 couterSlice
import counterSlice from './CounterSlice.js';
///创建
const store = configureStore({
reducer : counterSlice.reducer
})
export default store;
///-------CounterSlice.js-----------
import { createSlice } from "@reduxjs/toolkit";
///createSlice封装了不可变更新
const counterSlice = createSlice(
{
name: 'counter',
initialState:{
count: 0,
},
reducers:{
incrementOne: state => {state.count += 1},
incrementTwo: state => {state.count += 2},
incrementOther: (state,action) => {//action:{type: 'counter/incrementOther', payload: 5}
state.count += action.payload
}
}
}
)
export const {incrementOne,incrementTwo,incrementOther} = counterSlice.actions
export default counterSlice
///--------------CounterSlice.js-----
///第2步,组件中使用
import store from './Store.js'
import {incrementOne,incrementTwo,incrementOther} from './CounterSlice.js';
///函数组件
const Counter = (params) => {
// state 的值 {count: 0}
const state = store.getState()
///取出默认值,设置状态
const [count, setCount] = useState(state.count)
///订阅它
useEffect(()=>{
const unsubscribe = store.subscribe(()=>{
//状态改变时会回调,需重新读取
setCount(store.getState().count)
})
return unsubscribe
},[state])
//返回UI的操作函数,用于发送`action`对象
const dispatch = store.dispatch
return (
当前计数为:{count}
)
}
export default Counter
React-redux
React-redux
是React
与Redux
的官方绑定库,由Redux
团队维护,是独立存在的库,需要单独安装:npm install --save react-redux
。
我们使用React-redux
改造上述示例:
/*:
1.index.js文件中导入 Redux Store
2.使用`React-Redux`提供的` `全局包装`Store`----重要,不然找不到`Store`
*/
import Counter from './pages/redux/Tab3.jsx'
import { Provider } from "react-redux";
import store from "./pages/redux/Store.js";///Redux中定义的Store保持不变
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
} />
);
//3.在Tab3.jsx中导入connect函数
import { connect } from "react-redux";
//4.定义组件,参数会返回:store.dispatch & store.getState()& props
const Counter = (ownProps) => {//ownProps:{value: '5', count: 0, dispatch: ƒ}
return (
当前计数为:{ownProps.count}
{/* 自己调度dispatch */}
)
}
///从`Store`中获取组件需要的数据
const mapStateToProps = (state,ownProps) => {
//ownProps: {value: '5'}
return { count: state.count }
}
//5.连接到`ReduxStore`
const connectToStore = connect(mapStateToProps)
//6.连接到`组件`,返回容器组件
const connectToComponent = connectToStore(Counter)
export default connectToComponent
mapStateToProps
作为connect
函数的参数之一,用来从Store
中获取当前组件需要的数据:
- 每次存储状态更改时都会调用它。
- 它接收整个存储的状态,并应返回该组件所需的数据对象。
connect
函数的另一个参数mapDispatchToProps
可实现对ownProps.dispatch
的封装,并最终将具体dispatch action
以函数的形式绑定到组件的props
。connect
提供了两种组件的数据调度的方式:
-
ownProps.dispatch
手动调度 - 创建调度函数,通过
ownProps.funcName
直接使用,不需要和action
交互
const mapStateToProps = (state,ownProps) => {
//ownProps: {value: '5'}
return { count: state.count }
}
const mapDispatchToProps = (dispatch,ownProps) => {
return {
incrementOne: () => dispatch({type: 'incrementOne'}),
incrementTwo: () => dispatch({type: 'incrementTwo'}),
//直接绑定到Props
incrementOther: () => dispatch({type: 'incrementOther',payload:parseInt(ownProps.value)}),
///组件内传参数
// incrementOther: (value) => dispatch({type: 'incrementOther',payload:parseInt(value)}),
}
}
const connectToStore = connect(mapStateToProps,mapDispatchToProps)
const connectToComponent = connectToStore(Counter)
export default connectToComponent
///组件使用
const Counter = (ownProps) => {
return (
当前计数为:{ownProps.count}
)
}
React-redux hook
使用React-redux hook
实现组件与React Store
的交互。通过useSelector
读取Store
中的数据,使用useDispatch
实现Action
的调度。
///1.Store.js RTK
const store = configureStore({
reducer : counterSlice.reducer
})
//RTK createSlice 自动生成action: {type: 'counter/incrementOther', payload: 5}
//2.CounterSlice.js
const counterSlice = createSlice(
{
name: 'counter',
initialState:{
count: 0,
status:'idle...'
},
reducers:{
incrementOne: state => {state.count += 1},
incrementTwo: state => {state.count += 2},
incrementOther: (state,action) => {//action:{type: 'counter/incrementOther', payload: 5}
state.count += action.payload
}
}
}
)
///导出Slice的action创建函数
export const {incrementOne,incrementTwo,incrementOther} = counterSlice.actions
///3.组件使用
import { incrementOne,incrementTwo,incrementOther } from "../redux/CounterSlice.js";
import { useDispatch, useSelector } from "react-redux"
const HookCounter = (props) => {
///以下两句等价于` const {count,status} = useSelector(state => state)`
const status = useSelector(state => state.status)
const count = useSelector(state => state.count)
const dispatch = useDispatch()
return (
当前计数为:{count}
)
}
export default HookCounter
总结:
React-Redux
提供了connect
函数实现了Store
与组件的独立绑定,代码逻辑清晰。
React-Redux hook
提供useDispatch
和useSelector
访问和操作Store
,代码更简洁
Redux-Thunk
Thunk
这个词是一个编程术语,意思是一段执行一些延迟工作的代码。Thunk
是在Redux
应用程序中编写异步逻辑的标准方法,比如数据获取,也可以用作同步。
Thunk 最适合用于复杂的同步逻辑,以及简单适度的异步逻辑,例如发出标准 AJAX 请求并根据请求结果调度操作。
将 Thunk
中间件添加到Redux
存储后,它允许将thunk
函数直接传递给store.dispatch
.
Thunk
函数将始终(dispatch, getState)
作为其参数调用,可以根据需要在thunk
中使用它们。
计数器异步更新示例:
///Store.js
import { createStore,applyMiddleware } from "redux";
import thunk from "redux-thunk"
///应用中间件
const store = createStore(counterReduce,initValues,applyMiddleware(thunk))
export default store;
///Reduce
const CounterReduce = (state, action) => {
switch (action.type) {
case 'incrementOther':
return {
...state,
count: state.count + action.payload
}
default:
return state
}
}
export default CounterReduce
///thunk函数
const incrementOtherAsync = (number) => (dispatch,getState) => {
console.log(getState());//{count: 10}
setTimeout(() => dispatch({ type: 'incrementOther', payload: number }), 1000)
}
///组件
const AsyncCounter = (props)=>{
const [count,setCount] = useState(store.getState().count)
useEffect(()=>{
return store.subscribe(()=>{
setCount(store.getState().count);
})
},[count])
return (
异步计数:{count}
)
}
export default AsyncCounter
///使用react-redux改进一下组件
const AsyncCounter = (props) => {
return (
异步计数:{props.count}
)
}
export default connect((state) => {
return {
count: state.count
}
}, (dispatch, ownProps) => {
return {
incrementOtherAsync: () => { dispatch(incrementOtherAsync(parseInt(ownProps.value))) }
}
})(AsyncCounter)
Redux ToolKit
包含了Redux
、Redux-Thunk
、Reselect
并且 RTK
的configureStore
已经默认帮我们添加了Thunk
中间件。
///RTK 创建Store
import { configureStore } from '@reduxjs/toolkit'
import counterSlice from './CounterSlice.js';
const store = configureStore({
reducer : counterSlice.reducer
})
export default store;
//组件使用
import {incrementOne,incrementTwo,incrementOther} from '../redux/CounterSlice.js';
///thunk
const incrementOtherAsync = (number) => (dispatch,getState) => {
console.log(getState());//{count: 10}
setTimeout(() => dispatch(incrementOther(number)), 1000)
}
//react-redux connect同上
Thunk
函数通常写在Slice
文件中。createSlice
本身对定义thunk
没有任何特殊支持,只是这样当Thunk
函数访问普通的动作创建函数,如incrementOther
比较方便。
传统的Thunk
在对异步请求的状态进行监听时,需要做许多重复的工作:
const getRepoDetailsStarted = () => ({
type: 'repoDetails/fetchStarted'
})
const getRepoDetailsSuccess = repoDetails => ({
type: 'repoDetails/fetchSucceeded',
payload: repoDetails
})
const getRepoDetailsFailed = error => ({
type: 'repoDetails/fetchFailed',
error
})
//Thunk函数,直接Store.dispatch(fetchIssuesCount(...))便可触发异步操作
const fetchIssuesCount = (org, repo) => async dispatch => {
dispatch(getRepoDetailsStarted())
try {
const repoDetails = await getRepoDetails(org, repo)
dispatch(getRepoDetailsSuccess(repoDetails))
} catch (err) {
dispatch(getRepoDetailsFailed(err.toString()))
}
}
基于此Redux ToolKit
提供了createAsyncThunk
函数,来简化上述操作。
下面基于异步计数器的例子,我们再加入网络数据请求(模拟的),使用createAsyncThunk
来监听异步请求的状态。
///CounterSlice.js
///模拟异步数据返回
let number = 100
// 'asyncGetData' action 的 type
const asyncGetData = createAsyncThunk('asyncGetData',async ()=> {
const result = await new Promise((resolve,reject) =>{
setTimeout(() => {
number += 100
resolve(number) // number is action 的 payload
},2000)
})
return result
})
const counterSlice = createSlice(
{
name: 'counter',
initialState:{
count: 0,
status:'idle...'
},
reducers:{ ///此处同上,省略..
},
extraReducers(builder) {
///监听定义的异步数据模拟函数,请求成功的状态
///若要监听生效,需先Store.dispatch该异步函数
builder.addCase(asyncGetData.fulfilled,(state, action) => {
console.log("请求成功");
console.log(state);
console.log(action);//{type: 'asyncGetData/fulfilled', payload: 109, meta: {…}}
state.status = 'async end...'
state.asyncData = action.payload
}).addCase(asyncGetData.pending,(state, action) => {
console.log("请求加载中...");
state.status = 'async start..'
}).addCase(asyncGetData.rejected,(state, action) => {
console.log("请求出错");
})
}
}
)
export {asyncGetData}
///组件内
const AsyncCounter = (props) => {
return (
异步请求的状态:{props.status}
异步请求返回的数据:{props.asyncData}
异步计数:{props.count}
)
}
export default connect((state) => {
return {
count: state.count,
status: state.status,
asyncData : state.asyncData
}
}, (dispatch, ownProps) => {
return {
incrementOtherAsync: () => {
//异步计数:加
dispatch(incrementOtherAsync(parseInt(ownProps.value)))
///模拟网路请求
dispatch(asyncGetData())
}
}
})(AsyncCounter)
显示如下:
Redux-Thunk
中间件的数据流向如下图:
参考资料
https://redux.js.org/tutorials/quick-start
https://react-redux.js.org