系列

什么是虫洞状态管理模式?

您可以逃脱的最小 state 共享量是多少?

保持你的 state。尽可能靠近使用它的地方。

如果有一个组件关心这个问题,使用它。如果有几个组件在意,就用 props 分享一下。 如果很多组件都关心,把它放在 context 中。

Context 就像一个虫洞。它使您的组件树弯曲,因此相距很远的部分可以接触。

利用自定义 hooks 使这变得容易。

一个例子

构建一个点击计数器。虫洞状态管理模式最好通过示例来解释 ️

CodeSandbox(示例代码)

步骤 1

我们从 useState 开始,因为它是最简单的。

const ClickCounter = () => {
const [count, setCount] = useState(0); function onClick() {
setCount(count => count + 1);
} return <button onClick={onClick}>{count} +1</button>;
};

count 保存当前的点击次数,setCount 让我们在每次点击时更新值。

足够简单。

不过,外观并不是很漂亮。让我们用一个自定义按钮组件和一些嵌套来改进它。

步骤 2

我们创建了一个可重复使用的 PrettyButton,确保您应用中的每个按钮看起来都很棒。

状态保留在 ClickCounter 组件中。

const ClickCounter = () => {
const [count, setCount] = useState(0); function onClick() {
setCount(count => count + 1);
} return (
<>
<p>You have clicked buttons {count} times</p>
<div style={{ textAlign: "right" }}>
<PrettyButton onClick={onClick}>+1</PrettyButton>
</div>
</>
);
};

这是必要的最少状态共享。我们也保持了简单的状态。

计数器组件关心点击次数计数,因此它将回调作为 props 传递到按钮中。函数被调用,状态更新,组件重新渲染。

不需要复杂的操作。

步骤 3

如果我们的状态更复杂怎么办? 我们有 2 个属于一起的项。

您可以在您的状态中保留复杂的值。效果很好。

const ClickCounter = () => {
const [count, setCount] = useState({ A: 0, B: 0 }); function onClickA() {
setCount(count => {
return { ...count, A: count.A + 1 };
});
} function onClickB() {
setCount(count => {
return { ...count, B: count.B + 1 };
});
} return (
<>
<p>
You have clicked buttons A: {count.A}, B: {count.B} times
</p>
<div style={{ textAlign: "right" }}>
<PrettyButton onClick={onClickA}>A +1</PrettyButton>
&nbsp;
<PrettyButton onClick={onClickB}>B +1</PrettyButton>
</div>
</>
);
};

我们已将 count 拆分为一个对象 – { A, B }

现在单个状态可以保存多个值。单独按钮点击的单独计数。

React 使用 JavaScript 相等来检测重新渲染的更改,因此您必须在每次更新时制作完整状态的副本。这在大约 10,000 个元素时变慢。

您也可以在这里使用 useReducer

特别是当您的状态变得更加复杂并且项目经常单独更新时。

使用 useReducer 的类似状态如下所示:

const [state, dispatch] = useReducer((action, state) => {
switch (action.type) {
case 'A':
return { ...state, A: state.A + 1 }
case 'B':
return { ...state, A: state.A + 1 }
}
}, { A: 0, B: 0}) function onClickA() {
dispatch({ type: 'A' })
}

你的状态越复杂,这就越有意义。

但我认为那些 switch 语句很快就会变得混乱,而且你的回调函数无论如何都已经是动作了。

步骤 4

如果我们想要 2 个按钮更新相同的状态怎么办?

您可以将 countsetCount 作为 props 传递给您的组件。但这变得越来越混乱。

const AlternativeClick = ({ count, setCount }) => {
function onClick() {
setCount(count => {
return { ...count, B: count.B + 1 };
});
} return (
<div style={{ textAlign: "left" }}>
You can also update B here
<br />
<PrettyButton onClick={onClick}>B +1</PrettyButton>
<p>It's {count.B} btw</p>
</div>
);
};

我们创建了一个难以移动并且需要理解太多父逻辑的组件。关注点是分裂的,抽象是奇怪的,我们造成了混乱。

你可以通过只传递它需要的状态部分和一个更自定义的 setCount 来修复它。但这是很多工作。

步骤 5

相反,您可以使用虫洞与自定义 hook 共享状态。

