从子组件中修改 props.value

Posted

技术标签:

【中文标题】从子组件中修改 props.value【英文标题】:Modify props.value from within child component 【发布时间】:2018-07-12 12:21:00 【问题描述】:

我是 Vue 的新手,正在尝试构建一个“下拉”组件。我想从这样的父组件中使用它:

<my-dropdown v-model="selection"></my-dropdown>

其中selection 在父级上存储为data,应更新以反映用户的选择。为此,我相信我的下拉组件需要一个 value 属性,并且它需要在选择更改时发出 input 事件。

但是,我还想从子本身内部修改value,因为我希望能够自己使用下拉组件(我需要修改它,否则 UI 将不会更新以反映如果组件单独使用,则新选择的值)。

有没有一种方法可以像上面那样与v-model 绑定,但也可以从孩子内部修改值(似乎我不能,因为value 是一个道具,孩子不能修改自己的道具)。

【问题讨论】:

无论如何你都不能修改 prop,但是你可以在组件内部设置一个镜像数据,并在没有 v-model 传递给它的情况下使用该数据更新 UI。跨度> @wxsm 我应该如何镜像它?我遇到的问题是父更改选择>子检测道具更改并更新数据>子发出事件,因为数据更改>父检测事件并更新选择并且循环无限重复。 阅读同步修饰符:vuejs.org/v2/guide/components.html#sync-Modifier @btl 我不太确定这对我有什么帮助,因为在那个例子中,状态似乎只存在于父母身上。 【参考方案1】:

您需要为处理输入/值值的本地值提供计算属性代理。

props: 
  value: 
    required: true,
  
,
computed: 
  mySelection: 
    get() 
      return this.value;
    ,
    set(v) 
      this.$emit('input', v)
    
  

现在您可以将您的模板设置为使用 mySelection 值来管理此组件内的数据,并且随着它的更改,数据会正确发出并且始终与 v-model (selected) 同步在父级中使用它。

【讨论】:

@Ferrybig 你是对的。它应该发出v。我的错。我已经编辑了答案。 这假设我有一个父组件来接收事件并更新道具。 @Flash 你不是在另一个组件中使用这个组件吗? 这不是最佳答案吗?它以一种非常干净、可重复使用的模式以最优雅的方式解决了最初的问题。【参考方案2】:

Vue 的理念是:“道具下降,事件上升”。它甚至在文档中这样说:Composing Components。

组件旨在一起使用,最常见于 父子关系:组件 A 可以自己使用组件 B 模板。他们不可避免地需要相互交流: 父母可能需要将数据向下传递给孩子,孩子可能需要 通知父母孩子发生的事情。然而, 保持父母和孩子的关系也很重要 通过明确定义的接口尽可能地解耦。这确保 每个组件的代码都可以相对编写和推理 隔离,从而使它们更易于维护并且可能更容易 重复使用。

在Vue中,父子组件关系可以概括为 道具下降,事件上升。父母通过以下方式将数据传递给孩子 props,子节点通过事件向父节点发送消息。让我们 看看他们接下来如何工作。

不要尝试从子组件中修改值。告诉父组件一些事情,如果父母关心,它可以做一些事情。让子组件改变事物会给孩子带来太多的责任。

【讨论】:

【参考方案3】:

您可以使用自定义表单输入组件

Form Input Components using Custom Events

基本上,您的自定义组件应该接受 value 属性并在值更改时发出 input 事件

【讨论】:

【参考方案4】:

您可以使用以下模式:

    接受 1 个输入道具 数据中有另一个变量 在创建时,将传入的 prop 添加到数据变量中 使用观察者,观察传入的道具的变化 在组件内部发生更改时,将更改发送出去

演示:

'use strict';
Vue.component('prop-test', 
  template: "#prop-test-template",
  props: 
    value:  // value is the default prop used by v-model
      required: true,
      type: String,
    ,
  ,
  data() 
    return 
      dataObject: undefined,
      // For testing purposes:
      receiveData: true,
      sendData: true,
    ;
  ,
  created() 
    this.dataObject = this.value;
  ,
  watch: 
    value() 
      // `If` is only here for testing purposes
      if(this.receiveData)
      this.dataObject = this.value;
    ,
    dataObject() 
      // `If` is only here for testing purposes
      if(this.sendData)
      this.$emit('input', this.dataObject);
    ,
  ,
);

