17【redux】

news2025/2/24 9:22:21

17 【redux】

引言

我们现在开始学习了 Redux ,在我们之前写的案例当中,我们对于状态的管理,都是通过 state 来实现的,比如,我们在给兄弟组件传递数据时,需要先将数据传递给父组件,再由父组件转发 给它的子组件。这个过程十分的复杂,后来我们又学习了消息的发布订阅,我们通过 pubsub 库,实现了消息的转发,直接将数据发布,由兄弟组件订阅,实现了兄弟组件间的数据传递。但是,随着我们的需求不断地提升,我们需要进行更加复杂的数据传递,更多层次的数据交换。因此我们为何不可以将所有的数据交给一个中转站,这个中转站独立于所有的组件之外,由这个中转站来进行数据的分发,这样不管哪个组件需要数据,我们都可以很轻易的给他派发。

而有这么一个库就可以帮助我们来实现,那就是 Redux ,它可以帮助我们实现集中式状态管理

1.简介

Redux – 李立超 | lilichao.com

李立超老师的解释

A Predictable State Container for JS Apps是Redux官方对于Redux的描述,这句话可以这样翻译“一个专为JS应用设计的可预期的状态容器”,简单来说Redux是一个可预测的状态容器,什么玩意?这几个字单独拿出来都认识,连到一起后怎么就不像人话了?别急,我们一点一点看。

1.1 状态(State)

state直译过来就是状态,使用React这么久了,对于state我们已经是非常的熟悉了。state不过就是一个变量,一个用来记录(组件)状态的变量。组件可以根据不同的状态值切换为不同的显示,比如,用户登录和没登录看到页面应该是不同的,那么用户的登录与否就应该是一个状态。再比如,数据加载与否,显示的界面也应该不同,那么数据本身就是一个状态。换句话说,状态控制了页面的如何显示。

但是需要注意的是,状态并不是React中或其他类似框架中独有的。所有的编程语言,都有状态,所有的编程语言都会根据不同的状态去执行不同的逻辑,这是一定的。所以状态是什么,状态就是一个变量,用以记录程序执行的情况。

1.2 容器(Container)

容器当然是用来装东西的,状态容器即用来存储状态的容器。状态多了,自然需要一个东西来存储,但是容器的功能却不是仅仅能存储状态,它实则是一个状态的管理器,除了存储状态外,它还可以用来对state进行查询、修改等所有操作。(编程语言中容器几乎都是这个意思,其作用无非就是对某个东西进行增删改查)

1.3 可预测(Predictable)

可预测指我们在对state进行各种操作时,其结果是一定的。即以相同的顺序对state执行相同的操作会得到相同的结果。简单来说,Redux中对状态所有的操作都封装到了容器内部,外部只能通过调用容器提供的方法来操作state,而不能直接修改state。这就意味着外部对state的操作都被容器所限制,对state的操作都在容器的掌控之中,也就是可预测。

总的来说,Redux是一个稳定、安全的状态管理器

2.为什么是Redux?

问:不对啊?React中不是已经有state了吗?为什么还要整出一个Redux来作为状态管理器呢?

答:state应付简单值还可以,如果值比较复杂的话并不是很方便。

问:复杂值可以用useReducer嘛!

答:的确可以啊!但无论是state还是useReducer,state在传递起来还是不方便,自上至下一层一层的传递并不方便啊!

问:那不是还有context吗?

答:的确使用context可以解决state的传递的问题,但依然是简单的数据尚可,如果数据结构过于复杂会使得context变得异常的庞大,不方便维护。

Redux可以理解为是reducer和context的结合体,使用Redux即可管理复杂的state,又可以在不同的组件间方便的共享传递state。当然,Redux主要使用场景依然是大型应用,大型应用中状态比较复杂,如果只是使用reducer和context,开发起来并不是那么的便利,此时一个有一个功能强大的状态管理器就变得尤为的重要。

3.什么情况使用 Redux

首先,我们先明晰 Redux 的作用 ,实现集中式状态管理。

Redux 适用于多交互、多数据源的场景。简单理解就是复杂

从组件角度去考虑的话,当我们有以下的应用场景时,我们可以尝试采用 Redux 来实现

  1. 某个组件的状态需要共享时
  2. 一个组件需要改变其他组件的状态时
  3. 一个组件需要改变全局的状态时

除此之外,还有很多情况都需要使用 Redux 来实现

image-20221030202337038

如上图所示,redux 通过将所有的 state 集中到组件顶部,能够灵活的将所有 state 各取所需地分发给所有的组件。

redux 的三大原则:

  • 整个应用的 state 都被存储在一棵 object tree 中,并且 object tree 只存在于唯一的 store 中(这并不意味使用 redux 就需要将所有的 state 存到 redux 上,组件还是可以维护自身的 state )。
  • state 是只读的。state 的变化,会导致视图(view)的变化。用户接触不到 state,只能接触到视图,唯一改变 state 的方式则是在视图中触发actionaction是一个用于描述已发生事件的普通对象。
  • 使用 reducers 来执行 state 的更新。 reducers 是一个纯函数,它接受 action 和当前 state 作为参数,通过计算返回一个新的 state ,从而实现视图的更新。

4.Redux 的工作流程

image-20221030202728807

如上图所示,redux 的工作流程大致如下:

  • 首先,用户在视图中通过 store.dispatch 方法发出 action
  • 然后,store 自动调用 reducers,并且传入两个参数:当前 state 和收到的 actionreducers 会返回新的 state
  • 最后,当store 监听到 state 的变化,就会调用监听函数,触发视图的重新渲染。

5.Redux API

5.1 store

  • store 就是保存数据的地方,整个应用只能有一个 store
  • redux 提供 createStore 这个函数,用来创建一个 store 以存放整个应用的 state
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);

createStore用来创建一个Redux中的容器对象,它需要三个参数:reducerpreloadedStateenhancer

  • reducer是一个函数,是state操作的整合函数,每次修改state时都会触发该函数,它的返回值会成为新的state。

  • preloadedState就是state的初始值,可以在这里指定也可以在reducer中指定。

  • enhancer增强函数用来对state的功能进行扩展,暂时先不理它。

5.2 state

  • store 对象包含所有数据。如果想得到某个时点的数据,就要对 store 生成快照。这种时点的数据集合,就叫做 state
  • 如果要获取当前时刻的 state,可以通过 store.getState() 方法拿到:
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);

const state = store.getState();

5.3 action

  • state 的变化,会导致视图的变化。但是,用户接触不到 state,只能接触到视图。所以,state 的变化必须是由视图发起的。
  • action 就是视图发出的通知,通知store此时的 state 应该要发生变化了。
  • action 是一个对象。其中的 type 属性是必须的,表示 action 的名称。其他属性可以自由设置,社区有一个规范可以参考:
const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux' // 可选属性
};

上面代码定义了一个名称为 ADD_TODOaction,它携带的数据信息是 Learn Redux

5.4 Action Creator

  • view 要发送多少种消息,就会有多少种 action,如果都手写,会很麻烦。
  • 可以定义一个函数来生成 action,这个函数就称作 Action Creator,如下面代码中的 addTodo 函数:
const ADD_TODO = '添加 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');
  • redux-actions 是一个实用的库,让编写 redux 状态管理变得简单起来。该库提供了 createAction 方法用于创建动作创建器:
import { createAction } from "redux-actions"

export const INCREMENT = 'INCREMENT'
export const increment = createAction(INCREMENT)
  • 上边代码定义一个动作 INCREMENT, 然后通过 createAction

    创建了对应 Action Creator

    • 调用 increment() 时就会返回 { type: 'INCREMENT' }
    • 调用increment(10)返回 { type: 'INCREMENT', payload: 10 }

5.5 store.dispatch()

  • store.dispatch() 是视图发出 action 的唯一方法,该方法接受一个 action 对象作为参数:
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});
  • 结合 Action Creator,这段代码可以改写如下:
import { createStore } from 'redux';
import { createAction } from "redux-actions"
const store = createStore(reducer, [preloadedState], enhancer);

const ADD_TODO = 'ADD_TODO';
const add_todo = createAction('ADD_TODO'); // 创建 Action Creator

store.dispatch(add_todo('Learn Redux'));

5.6 reducer

  • store 收到 action 以后,必须给出一个新的 state,这样视图才会进行更新。state 的计算(更新)过程则是通过 reducer 实现。
  • reducer 是一个函数,它接受 action 和当前 state 作为参数,返回一个新的 state
const reducer = function (state, action) {
  // ...
  return new_state;
};
  • 为了实现调用 store.dispatch 方法时自动执行 reducer 函数,需要在创建 store 时将将 reducer 传入 createStore 方法:
import { createStore } from 'redux';
const reducer = function (state, action) {
  // ...
  return new_state;
};
const store = createStore(reducer);
  • 上面代码中,createStore 方法接受 reducer 作为参数,生成一个新的 store。以后每当视图使用 store.dispatch 发送给 store 一个新的 action,就会自动调用 reducer函数,得到更新的 state
  • redux-actions 提供了 handleActions 方法用于处理多个 action
// 使用方法:
// handleActions(reducerMap, defaultState)

import { handleActions } from 'redux-actions';
const initialState = { 
  counter: 0 
};

const reducer = handleActions(
  {
    INCREMENT: (state, action) => ({
      counter: state.counter + action.payload
    }),
    DECREMENT: (state, action) => ({
      counter: state.counter - action.payload
    })
  },
  initialState,
);

6.在网页中直接使用

我们先来在网页中使用以下Redux,在网页中使用Redux就像使用jQuery似的,直接在网页中引入Redux的库文件即可:

<script src="https://unpkg.com/redux@4.2.0/dist/redux.js"></script>

网页中我们实现一个简单的计数器功能,页面长成这样:

image-20221030210318059

<button id="btn01">减少</button>
<span id="counter">1</span>
<button id="btn02">增加</button>

我们要实现的功能很简单,点击减少数字变小,点击增加数字变大。如果用传统的DOM编写,可以创建一个变量用以记录数量,点击不同的按钮对变量做不同的修改并设置到span之中,代码像是这样:

不使用Redux:

const btn01 = document.getElementById('btn01');
const btn02 = document.getElementById('btn02');
const counterSpan = document.getElementById('counter');

let count = 1;

btn01.addEventListener('click', ()=>{
   count--;
   counterSpan.innerText = count;
});

btn02.addEventListener('click', ()=>{
   count++;
   counterSpan.innerText = count;
});

上述代码中count就是一个状态,只是这个状态没有专门的管理器,它的所有操作都在事件的响应函数中进行处理,这种状态就是不可预测的状态,因为在任何的函数中都可以对这个状态进行修改,没有任何安全限制。

使用Redux:

Redux是一个状态容器,所以使用Redux必须先创建容器对象,它的所有操作都是通过容器对象来进行的

Redux.createStore(reducer, [preloadedState], [enhancer])

三个参数中,只有reducer是必须的,来看一个Reducer的示例:

const countReducer = (state = {count:0}, action) => {
    switch (action.type){
        case 'ADD':
            return {count:state.count+1};
        case 'SUB':
            return {count:state.count-1};
        default:
            return state
    }
};

state = {count:0}这是在指定state的默认值,如果不指定,第一次调用时state的值会是undefined。也可以将该值指定为createStore()的第二个参数。action是一个普通对象,用来存储操作信息。

将reducer传递进createStore后,我们会得到一个store对象:

const store = Redux.createStore(countReducer);

store对象创建后,对state的所有操作都需要通过它来进行:

读取state:

store.getState()

修改state:

store.dispatch({type:'ADD'})

dipatch用来触发state的操作,可以将其理解为是想reducer发送任务的工具。它需要一个对象作为参数,这个对象将会成为reducer的第二个参数action,需要将操作信息设置到对象中传递给reducer。action中最重要的属性是type,type用来识别对state的不同的操作,上例中’ADD’表示增加操作,’SUB’表示减少的操作。

除了这些方法外,store还拥有一个subscribe方法,这个方法用来订阅state变化的信息。该方法需要一个回调函数作为参数,当store中存储的state发生变化时,回调函数会自动调用,我们可以在回调函数中定义state发生变化时所要触发的操作:

store.subscribe(()=>{
    // store中state发生变化时触发
    console.log('state变化了')
    console.log(store.getState())
    spanRef.current.innerText = store.getState().count
});

如此一来,刚刚的代码被修改成了这个样子:

修改后的代码相较于第一个版本要复杂一些,同时也解决了之前代码中存在的一些问题:

  1. 前一个版本的代码state就是一个变量,可以任意被修改。state不可预测,容易被修改为错误的值。新代码中使用了Redux,Redux中的对state的所有操作都封装到了reducer函数中,可以限制state的修改使state可预测,有效的避免了错误的state值。
  2. 前一个版本的代码,每次点击按钮修改state,就要手动的修改counterSpan的innerText,非常麻烦,这样一来我们如果再添加新的功能,依然不能忘记对其进行修改。新代码中,counterSpan的修改是在store.subscribe()的回调函数中进行的,state每次发生变化其值就会随之变化,不需要再手动修改。换句话说,state和DOM元素通过Redux绑定到了一起。

通过上例也不难看出,Redux中最最核心的东西就是这个store,只要拿到了这个store对象就相当于拿到了Redux中存储的数据。在加上Redux的核心思想中有一条叫做“单一数据源”,也就是所有的state都会存储到一课对象树中,并且这个对象树会存储到一个store中。所以到了React中,组件只需获取到store即可获取到Redux中存储的所有state。

7.React-Redux 基本使用

7.1 引言

在前面我们学习了 Redux ,我们在写案例的时候,也发现了它存在着一些问题,例如组件无法状态无法公用,每一个状态组件都需要通过订阅来监视,状态更新会影响到全部组件更新,面对着这些问题,React 官方在 redux 基础上提出了 React-Redux 库

在前面的案例中,我们如果把 store 直接写在了 React 应用的顶层 props 中,各个子组件,就能访问到顶层 props

<顶层组件 store={store}>
  <App />
</顶层组件/>

这就类似于 React-Redux

7.2 开始使用 React-Redux

当我们需要在React中使用Redux时,我们除了需要引入Redux核心库外,还需要引入react-redux库,以使React和redux适配,可以通过npm或yarn安装:

npm install -S redux react-redux

接下来我们尝试在Redux,添加一些复杂的state,比如一个学生的信息:

{name:'孙悟空', age:18, gender:'男', address:'花果山'}

创建reducer:

const reducer = (state = {
    name: '孙悟空',
    age: 18,
    gender: '男',
    address: '花果山'
}, action) => {
    switch (action.type) {
        case 'SET_NAME':
            return {
                ...state,
                name: action.payload
            };
        case 'SET_AGE':
            return {
                ...state,
                age: action.payload
            };
        case 'SET_ADDRESS':
            return {
                ...state,
                address: action.payload
            };
        case 'SET_GENDER':
            return {
                ...state,
                gender: action.payload
            };
        default :
            return state
    }

};

reducer的编写和之前的案例并没有本质的区别,只是这次的数据和操作方法变得复杂了一些。以SET_NAME为例,当需要修改name属性时,dispatch需要传递一个有两个属性的action,action的type应该是字符串”SET_NAME”,payload应该是要修改的新名字,比如要将名字修改为猪八戒,则dispatch需要传递这样一个对象{type:'SET_NAME',payload:'猪八戒'}

创建store:

const store = createStore(reducer);

创建store和前例并无差异,传递reducer进行构建即可。

7.3 设置 provider

由于我们的状态可能会被很多组件使用,所以 React-Redux 给我们提供了一个 Provider 组件,可以全局注入 redux 中的 store ,只需要把 Provider 注册在根部组件即可

例如,当以下组件都需要使用 store 时,我们需要这么做,但是这样徒增了工作量,很不便利

<Count store={store}/>
{/* 示例 */}
<Demo1 store={store}/>
<Demo2 store={store}/>
<Demo3 store={store}/>
<Demo4 store={store}/>
<Demo5 store={store}/>

我们可以这么做:在 src 目录下的 main.jsx 文件中,引入 Provider ,直接用 Provider 标签包裹 App 组件,将 store 写在 Provider 中即可

ReactDOM.createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
)

7.4 操作数据

访问数据:

const stu = useSelector(state => state);

react-redux还为我们提供一个钩子函数useSelector,用于获取Redux中存储的数据,它需要一个回调函数作为参数,回调函数的第一个参数就是当前的state,回调函数的返回值,会作为useSelector的返回值返回,所以state => state表示直接将整个state作为返回值返回。现在就可以通过stu来读取state中的数据了:

<p>
    {stu.name} -- {stu.age} -- {stu.gender} -- {stu.address}
</p>

操作数据:

const dispatch = useDispatch();

useDispatch同样是react-redux提供的钩子函数,用来获取redux的派发器,对state的所有操作都需要通过派发器来进行。

通过派发器修改state:

dispatch({type:'SET_NAME', payload:'猪八戒'})
dispatch({type:'SET_AGE', payload:28})
dispatch({type:'SET_GENDER', payload:'女'})
dispatch({type:'SET_ADDRESS', payload:'高老庄'})

完整代码:

import ReactDOM from 'react-dom/client';
import {Provider, useDispatch, useSelector} from "react-redux";
import {createStore} from "redux";

const reducer = (state = {
    name: '孙悟空',
    age: 18,
    gender: '男',
    address: '花果山'
}, action) => {
    switch (action.type) {
        case 'SET_NAME':
            return {
                ...state,
                name: action.payload
            };
        case 'SET_AGE':
            return {
                ...state,
                age: action.payload
            };
        case 'SET_ADDRESS':
            return {
                ...state,
                address: action.payload
            };
        case 'SET_GENDER':
            return {
                ...state,
                gender: action.payload
            };
        default :
            return state
    }

};

const store = createStore(reducer);

