Nuxt 3 应用中的父子组件通信

huangapple go评论74阅读模式
英文:

Parent to child communication in Nuxt 3 app

问题

我目前有一个使用Nuxt 3、Typescript和Composition API的web应用程序设置。

Nuxt有这些布局文件,我将它们视为我的父级。所以我的layouts/default.vue文件包含一个名为Qdrawer的Quasar侧边菜单。在父级布局文件中,我也有一个<NuxtPage />,我的页面会在这里加载。

其中一个页面包含一个Leaflet地图,我希望可以从父级的Qdrawer与之通信。例如,当我在Qdrawer菜单中点击一个项目时,我希望地图跳转到特定位置。

Qdrawer具有内置事件,可以在您能想到的所有操作上发出。但据我理解,事件用于从子级向父级传递信息,而将信息从父级传递给子级则使用props。

我使用props绑定使事情运行起来了,但我必须将props绑定到<NuxtPage>标签,如<NuxtPage :mydrawer="qdrawer-state" >。我遇到的问题是,在除了/map之外的其他路由上,我会得到控制台日志错误。这是因为props绑定会在所有页面上执行,而不仅仅是在MapPage上。

当我在MapPage.vue内部的一个div上进行props绑定时,我会收到关于渲染顺序的控制台警告。

所以从某种意义上说,我认为使用内置的Qdrawer事件可能比props绑定更有意义。但如果props绑定是正确的做法,我不知道如何将其绑定到只在父级上的Map组件。

对于这一点,<NuxtPage />似乎过于通用了。也许我可以在路由器级别上进行props绑定?

英文:

I have currently a webapp setup with Nuxt 3, Typescript and using Composition API <script setup>

Nuxt has these layout files which I consider as my parent.
So my layouts/default.vue file contains a Quasar side menu called Qdrawer.
In the parent layout file I also have a &lt;NuxtPage /&gt; where my pages are loaded.

One of those pages contains a Leaflet map I want to talk to from the parent Qdrawer.
For example when I click an item in the Qdrawer menu, I want the map to jump to a specific location.

The Qdrawer has build in events that are emitted on all the actions you can think of.
But from my understanding events are used for child to parent communication and you do parent to child with props.

I got thinks working with prop binding, but I had to bind the props to the &lt;NuxtPage \&gt; tag like &lt;NuxtPage :mydrawer=&quot;qdrawer-state&quot; \&gt;.
The problem I faced was that on routes other then /map I got console log errors. This is because the prop binding is done on all pages and not only on MapPage.

When I do prop binding on a div inside my MapPage.vue I get console warnings about rendering order.

So in one way I'm thinking that working with the Qdrawer events that are build in makes more sense then prop bindings. But if prop binding is the right way to do this, I somehow need to bind it inside parent on a Map only component.

&lt;NuxtPage /&gt; seems to be too generic for this.
Maybe I can do prop binding on router level?

答案1

得分: 1

以下是翻译好的部分:

正如您所说,事件会向父组件发出,而props用于向子组件发送数据。但是,在这种情况下,这不是从API加载并要在子组件中显示或操作的数据。

我建议简单地添加一个事件总线,例如Mitt(https://github.com/developit/mitt),然后在您的地图组件中注册侦听器,并从您的菜单中触发它们。但是,如果您的菜单已经发出事件,您可以在地图中使用它们,我建议直接使用它们。

对于简单地传递数据,您不应该养成这个习惯,但在这种情况下似乎是合理的。

此外,您关于不向<NuxtPage />传递props是正确的。我会避免这样做,即使它能正常工作也令人印象深刻:D

希望对您有所帮助。

英文:

As you say events emit to parents and props are used to send data to children. However in this case it's not really data that you load from e.g. an API and want to display or act on in a child component.

I would simply add an event bus like Mitt (https://github.com/developit/mitt) and then register a listener in your map component and trigger them from your menu. However if your menu already emits events that you can use in your map i would simply use that.

For simply passing data you should not make that a habit but in this case that seems reasonable.

Also you're right about not passing props to <NuxtPage />. I would avoid that, impressed that it even works Nuxt 3 应用中的父子组件通信

Hope it helps

答案2

得分: 1

这听起来像是你有两个无关的组件,有时其中一个需要对另一个被点击时做出反应。

可能的解决方案包括:

  1. 一个高级组件监听事件并provide一些地图组件可以inject或绑定并做出反应的内容。这不够优雅,因为高级组件会受到深度嵌套特化的影响。

  2. 一些独立的全局单例可以被发布和订阅。@ChristofferOne建议为此目的使用消息总线。

  3. 也可以通过使用Pinia存储(另一个单例)来实现状态修改,表示地图位置状态的存储以及地图组件监视或绑定的getter。可能地图组件已经在使用某种存储,菜单可以直接访问。存储即使在地图组件不活动时也会保持活动状态,因此始终有地方可以写入新的位置,并且在菜单组件之上的事件监听器可以执行这个操作。

  4. 正如你所提到的,你的菜单事件可以触发导航,包括启动地图组件并将位置绑定到它。根据你的描述,似乎你不希望这种情况一直发生,这有些棘手。

哪种选项最适合你取决于你的依赖关系中已经有什么。因此,我会对一个事件总线(或者同样的情况下的Pinia)仅用于一个组件的目的持谨慎态度。正确的选择还取决于你可能计划的其他交互方式。

如果没有人需要知道地图正在关注什么,事件总线方法可能是正确的选择;如果你希望查询地图的位置或让地图在选择最后的位置时“自动”关注,即使它不可见,存储可能更好。

英文:

It sounds like you have two unrelated components, and sometimes one of them needs to react to the other being clicked on.

Possibilities include:

  1. A higher level component listens for events and provides something the map component can inject or bind and react to. This is inelegant because the high level component is polluted with knowledge of deeply nested specialisations
  2. Some out-of-band global singleton can be pubbed and subbed to. @ChristofferOne suggests a message bus for this purpose.
  3. State modification can also be achieved with a Pinia store (another singleton) representing map location state and a getter that the map component watches or binds. It is possible the map component is already using some kind of store that the menu can address directly. The store would be active even in when the map component was not active, so there would always be somewhere to write new locations to, and an event listener above your menu component could do that.
  4. As you allude, your menu events can trigger a navigation that includes bringing up the map component and binds the location to it. Your description suggests you don't want that to happen all the time, tricky.

Which option is best for you depends a bit on what is already in your dependencies. For this reason I would be cautious about an event bus (or Pinia for that matter) for one single component's purpose. The right choice also depends on what other interactions you might plan on.

The event bus approach could be right if nobody ever needs to know what the map is focused on, the store might be better if you want to interrogate the map for where it's at or have the map "magically" be focused on the last location selected even when it wasn't visible.

答案3

得分: 1

以下是翻译好的部分:

要实现父布局(layouts/default.vue)和MapPage.vue组件之间的通信,您可以使用props和自定义事件的组合。Prop绑定适用于从父组件传递数据到子组件,而自定义事件适用于子组件向父组件通信。

建议的方法如下:

使用Props:在MapPage.vue组件中,定义props以接收从父布局传递的必要数据。这些props将用于更新地图的状态。

MapPage.vue:

<template>
  <div>
    <!-- 在这里放置您的Leaflet地图代码使用myDrawerprop -->
  </div>
</template>

<script setup>
import { ref, defineProps } from 'vue';

const { myDrawer } = defineProps(['myDrawer']);
</script>

发出自定义事件:在父布局(layouts/default.vue)中,监听Qdrawer事件并在项目被点击时发出自定义事件。自定义事件将携带必要的数据(例如,特定位置)。

layouts/default.vue:

<template>
  <div>
    <Qdrawer @itemClicked="handleItemClicked" />
    <NuxtPage />
  </div>
</template>

<script setup>
import { ref } from 'vue';

const qdrawerState = ref({});

function handleItemClicked(location) {
  // 发出带有位置数据的自定义事件
  emit('mapLocationChanged', location);
}
</script>

监听自定义事件:回到MapPage.vue组件,在那里监听父布局发出的自定义事件。当接收到事件时,相应地更新地图的位置。

MapPage.vue:

<template>
  <div>
    <!-- 在这里放置您的Leaflet地图代码使用myDrawerprop -->
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, onUpdated, defineProps } from 'vue';

const { myDrawer } = defineProps(['myDrawer']);

onMounted(() => {
  // 监听来自父布局的自定义事件
  const handleMapLocationChanged = (location) => {
    // 使用接收到的数据(location)更新地图的位置
  };

  // 注册事件监听器
  window.addEventListener('mapLocationChanged', handleMapLocationChanged);

  // 在组件卸载时清理事件监听器
  onUnmounted(() => {
    window.removeEventListener('mapLocationChanged', handleMapLocationChanged);
  });
});
</script>

这样,通信是通过自定义事件完成的,并且您不会在不需要prop的页面上遇到问题。自定义事件仅在单击Qdrawer项目时触发,MapPage组件将根据事件做出响应。

英文:

To achieve communication between your parent layout (layouts/default.vue) and the MapPage.vue component, you can use a combination of props and custom events. Prop binding is a good approach for passing data from parent to child components, and custom events are suitable for child to parent communication.

Here's a suggested approach:

Use Props: In your MapPage.vue component, define props to receive the necessary data from the parent layout. These props will be used to update the map's state.

MapPage.vue:

&lt;template&gt;
  &lt;div&gt;
    &lt;!-- Your Leaflet map code goes here, using the &quot;myDrawer&quot; prop --&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref, defineProps } from &#39;vue&#39;;

const { myDrawer } = defineProps([&#39;myDrawer&#39;]);
&lt;/script&gt;

Emit Custom Event: In your parent layout (layouts/default.vue), listen to the Qdrawer events and emit a custom event when an item is clicked. The custom event will carry the necessary data (e.g., the specific location).

layouts/default.vue:

&lt;template&gt;
  &lt;div&gt;
    &lt;Qdrawer @itemClicked=&quot;handleItemClicked&quot; /&gt;
    &lt;NuxtPage /&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref } from &#39;vue&#39;;

const qdrawerState = ref({});

function handleItemClicked(location) {
  // Emit custom event with the location data
  emit(&#39;mapLocationChanged&#39;, location);
}
&lt;/script&gt;

Listen to Custom Event: Back in your MapPage.vue component, listen for the custom event emitted by the parent layout. When the event is received, update the map's location accordingly.

MapPage.vue:

&lt;template&gt;
  &lt;div&gt;
    &lt;!-- Your Leaflet map code goes here, using the &quot;myDrawer&quot; prop --&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref, onMounted, onUnmounted, onUpdated, defineProps } from &#39;vue&#39;;

const { myDrawer } = defineProps([&#39;myDrawer&#39;]);

onMounted(() =&gt; {
  // Listen to the custom event from the parent layout
  const handleMapLocationChanged = (location) =&gt; {
    // Update the map&#39;s location using the received data (location)
  };

  // Register the event listener
  window.addEventListener(&#39;mapLocationChanged&#39;, handleMapLocationChanged);

  // Clean up the event listener when the component is unmounted
  onUnmounted(() =&gt; {
    window.removeEventListener(&#39;mapLocationChanged&#39;, handleMapLocationChanged);
  });
});
&lt;/script&gt;

This way, the communication is done using custom events, and you won't face issues with prop binding on pages where the prop is not required. The custom event will only be triggered when the Qdrawer item is clicked, and the MapPage component will respond to the event accordingly.

huangapple
  • 本文由 发表于 2023年7月18日 16:37:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76710922.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定