VueJS 组件 ref 在所有阶段都未定义
Posted
技术标签:
【中文标题】VueJS 组件 ref 在所有阶段都未定义【英文标题】:VueJS components ref is undefined at all stages 【发布时间】:2019-05-18 02:45:15 【问题描述】:我正在学习 VueJS,将其与 rails 应用程序一起使用,但在尝试从父组件访问组件时遇到问题。可能我做错了,但我不知道问题出在哪里。
我试图将我的问题归结为一个简单的 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://***.com/a/40884455/2730032 中一样。
额外问题:我的问题实际上是关于 ref 的工作原理。但是,我确实有一个更普遍的问题,可能会受益于不使用参考的建议。
我有一个我很满意的inner
组件,它为client_id 包装了一个输入字段,其中包含一个搜索字段,该字段提供来自API 的一些类似选择的选项,并将输入字段client_id 设置为所选选项。我想在outer
组件中重新使用该inner
组件,该组件将使用client_id(只要它被inner
更改)来调用API 并在outer
组件中填写一堆其他表单字段(这些字段通过插槽以inner
到outer
给出)。如果这有任何意义。
我认为最好的方法是将 client_id 作为inner
的数据字段并让outer
通过ref
访问它。所以在这里我试图让ref
工作。
EDIT1:有一个复制错误,对此深表歉意。 test1 标签来自以前的版本。但无论如何我都会遇到问题。
EDIT2:我接受了 Roy J 的第二个答案,因为我觉得它最好地回答了这个问题,即使它不是好的设计,它也可能对某些人来说是一个有效的解决方案。然而,在我的实际实现中,我使用了 Roy J 第一个答案,任何阅读此问题的人可能也应该这样做(我还设法将 inner
添加到 outer
的模板中,以避免使用 app
)。
【问题讨论】:
一般来说,不要看组件的内部,尤其是不要试图在视图中查看它们以获取有用的东西。这就是框架旨在解决的问题。管理数据,以便需要它的单位可以访问它。可能是内部组件将数据作为道具接收,而不是拥有它,并在它应该更改时发送事件。 您最初的问题是ref
是在根 Vue 实例中定义的,而不是作为任一组件的一部分。您正在尝试访问它,就好像它属于 outer
组件一样。
【参考方案1】:
由于您无法使用 ref
的唯一原因是它是在父级中定义的,因此您可以转到 $parent
并使用其 refs
。请注意,这使得outer
依赖于(纠缠)inner
的结构和ref
的存在来获得它。这是一个糟糕的设计,但它是对代码的最小更改,你必须让它做你想做的事情。
const 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
);
const outer = Vue.component('outer',
template: `
<div>
<h3 v-on:click="testos">Hello: client_id </h3>
<slot></slot>
</div>
`,
data: function()
return
client_id: ''
,
methods:
testos: function()
this.client_id = this.$parent.$refs.testref.searchText;
console.log('method', this.client_id);
)
new Vue(
el: '#app',
components:
'inner': inner,
'outer': outer
)
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
<outer>
<div>
<form>
<inner ref="testref"></inner>
</form>
</div>
</outer>
</div>
【讨论】:
感谢您提供有效且完全符合要求的答案。即使它是“糟糕的设计”,我也接受了它,但在我的问题中,我也建议您提出其他答案。任何阅读本文的人都应该在下面查看。【参考方案2】:您的ref
被定义为您的根级模板的一部分。您正在尝试从 outer
访问它,但它没有在那里定义,所以它永远不会出现。
一般来说,ref
s 是一种特殊情况。当您确实需要了解 DOM 的状态时,而不是需要共享数据时,您应该将它们视为最后的手段。
共享数据的正确方法是让需要使用它的组件的共同祖先拥有它。例如,它可能是 Vuex 之类的存储,也可能是根实例。
如果根实例拥有client_id
并与outer
和inner
共享,我已经写了您的示例代码如何工作。在传递给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)我的inner
组件在许多其他地方作为独立组件工作,难道没有一种方法不会用仅在一种情况下需要的发射“污染”它吗? 2) 另外我希望inner
在outer
的位置中,我不必将信息一直传送到应用程序,是真正使用 Vuex 的唯一选择吗?
如果inner
的数据通常不共享,您可以通过将共享版本包装在拥有数据项的组件中来制作非共享版本。您可以使用slot scope 将事件总线传递给inner
并让它在该总线上发射以与其插槽父级通信。 (您也可以为此制作一个非共享包装器。)保持封装很重要。否则就很难理解你构建了什么。【参考方案3】:
根据 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>
不匹配任何内容,并且会引发解析器错误。
以上都可以解决你的问题。
【讨论】:
感谢您发现我的错字,但 this.$nextTick 也不起作用。以上是关于VueJS 组件 ref 在所有阶段都未定义的主要内容,如果未能解决你的问题,请参考以下文章
VueJS 3 + Laravel:未捕获的类型错误:无法读取未定义的属性“组件”