• React笔记(八)Redux


    一、安装和配置

    React 官方并没有提供对应的状态机插件,因此,我们需要下载第三方的状态机插件 —— Redux。

    1、下载Redux

    在终端中定位到项目根目录,然后执行以下命令下载 Redux

    npm i redux
    2、创建配置文件

    在 React 中,不会自动生成状态机的相关配置代码,因此,需要我们自己手动去创建目录以及配置文件。

    我们可以在 src 的目录中创建一个 reduxstore 的目录,用来存放所有关于状态机的配置文件。然后,在该目录中创建一个 store.js 文件,作为整个状态机的入口文件。

    3、配置状态机
    3.1、创建store仓库对象
    1. import {legacy_createStore as createStore} from 'redux'
    2. const store=createStore();
    3.2、保存数据到store中

    createStore 方法接收一个函数作为参数,该函数的返回值,会保存到 store 仓库中

    1. const store = createStore((state = 数据初始值) => {
    2.    return state;
    3. });

    通过函数参数的默认值的方式,来设置仓库数据的初始值

    3.3、查看仓库数据

    store 仓库对象身上,提供了一个 getState() 方法,用来查看仓库中的所有数据:

    console.log(store.getState());

    由于目前没有任何文件中在引入状态机的配置文件,如果我们需要查看仓库中的数据,暂时需要在 index.js 中引入 /redux/store.js 文件来让其运行。

    二、Redux核心概念

    1、Redux工作流程

    2、Redux组成部分
    2.1、state

    state:状态,就是我们传递的数据

    2.2、action

    action是一个通知对象,里面必须有一个type属性,表示当前通知的类型,至于其他属性,你可以任意添加

    可以通过store.dispatch(action对象)来更新仓库中的数据

    注意:

    • 在实际开发中,更多人喜欢用action创建函数

    • 在实际开发中,大多数情况下,type会被定义成字符串常量

    2.3、reducer

    reducer本质是一个函数,它用来响应发送过来的actions,经过处理把state发送给store

    • 在reducer函数中,需要return返回值,这样store才能接收到数据

    • reducer函数接收两个参数,第一个参数是初始化store,第二个参数是action

    2.4、store

    数据仓库,存放组件数据的地方。一个项目一般只有一个数据仓库,store可以把action和reducer联系在一起。

    主要的职责

    • 维护应用的state

    • 提供getState()方法获取state

    • 提供dispatch()方法发送action

    • 通过subscribe()来注册监听

    • 通过subscribe()返回值来注销监听

    三、第一个Redux程序

    1、创建action
    • 在src目录下创建一个actions文件夹

    • 在该目录下创建一个index.js文件

    • action是一个通知对象,里面必须有一个type属性,这里的num属性是自定义的表示计数器每次增加的个数

    1. const incrementAction={
    2.    type:'increment',
    3.    num:1
    4. }
    5. export {incrementAction}
    2、创建reducer
    • 在src目录下创建reducers目录

    • 在该目录下创建index.js文件,用来构建reducer,注意reducer要接收两个参数

    • 第一个参数是默认状态,我们可以定义一个初始化的state,然后进行赋值

    • 在函数里面判断第二个参数action的type值是否是我们发送过的,如果是,我们可以通过return返回新的state

    1. const counterReducer=(state=0,action)=>{
    2.   switch(action.type){
    3.        case 'increment':
    4.            return state+action.num
    5.        default:
    6.            return state
    7.   }
    8. }
    9. export {counterReducer}
    3、创建store
    • 在src目录下创建一个文件夹store

    • 在该目录下创建index.js文件,用来构建store,注意createStore函数第一个参数接收的是reducer

    1. import {legacy_createStore as createStore} from 'redux'
    2. import {counterReducer} from '../reducers'
    3. const store=createStore(counterReducer)
    4. export default store
    4、在组件中使用
    • 创建components文件夹,该目录下存放自定义组件

    • 在该目录下新建Counter.jsx文件

    • 将Counter.jsx引入到App.js文件中

    1. import Counter from "./components/Counter";
    2. function App() {
    3.  return (
    4.    <div>
    5.        <Counter>Counter>
    6.    div>
    7. );
    8. }
    9. export default App;
    • 调用dispatch函数,更新仓库的数据

    1. import React,{useEffect,useState} from 'react'
    2. import store from '../redux/store'
    3. import {incrementAction} from '../redux/actions'
    4. export default function Counter() {
    5. const increment=()=>{
    6. store.dispatch(incrementAction)
    7. }
    8. return (
    9. <div>
    10. <h1>计数器:{store.getState()}h1>
    11. <button onClick={()=>{increment()}}>+button>
    12. div>
    13. )
    14. }
    • 当组件加载完毕后,调用 store. subscribe注册监听,监听state数据的变化

    1. import React,{useEffect,useState} from 'react'
    2. import store from '../redux/store'
    3. import {incrementAction} from '../redux/actions'
    4. export default function Counter() {
    5. const [count, setCount] = useState(0);
    6. const increment=()=>{
    7. store.dispatch(incrementAction)
    8. }
    9. useEffect(()=>{
    10. store.subscribe(()=>{
    11. console.log('正在监控'+store.getState());
    12. setCount({})
    13. })
    14. })
    15. return (
    16. <div>
    17. <h1>计数器:{store.getState()}h1>
    18. <button onClick={()=>{increment()}}>+button>
    19. div>
    20. )
    21. }

    四、redux中action和reducer的优化

    1、action优化
    1.1、action creator

    我们一般会在页面里面修改数据,即,在页面里面调用store的dispatch方法。那就意味着action对象中的num字段很大可能是动态的,即不同的页面num字段可能是不同的,这样我们就不能把action写成一个死的对象,最好是封装成一个函数,执行过后返回一个action通知对象,然后num的值通过函数的参数来决定。这样的函数我们称之为action创建函数(action creator)

    1. const incrementAction=(num)=>{
    2. return{
    3. type:'increment',
    4. num:num
    5. }
    6. }
    7. export {incrementAction}
    1.2、type常量

    在实际开发中,type在多数情况下会被定义成常量,如下所示

    • 在src/actions目录下,新建actionTypes.jsx文件,将所有type类型定义成常量

    export const INCREMENT ="increment"
    • 在action creator中引入

    1. import {INCREMENT} from './constant'
    2. const incrementAction=(num)=>{
    3. return{
    4. type:INCREMENT,
    5. num:num
    6. }
    7. }
    8. export {incrementAction}
    2、reducer优化

    项目中肯定不止一个数据,所以state的默认值应该是一个对象,而不是其他。而且除了默认值,每当case到一个条件,返回一个新的对象时,应该返回一个全新的对象,然后才是你要修改的数据(当然这些数据,如果是引用类型的话,应该修改其引用本身,否则界面可能不会更新,尽管数据发生变化了

    1. import { INCREMENT } from '../actions/constant'
    2. const counterReducer = (state = { num: 0 }, action) => {
    3. switch (action.type) {
    4. case INCREMENT:
    5. return {
    6. ...state,
    7. num: state.num + action.num
    8. }
    9. default:
    10. return state
    11. }
    12. }
    13. export { counterReducer }

    优化后,在组件中调用的代码如下

    1. import React,{useEffect,useState} from 'react'
    2. import store from '../redux/store'
    3. import {incrementAction} from '../redux/actions'
    4. export default function Counter() {
    5. const [count, setCount] = useState(0);
    6. const increment=()=>{
    7. store.dispatch(incrementAction(2))
    8. }
    9. useEffect(()=>{
    10. store.subscribe(()=>{
    11. console.log('正在监控'+store.getState());
    12. setCount({})
    13. })
    14. })
    15. return (
    16. <div>
    17. <h1>计数器:{store.getState()}h1>
    18. <button onClick={()=>{increment()}}>+button>
    19. div>
    20. )
    21. }

    五、react-redux概述

    为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,本文主要介绍它。

    React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。

    1、UI组件

    UI 组件有以下几个特征。

    • 只负责 UI 的呈现,不带有任何业务逻辑

    • 没有状态(即不使用this.state这个变量)

    • 所有数据都由参数(this.props)提供

    • 不使用任何 Redux 的 API

    2、容器组件

    容器组件有以下几个特征。

    • 负责管理数据和业务逻辑,不负责 UI 的呈现

    • 带有内部状态

    • 使用 Redux 的 API

    总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。

    React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。

    六、react-redux基本使用

    1、安装react-redux
    1. npm i react-redux
    2. npm i redux
    2、创建action
    • 在constant.jsx中添加INCREMENT

    export const INCREMENT="increment";
    • 在src/actions/index.jsx中添加incrementAction

    1. import { INCREMENT } from "./constant";
    2. export const incrementAction=num=>({type:INCREMENT,num})
    3、创建reducer
    • 在src/reducers/index.jsx中添加counterReducer

    1. import {INCREMENT} from '../actions/constant'
    2. const counterReducer=(state={num:0},action)=>{
    3. switch(action.type){
    4. case INCREMENT:
    5. return{
    6. ...state,
    7. num:state.num+action.num
    8. }
    9. default:
    10. return state
    11. }
    12. }
    13. export {counterReducer}
    4、创建store

    在src/store/index.jsx中编写代码如下

    1. import {legacy_createStore as createStore} from 'redux'
    2. import {counterReducer} from '../reducers'
    3. const store=createStore(counterReducer)
    4. export default store
    5、全局注入store仓库
    • 在index.js中导入Provider组件

    import {Provider} from 'react-redux'
    • 利用provider组件将整个接口进行包裹

    • 给Provider组件设置store的属性,该属性的值就是通过createStore构建出来的store实例对象

    1. import ReactDOM from 'react-dom/client'
    2. import App from './App';
    3. import {Provider} from 'react-redux'
    4. import store from './store'
    5. const root = ReactDOM.createRoot(document.getElementById('root'));
    6. root.render(
    7. <Provider store={store}>
    8. <App />
    9. Provider>
    10. );
    • Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。

    6、组件关联仓库

    由于UI组件不能使用Redux的API所以,如果在组件中如果要使用Redux就必须将UI组件变成容器类组件

    React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来

    1. import React from 'react'
    2. import { connect } from 'react-redux'
    3. function Counter() {
    4. render() {
    5. return (
    6. <div>
    7. div>
    8. )
    9. }
    10. }
    11. export default connect()(Counter)
    7、组件操作仓库
    7.1、获取仓库数据

    connect 方法接收一个回调函数作为参数:

    1. const mapStateToProps = () => {
    2. }
    3. export default connect(mapStateToProps)(Counter);

    该回调函数本身,又可以通过第一个参数接收到仓库中的所有数据

    1. const mapStateToProps = (state) => {
    2. console.log(state); // 仓库中所有的数据
    3. }
    4. export default connect(mapStateToProps)(Counter);

    在该回调函数中,返回一个对象,该对象会和组件的 props 进行合并。换句话说,该函数的返回值会添加到组件的 props 中:

    1. const mapStateToProps = (state) => {
    2. return {
    3. 数据名: 从 state 中获取的数据值
    4. }
    5. }
    6. export default connect(mapStateToProps)(Counter);

    处理完成后,我们就可以在组件的 props 中访问到对应的仓库数据了:

    1. function Counter(props)
    2. render() {
    3. return (
    4. <div>
    5. <h1>计数器h1>
    6. <h2>{props.数据名}h2>
    7. div>
    8. )
    9. }
    10. }
    7.2、修改仓库数据

    修改仓库数据,依然是通过 dispatch() 方法来触发修改操作。

    在组件中,只要和仓库关联过,就能在 props 上直接获取到 dispatch 方法。因此,在组件中可以直接通过 props.dispatch() 方法来触发修改数据的操作。

    1. import React,{useEffect,useState} from 'react'
    2. import store from '../redux/store'
    3. import {incrementAction} from '../redux/actions'
    4. import { connect } from 'react-redux';
    5. function Counter(props) {
    6. const increment=()=>{
    7. props.dispatch(incrementAction(3))
    8. }
    9. return (
    10. <div>
    11. <h1>计数器:{props.num}h1>
    12. <button onClick={()=>{increment()}}>+button>
    13. div>
    14. )
    15. }
    16. const mapStateToProps = (state) => {
    17. console.log(state); // 仓库中所有的数据
    18. return{
    19. num:state.num
    20. }
    21. }
    22. export default connect(mapStateToProps)(Counter)

    七、状态机的Hook

    针对 React 中的函数组件,React-Redux 中也提供了第三方的 Hook。

    1、useSelector

    通过调用 useSelector 方法,并传递一个回调函数作为参数,我们可以在这个回调函数中获取到仓库中的 state。

    1. import { useSelector } from 'react-redux'
    2. useSelector((state) => {
    3. console.log(state); // 仓库中所有的数据
    4. })

    然后我们可以在回调函数中,将我们需要使用的数据 return 出来,然后用一个变量来接收:

    1. const data = useSelector((state) => {
    2. return 数据;
    3. })

    后续组件要使用数据,就可以直接通过变量进行数据的访问了。

    2、useDispatch

    调用 useDispatch() 方法,可以直接获取到 dispatch() 方法。

    1. import { useDispatch } from 'react-redux'
    2. export default fucntion Test() {
    3. const dispatch = useDispatch();
    4. return (
    5. )
    6. }

    如果组件中使用 Hook 来获取 dispatch 方法的话,就不再需要使用 connect 来对组件和仓库进行关联了。

    获取到 dispatch 方法后,后续的使用就和 props.dispatch 的使用一致。

    关键代码

    1. import React from 'react'
    2. import {useDispatch,useSelector} from 'react-redux'
    3. import {incrementAction} from '../../redux/action'
    4. export default function Counter(props) {
    5. const num=useSelector((state)=>{
    6. return state.num
    7. })
    8. const dispatch=useDispatch()
    9. const increment=()=>{
    10. dispatch(incrementAction(3))
    11. }
    12. return (
    13. <div>
    14. <h1>计数器:{num}h1>
    15. <button onClick={()=>{increment()}}>+button>
    16. div>
    17. )
    18. }

    八、reducer拆分

    当项目越来越大的时候,需要管理的数据也会越来越多,如果所有的数据都由一个reducer管理的话,则这个reducer肯定会变得非常的臃肿,且难以维护。所以有必要对reducer做一个拆分,不同功能模块的数据切片,由不同的reducer来管理。假设现在有两个模块,账户管理模块和商品管理模块,每个模块都有数据需要管理

    1. import {combineReducers} from 'redux'
    2. import counterReducer from './counterReducer'
    3. export default combineReducers({
    4. counter:counterReducer
    5. })

    注意:调用时候需要使用使用到切片的名字才能访问到,比如

    1. import React from 'react'
    2. import {useSelector} from 'react-redux'
    3. export default function Counter(props) {
    4. const count=useSelector((state)=>{
    5. console.log(state);
    6. return state.counter.num //state.切片名的key.num
    7. })
    8. return (
    9. <div>
    10. <h1>计数器:{count}h1>
    11. div>
    12. )
    13. }
  • 相关阅读:
    14.前端笔记-CSS-浮动
    红海有鱼群,共话医疗器械行业之渠道开发
    智慧能源方案:TSINGSEE青犀AI算法中台在能源行业的应用
    PHICOMM(斐讯)N1盒子 - Armbian5.77(Debian 9)刷入EMMC
    Java中的继承——详解
    数据库迭代模型扩展
    《Solar Energy Materials and Solar Cells》期刊介绍(SCI 2区)
    客厅窗帘要安装纱帘吗?怎么选择纱帘?-好佳居窗帘十大品牌
    idea中还原dont ask again
    Linux下安装mysql8.0
  • 原文地址:https://blog.csdn.net/m0_74343097/article/details/132644600