const App = () =>{
    const stu = useSelector(state => state);
    const dispatch = useDispatch();
    return  <div>
        <p>
            {stu.name} -- {stu.age} -- {stu.gender} -- {stu.address}
        </p>
        <div>
            <button onClick={()=>{dispatch({type:'SET_NAME', payload:'猪八戒'})}}>改name</button>
            <button onClick={()=>{dispatch({type:'SET_AGE', payload:28})}}>改age</button>
            <button onClick={()=>{dispatch({type:'SET_GENDER', payload:'女'})}}>改gender</button>
            <button onClick={()=>{dispatch({type:'SET_ADDRESS', payload:'高老庄'})}}>改address</button>
        </div>
  </div>
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
)

8.复杂的State

上例中的数据结构已经变得复杂,但是距离真实项目还有一定的差距。因为Redux的核心思想是所有的state都应该存储到同一个仓库中,所以只有一个学生数据确实显得有点单薄,现在将数据变得复杂一些,出来学生数据外,还增加了一个学校的信息,于是state的结构变成了这样:

{
    stu:{
        name: '孙悟空',
        age: 18,
        gender: '男',
        address: '花果山' 
    },
    school:{
        name:'花果山一小',
        address:'花果山大街1号'
    }
}

数据结构变得复杂了,我们需要对代码进行修改,首先看reducer:

const reducer = (state = {
    stu: {
        name: '孙悟空',
        age: 18,
        gender: '男',
        address: '花果山'
    },
    school: {
        name: '花果山一小',
        address: '花果山大街1号'
    }

}, action) => {
    switch (action.type) {
        case 'SET_NAME':
            return {
                ...state,
                stu: {
                    ...state.stu,
                    name: action.payload
                }
            };
        case 'SET_AGE':
            return {
                ...state,
                stu: {
                    ...state.stu,
                    age: action.payload
                }
            };
        case 'SET_ADDRESS':
            return {
                ...state,
                stu: {
                    ...state.stu,
                    address: action.payload
                }
            };
        case 'SET_GENDER':
            return {
                ...state,
                stu: {
                    ...state.stu,
                    gender: action.payload
                }
            };
        case 'SET_SCHOOL_NAME':
            return {
                ...state,
                school: {
                    ...state.school,
                    name:action.payload
                }
            };
        case 'SET_SCHOOL_ADDRESS':
            return {
                ...state,
                school: {
                    ...state.school,
                    address: action.payload
                }
            }
        default :
            return state;
    }

};

数据层次变多了,我们在操作数据时也变得复杂了,比如修改name的逻辑变成了这样:

case 'SET_NAME':
    return {
         ...state,
        stu: {
            ...state.stu,
            name: action.payloadjs
    }
};

同时数据加载的逻辑也要修改,之前我们是将整个state返回,现在我们需要根据不同情况获取state,比如获取学生信息要这么写:

const stu = useSelector(state => state.stu);

获取学校信息:

const school = useSelector(state => state.school);

完整代码:

store/stuReducer.js

const stuReducer = (
  state = {
    name: '孙悟空',
    age: 18,
    gender: '男',
    address: '花果山',
  },
  action,
) => {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.payload,
      }
    case 'SET_AGE':
      return {
        ...state,
        age: action.payload,
      }
    case 'SET_ADDRESS':
      return {
        ...state,
        address: action.payload,
      }
    case 'SET_GENDER':
      return {
        ...state,
        gender: action.payload,
      }
    default:
      return state
  }
}

export default stuReducer

store/index.js

import { createStore } from 'redux'
import stuReducer from './stuReducer.js'

const store = createStore(stuReducer)

export default store

main.js

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { Provider } from 'react-redux'
import store from './store'

ReactDOM.createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>,
)

App.jsx

import React from 'react'
import { useDispatch, useSelector } from 'react-redux'

export default function App() {
  const stu = useSelector(state => state)
  const dispatch = useDispatch()
  return (
    <div>
      <p>
        {stu.name} -- {stu.age} -- {stu.gender} -- {stu.address}
      </p>
      <div>
        <button
          onClick={() => {
            dispatch({ type: 'SET_NAME', payload: '猪八戒' })
          }}
        >
          改name
        </button>
        <button
          onClick={() => {
            dispatch({ type: 'SET_AGE', payload: 28 })
          }}
        >
          改age
        </button>
        <button
          onClick={() => {
            dispatch({ type: 'SET_GENDER', payload: '女' })
          }}
        >
          改gender
        </button>
        <button
          onClick={() => {
            dispatch({ type: 'SET_ADDRESS', payload: '高老庄' })
          }}
        >
          改address
        </button>
      </div>
    </div>
  )
}

麻烦确实是麻烦了一些,但是还好功能实现了。

9.多个Reducer

上边的案例的写法存在一个非常严重的问题!将所有的代码都写到一个reducer中,会使得这个reducer变得无比庞大,现在只有学生和学校两个信息。如果数据在多一些,操作方法也会随之增多,reducer会越来越庞大变得难以维护。

Redux中是允许我们创建多个reducer的,所以上例中的reducer我们可以根据它的数据和功能进行拆分,拆分为两个reducer,像是这样:

const stuReducer = (state = {
    name: '孙悟空',
    age: 18,
    gender: '男',
    address: '花果山'
}, action) => {
    switch (action.type) {
        case 'SET_NAME':
            return {
                ...state,
                name: action.payload
            };
        case 'SET_AGE':
            return {
                ...state,
                age: action.payload
            };
        case 'SET_ADDRESS':
            return {
                ...state,
                address: action.payload
            };
        case 'SET_GENDER':
            return {
                ...state,
                gender: action.payload
            };
        default :
            return state;
    }

};

const schoolReducer = (state = {

    name: '花果山一小',
    address: '花果山大街1号'

}, action) => {
    switch (action.type) {
        case 'SET_SCHOOL_NAME':
            return {
                ...state,
                name: action.payload
            };
        case 'SET_SCHOOL_ADDRESS':
            return {
                ...state,
                address: action.payload
            };
        default :
            return state;
    }

};

修改后reducer被拆分为了stuReducer和schoolReducer,拆分后在编写每个reducer时,只需要考虑当前的state数据,不再需要对无关的数据进行复制等操作,简化了reducer的编写。于此同时将不同的功能编写到了不同的reducer中,降低了代码间的耦合,方便对代码进行维护。

拆分后,还需要使用Redux为我们提供的函数combineReducer将多个reducer进行合并,合并后才能传递进createStore来创建store。

const reducer = combineReducers({
    stu:stuReducer,
    school:schoolReducer
});

const store = createStore(reducer);

combineReducer需要一个对象作为参数,对象的属性名可以根据需要指定,比如我们有两种数据stu和school,属性名就命名为stu和school,stu指向stuReducer,school指向schoolReducer。读取数据时,直接通过state.stu读取学生数据,通过state.school读取学校数据

完整代码:

store/reducers.js

export const stuReducer = (
  state = {
    name: '孙悟空',
    age: 18,
    gender: '男',
    address: '花果山',
  },
  action,
) => {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.payload,
      }
    case 'SET_AGE':
      return {
        ...state,
        age: action.payload,
      }
    case 'SET_ADDRESS':
      return {
        ...state,
        address: action.payload,
      }
    case 'SET_GENDER':
      return {
        ...state,
        gender: action.payload,
      }
    default:
      return state
  }
}

export const schoolReducer = (
  state = {
    name: '花果山一小',
    address: '花果山大街1号',
  },
  action,
) => {
  switch (action.type) {
    case 'SET_SCHOOL_NAME':
      return {
        ...state,
        name: action.payload,
      }
    case 'SET_SCHOOL_ADDRESS':
      return {
        ...state,
        address: action.payload,
      }
    default:
      return state
  }
}

store/index.js

import { createStore, combineReducers } from 'redux'
import { stuReducer, schoolReducer } from './reduces.js'

const reducer = combineReducers({
  stu: stuReducer,
  school: schoolReducer,
})

const store = createStore(reducer)

export default store

main.js

8.复杂的State的一样

App.jsx

import React from 'react'
import { useDispatch, useSelector } from 'react-redux'

export default function App() {
  const stu = useSelector(state => state.stu)
  const school = useSelector(state => state.school)
  const dispatch = useDispatch()
  
  return (
    <div>
      <p>
        {stu.name} -- {stu.age} -- {stu.gender} -- {stu.address}
      </p>
      <div>
        <button
          onClick={() => {
            dispatch({ type: 'SET_NAME', payload: '猪八戒' })
          }}
        >
          改name
        </button>
        <button
          onClick={() => {
            dispatch({ type: 'SET_AGE', payload: 28 })
          }}
        >
          改age
        </button>
        <button
          onClick={() => {
            dispatch({ type: 'SET_GENDER', payload: '女' })
          }}
        >
          改gender
        </button>
        <button
          onClick={() => {
            dispatch({ type: 'SET_ADDRESS', payload: '高老庄' })
          }}
        >
          改address
        </button>
      </div>

      <hr />

      <p>
        {school.name} -- {school.address}
      </p>
      <div>
        <button
          onClick={() => {
            dispatch({ type: 'SET_SCHOOL_NAME', payload: '高老庄小学' })
          }}
        >
          改学校name
        </button>
        <button
          onClick={() => {
            dispatch({ type: 'SET_SCHOOL_ADDRESS', payload: '高老庄中心大街15号' })
          }}
        >
          改学校address
        </button>
      </div>
    </div>
  )
}

