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 (
<div>
<div> 当前计数为:{count} </div>
<button onClick={() => {setCount(count + 1)}}>计数加1</button>
<button onClick={() => {setCount(count + 2)}}>计数加2</button>
</div>
)
}
计数器组件采用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 (
<div>
<h3> 当前计数为:{count} </h3>
<button onClick={() => {
dispatch({type: 'incrementOne'})
}}>计数加1</button>
<span> </span>
<button onClick={() => {
dispatch({type: 'incrementTwo'})
}}>计数加2</button>
</div>
)
}
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 (
<div>
<h3> 当前计数为:{count} </h3>
<button onClick={() => dispatch(incrementOne())}>计数加1</button>
<button onClick={() => dispatch(incrementTwo())}>计数加2</button>
<button onClick={() => dispatch(incrementOther(parseInt(params.value)))}>计数加特定值</button>
</div>
)
}
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`提供的`<Provider/>`全局包装`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(
<Provider store={store} >
<BrowserRouter>
<Route path='Tab3' element={<Counter value ='5'/>} />
</BrowserRouter>
</Provider>
);
//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 (
<div>
<div> 当前计数为:{ownProps.count} </div>
{/* 自己调度dispatch */}
<button onClick={() => { ownProps.dispatch({type: 'incrementOne'}) }}>计数加1</button>
<button onClick={() => { ownProps.dispatch({type: 'incrementTwo'}) }}>计数加2</button>
<button onClick={() => {
ownProps.dispatch({type: 'incrementOther',payload: parseInt(ownProps.value)})
}}>计数加特定值</button>
</div>
)
}
///从`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 (
<div>
<div> 当前计数为:{ownProps.count} </div>
<button onClick={ownProps.incrementOne}>计数加1</button>
<button onClick={ownProps.incrementTwo}>计数加2</button>
<button onClick={ownProps.incrementOther}>计数加特定值</button>
</div>
)
}
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 (
<div>
<div> 当前计数为:{count} </div>
<button onClick={()=>{dispatch(incrementOne())}}>计数加1</button>
<button onClick={()=>{dispatch(incrementTwo())}}>计数加2</button>
<button onClick={()=>{dispatch(incrementOther(parseInt(props.value)))}}>计数加特定值</button>
</div>
)
}
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 (<div>
<h2>异步计数:{count}</h2>
<button onClick={() =>{
store.dispatch(incrementOtherAsync(5))
}}>异步计数加特定值</button>
</div>)
}
export default AsyncCounter
///使用react-redux改进一下组件
const AsyncCounter = (props) => {
return (<div>
<h2>异步计数:{props.count}</h2>
<button onClick={props.incrementOtherAsync}>异步计数加特定值</button>
</div>)
}
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 (<div>
<h2>异步请求的状态:{props.status} </h2>
<h2>异步请求返回的数据:{props.asyncData} </h2>
<h2>异步计数:{props.count}</h2>
<button onClick={props.incrementOtherAsync}>异步计数加特定值</button>
</div>)
}
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中间件的数据流向如下图: