学习疑难解答

迁移到 v10

您可以在此处找到 React Flow 旧版本的文档:v11v10v9

欢迎使用 React Flow v10!随着主要版本更新,将带来许多新功能,但也有一些重大变更。

新功能

  • 子流程: 您现在可以将节点添加到父节点,并创建组和嵌套流程
  • 节点类型 ‘group’: 一种没有句柄的新节点类型,可用作组节点
  • 触摸设备支持: 现在可以在触摸设备上连接节点
  • 初始化时调整视图大小: 您可以使用新的 fitView 属性来调整初始视图的大小
  • 按键处理: 现在不仅支持单个按键,还支持多个按键和按键组合
  • useKeyPress 钩子: 用于处理键盘事件的实用钩子
  • useReactFlow 钩子: 返回一个 React Flow 实例,该实例公开用于操作流程的功能
  • useNodesuseEdgesuseViewport 钩子: 用于接收节点、边和视窗的钩子
  • 边标记: 更多选项来配置边的开始和结束标记

重大变更

TLDR

  • elements 数组拆分为 nodesedges 数组,并实现 onNodesChangeonEdgesChange 处理程序(核心概念部分 中有详细指南)
  • 记忆你的自定义 nodeTypesedgeTypes
  • onLoad 重命名为 onInit
  • paneMoveable 重命名为 panOnDrag
  • useZoomPanHelper 重命名为 useReactFlow(以及 setTransform 重命名为 setViewport
  • 将节点和边选项 isHidden 重命名为 hidden

重大变更的详细说明

1. 元素 - 节点和边

我们看到很多人在使用半受控的 elements 属性时遇到了困难。始终难以将本地用户状态与 React Flow 的内部状态同步。有些人使用了从未记录过的内部存储,这始终是一种不太好的解决方案。在新版本中,我们提供了两种使用 React Flow 的方式 - 非受控和受控。

1.1. 受控 nodesedges

如果您想完全控制并使用来自本地状态或存储的节点和边,您可以结合使用 nodesedges 属性和 onNodesChangeonEdgesChange 处理程序。对于交互式流程,您需要实现这些处理程序(如果您仅对平移和缩放感到满意,则不需要它们)。当节点(s)被初始化、拖动、选中或移除时,您将收到一个更改。这意味着您始终知道节点的精确位置和尺寸,或者例如它是否被选中。我们导出了辅助函数 applyNodeChangesapplyEdgeChanges,您应该使用它们来应用更改。

旧 API

import { useState, useCallback } from 'react';
import { ReactFlow, removeElements, addEdge } from 'react-flow-renderer';
 
const initialElements = [
  { id: '1', data: { label: 'Node 1' }, position: { x: 250, y: 0 } },
  { id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
  { id: 'e1-2', source: '1', target: '2' },
];
 
const BasicFlow = () => {
  const [elements, setElements] = useState(initialElements);
  const onElementsRemove = useCallback(
    (elementsToRemove) =>
      setElements((els) => removeElements(elementsToRemove, els)),
    [],
  );
  const onConnect = useCallback((connection) =>
    setElements((es) => addEdge(connection, es)),
  );
 
  return (
    <ReactFlow
      elements={elements}
      onElementsRemove={onElementsRemove}
      onConnect={onConnect}
    />
  );
};
 
export default BasicFlow;

新 API

import { useState, useCallback } from 'react';
import {
  ReactFlow,
  applyNodeChanges,
  applyEdgeChanges,
  addEdge,
} from 'react-flow-renderer';
 
const initialNodes = [
  { id: '1', data: { label: 'Node 1' }, position: { x: 250, y: 0 } },
  { id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
];
 
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
 
const BasicFlow = () => {
  const [nodes, setNodes] = useState(initialNodes);
  const [edges, setEdges] = useState(initialEdges);
 
  const onNodesChange = useCallback(
    (changes) => setNodes((ns) => applyNodeChanges(changes, ns)),
    [],
  );
  const onEdgesChange = useCallback(
    (changes) => setEdges((es) => applyEdgeChanges(changes, es)),
    [],
  );
  const onConnect = useCallback((connection) =>
    setEdges((eds) => addEdge(connection, eds)),
  );
 
  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
    />
  );
};
 
export default BasicFlow;

您还可以使用新的钩子 useNodesStateuseEdgesState 来快速入门

const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

相关变更

  • onElementsClick ->onNodeClickonEdgeClick
  • onElementsRemove -> 被 onNodesChangeonEdgesChange 处理程序替换

1.2 非受控 defaultNodesdefaultEdges

最简单的入门方法是使用 defaultNodesdefaultEdges 属性。设置这些属性后,所有操作都在内部处理。您无需添加任何其他处理程序即可获得完全交互式的流程,并具有拖动节点、连接节点以及删除节点和边的功能。

新 API

import ReactFlow from 'react-flow-renderer';
 
const defaultNodes = [
  { id: '1', data: { label: 'Node 1' }, position: { x: 250, y: 0 } },
  { id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
];
 
const defaultEdges = [{ id: 'e1-2', source: '1', target: '2' }];
 
const BasicFlow = () => {
  return <ReactFlow defaultNodes={defaultNodes} defaultEdges={defaultEdges} />;
};
 
export default BasicFlow;

如果您想添加、删除或更新节点或边,您只能使用 ReactFlow 实例,您可以通过新的 useReactFlow 钩子或使用将实例作为函数参数的 onInit 处理程序来接收该实例。

2. 记忆您的自定义 nodeTypesedgeTypes

每次您传递新的节点或边类型时,我们都会在后台创建包装的节点或边组件类型。这意味着您不应该在每次渲染时创建一个新的 nodeTypeedgeType 对象。记忆您的 nodeTypes 和 edgeTypes,或者在它们不变时在组件外部定义它们

不要这样做

这会在每次渲染时创建一个新对象,并导致错误和性能问题

// this is bad! Don't do it.
<ReactFlow
  nodes={[]}
  nodeTypes={{
    specialType: SpecialNode, // bad!
  }}
/>

这样做

function Flow() {
  const nodeTypes = useMemo(() => ({ specialType: SpecialNode }), []);
 
  return <ReactFlow nodes={[]} nodeTypes={nodeTypes} />;
}

或者在类型不变时在组件外部创建它们

const nodeTypes = { specialType: SpecialNode };
 
function Flow() {
  return <ReactFlow nodes={[]} nodeTypes={nodeTypes} />;
}

3. Redux - Zustand

我们将状态管理库从 Redux 切换到 Zustand。通过这种更改,我们能够从与状态相关的代码中删除大约 300LOC。如果您需要访问内部存储,您可以使用 useStore 钩子

旧 API

import { useStoreState, useStoreActions } from 'react-flow-renderer';
 
...
 
const transform = useStoreState((store) => store.transform);

新 API

import { useStore } from 'react-flow-renderer';
 
...
const transform = useStore((store) => store.transform);

如果您想访问内部存储,您仍然需要使用 <ReactFlowProvider /> 包装您的组件。

我们还导出 useStoreApi,如果您需要在事件处理程序中获取存储,例如,无需触发重新渲染。

import { useStoreApi } from 'react-flow-renderer';
 
...
 
const store = useStoreApi();
 
...
// in an event handler
const [x, y, zoom] = store.getState().transform;

4. onLoad - onInit

onLoad 回调已重命名为 onInit,现在在节点初始化时触发。

旧 API

const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.zoomTo(2);
...
<ReactFlow
   ...
  onLoad={onLoad}
/>

新 API

const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.zoomTo(2);
...
<ReactFlow
   ...
  onInit={onInit}
/>

5. paneMoveable - panOnDrag

这与 API 的其他部分 (panOnScroll, zoomOnScroll 等) 更加一致。

旧 API

<ReactFlow
   ...
  paneMoveable={false}
/>

新 API

<ReactFlow
   ...
  panOnDrag={false}
/>

6. useZoomPanHelper transform - 在 useReactFlow 中统一

由于“transform”也是存储中变换的变量名称,而且不清楚 transform 是一个 setter,因此我们将它重命名为 setViewport。这也与其他函数更加一致。此外,所有 useZoomPanHelper 函数已移至 React Flow 实例,您可以从 useReactFlow 钩子onInit 处理程序获取该实例。

旧 API

const { transform, setCenter, setZoom  } = useZoomPanHelper();
...
transform({ x: 100, y: 100, zoom: 2 });

新 API

const { setViewport, setCenter, setZoom } = useReactFlow();
...
setViewport({ x: 100, y: 100, zoom: 2 });

新的视口函数

  • getZoom
  • getViewport

7. isHidden - hidden

我们混合了带前缀 (is...) 和不带前缀的布尔选项名称。所有节点和边选项现在都不带前缀。因此它是 hidden, animated, selected, draggable, selectableconnectable

旧 API

const hiddenNode = { id: '1', isHidden: true, position: { x: 50, y: 50 } };

新 API

const hiddenNode = { id: '1', hidden: true, position: { x: 50, y: 50 } };

8. arrowHeadType markerEndId - markerStart / markerEnd

我们改进了用于自定义边标记的 API。使用新 API,您可以在边的开头和结尾设置单独的标记,以及使用颜色、strokeWidth 等对其进行自定义。您仍然可以设置 markerEndId,但现在不再使用不同的属性,markerStartmarkerEnd 属性接受字符串(您需要自己定义的 svg 标记的 ID)或用于使用内置 arrowclosed 或 arrow 标记的配置对象。

旧 API

const markerEdge = { source: '1', target: '2', arrowHeadType: 'arrow' };

新 API

const markerEdge = {
  source: '1',
  target: '2',
  markerStart: 'myCustomSvgMarker',
  markerEnd: { type: 'arrow', color: '#f00' },
};

9. ArrowHeadType - MarkerType

这只是为了使标记 API 更加一致而进行的措辞更改。由于我们现在能够为边的开头设置标记,因此 ArrowHeadType 类型已重命名为 MarkerType。将来,这不仅可以包含箭头形状,还可以包含其他形状,例如圆形、菱形等。

10. 属性

这不是对 API 的真正重大更改,而是在 React Flow 的整体外观上进行了一点更改。我们在右下角添加了一个很小的“React Flow”属性(位置可以通过 attributionPosition 属性配置)。此更改与新的“React Flow Pro”订阅模式一起提供。如果您想在商业应用程序中删除属性,请订阅 “React Flow Pro”