当前位置: 首页 > news >正文

网络建设 之 React数据管理

React作为一个用于构建用户界面的JavaScript库,很多人认为React仅仅只是一个UI 库,而不是一个前端框架,因为它在数据管理上是缺失的。在做一个小项目的时候,维护的数据量不多,管理/维护数据用useState/useRef就足够了;可是当项目变大,需要的数据量成百上千,然后就会发现:

  1. 全局变量到处都是。

  2. 在某些组件里定义的数据无法传递到其他组件里。

  3. 数据传来传去找不到定义位置,很难维护。

因此这时候就需要数据管理了。

最简单的数据管理

就是把这些useState/useRef定义的数据放到根组件上,然后哪个子组件用,就用props传下去,这样没有其他概念浅显易懂,也起到了一定的数据管理的作用。但这样做的缺点就是这些数据需要在子组件一层层的传下去,代码要写很多,比较麻烦,如果不嫌麻烦的话,在大型项目里,这么做其实也没什么问题了。

更进一步的数据管理,用useContext

React的api,useContext,正是为了解决数据层层传递的问题而出现的,它可以看作是一个数据中心,所有需要管理的数据都在这里。

它怎么用呢,首先新开一个文件context.js,在里用React.createContext()定义一个Context然后导出:

//context.js
import React from "react";
export const Context = React.createContext();

然后在根节点这里,用这个Context的Provider属性将整个根节点包裹住:

// rootView.jsx
import React from "react";
import { Context } from "./context";export default function RootView() {const defaultValue = {a: 1, b: 'hello'};return <Context.Provider value={defaultValue}><View class='root-view'>...各种子组件...</View></Context.Provider>;
}

这里的defaultValue就是我们数据中心的所有数据的初始化的默认值。

然后在子组件里,不管是子组件还是孙组件还是孙孙组件,都不用再把 props 当传家宝传下去了,只需要在组件里像useState一样调用useContext,就能获取到数据中心的所有数据:

//child.jsx
import React, { useContext } from "react";
import { Context } from "./context";export default function() {const state = useContext(Context);return <Text>{state.b}</Text>
}

state就是数据中心的所有数据,可以理解为useState中的State,这样这个子组件显示的就是上面默认的初始化数据“hello”,但这还不够好用,因为目前还没有办法改变数据,那么我们接下来就需要对defaultValue做一些变动,把这些数据都用useState变成响应式的,然后再一股脑地传进Provider的value里:

// rootView.jsx
import React from "react";
import { Context } from "./context";export default function RootView() {const [value, setValue] = useState({a: 1, b: 'hello'});return <Context.Provider value={{value, setValue}}><View class='root-view'>...各种子组件...</View></Context.Provider>;
}

然后在子组件里这样调用:

//child.jsx
import React, { useContext } from "react";
import { Context } from "./context";export default function() {const state = useContext(Context);useEffect(()=>{state.setValue({...state.value, b: 'world'});}, []);return <Text>{state.value.b}</Text>
}

然后,这个子组件显示的就是已经改动的数据“world”,关于Context还有一个比较重要的点是:当Context Provider的value发生变化时,他的所有调用useContext的子组件,都会重新渲染,这往往会造成比较严重的性能问题,在大型项目里百分百会出现。

第一个问题是state改变,造成Provider标签下的整体渲染。Context.Provider说到底还是组件,也是用React.createElement()实现的,也按照组件基本法来办事,React.createElement()在每次props发生变动时,都会创建一个新对象,那么只要让props不发生变动就行了。我给Provider再包裹一层ProviderWrapper,然后在这个ProviderWrapper组件里去定义数据,这样,由于ProviderWrapper是不变的,那么在RootView组件里没有任何状态改变,子组件也用不着重复渲染了。

const ProviderWrapper = ({ children }) => {const [value, setValue] =useState(defaultValue); return (<Context.Provider value={{ value, setValue }}>{children}</Context.Provider>);
};export default function RootView() {return <ProviderWrapper><View class='root-view'>...各种子组件...</View></ProviderWrapper>;
}

