Recently i'm reading the document of Vue, and when i was reading functional component, i found a statement that :
Note: in versions before 2.3.0, the props option is required if you wish to accept props in a functional component. In 2.3.0+ you can omit the props option and all attributes found on the component node will be implicitly extracted as props.
The reference will be HTMLElement when used with functional components because they’re stateless and instanceless.
You can see the original document here.
I was so confused about the word "reference" in the second quote, what does this "reference" denotes?
Hope someone can help me figure out that, thanks my friend :)
I'm not 100% certain, but I think it's referring to this situation:
Vue.component('functional', {
functional: true,
render(h, ctx) {
return h('button', ctx.data, ctx.children)
}
})
Vue.component('full', {
template: '<button><slot/></button>'
})
new Vue({
el: '#app',
mounted() {
console.log(this.$refs.func instanceof HTMLElement)
console.log(this.$refs.full instanceof Vue)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<functional ref="func">Functional</functional>
<full ref="full">Full</full>
</div>
If you get a ref to a functional component, you may not get a component instance back because functional components do not have a component instance itself.
It's best to think of a functional component as just a function. It isn't a "real" component.
The ref you get back depends on how the functional component is implemented. It is the responsibility of the functional component's render function to pass on the ref data to one of its children that it renders. In the example above, ctx.data.ref is equal to 'func' and it gets assigned to the <button>, so in the root component this.$refs.func is equal to that DOM element. If I didn't pass on ctx.data to the button, then this.$refs.func will always be undefined.
Related
I have a page with a component and the page needs to access a variable in that component. Would be nice if it were reactive. Then from the page I need to activate a function in the component. Would be nice if it could be done without a reactive variable. My question is 1: what's the best way to activate the function from the parent, for example when I click a button and 2: it seems very unintuitive and random to me that they aren't both possible in both directions? Anyone maybe know how Vue suggest you do it? This whole thing seems so complex relative to the relatively simple thing I'm trying to do.
I guess I try to use props? Or are refs a better option here?
So in general: you use refs, if you need the dom element, that's the whole purpose of refs. Since you don't mention that you n ed the dom element, you don't need to use that here.
There are 3 ways of communication: parent to child via props: https://vuejs.org/guide/components/props.html
child to parent via events
https://vuejs.org/guide/components/events.html
and anyone to anyone via event bus, which need an extra lib in vue3 and is out of scope for your question
https://v3-migration.vuejs.org/breaking-changes/events-api.html#event-bus
If you want to execute a function in the component whenever the value changes, you can put a watcher on the prop.
The other way around, from child to parent, you just create a listener to your emitted event and invoke a function of your choice. There are good examples in the docs in my opinion.
As per my understanding, You want to trigger the child component method from the Parent component without passing any prop as a input parameter and in same way you want to access child component data in the parent component without $emit. If Yes, You can simply achieve this using $refs.
You can attach the ref to the child component and then access it's variables and methods with the help of this $refs.
Live Demo (Just for a demo purpose I am using Vue 2, You can make the changes as per Vue 3) :
Vue.component('child', {
data: {
childVar: ''
},
methods: {
triggerEventInChildFromParent() {
console.log('Child Function triggered from Parent');
}
},
mounted() {
this.childVar = 'Child component variable'
}
});
var app = new Vue({
el: '#app',
methods: {
triggerEventInChild() {
this.$refs.child.triggerEventInChildFromParent()
}
},
mounted() {
console.log(this.$refs.child.childVar)
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="triggerEventInChild">
Button in Parent
</button>
<child ref="child"></child>
</div>
So I’m building a Nuxt app for working with docs (in a broad sense), and it will have a menu, which I will obviously make a component. The menu will be home to lots of actions on the doc itself, such as opening/saving files, editing, etc. etc.
I know the standard way to pass info from a component to its parent (the doc vm in this case) is via messages, but it feels like a bit of an overkill, what with the syntax (emit handlers just don’t feel natural to me in this case) and whatnot.
For this reason I was wondering why can’t I just pass the parent vm as a prop to the menu component? It will contain all kinds of methods, and I will be able to easily invoke them via the menu. Something like:
Parent (Document.vue):
<template>
<main-menu :document='vm'/>
</template>
<script>
import MainMenu from '~/components/MainMenu.vue'
export default {
data(): {
return {
vm: this,
//...
}
},
methods: {
save() {
//...
}
}
//...
</script>
Menu component (MainMenu.vue):
<template>
<button #click='document.save()'>Save document</button>
</template>
<script>
export default {
props = ['document']
}
</script>
The question: Is there something intrinsically bad in this approach?
(I imagine this could be problematic if the app architecture could change, but it’s hard to imagine that I would for some reason need a menu without an underlying document.)
IF your Menu is always the child of the component, then you don't have to pass your parent. It is already held in a Vue variable called this.$parent.
I made a little sandbox to give you an example.
The parent has a function, for example:
/// PARENT
export default {
name: "App",
components: {
HelloWorld,
},
methods: {
iExist(add) {
console.log("I am in parent" + add);
},
},
};
Then you can call it from child with this.$parent.iExist('something').
Since this.$parent is not defined when the template is being evaluated, we have to make a method in the child as well, to call(super) the corresponding function on it's parent.
/// CHILD
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button #click="iExist(', but was called from child')">Click Me</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
methods: {
iExist(add) {
this.$parent.iExist(add);
},
},
};
</script>
The question: Is there something intrinsically bad in this approach?
(I imagine this could be problematic if the app architecture could change, but it’s hard to imagine that I would for some reason need a menu without an underlying document.)
Yes, this is bad design. Parents can be aware of children, children shouldn't be aware of parents. A child could be tested in isolation, or be nested inside wrapper component that doesn't have this method.
As another answer suggests, a way to access a parent is to use $parent property. This part was borrowed in Vue from AngularJS 1.x, accessing it was considered a bad practice even then.
This is generally achieved by providing a callback from a parent that does exactly a desired thing, without allowing to access the whole instance and break the encapsulation. It's unnecessary to explicitly define callback function in Vue because this is naturally provided by Vue template syntax:
In a parent:
<child #save="save()">
In a child:
<button #click="$emit('save')">
In case of deeply nested components the event can be passed through them to a parent.
I have a Component where each instance needs some data being passed onto it, that Component is used multiple times and all instances of the component should receive the same data/prop.
<my-component :someProp="someValue"></my-component>
<my-component :someProp="someValue"></my-component>
<my-component :someProp="someValue"></my-component>
<!-- ...and lots more... -->
This is my current method:
Which gets kind of redundant, how can I pre-populate this someProp for ALL instances of my component?
I tried Vue.extend() but cannot figure out what syntax it expects, the documentation is not clear enough on that part.
This is how I imagined it to work:
// App.vue
import MyComponent from './components/MyComponent'
const PreConfiguredComponent = Vue.extend(MyComponent, {
props: {
someProp: "someValue"
}
})
export default {
name: 'app',
data: () => ({}),
components: {
MyComponent: PreConfiguredComponent
}
}
You get the idea, I don't know how to express it better. Doesn't have to be done with props but I don't know other methods to pass data along.
There are quite a lot of different ways to approach this. I doubt this will be anything like an exhaustive list but I can give some sense of the possibilities.
1. Store the value globally
The obvious choice here is the Vuex store. Just put the relevant data in the store and then the components can grab what they need.
However, a global store doesn't have to be Vuex. If you have no other reason to introduce Vuex then you might prefer something more ad hoc.
An alternative to the store is to hold the data on $root, an approach described in the official documentation:
https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
A further alternative is just to have a .js file that can hold the data as a singleton. There are various ways to do it but in its simplest form it might be something like this:
export default {
myData: null
}
You'd then import that object and read/write the value of myData as required. It won't necessarily be reactive but I'm assuming that the initial value would be set at the start, before the application is created, and wouldn't change after that.
2. Store the value on the Vue prototype
This is quite similar to the above but I think it warrants a separate mention. It'd look something like this:
Vue.prototype.$myComponentData = 'someValue'
Then within any component you could access the value via the property $myComponentData.
Documentation: https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html
3. Provide/Inject
The provide/inject mechanism is one of the lesser known Vue features but it provides an alternative to using props to pass data down to child components. It has various pros and cons and typically you'd try to use props instead.
https://v2.vuejs.org/v2/api/#provide-inject
In short, it allows a component to provide a named value to all of its descendants without needing to explicitly pass it to each one individually. The descendant components can then choose which named properties they would like to have injected.
You wouldn't be able to use provide/inject to pass different values to descendant components but in your case the value is the same so it should work.
If you think this approach might be for you then I suggest some further reading:
https://blog.logrocket.com/how-to-make-provide-inject-reactive/
4. Refactor to remove the duplication
While this is unlikely to be the solution you go with I do think it is worth mentioning.
The starting premise for the question seems to be that passing the prop explicitly is a form of duplication. Extra noise and extra effort with the potential to make mistakes.
Potentially we can remove that duplication while still passing the prop. The key here is to refactor the template so that the child component only features once.
Obviously that would need to be within a loop so that we still get all the desired components. That loop would need a suitable data structure to drive it so that all the right components get created.
I assume the template in the question is a simplification. If you really do have several instances consecutively then refactoring to use a v-for should be pretty trivial. If, as seems more likely, the components are nested in various places within the layout then it can get unwieldy trying to encode that in data structures just to avoid a bit of template duplication.
Hopefully the idea is clear but if not you could give this a read:
https://michaelnthiessen.com/reducing-redundant-repetition
5. Dynamic components
This is what's alluded to in the question. There are various ways to do it but the core of the idea is that we change our component definitions at runtime. That doesn't necessarily mean creating a new component, it could equally mean prodding something into an existing component definition. We've already seen a variation of this idea with the Vue.prototype approach mentioned earlier.
In theory it could be done with the default value of a prop but it seems unnecessary to use a prop unless that prop is going to be set from the outside in the usual way in some cases.
We could set it as a property using data but personally I would be tempted to use a variation of the Vue.prototype trick to add the property to the component's own prototype:
MyComponent = Vue.extend({
template: `<div>{{ value }}</div>`
})
MyComponent.prototype.value = 'some value'
new Vue({
el: '#app',
components: {
MyComponent
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-component></my-component>
</div>
Using the prototype is relatively cheap from a performance perspective. There are potential problems with reactivity but they would only apply if the value can change, which it can't in this case.
Just to illustrate the prop and data approaches explicitly:
MyComponent = {
template: `<div>{{ value }}</div>`
}
PreConfiguredComponent = Vue.extend({
extends: MyComponent,
props: {
value: {
default: 'some prop value'
}
}
})
new Vue({
el: '#app',
components: {
MyComponent: PreConfiguredComponent
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-component></my-component>
</div>
and:
MyComponent = {
template: `<div>{{ value }}</div>`
}
PreConfiguredComponent = Vue.extend({
extends: MyComponent,
data () {
return {
value: 'some data value'
}
}
})
new Vue({
el: '#app',
components: {
MyComponent: PreConfiguredComponent
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-component></my-component>
</div>
You could equally try to tweak the imported MyComponent directly rather than extending it.
I'm currently trying to create a component which manages several linked dropdowns and elements on a page. In addition, this element supplies a rather fancy navigation element, containing anchor links which automatically scroll to the desired element in this component.
The problem is that the actual contents of the component are completely dynamic and partially determined by the content manager in the CMS. There are several sub components that are always present, but apart from that the content manager can add any number of sections, (using various named and an unnamed ) and each of these sections should be added to the navigation of the component.
I see 3 options:
For every component added, it's title and unique id is added to a property array on the parent component. This, however, is a bit prone to errors. (Unfortunately I will have no control over the eventual backend implementation, so I'm trying to create a foolproof system to avoid to much wasted time.)
Due to other components, I'm already using Vuex to manage most of the data in the app. I figured I use a simple directive, to be added on each item in the parent component. This directive is responsible for adding that element to the Vuex store. The parent component simply reads the contents of the store and generates the navigation based on this.
The problem here is that, as far as I can tell, I have to use the vNode argument in the bind hook of my directive to access the Vuex store. This seems... hacky. Is there anything wrong with this approach?
In the mounted hook of my parent component, I traverse the DOM, searching for elements with a particular data-property, and add a link to each of these elements to my navigation. Seems prone to failure and fragile.
What is the preferred approach for this situation?
As a follow up question - What is the correct use case for the vNode argument in a vue directive? The documentation seems rather sparse on this subject.
I would steer away from using a directive in this case. In Vue 2, the primary use case for directives is for low level DOM manipulation.
Note that in Vue 2.0, the primary form of code reuse and abstraction
is components - however there may be cases where you just need some
low-level DOM access on plain elements, and this is where custom
directives would still be useful.
Instead I would suggest a mixin approach, where your mixin essentially registers your components that should be included in navigation with Vuex.
Consider the following code.
const NavMixin = {
computed:{
navElement(){
return this.$el
},
title(){
return this.$vnode.tag.split("-").pop()
}
},
mounted(){
this.$store.commit('addLink', {
element: this.navElement,
title: this.title
})
}
}
This mixin defines a couple of computed values that determine the element that should be used for navigation and the title of the component. Obviously the title is a placeholder and you should modify it to suit your needs. The mounted hook registers the component with Vue. Should a component need a custom title or navElement, mixed in computed properties are overridden by the component's definition.
Next I define my components and use the mixin.
Vue.component("child1",{
mixins:[NavMixin],
template:`<h1>I am child1</h1>`
})
Vue.component("child2",{
mixins:[NavMixin],
template:`<h1>I am child2</h1>`
})
Vue.component("child3",{
template:`<h1>I am child3</h1>`
})
Note that here I am not adding the mixin to the third component, because I could conceive of a situation where you may not want all components included in navigation.
Here is a quick example of usage.
console.clear()
const store = new Vuex.Store({
state: {
links: []
},
mutations: {
addLink (state, link) {
state.links.push(link)
}
}
})
const NavMixin = {
computed:{
navElement(){
return this.$el
},
title(){
return this.$vnode.tag.split("-").pop()
}
},
mounted(){
this.$store.commit('addLink', {
element: this.navElement,
title: this.title
})
}
}
Vue.component("child1",{
mixins:[NavMixin],
template:`<h1>I am child1</h1>`,
})
Vue.component("child2",{
mixins:[NavMixin],
template:`<h1>I am child2</h1>`
})
Vue.component("child3",{
template:`<h1>I am child3</h1>`
})
Vue.component("container",{
template:`
<div>
<button v-for="link in $store.state.links" #click="scroll(link)">{{link.title}}</button>
<slot></slot>
</div>
`,
methods:{
scroll(link){
document.querySelector("body").scrollTop = link.element.offsetTop
}
},
})
new Vue({
el:"#app",
store
})
h1{
height:300px
}
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<container>
<child1></child1>
<child3></child3>
<child2></child2>
</container>
</div>
This solution is pretty robust. You do not need to parse anything. You have control over which components are added to the navigation. You handle potentially nested components. You said you don't know which types of components will be added, but you should have control over the definition of the components that will be used, which means its relatively simple to include the mixin.
In the release notes for Vue 1.0.0-rc.1, we are told
"The inherit option has been deprecated. Alway pass data to child
components via props."
However, the Component API section says
"$data can no longer be used as a prop."
I have been trying to pass data to child components of my root Vue instance, and have had no luck whatsoever.
In version 0.12.*, if you want/need access to a parent instance's data, methods, etc., you would simply add...
inherit: true
...to a child component.
Now, in attempting to access the parent data via props, I continue to hit a brick wall. Here is a simplified example:
app.js:
new Vue({
el: '#app',
data: {
authorized: false,
currentView: 'welcome-view'
},
components: {
'welcome-view': require('./views/welcome')
}
});
views/welcome.js:
module.exports = {
props: ['authorized'],
template: require('./welcome.template.html')
};
views/welcome.template.html:
<div v-if="authorized"><p>You Are Logged In</p></div>
<div v-else>Please Log In</div>
Main View File (app.blade.php)
...
<body id="app">
<component :is="currentView"></component>
</body>
...
The 'authorized' prop is not recognized at all this way. It works outside of the component (within the "app" id) just fine, but not within the template.
At the moment, I can access the data I need by using $root everywhere I need it. For instance:
<div v-if="$root.authorized"><p>You Are Logged In</p></div>
But, my understanding is that this is 'bad form' all around, as the docs say:
Although it’s possible to access any instance the parent chain, you
should avoid directly relying on parent data in a child component and
prefer passing data down explicitly using props.
So, what I need to know is... how can I explicitly use props? I am clearly going about it the wrong way, since they are not available to my child components if I just list them in the 'props: []' array. What am I missing here?
At the end of the day, what is the best way (standards and practices) to refactor my current code to replace 'inherit: true', and still have access to the root instance data and functions? Any help/advice on this would be most welcome. Thanks in advance.
See #StephenHallgren's answer on this page for the correct way to access props in the HTML.
As for the rest of it, (how to properly refactor code to replace 'inherit:true', I am including here the answer I received from Evan Y. on the official Vue forum, in case anyone else runs across this in the future.
His answer to the question posed above was:
If you are fairly certain about the structure, you can use
$root.authorized.
Alternatively, don't put the authorized state in the root at all. Have
a dedicated module for user state that can be imported in any
component. See
http://rc.vuejs.org/guide/application.html#State_Management
My take-away from this is that - where there are concrete, global variables that will not change, and the app structure is sound, it is okay to use $root (or $parent as the case may be), and - where elements have state that will sometimes change (such as whether or not a user is authorized/logged in), the key is to use a state management module.
Meanwhile, when passing down props between parent and child, one must declare the props in the props array, then bind them to the component in the HTML.
For example...
app.js:
new Vue({
el: '#app',
data: {
authorized: false,
currentView: 'welcome-view'
},
components: {
'welcome-view': require('./views/welcome')
}
});
views/welcome.js:
module.exports = {
props: ['authorized'],
template: require('./welcome.template.html')
}
welcome.template.html:
<div v-if="authorized"><p>You Are Logged In</p></div>
<div v-else>Please Log In</div>
main HTML
<body id="app">
<component :is="currentView" v-bind:authorized="authorized"></component>
</body>
(or shorthand)
<body id="app">
<component :is="currentView" :authorized="authorized"></component>
</body>
I was having the same issue and didn't realize that you also have to bind the value to the component prop like this:
v-bind:authorized="authorized"
or the shorthand
:authorized="authorized"
Here's an example of something that I had been working on that illustrates the solution: http://jsfiddle.net/yyx990803/2uqmj2jj/6/