将 Angular 应用程序与 ReactJS 应用程序连接起来?

IT技术 angular reactjs
2021-03-23 02:50:52

我有一个 Angular 应用程序和我想用 ReactJS 编写的应用程序的某些部分。

如何将 ReactJS 应用程序注入现有的 Angular 应用程序?我还需要组件的双向通信。

2个回答

简介:您可以拥有不同的环境、不同的用例和不同的需求。请记住,此解决方案只有一些方法可以触发您发明不同的东西 - 更适合您的用例。

两种不同的解决方案,例如:

  • 没有通信的 Angular-ReactJS
  • 具有双向通信的 Angular-ReactJS

下面的所有代码都是最小的,可以在所呈现的步骤中显示问题。在 GitHub 上,你有一个完整的代码来解决一个问题,与下面的例子并不总是 1:1 的,因为这段代码是扩展的。

没有通信的 Angular-ReactJS

要将 ReactJS 应用程序添加到现有的 Angular 应用程序中,您需要安装 5 个 npm 依赖项:reactreact-dom

npm install --save react
npm install --save react-dom
npm install --save-dev @types/react
npm install --save-dev @types/react-dom
npm install --save-dev @types/react-select

下一步 - 我们应该允许jsx.tsx文件中使用模板,所以我们应该编辑tsconfig.json,并添加:

{
    ...
   "compilerOptions": {
    …
    "jsx": "react"
}

如果您使用 WebStorm,您应该重新启动您的项目,因为 TSLint 会在重新启动之前显示错误。

为了保持清晰的结构,我创建了这个目录结构:

angular /
  ng-hero.component.ts // Component in Angular
  react-renderer.component.ts // ReactJS renderer without communication
react /
  react-application.tsx // React init application
  react-hero.tsx // React hero component
app.component.html
app.component.ts

现在你需要在 Angular 中创建一个特殊的组件,它将负责嵌入 ReactJS 应用程序。我将调用这个组件ReactRendererComponent这个组件非常简单,它只有一个模板行、一个构造函数import Injector和一行ngOnInit

@Component({
  selector: 'app-react-renderer',
  template: `<div class="react-container" id="react-renderer"></div>`
})
export class ReactRendererComponent implements OnInit {
  constructor(public injector: Injector) { }

  ngOnInit() {
    ReactApplication.initialize('react-renderer', this.injector);
  }
}

现在我们需要ReactApplication初始化 ReactJS 应用程序的组件:

interface IReactApplication {
  injector: Injector;
}

class ReactApp extends React.Component<IReactApplication, any> {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className={'renderer'}>
        <h2>ReactJS component: </h2>
        <br/>
        <ReactHero/>
      </div>
    );
  }
}

export class ReactApplication {

  static initialize(
    containerId: string,
    injector: Injector
  ) {
    ReactDOM.render(
      <ReactApp injector={injector}/>,
      document.getElementById(containerId)
    );
  }
}

我们需要ReactHero在下面的例子中使用的组件:

class ReactHero extends React.Component<any, any> {

  constructor(props) {
    super(props);
  }

  render() {
    return (
      <span>
        <span>react-hero works!</span><br/>
        <span>Don't have any data</span>
      </span>
    );
  }
}
export default ReactHero;

在 Angular App 中我们应该使用ReactRenderer组件,所以我们使用:

App.component data:
<hr>
<h2>This is Angular</h2>
<img width="100" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
<hr>

<!-- Without data binding -->
<app-react-renderer></app-react-renderer>

此时我们有一个带有嵌入式 ReactJS 应用程序的 Angular 应用程序,但没有任何通信。对你来说够了吗?如果是,那就是全部。如果您需要在两个应用程序之间进行任何类型的通信,我会在下面为您提供 RxJS 选项。

具有双向通信的 Angular-ReactJS

在这个例子中,你有 RxJS 支持的双向数据绑定。您可以获取这些数据,并在您的 ReactJS 应用程序和 Angular 应用程序中使用它们来查看所有更改。这对于很多项目来说已经足够了,但是您可以使用不同的选项来获得这种双向通信,例如,您可以为它们使用 Redux。

为了清楚起见,下面我为这部分提供了一个完整的目录结构:

angular /
  hero.service.ts
  ng-hero.component.ts // Component in Angular
  react-bidirectional-renderer.component.ts // ReactJS renderer with bidirectional communication
model /
  hero.ts // interface for Hero object
