VueJS组件ref在所有阶段均未定义

时间:2018-12-17 17:26:00

标签: javascript vue.js vuejs2 vue-component

我正在通过Rails应用程序学习VueJS,但在尝试从父组件访问组件时遇到问题。可能我做错了,但我不知道是什么问题。

我试图将问题简化为一个简单的html示例:

<html>
  <head>
    <meta charset='utf-8' />
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <outer>
        <div>
          <form>
            <inner ref="testref"><input/></inner>
          </form>
        </div>
      </outer>
    </div>
    <script>
      let inner = Vue.component('inner', {
        template: `
          <div>
            <span hidden="true"><slot></slot></span>
            <input v-model="searchText" />
          </div>
        `,
        props: ['placeholder'],
        data: function() {
          return {
            searchText: this.placeholder
          }
        }
      })
      let outer = Vue.component('outer', {
        template: `
          <div>
            <h3 v-on:click="testos">Hello: {{ client_id }}</h3>
            <slot></slot>
          </div>
        `,
        data: function() {
          console.log('data', this.$refs.testref)
          return {
            client_id: ''
          }
        },
        mounted: function() {
          console.log('mounted', this.$refs.testref)
        },
        methods: {
          testos: function() {
            console.log('method', this.$refs.testref)
          }
        }
      })
      new Vue({
        el: '#app',
        components: {
          'inner': inner,
          'outer': outer
        }
      })
    </script>
  </body>
</html>

我得到以下控制台日志:

data undefined
mounted undefined
method undefined

我尝试遵循https://vuejs.org/v2/api/#ref中的语法,但是我也尝试使用:ref而不是ref,但效果不佳。

我了解到即使没有完全理解,也会出现“引用注册时间”并发症。但是,单击H3时机不是很好吗?我希望mounted能够像https://stackoverflow.com/a/40884455/2730032中那样解决它。

奖金问题:我的问题实际上是关于ref的工作原理。但是,我确实有一个更普遍的问题,可能会因不使用ref的建议而受益。

我有一个令我满意的inner组件,它包装了client_id的输入字段,并带有一个搜索字段,该搜索字段提供了API中的一些类似于select的选项,并将输入字段client_id设置为selected选项。我想重用inner组件中的outer组件,该组件将使用client_id(无论何时被inner更改)来调用API并填充其他一系列表单字段outer组件中的字符(这些字段是通过插槽从innerouter给出的)。如果这样的话。

我想出了最好的方法,将client_id放在inner的数据字段中,并让outer通过ref访问它。因此,我在这里尝试使ref正常工作。

EDIT1 :出现复制错误,对此感到抱歉。 test1标签来自以前的版本。但不管如何,我都会遇到问题。

EDIT2:我接受了Roy J的第二个答案,因为我觉得它最能回答问题,即使它不是一个好的设计,对于某些人来说也可能是一个有效的解决方案。但是,在我的实际实现中,我使用了Roy J的第一个答案,阅读此问题的任何人可能也应该这样做(我还设法将inner添加到outer的模板中,以避免使用app

3 个答案:

答案 0 :(得分:1)

您的ref被定义为您的根级模板的一部分。您正在尝试从outer访问它,但是在那里没有定义它,所以它永远不会显示。

通常,ref是一种例外情况。当您确实需要了解DOM状态时,而不是在需要共享数据时,应该将它们作为最后的选择。

共享数据的正确方法是使数据归需要使用数据的组件的共同祖先拥有。例如,可能是像Vuex这样的商店,或者是根实例。

我已写出了如果根实例拥有client_id并与outerinner共享的情况,示例代码将如何工作。在传递给inner的过程中,我加入了.sync modifier以便对更新进行干净的处理。

我将searchText定义为可计算的可设置表,它会发出触发sync的更新事件,因此您仍然可以v-model="searchText"

const inner = Vue.component('inner', {
  template: `
          <div>
            <span hidden="true"><slot></slot></span>
            <input v-model="searchText" />
          </div>
        `,
  props: ['placeholder'],
  computed: {
    searchText: {
      get() { return this.placeholder; },
      set(newValue) { this.$emit('update:placeholder', newValue); }
    }
  }
});
const outer = Vue.component('outer', {
  template: `
          <div>
            <h3 v-on:click="testos">Hello: {{ client_id }}</h3>
            <slot></slot>
          </div>
        `,
  props: ['client_id'],
  methods: {
    testos() {
      console.log("Yes, client_id is", this.client_id);
    }
  }
})
new Vue({
  el: '#app',
  data: {
    client_id: 'Initial Client ID'
  },
  components: {
    'inner': inner,
    'outer': outer
  }
})
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <outer :client_id="client_id">
    <div>
      <form>
        <inner :placeholder.sync="client_id"></inner>
      </form>
    </div>
  </outer>
</div>

答案 1 :(得分:1)

由于您无法使用bindingContext的唯一原因是它是在父级中定义的,因此您可以转到ref并使用其{ {1}}。请注意,这使得$parent依赖于refs的结构(并与之纠缠)以及outer的存在来达到目的。这是不好的设计,但这是对代码的最小更改,以使其能够执行您要尝试执行的操作。

inner
ref

答案 2 :(得分:0)

详细解释Roy的注释,如果需要属性的2路数据绑定,请使用the sync modifier。就您而言,您可以。子级需要能够更新属性,并让父级知道更改。实际上,这是一个魔术,它的值是一个回调函数,该函数返回子代的值,而父代将变量的自身更新。

至于为什么您的ref无法正常工作-即使已经安装了父组件,也没有创建子组件。这是因为._render()方法在VueJS中的工作方式。因此,如果您需要在作为子项的自定义组件上使用ref,则需要在next tick中捕获更改:

mounted: function() {
  this.$nextTick(() => {
      console.log(this.$refs.testref)
  })
}

最后,也许只是一个糟糕的复制工作,您的代码中出现语法错误:

<inner ref="testref"><inner/></test1>

该结束标记</test1>不匹配任何内容,将引发解析器错误。

以上所有内容都可以解决您的问题。