10.总结

[外链图片转存中…(img-KzjcqajQ-1669180737560)]

Redux 确实有许多新的术语和概念需要记住。提醒一下,这是我们刚刚介绍的内容:

  • Redux 是一个管理全局应用状态的库
    • Redux 通常与 React-Redux 库一起使用,把 Redux 和 React 集成在一起
    • Redux Toolkit 是编写 Redux 逻辑的推荐方式
  • Redux 使用 “单向数据流”
    • State 描述了应用程序在某个时间点的状态,视图基于该 state 渲染
    • 当应用程序中发生某些事情时:
      • 视图 dispatch 一个 action
      • store 调用 reducer,随后根据发生的事情来更新 state
      • store 将 state 发生了变化的情况通知 UI
    • 视图基于新 state 重新渲染
  • Redux 有这几种类型的代码
    • Action 是有 type 字段的纯对象,描述发生了什么
    • Reducer 是纯函数,基于先前的 state 和 action 来计算新的 state
    • 每当 dispatch 一个 action 后,store 就会调用 root reducer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/28686.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]Python计算机毕业设计大学生志愿者管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

软件工程经济学期末复习

1、利润 收入-成本费用 2、资产 流动资产非流动资产 3、显性成本可以用货币计量&#xff0c;是可以在会计的帐目上反映出来的 4、领取什么保险应缴纳个人所得税 商业保险 某企业一项固定资产的原价为800 0000元&#xff0c;预计使用年限为6年&#xff0c;预计净残值为5 0…

软件测试linux面试相关的知识

一、常用的命令 ls&#xff08;查看目录下的内容&#xff0c;-a显示隐藏目录&#xff09; cd&#xff08;进入某个目录&#xff0c;cd .. 返回上一层目录&#xff0c;cd - 返回上一次的目录&#xff0c;cd / 返回根目录&#xff09; pwd&#xff08;显示当前绝对路径&#x…

阿里云易立:以增效促降本,容器服务全面进入智能化时代

容器技术已经跨越鸿沟&#xff0c;广泛应用于金融、通讯、制造、交通等千行百业。Kubernetes支撑的工作负载也从早期单一的互联网应用发展到数据库、AI、大数据等等&#xff0c;并覆盖了公共云、专有云、边缘云等多样化、动态的云环境。 11月5日&#xff0c;2022杭州 云栖大会…

在Windows7在部署Hadoop+Hbase

0. 准备工作 0.1 电脑上现在没有jdk 0.1 提前准备好文件 1. 现在开始安装jdk 1.8.0_60 安装成功&#xff0c;没啥问题 小疑问&#xff1a;自动配置好了环境变量? 1.1 小记 在安装jdk的时候&#xff0c;有三种小工具&#xff0c;可以根据需要选择性安装 JDKjre源代码 虽…

牛客网语法篇练习基础语法(二)

1.牛牛正在给他的朋友们买电影票&#xff0c;已知一张电影票价是100元&#xff0c;计算 x 位朋友的总票价是多少&#xff1f; x int(input()) a x*100 print(a) 2.给定两个整数a和b (0 < a,b < 10,000)&#xff0c;计算a除以b的整数商和余数。 a,b map(int,input().…

【深入浅出Java并发编程指南】「剖析篇」Fork/Join框架的实战开发和原理探究指南

前提概述 Java 7开始引入了一种新的Fork/Join线程池&#xff0c;它可以执行一种特殊的任务&#xff1a;把一个大任务拆成多个小任务并行执行。 我们举个例子&#xff1a;如果要计算一个超大数组的和&#xff0c;最简单的做法是用一个循环在一个线程内完成&#xff1a; 算法原理…

使用Typora+EasyBlogImageForTypora写博客,无图床快速上传图片

Typora下载 Typora以前都是免费的&#xff0c;自去年开始竟然要收费&#x1f914;&#xff08;不过也能理解&#xff0c;毕竟真心好用&#x1f60a;&#xff09;&#xff0c;在这里给大家提供的是免费的版本&#xff08;免安装&#xff0c;下载即可使用&#xff09;&#xff0…

【密码学篇】(商密)密码算法分析理论知识

【密码学篇】&#xff08;商密&#xff09;密码算法分析理论知识 密码算法分析理论—【蘇小沐】 文章目录【密码学篇】&#xff08;商密&#xff09;密码算法分析理论知识&#xff08;一&#xff09;密码系统安全性1.实际安全性2.无条件安全性&#xff08;二&#xff09;密码算…