react-bidirectional
  react-bidirectional-application.tsx // React init application with bidirectional communication
  react-bidirectional-hero.tsx // React hero component with RxJS support
app.component.html
app.component.ts

首先,我们创建IHero带有数据的接口:/model/hero.ts

export interface IHero {
  name: string;
  age: number;
}

在下一步中,我们创建angular/hero.service.ts服务,以在应用程序的 Angular 部分使用它:

@Injectable({
  providedIn: 'root'
})
export class HeroService {
  private heroes$: BehaviorSubject<IHero[]> = new BehaviorSubject([]);

  constructor() {
  }

  addHeroes(hero: IHero) { // To add new hero
    const actualHero = this.heroes$.value;
    actualHero.push(hero);
    this.heroes$.next(actualHero);
  }

  updateHeroAge(heroId: number, age: number) { // To update age of selected hero
    const actualHero = this.heroes$.value;
    actualHero[heroId].age = age;
    this.heroes$.next(actualHero);
  }

  getHeroes$(): BehaviorSubject<IHero[]> { // To get BehaviorSubject and pass it into ReactJS
    return this.heroes$;
  }
}

app.component.ts我们用数据(宙斯和波塞冬)初始化:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  public heroesObj$: BehaviorSubject<IHero[]>;
  public heroes: IHero[];

  constructor(private heroService: HeroService) {}

  ngOnInit(): void {
    this.heroService.getHeroes$().subscribe((res: IHero[]) => {
      this.heroes = res;
    });

    this.heroesObj$ = this.heroService.getHeroes$();

    this.initHeroes();
  }

  initHeroes() {
    this.heroService.addHeroes({name: 'Zeus', age: 88});
    this.heroService.addHeroes({name: 'Poseidon', age: 46});
  }
}

在下一步中,我们应该准备应用程序的 ReacJS 部分,因此我们创建react-bidirectional/react-bidirectional-application.tsx文件:

interface IReactBidirectionalApp {
  injector: Injector;
  heroes$: BehaviorSubject<IHero[]>; // We use this interface to grab RxJS object
}

class ReactBidirectionalApp extends React.Component<IReactBidirectionalApp, any> {
  constructor(props) {
    super(props);

    this.state = {
      heroes$: this.props.heroes$ // and we pass this data into ReactBidirectionalHero component
    };
  }

  render() {
    return (
      <div className={'renderer'}>
        <h2>ReactJS component (bidirectional data binding): </h2>
        <ReactBidirectionalHero heroes$={this.state.heroes$}/>
      </div>
    );
  }
}

export class ReactBidirectionalApplication {

  static initialize(
    containerId: string,
    injector: Injector,
    heroes$: BehaviorSubject<IHero[]>, // This is necessary to get RxJS object
  ) {
    ReactDOM.render(
      <ReactBidirectionalApp injector={injector} heroes$={heroes$}/>,
      document.getElementById(containerId)
    );
  }
}

下一步我们需要ReactBidirectionalHero组件,所以我们创建它:

interface IReactBidirectionalHero {
  heroes$: BehaviorSubject<IHero[]>;
}

class ReactBidirectionalHero extends React.Component<IReactBidirectionalHero, any> {
  constructor(props) {
    super(props);

    this.state = {
      heroes: []
    };

    this.addAge = this.addAge.bind(this); // Register function to bump age
    this.addHero  = this.addHero.bind(this); // Register function to add new Hero
  }

  componentDidMount(): void {
    // In componentDidMount we subscribe heroes$ object
    this.props.heroes$.subscribe((res: IHero[]) => {
      // and we pass this data into React State object
      this.setState({heroes: res});
    });
  }

  addAge(i: number) {
    const temp = this.state.heroes;
    temp[i].age = temp[i].age + 1;

    // In this way we update RxJS object
    this.props.heroes$.next( temp);
  }

  addHero() {
    const temp = this.state.heroes;
    temp.push({name: 'Atena', age: 31});

    // In this way we update RxJS object
    this.props.heroes$.next(temp);
  }

  render() {
    // Hire we render RxJS part of application with addAge button and ADD ATENA button below
    const heroes = this.state.heroes.map((hero: IHero, i) => {
      return <span key={i}>{hero.name} - {hero.age} <button onClick={() => this.addAge(i)}>Add {hero.name} age</button><br/></span>;
    });
    return (
      <span>
        <span>react-hero works!</span><br/>
        {heroes}
        <br/>
        <button onClick={this.addHero}>ADD ATENA</button>
      </span>
    );
  }
}

export default ReactBidirectionalHero;