这样,babel在编译的时候,标签转译成React.createElement()的时候,只是在RootView组件里完成转译,React.createElement()执行完的节点数据将通过props.children传入ProviderWrapper,在ProviderWrapper内部就没有重复的React.createElement(),这样就避免了整体的重复渲染。

二是上述的所有调用useContext的子组件的局部重复渲染。即便在某一个子组件中只是使用了setState,并没有使用state,但是当state变动时,这个子组件仍然会重复渲染,因为仅仅是调用了useContext,但理论上来说是不需要重复渲染的。那解决办法是什么呢?解决办法就是将state和setState分别用不同的Provider传入,这样一个组件仅仅只是调用setState的话,就不会被state的变动影响而重复渲染:

const ProviderWrapper = ({ children }) => {const [value, setValue] =useState(defaultValue); return (<SetValueContext.Provider value={{ setValue }}><ValueContext.Provider value={{ value }}>{children}</Context.Provider></Context.Provider>);
};

其中SetValueContext和ValueContext是两个毫不相干的有React.createContext()产生的对象,仅仅只是用来区分开state和setState,这样在子组件里,如果只想调用setState,那么就通过React.useContext()引入SetValueContext即可,子组件就不会因state变动而重复渲染。

这样基本上就差不多了,难懂的代码多了一些,但冗余的代码少了不少。概念越多就能解决的更多的问题,现在又出现了一个问题,state里有很多数据,一些子组件引用了React.useContext(),但是对state里的一些数据是不关心用不到的,但这些数据在发生变动的时候,这些子组件也会重复渲染,说白了,就是state细粒度不够的问题,但是本着尽可能消除重复渲染的思想,我们把state根据数据种类进行拆分成多个state,这样每个子组件调用对自己有用的state,这样就减少了重复渲染:

const ProviderWrappers = ({ children }) => (<LoginProviderWrapper><SignupProviderWrapper><MainPageProviderWrapper><MenuProviderWrapper>{children}</MenuProviderWrapper></MainPageProviderWrapper></SignupProviderWrapper></LoginProviderWrapper>
);export default function RootView() {return <ProviderWrappers><View class='root-view'>...各种子组件...</View></ProviderWrappers>;
}

等一下,代码怎么变冗余了?我们最初的目的是什么?消除冗余,我们为了消除一种冗余,带来了另一种冗余,这是不可接受的,所以还得接着改,当前情况是,由于state被拆分,造成出现了很多ProviderWrapper支持不同的state和setState,那么我们需要对这些ProviderWrapper进行某种程度上的组合,至少我们可以用一个for循环去组合这些ProviderWrapper:

// RootView.tsx
function composeProviderWrappers(ProviderWrappers) {const element;for(ProviderWrapper of ProviderWrappers) {element = <ProviderWrapper>{element}</ProviderWrapper>}return element;
}export default function RootView() {const ComposeProviderWrappers = composeProviderWrappers([LoginProviderWrapper, SignupProviderWrapper, MainPageProviderWrapper, MenuProviderWrapper]);return <ComposeProviderWrappers><View class='root-view'>...各种子组件...</View></ComposeProviderWrappers>;
}

这个优化意义不大,并没有减少多少冗余代码,但是说实话,我们现在已经走歪了,而导致我们走歪的罪魁祸首,就是React.useContext()的性能问题:只要调用React.useContext()的组件,当state变动的时候,全部都会重新渲染。回到最开始说的,React相对于Framwork,其实它更类似于一个UI库,用React本身的功能勉强实现数据管理,代价就是有很多坑,毕竟使用一些第三方数据管理库例如Redux,zustand之类的,既能实现React.useContext()的功能,又能避免React.useContext()的问题,何乐而不为呢?下面就来介绍一些第三方数据管理库:

Redux

Redux可以说是最正统的React数据管理工具,Redux的用法与React.useContext()类似,但没有React.useContext()的缺点,只有组件在使用到变动的数据的时候,这个组件才会重新渲染,如果你在因使用React.useContext()导致的无限渲染大卡关时,不妨试试Redux。

Redux只有2KB,Redux Toolkit是官方推荐的编写 Redux 逻辑的方法,使编写 Redux 更加容易。安装方式如下:

# NPM
npm install @reduxjs/toolkit redux# Yarn
yarn add @reduxjs/toolkit redux

使用时,首先像React.createContext()一样,使用configureStore导出一个实例:

import { configureStore } from '@reduxjs/toolkit'export default configureStore({reducer: {}
})

然后用react-redux提供的Provider标签,将整个根节点包裹起来,唯一的区别就是,我们再也不用考虑担心性能问题了,这里不会有的:

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './app/store'
import { Provider } from 'react-redux'export default function RootView() {return <Provider store={store}><View class='root-view'>...各种子组件...</View></Provider>;
}

然后不一样的来了,创建slice:

import { createSlice } from '@reduxjs/toolkit'export const counterSlice = createSlice({name: 'counter',initialState: {value: 0},reducers: {increment: state => {// Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它// 并不是真正的改变状态值,因为它使用了 Immer 库// 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的// 不可变的状态state.value += 1},decrement: state => {state.value -= 1},incrementByAmount: (state, action) => {state.value += action.payload}}
})
// 每个 case reducer 函数会生成对应的 Action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actionsexport default counterSlice.reducer

这里的createSlice实际上可以考虑为创建state和setState,reducers就是setState。然后将 Slice Reducers 添加到 Store 中:

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'export default configureStore({reducer: {counter: counterReducer}
})

最后就是使用了,在 React 组件中使用 Redux 状态和操作:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
import styles from './Counter.module.css'export function Counter() {const count = useSelector(state => state.counter.value)const dispatch = useDispatch()return (<div><div><buttonaria-label="Increment value"onClick={() => dispatch(increment())}>Increment</button><span>{count}</span><buttonaria-label="Decrement value"onClick={() => dispatch(decrement())}>Decrement</button></div></div>)
}

虽然起的名字不同,但是通过上述的React.useContext()的学习,基本上也是能一一对应的,最重要的是,这里不会再有性能问题了。

zustand

"Zustand" 只是德语的"state",一个轻量,现代的状态管理库,它的好处就是更简单。

安装:

npm install zustand

然后老生常谈的定义一个实例:

const useStore = create(set => ({votes: 0,addVotes: () => set(state => ({ votes: state.votes + 1 })),subtractVotes: () => set(state => ({ votes: state.votes - 1 })),
}));

然后,就可以使用了,这个真的比较方便:

function App() {const addVotes = useStore(state => state.addVotes);const subtractVotes = useStore(state => state.subtractVotes);return <div className="App"><h1>{getVotes} people have cast their votes</h1><button onClick={addVotes}>Cast a vote</button><button onClick={subtractVotes}>Delete a vote</button></div>
}

Rematch

Rematch在Redux的基础上构建并减少了样板代码和执行了一些最佳实践。Redux对于初学者来说简直就是噩梦,他仿佛不是一个状态管理工具,而是一个涉及了众多概念的状态管理模型。要想搞明白Redux如何使用,就要先了解10个以上名词的含义;这还只是Redux的主流程使用中涉及到的名词。Redux的主流程里充斥了各种各样的概念,比如,Dispatch、Reducer、CreateStore、ApplyMiddleware、Compose、CombineReducers、Action、ActionCreator、Action Type、Action Payload、BindActionCreators...Rematch将这些概念进行了整合,提出了一个更简洁的状态管理模型;

安装:

npm install @rematch/core react-redux

首先,定义一个实例:

import { init } from "@rematch/core";// 定义一个model,包含了之前redux中的一些内容
// 拥有对应的state和reducers//model
const count = {state: 0,reducers: {upBy: (state, payload) => state + payload,},
};
// 使用init初始化
// 相当于Redux中的store
init({models: { count },
});

然后,就可以使用了:

import { connect } from "react-redux";// Component
// 将count内容赋值给count
const mapStateToProps = (state) => ({count: state.count,
});
// 将指定动作传输给组件
const mapDispatchToProps = (dispatch) => ({countUpBy: dispatch.count.upBy,
});connect(mapStateToProps, mapDispatchToProps)(Component);
// connect倒是没有怎么变

jotai,recoil,redux,rematch,zustand,Reducer,react数据管理的哲学

