iT邦幫忙

0

# 在 Angular 中調用 React 庫

  • 分享至 

  • xImage
  •  
  • 在Angular中可以調用React庫,但是,需要手動插入,所有寫法全部是以React為主,Angular中只負責傳入數據,這樣寫的很麻煩也很不優雅
  • 這樣不妨使用 ngx-bridge, 實現與 react 一樣的節點結構,在ng組件中使用react組件或在react組件中調用ng組件

ngx-bridge 簡介

  • 在Angular開發中,為了方便開發者調用任意React庫而設計
  • 為了使任意React庫都可以像ng組件一樣調用,減少開發心智
  • 在Angular開發中使開發使用庫時多一種選擇

演示地址

npm包

目前支持調用

  • ng組件中直接調用react組件
<react-outlet [component]="xxxx" [root]="true" #root></react-outlet>
  • react組件中直接調用ng組件
<NgOutlet component={OutletRefTestComponent}></NgOutlet>
  • react組件中的函數組件/節點可以使用ng組件
componentWrapper(xxxx, {}).reactFunctionComponent
componentWrapper(xxxx, {}).reactElement
  • ng調用react組件時,children可以為react組件,也可以是ng組件(投影)
<!-- 直接子組件,也就是react-outlet的子級 -->
<react-outlet [component]="xxxx" #child></react-outlet>
<!-- 非直接子組件,父級不是react-outlet,但是父級的?級有react-outlet -->
<react-outlet [component]="xxxx" [parent]="root"></react-outlet>

<xxx [parent]="root"></xxx>

舉例

import { stratify, tree } from 'd3-hierarchy';
import React, { useCallback, useMemo } from 'react';
import ReactFlow, {
  ReactFlowProvider,
  Panel,
  useNodesState,
  useEdgesState,
  useReactFlow,
} from 'reactflow';

import { initialNodes, initialEdges } from './nodes-edges.js';
import 'reactflow/dist/style.css';

const g = tree();

const getLayoutedElements = (nodes, edges, options) => {
  if (nodes.length === 0) return { nodes, edges };

  const { width, height } = document
    .querySelector(`[data-id="${nodes[0].id}"]`)
    .getBoundingClientRect();
  const hierarchy = stratify()
    .id((node) => node.id)
    .parentId((node) => edges.find((edge) => edge.target === node.id)?.source);
  const root = hierarchy(nodes);
  const layout = g.nodeSize([width * 2, height * 2])(root);

  return {
    nodes: layout
      .descendants()
      .map((node) => ({ ...node.data, position: { x: node.x, y: node.y } })),
    edges,
  };
};

const LayoutFlow = () => {
  const { fitView } = useReactFlow();
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onLayout = useCallback(
    (direction) => {
      const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(nodes, edges, {
        direction,
      });

      setNodes([...layoutedNodes]);
      setEdges([...layoutedEdges]);

      window.requestAnimationFrame(() => {
        fitView();
      });
    },
    [nodes, edges]
  );

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      fitView
    >
      <Panel position="top-right">
        <button onClick={onLayout}>layout</button>
      </Panel>
    </ReactFlow>
  );
};

export default function () {
  return (
    <ReactFlowProvider>
      <LayoutFlow />
    </ReactFlowProvider>
  );
}
  • ng下
<react-outlet [component]="ReactFlowProvider" [root]="true">
  <react-outlet [component]="ReactFlow" #child #root [runInReact]="context">
    <react-outlet
      [component]="Panel"
      #child
      [props]="$any({ position: 'top-right' })"
    >
      <button #child (click)="root.output()?.['onLayout']('TB')">
        vertical layout
      </button>
      <button #child (click)="root.output()?.['onLayout']('LR')">
        horizontal layout
      </button>
    </react-outlet>
  </react-outlet>
</react-outlet>

import { Component } from '@angular/core';
import { ReactOutlet } from '@cyia/ngx-bridge';
import ReactFlow, {
  ReactFlowProvider,
  Panel,
  useReactFlow,
  useNodesState,
  useEdgesState,
} from 'reactflow';
import Dagre from '@dagrejs/dagre';
import { initialNodes, initialEdges } from './nodes-edges';
import { useCallback } from 'react';

const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));

const getLayoutedElements = (nodes: any, edges: any, options: any) => {
  g.setGraph({ rankdir: options.direction });

  edges.forEach((edge: any) => g.setEdge(edge.source, edge.target));
  nodes.forEach((node: any) => g.setNode(node.id, node));

  Dagre.layout(g);

  return {
    nodes: nodes.map((node: any) => {
      const { x, y } = g.node(node.id);

      return { ...node, position: { x, y } };
    }),
    edges,
  };
};
@Component({
  selector: 'app-custom-layout',
  standalone: true,
  imports: [ReactOutlet],
  templateUrl: './custom-layout.component.html',
  styleUrl: './custom-layout.component.scss',
  host: { style: 'display:block;height:400px' },
})
export class CustomLayoutComponent {
  ReactFlowProvider = ReactFlowProvider;
  ReactFlow = ReactFlow;
  Panel = Panel;
  context = (props: any, output: any) => {
    const { fitView } = useReactFlow();
    const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
    const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
    const onLayout = useCallback(
      (direction: any) => {
        const layouted = getLayoutedElements(nodes, edges, { direction });
        setNodes([...layouted.nodes]);
        setEdges([...layouted.edges]);
        window.requestAnimationFrame(() => {
          fitView();
        });
      },
      [nodes, edges]
    );
    return {
      props: {
        nodes,
        edges,
        onNodesChange,
        onEdgesChange,
        fitView: true,
      },
      output: {
        onLayout,
      },
    };
  };
}

  • 我們可以看到節點部分,完全分離到html模板中,並且結構完全與例子相同.而函數組件的邏輯部分,直接復製傳入runInReact屬性中就可以了,我們只需要在返回對象的output中寫出導出相關方法,供給ng環境下調用

已測試庫

  • ngx-bridge 在開發時還在以下庫中進行了測試,均可正常執行

理論上支持所有react庫,但是無法一一測試,只是挑選了一些 star 比較高的庫進行測試

  • reactflow
  • slate
  • @tiptap
  • react-hot-toast
  • @react-pdf
  • antd
  • react-icons
  • react-spinners
  • react-dnd
  • react-hook-form

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言