您现在有 2 个共享状态的独立组件。将它们放在您的代码库中的任何位置,它 Just Works

需要在其他地方访问共享状态?添加 useSharedCount hook,瞧。

这是这部分的工作原理。

我们有一个 context provider,里面有一些操作:

export const SharedCountProvider = ({ children }) => {
// replace with useReducer for more flexiblity
const [state, setState] = useState(defaultState); const [contextValue, setContextValue] = useState({
state,
// dispatch // from your reducer
// this is where a reducer comes handy when this grows
setSharedCount: (key, val) => {
setState(state => {
return { ...state, [key]: val };
});
}
// other stuff you need in context
}); // avoids deep re-renders
// when instances of stuff in context change
useEffect(() => {
setContextValue(currentValue => ({
...currentValue,
state
}));
}, [state]); return (
<SharedCountContext.Provider value={contextValue}>
{children}
</SharedCountContext.Provider>
);
};

Context Provider 使用丰富的 state 变量来保持您的状态。

这里对我们来说是 { A, B }

contextValue 是一个更丰富的状态,它也包含操作该状态所需的一切。通常,这将是来自您的 reducerdispatch 方法,或者像我们这里的自定义状态设置器。

我们的 setSharedCount 方法获取一个 key 和一个 val 并更新该部分状态。

setSharedCount("B", 10);

然后我们有一个副作用,它观察 state 的变化并在需要时触发重新渲染。这避免了每次我们重新定义我们的 dispatch 方法或其他任何东西时的深度重新渲染。

使 React 树更稳定 ️

在这个 provider 中呈现的每个组件都可以使用这个相同的自定义 hook 来访问它需要的一切。

export function useSharedCount() {
const { state, setSharedCount } = useContext(SharedCountContext); function incA() {
setSharedCount("A", state.A + 1);
} function incB() {
setSharedCount("B", state.B + 1);
} return { count: state, incA, incB };
}

自定义 hook 利用 React Context 共享状态,定义更简单的 incAincB 辅助方法,并返回它们的状态。

这意味着我们的 AlternativeClick 组件可以是这样的:

import {
useSharedCount
} from "./SharedCountContextProvider"; const AlternativeClick = () => {
const { count, incB } = useSharedCount(); return (
<div style={{ textAlign: "left" }}>
You can also update B here
<br />
<PrettyButton onClick={incB}>B +1</PrettyButton>
<p>It's {count.B} btw</p>
</div>
);
};

从自定义 hook 获取 countincB。使用它们。

性能怎么样?

很好。

尽可能少地共享 state。对应用程序的不同部分使用不同的 context provider

不要让它成为 global,除非它需要是 global 的。包裹你可以逃脱的树的最小部分。

复杂度如何?

什么复杂度?保持小。不要把你不需要的东西塞进去。

讨厌管理自己的状态

看到我们 SharedCountProvider 中处理状态变化的部分了吗? 这部分:

const [contextValue, setContextValue] = useState({
state,
// dispatch // from your reducer
// this is where a reducer comes handy when this grows
setSharedCount: (key, val) => {
setState(state => {
return { ...state, [key]: val };
});
}
// other stuff you need in context
});

为此,您可以使用 XState。或者 reducer。甚至 Redux,如果你真的想要的话。

不过,如果你使用 Redux,你不妨一路走下去

顶级开源项目是如何使用的?(Sentry)

organizationContext.tsx(详细代码)

Refs

Sentry 开发者贡献指南 - 前端 React Hooks 与虫洞状态管理模式的更多相关文章

  1. Sentry 开发者贡献指南 - 前端(ReactJS生态)

    内容整理自官方开发文档 系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Map ...

  2. Sentry 开发者贡献指南 - SDK 开发(性能监控)

    内容整理于官方开发文档 系列 Docker Compose 部署与故障排除详解 K8S + Helm 一键微服务部署 Sentry 开发者贡献指南 - 前端(ReactJS生态) Sentry 开发者 ...

  3. Sentry 开发者贡献指南 - SDK 开发(事件负载)

    内容整理自官方开发文档 系列 Docker Compose 部署与故障排除详解 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentr ...

  4. Sentry 开发者贡献指南 - 后端服务(Python/Go/Rust/NodeJS)

    内容整理自官方开发文档 系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Map ...

  5. Sentry 开发者贡献指南 - Feature Flag

    功能 flag 在 Sentry 的代码库中声明. 对于自托管用户,这些标志然后通过 sentry.conf.py 进行配置. 对于 Sentry 的 SaaS 部署,Flagr 用于在生产中配置标志 ...

  6. Sentry 开发者贡献指南 - SDK 开发(性能监控:Sentry SDK API 演进)

    内容整理自官方开发文档 本文档的目标是将 Sentry SDK 中性能监控功能的演变置于上下文中. 我们首先总结了如何将性能监控添加到 Sentry 和 SDK, 然后我们讨论 identified ...

  7. Sentry 开发者贡献指南 - 配置 PyCharm

    概述 如果您使用 PyCharm 进行开发,则需要配置一些内容才能运行和调试. 本文档描述了一些对 sentry 开发有用的配置 配置 Python 解释器:(确保它是 venv 解释器)例如 ~/v ...

  8. Sentry 开发者贡献指南 - 数据库迁移

    Django 迁移是我们处理 Sentry 中数据库更改的方式. Django 迁移官方文档:https://docs.djangoproject.com/en/2.2/topics/migratio ...

  9. Sentry 开发者贡献指南 - Django Rest Framework(Serializers)

    Serializer 用于获取复杂的 python 模型并将它们转换为 json.序列化程序还可用于在验证传入数据后将 json 反序列化回 Python 模型. 在 Sentry,我们有两种不同类型 ...

随机推荐

  1. [loj6736]最小连通块

    定义$f(S)$表示点集$S$的最小连通块 做法1 通过对所有节点判定,可以在$n$次询问中求出具体的$f(S)$ 对于$x\ne y$,显然$(x,y)\in E$当且仅当$f(\{x,y\})=\ ...

  2. [hdu4388]Stone Game II

    不管是否使用技能,发现操作前后所有堆二进制中1的个数之和不变.那么对于一个堆其实可以等价转换为一个k个石子的堆(k为该数二进制的个数),然后就是个nim游戏. 1 #include<bits/s ...

  3. [hdu5379]Mahjong tree

    一棵子树的每一个儿子相当于划分一个区间,同时这些区间一定要存在一个点连续(直接的儿子),因此每一棵树最多只有两个儿子存在子树,并且这两个儿子所分到的区间一定是该区间最左和最右两段,所以ans*=(so ...

  4. Elasticsearch分布式搜索和数据分析引擎-ElasticStack(上)v7.14.0

    Elasticsearch概述 **本人博客网站 **IT小神 www.itxiaoshen.com Elasticsearch官网地址 https://www.elastic.co/cn/elast ...

  5. AtCoder Regular Contest 127 题解

    sb atcoder 提前比赛时间/fn/fn/fn--sb atcoder 还我 rating/zk/zk/zk A 签到题,枚举位数 \(+\) 前导 \(1\) 个数然后随便算算贡献即可,时间复 ...

  6. 为什么Mysql用B+树做索引而不用B-树或红黑树

    B+树做索引而不用B-树 那么Mysql如何衡量查询效率呢?– 磁盘IO次数. 一般来说索引非常大,尤其是关系性数据库这种数据量大的索引能达到亿级别,所以为了减少内存的占用,索引也会被存储在磁盘上. ...

  7. miRNA分析--数据过滤(一)

    miRNA 数据过滤我使用cutadapt 1 cutadapt -a AGATCGGAAGAGCACACGTCT -m 15 -q 20 --discard-untrimmed -o outname ...

  8. Jumpserver堡垒机容器化部署

    JumpServer 是符合 4A 的专业运维安全审计系统. 前提条件 已部署docker Jumpserver 对外需要开放 80 443 和 2222 端口 服务器.数据库.redis 等依赖组件 ...

  9. 日常Javaweb 2021/11/19

    Javaweb Dao层: //连接数据库,实现增查功能 package dao; import java.sql.Connection; import java.sql.DriverManager; ...

  10. RTSP, RTP, RTCP, RTMP傻傻分不清?

    RTSP基于TCP传输请求和响应报文,RTP基于UDP传输流媒体数据,RTCP基于UDP传送传输质量信息(如丢包和延迟). 比如喀什一个局域网内10个人同时点播广州的同一个源,喀什和广州之间就要传10 ...