干货丨如何开启TiDB集群中的节点通信加密?

笔者在一个银行项目中&#xff0c;费尽千辛万苦&#xff0c;好不容易通过PoC测试。 就当一切就绪&#xff0c;准备正式上线时&#xff0c;突然传来了噩耗&#xff1a;未通过银行内部的漏洞扫描&#xff0c;发现存在高危漏洞&#xff0c;需要马上进行修复。 这可给我吓坏了&am…

Vue-axios的get、post请求

直接在控制台上打印axios会报错&#xff0c;打印fetch就不会&#xff1b; 因为fetch是标准&#xff0c;axios是第三方&#xff0c;要想用axios&#xff0c;就必须引入想应的js文件&#xff1b;axios-js文件下载&#xff1a;npm 搜索axios&#xff0c;点进去&#xff0c;往下找&…

智云通CRM:如何清除销售前被拒绝的怀疑和猜测?

在做CRM销售时&#xff0c;经常遇到被客户拒绝的情况&#xff0c;没有人喜欢被拒绝&#xff0c;因为拒绝辉让人痛苦、难过&#xff0c;但现实中又无法避免被拒绝&#xff0c;尤其是销售人员&#xff0c;对销售人员来说&#xff0c;被拒绝是家常便饭。遭到拒绝后&#xff0c;经常…

基于51单片机pwm调光护眼台灯智能检测光强光控灯设计proteus仿真原理图PCB

功能&#xff1a; 0.本系统采用STC89C52作为单片机 1.LCD1602液晶实时显示当前时间/模式/亮度等级 2.按’切换’键可切换四种不同的模式 a) 自动开关&#xff0c;自动调节亮度 b) 手动开关&#xff0c;自动调节亮度 c) 自动开关&#xff0c;手动调节亮度 d) 手动开关&#xff0…

arthas诊断工具

1.安装 linux: curl -o https://alibaba.github.io/arthas/arthas-boot.jar //是O不是零 浏览直接访问https://alibaba.github.io/arthas/arthas-boot.jar 在运行程序之前,需要运行一个java进程在内存种 java -jar arthas-boot.jar 按序号选择诊断的进程 选择序号回车 2.卸载…

​生成图片并添加文字Image.new()与ImageDraw.drawer.text()方法​

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 生成图片并添加文字 Image.new()与ImageDraw.drawer.text()方法 [太阳]选择题 以下python代码表述错误的一项是? from PIL import Image,ImageDraw,ImageFont imgImage.new(modeRGB,…

计算机网络第六章知识点回顾(自顶向下)

第六章知识点回顾1.链路层和局域网1.1网络层、链路层和物理层1.2链路层服务1.3链路层在哪儿实现&#xff1f;1.3.1网络适配器之间的通信1.4检错和纠错1.5如何检测与纠正错误&#xff1f;1.5.1编码集的检错与纠错能力1.5.2差错检测的实施1.5.3奇偶校验1.5.4循环冗余校验&#xf…

图像分割 - 区域生长

目录 1. 介绍 2. 代码详解 3. 代码 1. 介绍 分割的目的是将图像分为多个区域 常用的分割方法基于两个属性&#xff1a;不同区域间的灰度不连续性质&#xff08;Canny边缘检测等等&#xff09;、相同区域灰度的相似特征&#xff08;阈值处理、区域生长等等&#xff09; 区…

牛客网语法篇练习基础语法(一)

1.输出"Hello Nowcoder!"。开始你的编程之旅吧。 print(Hello Nowcoder!) 2.KiKi学会了print在屏幕输出信息&#xff0c;他想输出一架小飞机。请帮他编写程序输出这架小飞机。 print( *5 ** *5) print( *5 ** *5) print(**12) print(**12) print( *4 * * *4) prin…

Arduino操作MPU6050模块

MPU6050是集成三轴陀螺仪&#xff0c;三轴加速度计&#xff0c;温度传感器于一体的模块。本文档基于Adafruit_MPU6050实现MPU6050模块基本操作。 Adafruit_MPU6050库&#xff1a; https://github.com/adafruit/Adafruit_MPU6050 Adafruit_MPU6050依赖以下库&#xff0c;需要在…

Java中的char、Character和CharSequence的区别

char 与 Character char是一种基本的数据类型&#xff0c;Character是char类型的包装类&#xff0c;即通过Character创建出来的是一种对象。 Character是char的包装类&#xff0c;就像Integer和int&#xff0c;以及Long和long一样。 包装类和基本类型可以自动转换&#xff…