现在我们需要在 Angular 应用程序中初始化 ReactJS 应用程序,所以我们创建angular/react-bidirectional-renderer.component.ts- 非常简单,与没有通信的版本相比只有一个变化:

@Component({
  selector: 'app-react-owc-renderer',
  template: `<div class="react-container" id="react-owc-renderer"></div>`
})
export class ReactBidirectionalRendererComponent implements OnInit {
  // Hire we get data from the parent component, but of course, we can also subscribe this data directly from HeroService if we prefer this way
  @Input() heroes$: BehaviorSubject<IHero[]>;

  constructor(public injector: Injector) { }

  ngOnInit() {
    // We add only one parameter into initialize function
    ReactBidirectionalApplication.initialize('react-owc-renderer', this.injector, this.heroes$);
  }
}

现在我们应该稍微改变一下ng-hero.component.ts以查看所有效果:

@Component({
  selector: 'app-ng-hero',
  template: `
    <div>
      <span>ng-hero works!</span><br/>
      <span *ngFor="let hero of heroes; let i = index;">{{hero.name}} - {{hero.age}} - <button (click)="addAge(i)">Add {{hero.name}} age</button><br/></span>
      <br/>
      <button (click)="addHero()">ADD AFRODITA</button>
    </div>
  `
})
export class NgHeroComponent implements OnInit {
  public heroes: IHero[];

  constructor(private heroService: HeroService) { }

  ngOnInit() {
    this.heroService.getHeroes$().subscribe((res: IHero[]) => {
      this.heroes = res;
    });
  }

  addAge(heroId: number) {
    this.heroService.updateHeroAge(heroId, this.heroes[heroId].age + 1);
  }

  addHero() {
    this.heroService.addHeroes({name: 'Afrodita', age: 23});
  }

}

最后,我们改变app.component.html

App.component data:
<hr>
<h2>This is Angular component: </h2>
<app-ng-hero></app-ng-hero>
<hr>

<!-- With bidirectional data binding-->
<app-react-owc-renderer [heroes$]="heroesObj$"></app-react-owc-renderer>
<hr>

一切都应该有效。如果您有任何问题,请随时提出。

您可以在GitHub 上找到带有此解决方案的完整存储库

如果您要查找演示,请单击租用

一些额外的想法

正如你所看到的,我只介绍了解决这个问题的两种方法。这里只是一些提示,可以为您提供更广阔的视野,并让您有可能为您的用例找到更适合自己的解决方案。

  • Store - 使用 Redux 在 Angular 和 React 应用程序之间共享 Store - 这种方法非常好,因为您不需要关心在组件之间传递数据。这种方法有一些潜在的问题——如果 React 应用程序修改了 store 并且你订阅了这些数据,请记住 NG 更改检测的潜在问题。在某些情况下,在 Angular 应用程序之外修改的数据可能是一个问题,所以请记住这一点。
  • iframe - 这是在 Angular 应用程序中执行任何类型的仅使用 ReactJS 组件而无需双向通信的最简单方法。您可以只在 URL 的查询参数中传递一些数据并呈现 React 组件。
  • Preact.js - 如果您只想使用一个简单的React组件但又担心包的大小,那么这是一个非常好的方法。Preact 提供了许多 React 功能,但它确实很小,因此您的构建和客户不会对您的技术海市蜃楼产生任何影响。
@ABOS 真的很简单——我第一次用 Redux 通信损失了大约 50 小时,但是这个带有 RxJS 的版本很简单,所以我添加到 Stack。但是创建可重用的库是个好主意。当我找到一些时间时,我会尝试。
2021-06-01 02:50:52
@Andrew 如果我这个周末有时间研究它,我会再次给你写信。
2021-06-06 02:50:52
TBH,我没有详细说明:) 我会想象 angular/react 之间的一些消息通信,也许这就是您使用 rxjs 的意思?
2021-06-09 02:50:52
@kris_IV 很棒。刚刚让双向示例工作。真的很大的帮助,非常感谢!
2021-06-12 02:50:52
这似乎有很多工作:) 有机会将核心逻辑提取到可重用的库中,以便轻松重用吗?
2021-06-13 02:50:52

我一直在研究将现有的 Angular 服务集成到一个新的 React 应用程序中的模式,我看到了这篇文章。

对于双向通信模式,您能否解释一下 $injector 的用途 - 因为它已通过但从未使用过。

在评论中添加此问题而不是将其添加为答案
2021-06-06 02:50:52