React状态管理

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

image.png

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:是对 ReduxcreateStore() 函数的友好抽象。
  • 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-reduxReactRedux的官方绑定库,由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以函数的形式绑定到组件的propsconnect 提供了两种组件的数据调度的方式:

  1. ownProps.dispatch手动调度
  2. 创建调度函数,通过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 提供useDispatchuseSelector访问和操作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包含了ReduxRedux-ThunkReselect并且 RTKconfigureStore已经默认帮我们添加了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)

显示如下:

image.png

Redux-Thunk中间件的数据流向如下图:

image.png

参考资料

https://redux.js.org/tutorials/quick-start

https://react-redux.js.org

你可能感兴趣的:(React状态管理)