javascript - React:使用顶级常量对象打破 2 个相关上下文之间的依赖关系
问题描述
我正在为react-xarrows开发一个新的主要版本,但我遇到了一些混乱的情况。
解释起来并不简单,所以让我们从可视化开始:
考虑下一个示例 - 2 个可拖动的框,在它们之间绘制一个箭头,以及围绕它们的包装上下文。
重点代码:
<Xwrapper>
<DraggableBox box={box} />
<DraggableBox box={box2} />
<Xarrow start={'box1'} end={'box2'} {...xarrowProps} />
</Xwrapper>
Xwrapper
是上下文,DraggableBox
你Xarrow
可以猜到。
我的目标
我想在箭头上触发渲染,并且只在箭头上,只要其中一个连接的框渲染。
我的方法
我希望能够从框中重新渲染箭头,因此我必须在框上使用“重新渲染箭头”(我们称之为updateXarrow
)函数,我们可以在框上使用上下文和useContext
钩子来获取此功能。我将调用XelemContext
框上下文。
另外,我需要消耗useContext
,Xarrow
因为我想在我决定时在箭头上进行渲染。
这必须是 2 个不同的上下文(所以我可以单独渲染 xarrow)。一个在盒子上使用“updateXarrow”,在 Xarrow 上使用不同的上下文来触发 reredner。
那么如何将这个函数从一个上下文传递到另一个上下文呢?好吧,我不能不做一个无限循环(或者我可以但无法弄清楚),所以我使用了一个名为updateRef
.
// define a global object
const updateRef = { func: null };
const XarrowProvider = ({ children }) => {
// define updateXarrow here
...
// assign to updateRef.func
updateRef.func = updateXarrow;
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
//now updateRef.func is defined because XelemProvider defined later
const XelemProvider = ({ children }) => {
return <XelemContext.Provider value={updateRef.func}>{children}</XelemContext.Provider>;
};
问题是,这个对象不是由 react 管理的,而且,我需要处理有多个实例的情况Xwrapper
,我要离开 React 的领域,所以我有两个主要问题:
- 有更好的方法吗?也许我可以在不发疯的情况下实现我的目标?
- 如果没有更好的选择,这危险吗?我不想发布会在我的 lib 消费者应用程序的边缘情况下中断的代码。
代码
可拖动框
const DraggableBox = ({ box }) => {
console.log('DraggableBox render', box.id);
const handleDrag = () => {
console.log('onDrag');
updateXarrow();
};
const updateXarrow = useXarrow();
return (
<Draggable onDrag={handleDrag} onStop={handleDrag}>
<div id={box.id} style={{ ...boxStyle, position: 'absolute', left: box.x, top: box.y }}>
{box.id}
</div>
</Draggable>
);
};
使用Xarrow
import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { XelemContext } from './Xwrapper';
const useXarrow = () => {
const [, setRender] = useState({});
const reRender = () => setRender({});
const updateXarrow = useContext(XelemContext);
useLayoutEffect(() => {
updateXarrow();
});
return reRender;
};
export default useXarrow;
包装器
import React, { useState } from 'react';
export const XelemContext = React.createContext(null as () => void);
export const XarrowContext = React.createContext(null as () => void);
const updateRef = { func: null };
const XarrowProvider = ({ children }) => {
console.log('XarrowProvider');
const [, setRender] = useState({});
const updateXarrow = () => setRender({});
updateRef.func = updateXarrow;
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
const XelemProvider = ({ children }) => {
console.log('XelemProvider');
return <XelemContext.Provider value={updateRef.func}>{children}</XelemContext.Provider>;
};
const Xwrapper = ({ children }) => {
console.log('Xwrapper');
return (
<XarrowProvider>
<XelemProvider>{children}</XelemProvider>
</XarrowProvider>
);
};
export default Xwrapper;
const Xarrow: React.FC<xarrowPropsType> = (props: xarrowPropsType) => {
useContext(XarrowContext);
const svgRef = useRef(null);
....(more 1100 lines of code)
日志
我留下了一些日志。
在单个框的拖动事件中,您将获得:
onDrag
DraggableBox render box2
XarrowProvider
xarrow
笔记
目前,这正在按预期工作。
更新
经过数小时的测试,这似乎工作得很好。我管理自己的对象,该对象记住每个 Xwrapper 实例的更新函数,这打破了 2 个上下文之间的依赖关系。我会留下这篇文章,以防其他人也遇到这个问题。
更新(坏的)
这种架构打破了具有 <React.StrictMode>...</React.StrictMode> 的反应树:cry:
知道为什么吗?还有其他想法吗?
解决方案
以防万一有人需要类似的东西:这是一个即使在 react strictmode 下也能工作的版本(基本上是依赖于调用一次而不是渲染的效果):
import React, { FC, useEffect, useRef, useState } from 'react';
export const XelemContext = React.createContext(null as () => void);
export const XarrowContext = React.createContext(null as () => void);
// will hold a object of ids:references to updateXarrow functions of different Xwrapper instances over time
const updateRef = {};
let updateRefCount = 0;
const XarrowProvider: FC<{ instanceCount: React.MutableRefObject<number> }> = ({ children, instanceCount }) => {
const [, setRender] = useState({});
const updateXarrow = () => setRender({});
useEffect(() => {
instanceCount.current = updateRefCount; // so this instance would know what is id
updateRef[instanceCount.current] = updateXarrow;
}, []);
// log('XarrowProvider', updateRefCount);
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
// renders only once and should always provide the right update function
const XelemProvider = ({ children, instanceCount }) => {
return <XelemContext.Provider value={updateRef[instanceCount.current]}>{children}</XelemContext.Provider>;
};
const Xwrapper = ({ children }) => {
console.log('wrapper here!');
const instanceCount = useRef(updateRefCount);
const [, setRender] = useState({});
useEffect(() => {
updateRefCount++;
setRender({});
return () => {
delete updateRef[instanceCount.current];
};
}, []);
return (
<XelemProvider instanceCount={instanceCount}>
<XarrowProvider instanceCount={instanceCount}>{children}</XarrowProvider>
</XelemProvider>
);
};
export default Xwrapper;
推荐阅读
- python - 如何获取多个python模块中不同函数的执行顺序?
- ios - 读取条码后未打开 ViewController Swift 3.0
- flutter - 如果多级列表的节点有点击时应该显示的内容怎么办?(高级扩展瓷砖)
- objective-c - UISearchController - 搜索结果显示不同的记录
- c - 身份属性的位操作
- java - 为什么此方法只将 0 的值返回到新数组?
- jquery - 在 jQuery 中发布到 Firebase 实时数据库时出现“错误请求 400”,但可以使用 Postman
- javascript - 重构具有相同逻辑的两种方法仅因它们使用的运算符而异?
- python - 集合是无序的,但它仍然支持 2 集合的减法,它在内部是如何工作的?
- java - 如何从ansible下载jar文件