首页 > 解决方案 > React:使用顶级常量对象打破 2 个相关上下文之间的依赖关系

问题描述

我正在为react-xarrows开发一个新的主要版本,但我遇到了一些混乱的情况。

解释起来并不简单,所以让我们从可视化开始:

考虑下一个示例 - 2 个可拖动的框,在它们之间绘制一个箭头,以及围绕它们的包装上下文。

在此处输入图像描述

重点代码:

<Xwrapper>
  <DraggableBox box={box} />
  <DraggableBox box={box2} />
  <Xarrow start={'box1'} end={'box2'} {...xarrowProps} />
</Xwrapper>

Xwrapper是上下文,DraggableBoxXarrow可以猜到。

我的目标

我想在箭头上触发渲染,并且只在箭头上,只要其中一个连接的框渲染。

我的方法

我希望能够从框中重新渲染箭头,因此我必须在框上使用“重新渲染箭头”(我们称之为updateXarrow)函数,我们可以在框上使用上下文和useContext钩子来获取此功能。我将调用XelemContext框上下文。

另外,我需要消耗useContextXarrow因为我想在我决定时在箭头上进行渲染。

这必须是 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 的领域,所以我有两个主要问题:

  1. 有更好的方法吗?也许我可以在不发疯的情况下实现我的目标?
  2. 如果没有更好的选择,这危险吗?我不想发布会在我的 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:

知道为什么吗?还有其他想法吗?

标签: javascriptreactjstypescript

解决方案


以防万一有人需要类似的东西:这是一个即使在 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;


推荐阅读