相关文章:

网络建设 之 React数据管理

React作为一个用于构建用户界面的JavaScript库&#xff0c;很多人认为React仅仅只是一个UI 库&#xff0c;而不是一个前端框架&#xff0c;因为它在数据管理上是缺失的。在做一个小项目的时候&#xff0c;维护的数据量不多&#xff0c;管理/维护数据用useState/useRef就足够了&…...

如何隐藏woocommerce 后台header,woocommerce-layout__header

如何隐藏woocommerce 后台header&#xff0c;woocommerce-layout__header WooCommerce |Products Store Activity| Inbox| Orders| Stock| Reviews| Notices| breadcrumbs 在 functions.php 里添加如下代码即可&#xff1a; // Disable WooCommerce Header in WordPress Admi…...

通俗易懂的理解 解耦 概念

解耦&#xff08;Decoupling&#xff09;是计算机科学和软件工程中的一个概念&#xff0c;指的是降低系统中不同部分之间的依赖性&#xff0c;使系统的各个组件能够相对独立地进行开发、维护和演化。解耦的主要目标是减少组件之间的紧密耦合&#xff0c;以提高系统的灵活性、可…...

全志A40i android7.1 增加Vlan功能

一&#xff0c;VLAN基础知识 1.VLAN的定义&#xff1a; VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。VLAN内的主机间可以直接通信&#xff0c;而VLAN间不能直接通信&#xff0c;从…...

NAT技术与代理服务器

目录 一、NAT与NAPT技术 1.NAT技术 2.NAPT技术 &#xff08;1&#xff09;四元组的唯一性 &#xff08;2&#xff09;数据的传输过程 &#xff08;3&#xff09;NAPT的缺陷 二、代理服务器 1.正向代理和反向代理 2.代理服务器的应用 &#xff08;1&#xff09;游戏加…...

关于报错java.util.ConcurrentModificationException: null的源码分析和解决

一般有这种问题,方法中至少会有List或者Map下的至少两个子类,有可能参数类型相同,也有可能不同都有可能触发这个问题!其主要原因是使用了ArrayList进行删除操作或者使用iterator遍历集合的同时对集合进行修改都有可能会出现这个问题 ArrayList属于List下的子类 需要区分的是Li…...

使用koa搭建服务器(一)

最近有个需求需要使用到koa搭建服务器并编写接口对数据库进行增删改查&#xff0c;因此写一篇博客记录这段时间的收获。 一、新建koa项目 &#xff08;一&#xff09;安装koa及其相关依赖 npm i koa npm i koa-router// 中间件&#xff0c;用于匹配路由 npm i koa-bodyparse…...

echarts的柱状图的重叠和堆叠实现两个柱体的显示和之前的差值显示

效果图 主要思路 准备三个柱体&#xff08;原计划&#xff0c;实际进度&#xff0c;差值&#xff09; 原计划和实际进度设置成重叠 {barWidth: 20,// yAxisIndex: 1,z: 1,name: 原计划,type: bar,stack: ab,emphasis: { // 点击柱体其他柱体颜色会变浅disabled: true},label…...

泛积木-低代码 使用攻略

文档首发于 泛积木-低代码 使用攻略 我们以大纲的方式&#xff08;总体把握&#xff09;讲述如何高效、便捷使用 泛积木-低代码。 权限 首先说下权限&#xff0c;在 系统设置 / 权限设置 菜单内&#xff0c;我们可以新增调整项目内的权限&#xff0c;默认拥有管理员和成员两…...

红队专题-从零开始VC++C/S远程控制软件RAT-MFC-远控介绍及界面编写

红队专题 招募六边形战士队员[1]远控介绍及界面编写1.远程控制软件演示及教程简要说明主程序可执行程序 服务端生成器主机上线服务端程序 和 服务文件管理CMD进程服务自启动主程序主对话框操作菜单列表框配置信息 多线程操作非模式对话框 2.环境&#xff1a;3.界面编程新建项目…...

机器学习(五)如何理解机器学习三要素

