迁移到 React Flow v10
🌐 Migrate to React Flow v10
欢迎使用 React Flow v10!随着这个重大版本更新,带来了许多新功能,但也有一些不兼容的更改。
🌐 Welcome to React Flow v10! With the major version update, there are coming many new features but also some breaking changes.
新功能
🌐 New Features
- 子流程:你现在可以向父节点添加节点,并创建组和嵌套流程
- 节点类型 ‘group’:一种新的没有句柄的节点类型,可用作组节点
- 触摸设备支持:现在可以在触摸设备上连接节点
- 初始化视图自适应:你可以使用新的
fitView属性来适应初始视图 - 按键处理:现在不仅可以处理单个按键,还可以处理多个按键和按键组合
- useKeyPress 钩子:一个用于处理键盘事件的实用钩子
- useReactFlow 钩子:返回一个 React Flow 实例,该实例提供操作流程的函数
- useNodes、useEdges 和 useViewport 钩子:用于接收节点、边和视口的钩子
- 边缘标记:更多选项来配置边缘的起始和结束标记
重大变化
🌐 Breaking Changes
摘要:
🌐 TLDR:
- 将
elements数组拆分为nodes和edges数组,并实现onNodesChange和onEdgesChange处理器(详细指南见 核心概念部分) - 缓存你的自定义
nodeTypes和edgeTypes - 将
onLoad重命名为onInit - 将
paneMoveable重命名为panOnDrag - 将
useZoomPanHelper重命名为useReactFlow(并将setTransform重命名为setViewport) - 将节点和边选项
isHidden重命名为hidden
重大变化的详细说明:
🌐 Detailed explanation of breaking changes:
1. 元素 - 节点和边
🌐 1. Elements - Nodes and Edges
我们看到很多人在使用半受控的 elements 属性时遇到了困难。将本地用户状态与 React Flow 的内部状态同步总是有些混乱。你们中的一些人使用了从未被记录过的一种内部存储,这总是一种有点儿不规范的解决方案。对于新版本,我们提供了两种使用 React Flow 的方式——非受控和受控。
🌐 We saw that a lot of people struggle with the semi controlled elements prop. It was always a bit messy to sync the local user state with the internal state of React Flow. Some of you used the internal store that was never documented and always a kind of hacky solution. For the new version we offer two ways to use React Flow - uncontrolled and controlled.
1.1. 控制的 nodes 和 edges
🌐 1.1. Controlled nodes and edges
如果你想完全控制并使用来自本地状态或你的存储的节点和边,你可以结合使用 nodes、edges 属性以及 onNodesChange 和 onEdgesChange 处理函数。你需要为交互式流程实现这些处理函数(如果你只需要平移和缩放,则不需要它们)。当节点被初始化、拖动、选中或移除时,你会收到一个变化通知。这意味着你始终知道节点的确切位置和尺寸,或者例如它是否被选中。我们导出了辅助函数 applyNodeChanges 和 applyEdgeChanges,你应该使用它们来应用这些变化。
🌐 If you want to have the full control and use nodes and edges from your local state or your store, you can use the nodes, edges props in combination with the onNodesChange and onEdgesChange handlers. You need to implement these handlers for an interactive flow (if you are fine with just pan and zoom you don’t need them). You’ll receive a change when a node(s) gets initialized, dragged, selected or removed. This means that you always know the exact position and dimensions of a node or if it’s selected for example. We export the helper functions applyNodeChanges and applyEdgeChanges that you should use to apply the changes.
旧 API
🌐 Old 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
🌐 New 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;你还可以使用新的钩子 useNodesState 和 useEdgesState 快速入门:
🌐 You can also use the new hooks useNodesState and useEdgesState for a quick start:
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);相关更改:
🌐 related changes:
onElementsClick->onNodeClick和onEdgeClickonElementsRemove-> 被onNodesChange和onEdgesChange处理程序替换
1.2 失控的 defaultNodes 和 defaultEdges
🌐 1.2 Uncontrolled defaultNodes and defaultEdges
入门最简单的方法是使用 defaultNodes 和 defaultEdges 属性。当你设置这些属性时,所有操作都会在内部处理。你不需要添加其他处理程序,就可以获得一个完全交互的流程,能够拖动节点、连接节点以及删除节点和边:
🌐 The easiest way to get started is to use the defaultNodes and defaultEdges props. When you set these props, all actions are handled internally. You don’t need to add any other handlers to get a fully interactive flow with the ability to drag nodes, connect nodes and remove nodes and edges:
新 API
🌐 New 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 处理函数获取,该函数将实例作为参数传入。
🌐 If you want to add, remove or update a node or edge you can only do this by using the ReactFlow instance that you can receive either with the new useReactFlow hook or by using the onInit handler that gets the instance as a function param.
2. 对自定义的 nodeTypes 和 edgeTypes 进行记忆化
🌐 2. Memoize your custom nodeTypes and edgeTypes
每当你传入新的节点或边类型时,我们会在后台创建封装的节点或边组件类型。这意味着你不应该在每次渲染时创建新的 nodeType 或 edgeType 对象。如果节点类型和边类型不变,请对它们进行记忆化或在组件外部定义它们。
🌐 Whenever you pass new node or edge types, we create wrapped node or edge component types in the background. This means that you should not create a new nodeType or edgeType object on every render. Memoize your nodeTypes and edgeTypes or define them outside of the component when they don’t change.
不要这样做:
这会在每次渲染时创建一个新对象,并导致错误和性能问题:
🌐 This creates a new object on every render and leads to bugs and performance issues:
// 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} />;
}或者在组件外部创建类型(当它们不变时):
🌐 or create the types outside of the component when they don’t change:
const nodeTypes = { specialType: SpecialNode };
function Flow() {
return <ReactFlow nodes={[]} nodeTypes={nodeTypes} />;
}3. Redux - Zustand
🌐 3. Redux - Zustand
我们将状态管理库从 Redux 切换到了 Zustand 。通过这个更改,我们可以从状态相关的代码中删除大约 300 行代码。如果你需要访问内部存储,你可以使用 useStore 钩子:
🌐 We switched our state management library from Redux to Zustand . With this change we could remove about 300LOC from our state related code. If you need to access the internal store, you can use the useStore hook:
旧 API
🌐 Old API
import { useStoreState, useStoreActions } from 'react-flow-renderer';
...
const transform = useStoreState((store) => store.transform);新 API
🌐 New API
import { useStore } from 'react-flow-renderer';
...
const transform = useStore((store) => store.transform);如果你想访问内部存储,你仍然需要用 <ReactFlowProvider /> 封装你的组件。
🌐 You still need to wrap your component with the <ReactFlowProvider /> if you want to access the internal store.
如果你需要在事件处理程序中获取 store,例如,在不触发重新渲染的情况下,我们也在导出 useStoreApi。
🌐 We are also exporting useStoreApi if you need to get the store in an event handler for example without triggering re-renders.
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,并且现在在节点初始化时触发。
🌐 The onLoad callback has been renamed to onInit and now fires when the nodes are initialized.
旧 API
🌐 Old API
const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.zoomTo(2);
...
<ReactFlow
...
onLoad={onLoad}
/>新 API
🌐 New API
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.zoomTo(2);
...
<ReactFlow
...
onInit={onInit}
/>5. paneMoveable - 拖动平移
🌐 5. paneMoveable - panOnDrag
这与 API 的其余部分(panOnScroll、zoomOnScroll 等)更加一致
🌐 This is more consistent with the rest of the API (panOnScroll, zoomOnScroll, etc.)
旧 API
🌐 Old API
<ReactFlow
...
paneMoveable={false}
/>新 API
🌐 New API
<ReactFlow
...
panOnDrag={false}
/>6. useZoomPanHelper transform - 在 useReactFlow 中统一
🌐 6. useZoomPanHelper transform - unified in useReactFlow
由于“transform”也是存储中 transform 的变量名,并且不清楚 transform 是一个 setter,我们将其重命名为 setViewport。这也与其他函数更一致。此外,所有 useZoomPanHelper 函数已被移到你从 useReactFlow hook 或 onInit 处理程序获取的 React Flow 实例 中。
🌐 As “transform” is also the variable name of the transform in the store and it’s not clear that transform is a setter we renamed it to setViewport. This is also more consistent with the other functions. Also, all useZoomPanHelper functions have been moved to the React Flow instance that you get from the useReactFlow hook or the onInit handler.
旧 API
🌐 Old API
const { transform, setCenter, setZoom } = useZoomPanHelper();
...
transform({ x: 100, y: 100, zoom: 2 });新 API
🌐 New API
const { setViewport, setCenter, setZoom } = useReactFlow();
...
setViewport({ x: 100, y: 100, zoom: 2 });新视口功能:
🌐 New viewport functions:
getZoomgetViewport
7. isHidden - 隐藏的
🌐 7. isHidden - hidden
我们混合使用了带前缀(is...)和不带前缀的布尔选项名称。所有节点和边选项现在都不再带前缀。所以是 hidden、animated、selected、draggable、selectable 和 connectable。
🌐 We mixed prefixed (is...) and non-prefixed boolean option names. All node and edge options are not prefixed anymore. So it’s hidden, animated, selected, draggable, selectable and connectable.
旧 API
🌐 Old API
const hiddenNode = { id: '1', isHidden: true, position: { x: 50, y: 50 } };新 API
🌐 New API
const hiddenNode = { id: '1', hidden: true, position: { x: 50, y: 50 } };8. arrowHeadType markerEndId - 标记开始 / 标记结束
🌐 8. arrowHeadType markerEndId - markerStart / markerEnd
我们改进了用于自定义边缘标记的 API。使用新的 API,你可以在边缘的起点和终点设置单独的标记,并可以使用颜色、strokeWidth 等进行自定义。你仍然可以设置 markerEndId,但不再使用不同的属性,markerStart 和 markerEnd 属性可以接受字符串(表示你需要自己定义的 SVG 标记的 ID)或配置对象,用于使用内置的 arrowClosed 或 arrow 标记。
🌐 We improved the API for customizing the markers for an edge. With the new API you are able to set individual markers at the start and the end of an edge as well as customizing them with colors, strokeWidth etc. You still have the ability to set a markerEndId but instead of using different properties, the markerStart and markerEnd property accepts either a string (id for the svg marker that you need to define yourself) or a configuration object for using the built in arrowClosed or arrow markers.
旧 API
🌐 Old API
const markerEdge = { source: '1', target: '2', arrowHeadType: 'arrow' };新 API
🌐 New API
const markerEdge = {
source: '1',
target: '2',
markerStart: 'myCustomSvgMarker',
markerEnd: { type: 'arrow', color: '#f00' },
};9. 箭头类型 - 标记类型
🌐 9. ArrowHeadType - MarkerType
这只是为了使标记 API 更加一致而进行的措辞更改。由于我们现在可以为边的起点设置标记,类型名称 ArrowHeadType 已被重命名为 MarkerType。未来,这不仅可以包含箭头形状,还可以包含圆形、菱形等其他形状。
🌐 This is just a wording change for making the marker API more consistent. As we are now able to set markers for the start of the edge, the name type ArrowHeadType has been renamed to MarkerType. In the future, this can also not only contain arrow shapes but others like circles, diamonds etc.
10. 归因
🌐 10. Attribution
这实际上并不是对 API 的重大更改,而只是 React Flow 外观上的一个小变化。我们在右下角添加了一个微小的“React Flow”署名(位置可以通过 attributionPosition 属性配置)。此更改随新的“React Flow Pro”订阅模式一起推出。如果你希望在商业应用中移除署名,请订阅“React Flow Pro”。
🌐 This is not really a breaking change to the API but a little change in the general appearance of React Flow. We added a tiny “React Flow” attribution to the bottom right (the position is configurable via the attributionPosition prop). This change comes with the new “React Flow Pro” subscription model. If you want to remove the attribution in a commercial application, please subscribe to “React Flow Pro”.