网络建设 之 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数据管理的哲学

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

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

相关文章

Windows 和 Linux 这2个系统在进行编程实现的时候的一些区别:

很惭愧&#xff0c;学了很多年才意识到&#xff0c;噢&#xff0c;原来这两个系统实现一些功能的时候会使用到不同的库&#xff0c;使用不同的函数。 那么&#xff0c;也会延伸出一些问题&#xff1a; 比如&#xff0c;如何实现版本的迁移。一个在Linux上运行的代码如何可以比…

C#WPF嵌入字体实例

本文介绍C#WPF嵌入字体实例。 首先创建项目 添加Resources文件夹,添加字体文件,字体文件属性:生成操作为Resources,复制到输出目录:不复制 字体的使用可以采用以下两种方法: 方式一 直接引用 FontFamily="./Resources/#幼圆" 方式二 定义资源 <Applica…

【面试经典150 | 栈】简化路径

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;字符串数组模拟栈 其他语言python3 写在最后 Tag 【栈】【字符串】 题目来源 71. 简化路径 题目解读 将 Unix 风格的绝对路径转化成更加简洁的规范路径。字符串中会出现 字母、数字、/、_、. 和 .. 这几种字符&#…

阿里云企业邮箱基于Spring Boot快速实现发送邮件功能

邮件在项目中经常会被用到&#xff0c;比如用邮件发送通知。比如&#xff0c;通过邮件注册、认证、找回密码、系统报警通知、报表信息等。本篇文章带大家通过SpringBoot快速实现一个发送邮件的功能。 邮件协议 下面先简单了解一下常见的邮件协议。常用的电子邮件协议有SMTP、…

基于springboot,vue学生宿舍管理系统

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vuevue-element-admin 服务端技术&#xff1a;springboot,mybatis…

express session JWT JSON Web Token

了解 Session 认证的局限性 Session 认证机制需要配合 cookie 才能实现。由于 Cookie 默认不支持跨域访问&#xff0c;所以&#xff0c;当涉及到前端跨域请求后端接口的时候&#xff0c;需要做很多额外的配置&#xff0c;才能实现跨域 Session 认证。 注意&#xff1a; 当前端…

层次式架构的设计理论与实践

层次式架构的设计理论与实践 层次式架构概述 层次式架构的定义和特性 定义 特性 层次式架构的一般组成(表现层、中间层、数据访问层和数据层) 表现层框架设计 设计模式 MVC MVP MVVM XML技术 UIP设计思想 表现层动态生成设计思想(基于XML界面管理技术) 中间层架构设计 业务…

macOS 12 Monterey v12.7.1正式版:开启全新的操作系统体验

macOS 12 Monterey已经向所有兼容的Mac设备推出&#xff0c;为您带来了一系列强大的新功能和改进。这个全新的操作系统版本&#xff0c;不仅带来了更流畅的用户体验&#xff0c;还增强了与iOS设备的无缝集成&#xff0c;让您的设备使用更加高效&#xff0c;更加便捷。 macOS 1…

NVM 安装及使用

1.安装 我使用的是解压版&#xff0c;免安装 github下载压缩包 下载后放在一个【没有中文】的文件夹下&#xff0c;解压 然后需要配环境变量&#xff0c; 首先添加两个变量&#xff0c;分别是刚刚nvm解压的路径&#xff0c;和当前node安装的路径。 然后编辑path变量&#x…

牛客网刷题-(5)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

贝锐花生壳内网穿透推出全新功能,远程业务连接更安全

贝锐旗下内网穿透兼动态域名解析品牌花生壳目前推出了全新的“访问控制”功能&#xff0c;可精确设置访问权限&#xff0c;充分保障信息安全&#xff0c;满足更多用户安全远程访问内网服务的需求。 通过这一功能&#xff0c;可实现指定时间、IP、地区等条件下才能远程访问映射的…

算法通过村第十六关-滑动窗口|黄金笔记|结合堆的应用

文章目录 前言堆与滑动窗口结合的问题总结 前言 提示&#xff1a;不论记忆多么痛苦&#xff0c;它属于过去&#xff0c;已经逝去了&#xff0c;我们为什么还执着于它并让它代表我们&#xff1f;我们就这样&#xff0c;所以&#xff0c;我们受苦。 --丹津葩默 这个还是一个比较重…

进程/线程/PCB

进程&#xff1a;正在运行中的程序&#xff08;进程是驻留在内存中的&#xff09; 是系统执行资源分配和调度的独立单位每一个进程都有属于自己的存储空间和系统资源注意&#xff1a;进程A 和 进程B 的内存独立不共享 使用jdk自带的工具&#xff0c;jconsole查看当前Java进程中…

CentOS 编译安装 nginx

CentOS 编译安装 nginx 修改 yum 源地址为 阿里云 curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repoyum makecache升级内核和软件 yum -y update安装常用软件和依赖 yum -y install gcc gcc-c make cmake zlib zlib-devel openss…

IntelliJ IDEA 常用快捷键-个人查阅

Ctrl快捷键 介绍 Ctrl F 在当前文件进行文本查找 &#xff08;必备&#xff09; Ctrl R 在当前文件进行文本替换 &#xff08;必备&#xff09; Ctrl Z 撤销 &#xff08;必备&#xff09; Ctrl Y 删除光标所在行 或 删除选中的行 &#xff08;必备&#x…

electron27+react18集成搭建跨平台应用|electron窗口多开

基于Electron27集成React18创建一个桌面端exe程序。 electron27-vite4-react18基于electron27结合vite4构建工具快速创建react18跨端应用实践。 版本列表 "vite": "^4.4.5" "react": "^18.2.0" "electron": "^27.0.1&…

CloudQuery + StarRocks:打造高效、安全的数据库管控新模式

随着技术的迅速发展&#xff0c;各种多元化的数据库产品应运而生&#xff0c;它们不仅类型众多&#xff0c;而且形式各异&#xff0c;国产化数据库千余套&#xff0c;开源数据库百余套 OceanBase 、PolarDB 、StarRocks…还有一些像 Oracle、MySQL 这些传统数据库。这些数据库产…

读取Excel的工具类——ExcelKit

文章目录 ExcelKit工具类1、准备工作1.1、SheetInfoVo1.2、BizException 2、读取xlsx3、读取xls4、完整的ExcelKit.java源码 ExcelKit工具类 1、准备工作 1.1、SheetInfoVo import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import …

太极v14.0.4 免ROOT用Xposed

一个帮助你免 Root、免解锁免刷机使用 Xposed 模块的 APP 框架。 模块通过它改变系统和应用的行为&#xff0c;既能以传统的 Root/ 刷机方式运作&#xff0c; 也能免 Root/ 免刷机运行&#xff1b;并且它支持 Android 5.0 ~ 11。 简单来说&#xff0c;太极就是个 Xposed 框架…

2024线性代数复习——矩阵代数

相似矩阵&#xff1a;存在可逆矩阵 P P P&#xff0c;使得 P − 1 A P B P^{-1} A PB P−1APB&#xff0c;则称矩阵 A A A&#xff0c; B B B 相似&#xff0c;特征值相等。注意只有相似矩阵 B B B 是对角阵&#xff0c;我们才说它是可以相似对角化的。 A A A 可以相似对角…