var app = new Vue(
  el: '#app',
  data: 
    test: 'c',
  ,
);
<script src="https://unpkg.com/vue@2.0.1/dist/vue.js"></script>
<script type="text/x-template" id="prop-test-template">
  <fieldset>
    <select v-model="dataObject">
      <option value="a">a</option>
      <option value="b">b</option>
      <option value="c">c</option>
      <option value="d">d</option>
      <option value="e">e</option>
      <option value="f">f</option>
      <option value="g">g</option>
      <option value="h">h</option>
      <option value="i">i</option>
      <option value="j">j</option>
    </select>
    <!-- For testing purposed only: -->
    <br>
    <label>
      <input type="checkbox" v-model="receiveData">
      Receive updates
    </label>
    <br>
    <label>
      <input type="checkbox" v-model="sendData">
      Send updates
    </label>
    <!--/ For testing purposed only: -->
  </fieldset>
</script>
<div id="app">
  <prop-test v-model="test"></prop-test>
  <prop-test v-model="test"></prop-test>
  <prop-test v-model="test"></prop-test>
</div>

请注意,此演示有一个功能,您可以关闭每个选择框的事件传播,因此您可以测试值是否在本地正确更新,这当然不是生产所需要的。

【讨论】:

【参考方案5】:

如果您想修改组件内的 prop,我建议您将“默认值”prop 传递给您的组件。以下是我的做法

<MyDropdownComponent
  :default="defaultValue"
  :options="options"
  v-model="defaultValue"
/>

然后有两个选项可以让我从那里开始 -

选项 1 - 自定义下拉元素

当您使用自定义 HTML 时,您将无法设置选定属性。所以你需要对你的方法有创意。 在组件内部,您可以将默认值 prop 设置为组件创建时的数据属性。您可能希望以不同的方式执行此操作,并改用watcher。我已将其添加到下面的示例中。

export default 
  ...
  data() 
    return 
      selected: '',
    ;
  ,
  created() 
    this.selected = this.default;
  ,
  methods: 
    // This would be fired on change of your dropdown.
    // You'll have to pass the `option` as a param here,
    // So that you can send  that data back somehow
    myChangeMethod(option) 
      this.$emit('input', option);
    ,
  ,
  watch: 
    default() 
      this.selected = this.default;
    ,
  ,
;

$emit 会将数据传回父组件,该组件不会被修改。如果您使用的是标准 select 元素,则无需执行此操作。

选项 2 - 标准 select

<template>
  <select
    v-if="options.length > 1"
    v-model="value"
    @change="myChangeMethod"
  >
    <option
      v-for="(option, index) of options"
      :key="option.name"
      :value="option"
      :selected="option === default"
    > option.name </option>
  </select> 
</template>

<script>
export default 
  ...
  data() 
    return 
      value: '',
    ;
  ,
  methods: 
    // This would be fired on change of your dropdown
    myChangeMethod() 
      this.$emit('input', this.value);
    ,
  ,
;
</script>

这种方法绝对是最简单的,意味着你需要使用默认的select元素。

【讨论】:

在这两个选项中,您都无法在父级出于某种原因更新值时对条件做出反应 @Ferrybig 实际上在第二个选项中,值会更新:) 第一个示例需要watcher,我提到过编辑:哎呀!我没有发出正确的值! 查看我上面的评论:我遇到的问题是父更改选择 > 子检测道具更改并更新数据 > 子因为数据更改而发出事件 > 父检测事件并更新选择并且循环只是重复无限 @Flash 请重新创建一个示例,我可以帮忙看看 :)

以上是关于从子组件中修改 props.value的主要内容,如果未能解决你的问题,请参考以下文章

ReactJS 从子组件修改父状态

正确解构整个组件的 this.props

我可以从子组件获取计算数据到父组件吗?

Vue中从子组件导航到父组件

Angular2:如何从子组件调用主组件功能

如何从子组件中获取价值?