繁体   English   中英

VueJS 3 如何在两个非父/子组件之间触发方法

[英]VueJS 3 How to trigger methods between two non-parent/child components

我有两个不是彼此的子组件,我只需要通过第二个组件的 function 触发第一个组件中的方法/函数。

第一个组件:

// Component - Product Select
  app.component('product-select', {
    data() {
      return {
        options: null
      }
    },
    props: {
      modelValue: Array,
    },
    emits: ['update:modelValue'],
    template: `
      <div class="ui fluid labeled multiple search selection large dropdown">
        <input type="hidden"
               name="products"
               :value="modelValue"
               @change="selectProducts">
        <i class="dropdown icon"></i>
        <div class="default text">
          Select Products
        </div>

        <div class="menu">
          <div v-for="(option, index) in options"
               class="item"
               v-bind:data-value="option.name">
            {{ option.name }}
          </div>
        </div>
      </div>
    `,
    methods: {
      // Add the product selections to the modelValue array
      selectProducts(event) {
        let value = event.target.value.split(',');
        this.$emit('update:modelValue', value);

        // Properly empty out the modelValue array if no products are selected
        if (/^(""|''|)$/.test(value) || value == null) {
          this.$emit('update:modelValue', []);
        }
      },
      updateComponent() {
        console.log('Component Updated');
      }
    },
    created: function () {
      // Build the products array via the products JSON file
      fetch(productsJSON)
        .then(response => response.json())
        .then(options => {
          this.options = options;
        });
    }
  });

第二部分:

// Component - Product Card
  app.component('product-card', {
    data() {
      return {
        selectedSymptoms: []
      }
    },
    props: {
      modelValue: Array,
      product: String
    },
    template: `
      <div class="ui fluid raised card">
        <symptoms-modal v-bind:name="product + ' - Modal'"
                        v-bind:title="product + ' - Symptoms'"
                        v-on:child-method="updateParent">
        </symptoms-modal>
        <div class="content">
          <div class="header" @click="removeProduct(product)">
            {{ product }}
          </div>
        </div>
        <div v-if="selectedSymptoms.length === 0"
             class="content">
          <div class="description">
            No symptoms selected
          </div>
        </div>
        <div v-for="(symptom, index) in selectedSymptoms"
             class="content symptom">
          <h4 class="ui left floated header">
            {{ symptom }}
            <div class="sub header">
              Rate your {{ symptom }}
            </div>
          </h4>
          <button class="ui right floated icon button"
                  v-bind:name="symptom + ' - Remove'"
                  @click="removeSymptom(symptom)">
            <i class="close icon"></i>
          </button>
          <div class="clear"></div>
          <input type="text"
                 class="js-range-slider"
                 v-bind:name="product + ' - ' + symptom"
                 value=""
          />
        </div>
        <div class="ui bottom attached large button"
             @click="openModal">
          <i class="add icon"></i>
          Add/Remove Symptoms
        </div>
      </div>
    `,
    methods: {
      openModal() {
        // Gets the product name
        // Product Name
        let product = this.product;

        // Builds the modal name
        // Product Name - Modal
        let modal = product + ' - Modal';

        // Gets the modal element
        // name="Product Name - Modal"
        let target = $('[name="' + modal + '"]');

        // Assign the currently selected symptoms to a targettable array
        let array = this.selectedSymptoms;

        // Opens the appropriate modal
        $(target).modal({
          closable: false,
          // Updates all checkboxes when the modal appears if the user
          // removes a symptom from the main screen
          onShow: function () {
            // For each input
            $('input', $(target)).each(function () {
              // If it is checked
              if ($(this).is(':checked')) {
                // If it is a currently selected symptom
                if (jQuery.inArray(this.name, array) != -1) {
                  // Is checked and in array, re-check
                  $(this).prop('checked', true);
                } else {
                  // Is checked and not in array, un-check
                  $(this).prop('checked', false);
                }
              } else {
                if (jQuery.inArray(this.name, array) != -1) {
                  // Is not checked and in array, re-check
                  $(this).prop('checked', true);
                } else {
                  // Is not checked and not in array, do nothing
                }
              }
            });
          },
        }).modal('show');
      },
      updateParent(value_from_child) {
        // Update the symptoms values from the modal
        this.selectedSymptoms = value_from_child;
      },
      removeSymptom(symptom) {
        this.selectedSymptoms.splice($.inArray(symptom, this.selectedSymptoms), 1);
      },
      removeProduct(product) {
        this.$root.selectedProducts.splice($.inArray(product, this.$root.selectedProducts), 1);
      }
    },
    updated() {
      // Add custom range input functionality
      $(".js-range-slider").ionRangeSlider({
        skin: "round",
        grid: false,
        min: 1,
        max: 5,
        from: 2,
        step: 1,
        hide_min_max: true,
        values: [
          "1 - Adverse Reaction", "2 - No Change", "3 - Partial Resolution", "4 - Significant Resolution", "5 - Total Resolution"
        ],
        onChange: function (data) {
          // Name Attribute
          // data.input[0].attributes.name.nodeValue

          // Input Value
          // data.from_value
        }
      });
    }
  });