1.8如何理解机器学习三要素 统计学习模型策略算法 模型&#xff1a;规律yaxb 策略&#xff1a;什么样的模型是好的模型&#xff1f;损失函数 算法&#xff1a;如何高效找到最优参数&#xff0c;模型中的参数a和b 1.8.1模型 机器学习中&#xff0c;首先要考虑学习什么样的…...

【计算机视觉】3D视觉

文章目录 一、基本问题二、三个坐标系 X w \boldsymbol{X}_w Xw​到 X c \boldsymbol{X}_c Xc​的转换 X c \boldsymbol{X}_c Xc​到 x i \boldsymbol{x}_i xi​的转换投影矩阵尺度模糊问题 三、相机标定四、立体视觉 我的《计算机视觉》系列参考UC Berkeley的CS180课程&#x…...

策略路由和路由策略

目录 策略路由 路由策略 策略路由和路由策略 策略路由 Step1:配置ACL&#xff0c;匹配流量 acl number 2010 rule 10 permit source 192.168.10.0 0.0.0.255 acl number 2020 rule 10 permit source 192.168.20.0 0.0.0.255 Step2:流分类traffic classifier jiaoxue //匹配…...

[动态规划] (一) LeetCode 1137.第N个泰波那契数

[动态规划] (一) LeetCode 1137.第N个泰波那契数 文章目录 [动态规划] (一) LeetCode 1137.第N个泰波那契数题目解析解题思路状态表示状态转移方程初始化和填表顺序返回值 代码实现总结空间优化代码实现 总结 1137. 第 N 个泰波那契数 题目解析 解题思路 状态表示 (1) 题目要…...

SystemVerilog语法中,在Class中引用层次化信号

在class中可以像在verilog中一样&#xff0c;直接在class中引用层次化信号。示例如下&#xff1a; 1.DUT模块&#xff0c;文件名为top.v。 module top (input clk ,input rst_n ,//总线信号 input wr_n ,input rd_n ,input cs0_n ,input cs7_n …...

磁盘的结构(磁道,扇区,盘面,柱面,物理地址)

目录 1.磁盘、磁道、扇区的概念1.磁盘2.磁道3.扇区 2.如何在磁盘中读/写数据3.盘面、柱面的概念4.磁盘的物理地址1.根据地址读取一个“块” 5.磁盘的分类1.活动头磁道2.固定头磁盘3.根据盘片是否可更换 1.磁盘、磁道、扇区的概念 1.磁盘 磁盘的表面由一些磁性物质组成&#xf…...

uni-app集成uni-simple-router,报错:Uncaught ReferenceError: ROUTES is not defined

参考连接&#xff1a;GitHub - SilurianYang/uni-read-pages: read pages.json file to generate the routes table 作用&#xff1a;配置 vue.config.js 通过 webpack注入全局变量 问题&#xff1a;缺少Webpack 配置环境 方法&#xff1a; 项目根目录下打开终端&#xff0c;…...

几个常用的nosql数据库的操作方式

dynamoDB 键 partition key&#xff1a;分区键 定义&#xff1a;分区键是用于分布数据存储的主键&#xff0c;每个项&#xff08;Item&#xff09;在表中都必须有一个唯一的分区键值。 特点&#xff1a; 唯一性&#xff1a;每个分区键值在表中必须是唯一的&#xff0c;这是因为…...

如何使用 nvm-windows 这个工具来管理你电脑上的Node.js版本

nvm-windows 是一个用于管理在 Windows 上安装的多个 Node.js 版本的工具。以下是安装和使用 nvm-windows 的步骤&#xff1a; 第1步&#xff1a;下载 nvm-windows 访问 nvm-windows 的 GitHub发布页面.下载最新版本的 nvm-setup.zip 文件。 第2步&#xff1a;安装 nvm-wind…...

公司电脑禁用U盘的方法

公司电脑禁用U盘的方法 安企神U盘管理系统下载使用 在这个复杂的数据时代&#xff0c;保护公司数据的安全性至关重要。其中&#xff0c;防止未经授权的数据泄露是其中的一个关键环节。U盘作为一种常用的数据传输工具&#xff0c;也成为了潜在的安全风险。因此&#xff0c;公司…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...