Heim > Web-Frontend > js-Tutorial > Hauptteil

Eine ausführliche Analyse von 10 Möglichkeiten zur Kommunikation zwischen React-Komponenten

青灯夜游
Freigeben: 2021-02-25 10:11:32
nach vorne
4078 Leute haben es durchsucht

Eine ausführliche Analyse von 10 Möglichkeiten zur Kommunikation zwischen React-Komponenten

【Verwandte Tutorial-Empfehlung: React-Video-Tutorial

Ich wurde in den letzten zwei Tagen vorübergehend in ein anderes Projektteam versetzt, um an einem kleinen Projekt zu iterieren. Das Frontend dieses Projekts verwendet React. Da es sich nur um ein kleines Projekt handelt, werden keine Statusverwaltungsbibliotheken wie Redux verwendet. Ich bin gerade auf ein kleines Problem gestoßen: Wie kommuniziert man zwischen zwei unabhängigen Komponenten? Ich finde diese Frage sehr interessant, deshalb habe ich meinen Denkprozess aufgeschrieben, damit alle gemeinsam darüber diskutieren können.

Obwohl der Schwerpunkt auf der Kommunikation zwischen zwei unabhängigen Komponenten liegt, werde ich mit der häufigsten Kommunikation zwischen Eltern- und Kinderkomponenten beginnen, damit jeder etwas Neues lernen kann. Listen Sie zuerst die vollständige Zusammenfassung auf und gehen Sie dann auf Details ein.

Zusammenfassung der Kommunikationsmethoden zwischen Komponenten

  • Übergeordnete Komponente:

    1. Props
    2. Instanzmethoden
  • Untergeordnete Komponente:

    1. Rückruffunktionen
    2. Ereignissprudeln
  • Zwischen Geschwisterkomponenten:

    1. Übergeordnete Komponente
  • Zwischen weniger verwandten Komponenten:

    1. Kontext
    2. Portale
    3. Global. Variablen
    4. O Bserver-Muster
    5. Redux usw.

1. Requisiten

Dies ist die gebräuchlichste Methode zum Übertragen von Informationen zwischen Reaktionskomponenten. Die übergeordnete Komponente übergibt Daten über Requisiten an die untergeordnete Komponente, und die untergeordnete Komponente verwendet die entsprechenden this.props-Datenthis.props去使用相应的数据

const Child = ({ name }) => {
    <div>{name}</div>
}

class Parent extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: &#39;zach&#39;
        }
    }
    render() {
        return (
            <Child name={this.state.name} />
        )
    }
}
Nach dem Login kopieren

2. Instance Methods

第二种父组件向子组件传递信息的方式有些同学可能会比较陌生,但这种方式非常有用,请务必掌握。原理就是:父组件可以通过使用refs来直接调用子组件实例的方法,看下面的例子:

class Child extends React.Component {
  myFunc() {
    return "hello"
  }
}

class Parent extends React.Component {
  componentDidMount() {
    var x = this.foo.myFunc()   // x is now &#39;hello&#39;
  }
  render() {
    return (
      <Child
        ref={foo => {
          this.foo = foo
        }}
      />
    )
  }
}
Nach dem Login kopieren