在第一个组件中,我有 function updateComponent()这是我需要触发的。 在第二个组件中,我有 function removeProduct() ,它需要触发updateComponent() function。

我尝试过使用 $refs 并且它根本不起作用,并且据我了解,发出事件仅适用于子>父组件。

有几种方法可以做到这一点,实现取决于你的约束。

首先,vue3 不再像 Vue2 那样支持事件总线。 这意味着在组件冒泡之外侦听的事件不再是库功能。 相反,他们建议使用第 3 方选项

Vue 文档链接

在 Vue 3 中,不再可能使用这些 API 从组件内监听组件自己发出的事件,该用例没有迁移路径。

但是 eventHub 模式可以通过使用实现事件发射器接口的外部库来替换,例如mitttiny-emitter

底线是它缩小了您对三种“策略”的选择范围

  • 使用 3rd 方 pub/sub 库
  • 通过 function 本身
  • 使用watch (或反应性的某些部分)来执行 function。

等式的另一部分是交付方法。

  • 全局/单例
  • 原型/配置
  • 提供/注入

全局/单例

您可以设置一个包含 singleton 参考的文件,这将允许任何策略。

创建一个 pubsub 实例,然后您可以从任何地方收听和发出

// pubsub.js
import mitt from 'mitt'
export const emitter = mitt();

或者,如果您只想传递 function,您可以将其包装起来,实质上是创建一个 function,其中包含执行另一个 function 的指令。

// singleton.js
let updateComponentProxy = () => {};
export const setUpdateComponentProxy = (callback)=>updateComponentProxy=callback;
// component1
created(){
  setUpdateComponentProxy(()=>{this.updateComponent();})
}

// component2
// ..on some trigger
updateComponentProxy()

这是一个非常丑陋的实现,但它有效,并且在某些情况下可能是合适的

第三个选项是使用反应性。 您可以通过使用 vuex 或它的 diy 超级配对版本来做到这一点。

// mystore.js
import {ref} from 'vue';
export const updateComponentCount = ref(0);
export const pushUpdateComponentCount = () => updateComponentCount.value++;
// component1
import {watch} from 'vue';
import {updateComponentCount} from 'mystore.js';
created(){
  watch(updateComponentCount, this.updateComponent}
}

// component2
import {updateComponentCount} from 'mystore.js';
// ..on some trigger
pushUpdateComponentCount();

这将在updateComponentCount的值更改时执行updateComponent function。 您可以使用vuex做类似的事情,因为它不会(通常)设置为在组件中运行 function ,但在商店中提供一些变量会触发您要听的更改。 这个例子也使用false一个计数器,但你甚至可以在true之间切换,因为重要的不是值,而是突变。

提供/注入

如果您尝试在非直系子女和父母之间传递信息,但属于同一“祖先”,则提供/注入功能。 在此处输入图像描述

这是一种传递道具的方式,无需将它们从父母传递给孩子,只需让任何孩子都可以访问它。 然后,您可以使用任何策略。 然而有一个警告,如果你将它分配给根组件,它对所有组件都可用,这比其他策略更适合某些策略。

配置 Object

如果您将某些内容分配给app.config.globalProperties object(其中 app 是根组件实例)上的某个键,则您可以从任何子组件访问该键。 例如

import mitt from 'mitt';
const app = createApp(App)
app.config.globalProperties.emitter = mitt();

可以通过

// component 1
created(){
  this.emitter.on('removeProduct', this.updateComponent())
}

// component 2

      removeProduct(product) {
        this.$root.selectedProducts.splice($.inArray(product, this.$root.selectedProducts), 1);
        this.emitter.emit('removeProduct')
      }

如果你想在 vue3 setup() function 中使用,你需要使用getCurrentInstance().appContext.config.globalProperties.emitter来访问它,因为组件实例不在上下文中。

这是 Vue 中的一个常见挑战,因为该框架专注于父对子和子对父数据流。 你基本上有两个选择:

  1. 将 ProductSelect $emit事件传递给一个共同的父级,然后将props传递给 ProductCard。

  2. 创建并导入全局事件总线。

您可能还对 Vue 自己的框架解决方案感兴趣:

  1. 使用Vuex实现中央数据源。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM