React(react18)中组件通信04——redux入门
- 1. 前言
- 1.1 React中组件通信的其他方式
- 1.2 介绍redux
- 1.2.1 参考官网
- 1.2.2 redux原理图
- 1.2.3 redux基础介绍
- 1.2.3.1 action
- 1.2.3.2 store
- 1.2.3.3 reducer
- 1.3 安装redux
- 2. redux入门例子
- 3. redux入门例子——优化1(reducer 和 store拆开)
- 3.1 想要实现的效果
- 3.2 代码设计
- 3.3 添加 重新渲染
- 3.4 附代码
- 4. redux入门例子——优化2(拆出action)
- 4.1 关于action的其他说明
- 4.2 优化——拆出action
- 4.3 优化——动态加减数字
- 4.3.1 优化看效果
- 4.3.2 附代码
1. 前言
1.1 React中组件通信的其他方式
- React(react18)中组件通信01——props.
- React(react18)中组件通信02——消息订阅与发布、取消订阅以及卸载组件时取消订阅.
- React(react18)中组件通信03——简单使用 Context 深层传递参数.
1.2 介绍redux
1.2.1 参考官网
- 讲解、例子,参考官网,官网地址如下:
Redux 中文文档——https://www.redux.org.cn/.
1.2.2 redux原理图
- 原理图,如下:
- 简单解释
- 要想更新 state 中的数据,你需要发起一个 action。
Action
就是一个普通 JavaScript 对象(注意到没,这儿没有任何魔法?)用来描述发生了什么。 - 强制使用 action 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。action 就像是描述发生了什么的指示器。
- 最终,为了把 action 和 state 串起来,开发一些函数,这就是
reducer
。再次地强调,没有任何魔法,reducer 只是一个接收 state 和 action,并返回新的 state 的函数,并且是一个纯函数。 - 而整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个
store
中。
- 要想更新 state 中的数据,你需要发起一个 action。
1.2.3 redux基础介绍
1.2.3.1 action
- Action 是把数据从应用(这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过
store.dispatch()
将 action 传到 store。 - Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
1.2.3.2 store
- Store 就是把它们联系到一起的对象。Store 有以下职责:
- 维持应用的 state;
- 提供
getState()
方法获取 state; - 提供
dispatch(action)
方法更新 state; - 通过
subscribe(listener)
注册监听器; - 通过
subscribe(listener) 返回的函数
注销监听器。
- 强调一下 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。
1.2.3.3 reducer
- Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
- reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
- 整个应用只有一个单一的 reducer 函数:这个函数是传给 createStore 的第一个参数。一个单一的 reducer 最终需要做以下几件事:
- reducer 第一次被调用的时候,state 的值是 undefined。reducer 需要在 action 传入之前提供一个默认的 state 来处理这种情况。
- reducer 需要先前的 state 和 dispatch 的 action 来决定需要做什么事。
- 假设需要更改数据,应该用更新后的数据创建新的对象或数组并返回它们。
- 如果没有什么更改,应该返回当前存在的 state 本身。
1.3 安装redux
- 命令如下:
npm install --save redux
2. redux入门例子
- 直接从官网拷贝的例子,例子地址:
https://www.redux.org.cn/. - 例子如下:
import { createStore } from 'redux';/*** 这是一个 reducer,形式为 (state, action) => state 的纯函数。* 描述了 action 如何把 state 转变成下一个 state。** state 的形式取决于你,可以是基本类型、数组、对象、* 甚至是 Immutable.js 生成的数据结构。惟一的要点是* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。** 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。*/ function counter(state = 0, action) {switch (action.type) {case 'INCREMENT':return state + 1;case 'DECREMENT':return state - 1;default:return state;} }// 创建 Redux store 来存放应用的状态。 // API 是 { subscribe, dispatch, getState }。 let store = createStore(counter);// 可以手动订阅更新,也可以事件绑定到视图层。 store.subscribe(() =>console.log(store.getState()) );// 改变内部 state 惟一方法是 dispatch 一个 action。 // action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行 store.dispatch({ type: 'INCREMENT' });// 1 store.dispatch({ type: 'INCREMENT' });// 2 store.dispatch({ type: 'DECREMENT' });// 1
- 效果如下:
3. redux入门例子——优化1(reducer 和 store拆开)
3.1 想要实现的效果
-
因为上面的简单例子中的state没有在页面上渲染,所以简单优化一下,实现页面渲染操作,想呈现的效果如下:
-
如果用纯react写的话,很简单,代码如下:
import { useState } from "react";function CountNum(){const [count,setCount] = useState(0);function add(){setCount(count => count+1);}function subtract(){setCount(count => count-1);}return(<div>当前数字是:{count}<br /><br /><button onClick={add}>点我 +1</button> <br /><br /><button onClick={subtract}>点我 -1</button></div>) } export default CountNum;
-
但我们目的是用redux实现,所以继续……
3.2 代码设计
- 项目结构,如下:
- store.js 和 countReducer.js 如下:
- CountNumRedux.jsx 组件如下:
- 看效果,有问题
怎么重新渲染?继续……
3.3 添加 重新渲染
-
使用
useEffect
进行重新渲染,核心代码如下:useEffect(()=>{store.subscribe(()=>{console.log('订阅更新,打印2-----',store.getState());setCount(store.getState());}); });
-
然后再看效果:
-
关于useEffect ,可以看下面的文章:
React中组件通信02——消息订阅与发布、取消订阅以及卸载组件时取消订阅.
3.4 附代码
-
countReducer.js
/*** 这是一个 reducer,形式为 (state, action) => state 的纯函数。* 描述了 action 如何把 state 转变成下一个 state。** state 的形式取决于你,可以是基本类型、数组、对象、* 甚至是 Immutable.js 生成的数据结构。惟一的要点是* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。** 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。*/function countReducer(state = 0,action){console.log(`state:${state}---action:${action}---type:${action.type}`);switch (action.type){case 'INCREMENT':return state + 1;case 'DECREMENT':return state - 1;default:return state;} } export default countReducer;
-
store.js
import { createStore } from 'redux';import countReducer from './countReducer.js'const store = createStore(countReducer);export default store;
-
CountNumRedux.jsx
import { useState,useEffect } from "react"; import store from '../redux/store'function CountNumRedux(){const [count,setCount] = useState(0);function add(){// setCount(count => count+1);//派发action 改变内部 state 惟一方法是 dispatch 一个 action。store.dispatch({ type: 'INCREMENT' });}function subtract(){// setCount(count => count-1);store.dispatch({ type: 'DECREMENT' });}// 可以手动订阅更新,也可以事件绑定到视图层。// store.subscribe(() =>// console.log('订阅更新,打印1-----',store.getState())// );useEffect(()=>{store.subscribe(()=>{console.log('订阅更新,打印2-----',store.getState());setCount(store.getState());});});return(<div>当前数字是:{count} 当前数字是:{store.getState()} <br /><br /><button onClick={add}>点我 +1</button> <br /><br /><button onClick={subtract}>点我 -1</button></div>) }export default CountNumRedux;
4. redux入门例子——优化2(拆出action)
4.1 关于action的其他说明
- 看官网怎么说:
- Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
- 除了 type 字段外,action 对象的结构完全由你自己决定。比如下面的:
更多参考可看:https://github.com/redux-utilities/flux-standard-action.{type: ADD_TODO,text: 'Build my first Redux app'}{type: TOGGLE_TODO,index: 5}
- 在 Redux 中的 action 创建函数只是简单的返回一个 action。如下:
4.2 优化——拆出action
- 根据官网上说的,做个简单优化,改动点如下:
效果就不展示了,因为这个还能继续优化,如下……
4.3 优化——动态加减数字
4.3.1 优化看效果
- 如下:
- 效果如下:
4.3.2 附代码
-
代码在上面基础上做了简单的调整,action的type抽出了变量,直接给代码了,如下:
-
countConst.js
//定义常量export const INCREMENT = 'INCREMENT'; //加 export const DECREMENT = 'DECREMENT'; //减
-
countAction.js
// //加的时候 // function incrementNum(){ // return { type: 'INCREMENT' }; // }// //减的时候 // function decrementNum(){ // return { type: 'DECREMENT' }; // }import {INCREMENT,DECREMENT} from './countConst.js'//加的时候 function incrementNum(number){return { type: INCREMENT,number:number }; }//减的时候 function decrementNum(number){return { type: DECREMENT,number:number }; }export default{incrementNum,decrementNum}
-
CountNumRedux.jsx
import { useState,useEffect,useRef, createRef } from "react"; import store from '../redux/store' import countAction from '../redux/countAction'function CountNumRedux(){const [count,setCount] = useState(0);const numberRef = createRef();function add(){// setCount(count => count+1);//派发action 改变内部 state 惟一方法是 dispatch 一个 action。// store.dispatch({ type: 'INCREMENT' });// store.dispatch(countAction.incrementNum());// console.log(numberRef.current.value);let number = numberRef.current.value;// console.log(typeof number); //stringstore.dispatch(countAction.incrementNum(parseInt(number)));}function subtract(){let number = parseInt(numberRef.current.value);store.dispatch(countAction.decrementNum(number));}useEffect(()=>{store.subscribe(()=>{console.log('订阅更新,打印2-----',store.getState());setCount(store.getState());});});return(<div>当前数字是:{count} 当前数字是:{store.getState()} <br />浮动数字:<input type="number" ref={numberRef}/><br /><br /><button onClick={add}>点我 加数</button> <br /><br /><button onClick={subtract}>点我 减数</button></div>) }export default CountNumRedux;
-
countReducer.js
import {INCREMENT,DECREMENT} from './countConst.js'function countReducer(state = 0,action){console.log(`state:${state}---action:${action}---type:${action.type}---number${action.number}`);switch (action.type){case INCREMENT:// return state + 1;return state + action.number;case DECREMENT:// return state - 1;return state - action.number;default:return state;} } export default countReducer;