大致的过程:

  1. 首先子组件有一个方法myFunc
  2. 父组件给子组件传递一个ref属性,并且采用callback-refs的形式。这个callback函数接收react组件实例/原生dom元素作为它的参数。当父组件挂载时,react会去执行这个ref回调函数,并将子组件实例作为参数传给回调函数,然后我们把子组件实例赋值给this.foo
  3. 最后我们在父组件当中就可以使用this.foo
    class Modal extends React.Component {
      show = () => {// do something to show the modal}
      hide = () => {// do something to hide the modal}
      render() {
        return <div>I&#39;m a modal</div>
      }
    }
    
    class Parent extends React.Component {
      componentDidMount() {
        if(// some condition) {
            this.modal.show()
        }
      }
      render() {
        return (
          <Modal
            ref={el => {
              this.modal = el
            }}
          />
        )
      }
    }
    Nach dem Login kopieren

    2. Instanzmethoden

Die zweite Möglichkeit für übergeordnete Komponenten, Informationen an untergeordnete Komponenten weiterzugeben, mag einigen Schülern unbekannt sein, aber diese Methode ist sehr nützlich, also stellen Sie sicher, dass Sie sie beherrschen. Das Prinzip ist: Die übergeordnete Komponente kann die Methode der untergeordneten Komponenteninstanz mithilfe von refs direkt aufrufen. Siehe das folgende Beispiel:

const Child = ({ onClick }) => {
    <div onClick={() => onClick(&#39;zach&#39;)}>Click Me</div>
}

class Parent extends React.Component {
    handleClick = (data) => {
        console.log("Parent received value from child: " + data)
    }
    render() {
        return (
            <Child onClick={this.handleClick} />
        )
    }
}
Nach dem Login kopieren

Der allgemeine Prozess:

Zuerst hat die untergeordnete Komponente eine Methode myFunc

parent Die Komponente übergibt ein ref-Attribut an die untergeordnete Komponente und verwendet callback-refs

Formular. Diese Rückruffunktion erhält als Parameter eine Reaktionskomponenteninstanz/ein natives DOM-Element. Wenn die übergeordnete Komponente gemountet ist, führt React die Ref-Rückruffunktion aus und übergibt die Instanz der untergeordneten Komponente als Parameter an die Rückruffunktion. Anschließend weisen wir die Instanz der untergeordneten Komponente this.foo zu.

Schließlich können wir this.foo in der übergeordneten Komponente verwenden, um die Methode der Unterkomponente aufzurufen.

Nachdem wir das Prinzip dieser Methode verstanden haben, müssen wir uns die Frage stellen, warum wir sie verwenden sollten it Welche Einsatzszenarien gibt es für diese Methode? Das häufigste Verwendungsszenario: Die Unterkomponente ist beispielsweise eine modale Popup-Komponente. In der Unterkomponente gibt es verschiedene Methoden zum Ein-/Ausblenden des modalen Popup-Fensters. Diese Methoden können wir verwenden, um die Unterkomponente direkt aufzurufen der Instanz werden verwendet, um das Anzeigen/Ausblenden von untergeordneten Komponenten zu steuern. Diese Methode ist viel schöner als die Übergabe von Requisiten, die das Anzeigen/Ausblenden des Modals an die untergeordnete Komponente steuern.

class Parent extends React.Component {
  render() {
    return (
      <div onClick={this.handleClick}>
         <Child />
      </div>
    );
  }
  handleClick = () => {
    console.log(&#39;clicked&#39;)
  }
}
function Child {
  return (
    <button>Click</button>
  );    
}
Nach dem Login kopieren

3. Rückruffunktionen

Nachdem wir über die beiden Möglichkeiten gesprochen haben, wie übergeordnete Komponenten Informationen an untergeordnete Komponenten übertragen können, sprechen wir über die Methoden, mit denen untergeordnete Komponenten Informationen an übergeordnete Komponenten übertragen können. Die Rückruffunktionsmethode ist auch die häufigste Reaktionsmethode. Die untergeordnete Komponente übergibt die Daten an die übergeordnete Komponente, indem sie die von der übergeordneten Komponente übergebene Rückruffunktion aufruft.

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {count: 0}
  }
  setCount = () => {
    this.setState({count: this.state.count + 1})
  }
  render() {
    return (
      <div>
        <SiblingA
          count={this.state.count}
        />
        <SiblingB
          onClick={this.setCount}
        />
      </div>
    );
  }

}
Nach dem Login kopieren

4. Event-Bubbling

Diese Methode hat eigentlich nichts mit der Reaktion selbst zu tun. Wir verwenden den Event-Bubbling-Mechanismus des nativen DOM-Elements.

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}
Nach dem Login kopieren

Durch den geschickten Einsatz des Ereignis-Bubbling-Mechanismus können wir ganz einfach Klickereignisse von untergeordneten Komponentenelementen auf dem übergeordneten Komponentenelement empfangen

5. Nachdem wir über die Kommunikation zwischen übergeordneten und untergeordneten Komponenten gesprochen haben, schauen wir uns das an die Kommunikationsmethoden zwischen nicht-übergeordneten und untergeordneten Komponenten. Wenn zwei Nicht-Eltern-Kind-Komponenten kommunizieren möchten, können wir im Allgemeinen zunächst prüfen, ob es sich um Geschwisterkomponenten handelt, das heißt, ob sie sich unter derselben übergeordneten Komponente befinden. Wenn nicht, überlegen Sie, ob es angemessen wäre, sie in eine Komponente einzuschließen, damit sie zu Geschwistern werden. Auf diese Weise können sie die Datenkommunikation über die übergeordnete Komponente als mittlere Schicht erreichen.

const ThemeContext = React.createContext(&#39;light&#39;);

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
Nach dem Login kopieren
🎜6. Kontext🎜🎜Normalerweise verfügt eine Front-End-Anwendung über einige „globale“ Daten, wie z. B. die aktuell angemeldeten Benutzerinformationen, das UI-Thema, die vom Benutzer ausgewählte Sprache usw. Wenn die Komponentenebene sehr tief ist, müssen wir sie Schicht für Schicht über Requisiten weitergeben. Siehe das Beispiel unten: 🎜
    class ThemedButton extends React.Component {
      static contextType = ThemeContext;
      render() {
        const value = this.context
        return <Button theme={value} />;
      }
    }
Nach dem Login kopieren
Nach dem Login kopieren
🎜oben Um beispielsweise die Designfarbe für unser Button-Element zu erhalten, müssen wir das Design als Requisiten von App an Toolbar und dann von Toolbar an ThemedButton übergeben. Schließlich erhält Button schließlich das Design von den Requisiten der übergeordneten Komponente ThemedButton. Wenn wir Button in verschiedenen Komponenten verwenden, müssen wir das Thema wie in diesem Beispiel überall übergeben, was äußerst mühsam ist. 🎜🎜React stellt uns also eine neue API zur Verfügung: Context Wir verwenden Context, um das obige Beispiel neu zu schreiben🎜
    function ThemedButton {
        return (
            <ThemeContext.Consumer>
                {value => <Button theme={value} />}
            </ThemeContext.Consumer>
        )
    }
Nach dem Login kopieren
Nach dem Login kopieren
🎜Eine einfache Analyse:🎜
  1. React.createContext创建了一个Context对象,假如某个组件订阅了这个对象,当react去渲染这个组件时,会从离这个组件最近的一个Provider组件中读取当前的context值
  2. Context.Provider: 每一个Context对象都有一个Provider属性,这个属性是一个react组件。在Provider组件以内的所有组件都可以通过它订阅context值的变动。具体来说,Provider组件有一个叫value的prop传递给所有内部组件,每当value的值发生变化时,Provider内部的组件都会根据新value值重新渲染
  3. 那内部的组件该怎么使用这个context对象里的东西呢?
    a. 假如内部组件是用class声明的有状态组件:我们可以把Context对象赋值给这个类的属性contextType,如上面所示的ThemedButton组件

        class ThemedButton extends React.Component {
          static contextType = ThemeContext;
          render() {
            const value = this.context
            return <Button theme={value} />;
          }
        }
    Nach dem Login kopieren
    Nach dem Login kopieren

    b. 假如内部组件是用function创建的无状态组件:我们可以使用Context.Consumer,这也是Context对象直接提供给我们的组件,这个组件接受一个函数作为自己的child,这个函数的入参就是context的value,并返回一个react组件。可以将上面的ThemedButton改写下:

        function ThemedButton {
            return (
                <ThemeContext.Consumer>
                    {value => <Button theme={value} />}
                </ThemeContext.Consumer>
            )
        }
    Nach dem Login kopieren
    Nach dem Login kopieren

最后提一句,context对于解决react组件层级很深的props传递很有效,但也不应该被滥用。只有像theme、language等这种全局属性(很多组件都有可能依赖它们)时,才考虑用context。如果只是单纯为了解决层级很深的props传递,可以直接用component composition

7. Portals

Portals也是react提供的新特性,虽然它并不是用来解决组件通信问题的,但因为它也涉及到了组件通信的问题,所以我也把它列在我们的十种方法里面。

Portals的主要应用场景是:当两个组件在react项目中是父子组件的关系,但在HTML DOM里并不想是父子元素的关系。

举个例子,有一个父组件Parent,它里面包含了一个子组件Tooltip,虽然在react层级上它们是父子关系,但我们希望子组件Tooltip渲染的元素在DOM中直接挂载在body节点里,而不是挂载在父组件的元素里。这样就可以避免父组件的一些样式(如overflow:hiddenz-indexposition等)导致子组件无法渲染成我们想要的样式。

如下图所示,父组件是这个红色框的范围,并且设置了overflow:hidden,这时候我们的Tooltip元素超出了红色框的范围就被截断了。

Eine ausführliche Analyse von 10 Möglichkeiten zur Kommunikation zwischen React-Komponenten

怎么用portals解决呢?

首先,修改html文件,给portals增加一个节点

<html>
    <body>
        <div id="react-root"></div>
        <div id="portal-root"></div>
    </body>
</html>
Nach dem Login kopieren

然后我们创建一个可复用的portal容器,这里使用了react hooks的语法,看不懂的先过去看下我另外一篇讲解react hooks的文章:30分钟精通React今年最劲爆的新特性——React Hooks

import { useEffect } from "react";
import { createPortal } from "react-dom";

const Portal = ({children}) => {
  const mount = document.getElementById("portal-root");
  const el = document.createElement("div");

  useEffect(() => {
    mount.appendChild(el);
    return () => mount.removeChild(el);
  }, [el, mount]);

  return createPortal(children, el)
};

export default Portal;
Nach dem Login kopieren

最后在父组件中使用我们的portal容器组件,并将Tooltip作为children传给portal容器组件

const Parent = () => {
  const [coords, setCoords] = useState({});

  return <div style={{overflow: "hidden"}}>
      <Button>
        Hover me
      </Button>
      <Portal>
        <Tooltip coords={coords}>
          Awesome content that is never cut off by its parent container!
         </Tooltip>
      </Portal>
  </div>
}
Nach dem Login kopieren

这样就ok啦,虽然父组件仍然是overflow: hidden,但我们的Tooltip再也不会被截断了,因为它直接超脱了,它渲染到body节点下的<div id="portal-root"></div>里去了。

总结下适用的场景: Tooltip、Modal、Popup、Dropdown等等

8. Global Variables

哈哈,这也不失为一个可行的办法啊。当然你最好别用这种方法。

class ComponentA extends React.Component {
    handleClick = () => window.a = &#39;test&#39;
    ...
}
class ComponentB extends React.Component {
    render() {
        return <div>{window.a}</div>
    }
}
Nach dem Login kopieren

9. Observer Pattern

观察者模式是软件设计模式里很常见的一种,它提供了一个订阅模型,假如一个对象订阅了某个事件,当那个事件发生的时候,这个对象将收到通知。

这种模式对于我们前端开发者来说是最不陌生的了,因为我们经常会给某些元素添加绑定事件,会写很多的event handlers,比如给某个元素添加一个点击的响应事件elm.addEventListener(&#39;click&#39;, handleClickEvent),每当elm元素被点击时,这个点击事件会通知elm元素,然后我们的回调函数handleClickEvent会被执行。这个过程其实就是一个观察者模式的实现过程。

那这种模式跟我们讨论的react组件通信有什么关系呢?当我们有两个完全不相关的组件想要通信时,就可以利用这种模式,其中一个组件负责订阅某个消息,而另一个元素则负责发送这个消息。javascript提供了现成的api来发送自定义事件: CustomEvent,我们可以直接利用起来。

首先,在ComponentA中,我们负责接受这个自定义事件:

class ComponentA extends React.Component {
    componentDidMount() {
        document.addEventListener(&#39;myEvent&#39;, this.handleEvent)
    }
    componentWillUnmount() {
        document.removeEventListener(&#39;myEvent&#39;, this.handleEvent)
    }
    
    handleEvent = (e) => {
        console.log(e.detail.log)  //i&#39;m zach
    }
}
Nach dem Login kopieren

然后,ComponentB中,负责在合适的时候发送该自定义事件:

class ComponentB extends React.Component {
    sendEvent = () => {
        document.dispatchEvent(new CustomEvent(&#39;myEvent&#39;, {
          detail: {
             log: "i&#39;m zach"
          }
        }))
    }
    
    render() {
        return <button onClick={this.sendEvent}>Send</button>
    }
}
Nach dem Login kopieren

这样我们就用观察者模式实现了两个不相关组件之间的通信。当然现在的实现有个小问题,我们的事件都绑定在了document上,这样实现起来方便,但很容易导致一些冲突的出现,所以我们可以小小的改良下,独立一个小模块EventBus专门这件事:

class EventBus {
    constructor() {
        this.bus = document.createElement(&#39;fakeelement&#39;);
    }

    addEventListener(event, callback) {
        this.bus.addEventListener(event, callback);
    }

    removeEventListener(event, callback) {
        this.bus.removeEventListener(event, callback);
    }

    dispatchEvent(event, detail = {}){
        this.bus.dispatchEvent(new CustomEvent(event, { detail }));
    }
}

export default new EventBus
Nach dem Login kopieren

然后我们就可以愉快的使用它了,这样就避免了把所有事件都绑定在document上的问题:

import EventBus from &#39;./EventBus&#39;
class ComponentA extends React.Component {
    componentDidMount() {
        EventBus.addEventListener(&#39;myEvent&#39;, this.handleEvent)
    }
    componentWillUnmount() {
        EventBus.removeEventListener(&#39;myEvent&#39;, this.handleEvent)
    }
    
    handleEvent = (e) => {
        console.log(e.detail.log)  //i&#39;m zach
    }
}
class ComponentB extends React.Component {
    sendEvent = () => {
        EventBus.dispatchEvent(&#39;myEvent&#39;, {log: "i&#39;m zach"}))
    }
    
    render() {
        return <button onClick={this.sendEvent}>Send</button>
    }
}
Nach dem Login kopieren

最后我们也可以不依赖浏览器提供的api,手动实现一个观察者模式,或者叫pub/sub,或者就叫EventBus。

function EventBus() {
  const subscriptions = {};
  this.subscribe = (eventType, callback) => {
    const id = Symbol(&#39;id&#39;);
    if (!subscriptions[eventType]) subscriptions[eventType] = {};
    subscriptions[eventType][id] = callback;
    return {
      unsubscribe: function unsubscribe() {
        delete subscriptions[eventType][id];
        if (Object.getOwnPropertySymbols(subscriptions[eventType]).length === 0) {
          delete subscriptions[eventType];
        }
      },
    };
  };

  this.publish = (eventType, arg) => {
    if (!subscriptions[eventType]) return;

    Object.getOwnPropertySymbols(subscriptions[eventType])
      .forEach(key => subscriptions[eventType][key](arg));
  };
}
export default EventBus;
Nach dem Login kopieren

10. Redux等

最后终于来到了大家喜闻乐见的Redux等状态管理库,当大家的项目比较大,前面讲的9种方法已经不能很好满足项目需求时,才考虑下使用redux这种状态管理库。这里就先不展开讲解redux了...否则我花这么大力气讲解前面9种方法的意义是什么???

总结

十种方法,每种方法都有对应的适合它的场景,大家在设计自己的组件前,一定要好好考虑清楚采用哪种方式来解决通信问题。

文初提到的那个小问题,最后我采用方案9,因为既然是小迭代项目,又是改别人的代码,当然最好避免对别人的代码进行太大幅度的改造。而pub/sub这种方式就挺小巧精致的,既不需要对别人的代码结构进行大改动,又可以满足产品需求。

更多编程相关知识,请访问:编程视频!!

Das obige ist der detaillierte Inhalt vonEine ausführliche Analyse von 10 Möglichkeiten zur Kommunikation zwischen React-Komponenten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:segmentfault.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage