-
Notifications
You must be signed in to change notification settings - Fork 545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exposing DOM from components. #134
Comments
Don't you think that it violates the principle of component oriented programming? In fact, any component can interfere with the work of another, update if the latter did not provide a special API? |
I don't know if this is a good idea, for two reasons:
A common pattern, that I've seen in both React and Vue, is to use spreadable props. I mean something like this: <template>
<div>
<select v-bind="selectProps">...</select>
</div>
</template>
<script>
export default {
props: {
selectProps: { type: Object, default: () => {} }
}
}
</script> |
It could be. But being honest,
Yes, that's a workaround, but passing objects as props is something that even Vue documentation discourages, because of the nature of javascript objects being pointers (maybe this is not true anymore because of Vue 3.0 and new proxy system). Furthermore, it's less readable than splitting the object in many props. I don't remember where I read this, I think in style guide (I can't find it out). Anyway, thanks for your opinions 👍 |
On real world, you'd use a computed property or similar to avoid unnecessary re-renders. |
We don't have a concept of a forward ref as React has (hope I don't mix up terminology here), which is basically what you propose here: A way to pass a template ref to a child component, and that child can determine to which of its child elements it wants to apply it. Which means the parent doesn't have to be aware of the dom structure or class names in the child to find the "reference element". Might be worth discussing - i don't think that something like your proposed We could also solve this with a template ref + function in Vue 3, like this: setup(){
const el = ref(null)
const forwardRef = _el => el.value = _el
return (
<MyChildComponent forwardRef={forwardRef}/>
)
} Child: <div>
<input ref="forwardRef"
</div> This should work I think. |
If you are manually adding/removing classes, won't your changes get overridden if a parent component has to re-render since the vDom hasn't been updated and is unaware of the changes? (I believe this was the case in Vue 2, and assume it still is with Vue 3, but I really am not sure about that) |
Adding a class is just a trivial example and not the point. The point is that sometimes, a parent might need to get a hold of an element that belongs to a child. Those situations might be edge cases, often tied to the need to use a certain third party library that works more imperatively, but these use cases do exist. |
sure, but won't any native DOM manipulation outside of vue.js run into the same issue? If you are grabbing an element and reading a value from it or whatever this makes sense to me, but if you are updating/changing wouldn't this be an issue with the virtual dom not tracking changes? I only mentioned classes since the original issue's example was adding a class which seems to me it might cause a problem? |
Sure but again: it's just an example and not the point of the functionality we are discussing. |
I'll give you all an example that I've faced recently. Imagine a custom video component: <template>
<video></video>
</template> The problem I have found in a component like this is really weird to try to control it based on props. I assume this kind of components are tightly coupled and their intended use is via As you can see, I haven't defined methods for this component. If I want to <template>
<video ref="video"></video>
</template>
<script>
export default {
methods: {
play () {
this.$refs.video.play()
}
}
}
</script> Now from the parent, I can just do this: <template>
<MyVideo ref="video" />
</template>
<script>
export default {
components: {
MyVideo
},
created () {
this.$refs.video.play()
}
}
</script> Nice! This works. But all this component stuff is about composition, right? Currently, in my project we have many different video components that are composed between 1 and 3 layers of components. This means we had to just copy/paste this piece of code for every component we want to compose over the first <script>
export default {
methods: {
play () {
this.$refs.video.play()
},
pause () {
this.$refs.video.pause()
}
}
}
</script> ... to be able to play/pause the video. This could be solved if a parent could get access to a piece of DOM of a child component, via forwardRef or any other mechanism. As @LinusBorg said: Child: <template>
<video :ref="videoRef" />
</template>
<script>
export default {
props: ['videoRef']
}
</script> Parent: <script>
export default {
setup () {
const el = ref(null)
const videoRef = _el => el.value = _el
return (
<MyChildComponent videoRef={videoRef}/>
)
}
}
</script> The only thing with this approach is I don't really know the disadvantages of exposing the DOM like this.
This is an edge case? Yes The point of this issue is to raise that this problem exists and this is not even a third-party package. There are some tags in HTML5 that are designed to be used in an imperative way like |
i would say it should be done via props and watchers. a video player has states (video source, playing, volume, playback position) which make up a very intuitive reactive state. and if you want to use the DOM video API directly in a component there is no need to use a dedicated video component at all. instead you can just do this in the parent element: <template>
<div>
<video :ref="video" ... />
</div>
</template>
<script>
export default defineComponent((props) => {
const video = ref();
// if you need custom API:
const videoControl = useMyCustomVideoControlHelpers(video);
// do something with videoControl: expose it to template for inline listeners, use it in watchers, whatever
return { video };
});
</script> |
I don't think this will be a good approach. How, for example, would you implement rewind 10 seconds via props and watchers? Or move the cursor to a specific second? That will be a mess of watchers, internal state and props. Exposing the DOM will be a cleaner and more maintainable approach IMO.
I think you are assuming too much here. Our video component has more things that I didn't expose here because it was only an example. |
In 2018 I asked in Vue forums if there was an API for replicate the behaviour in React that they named "Ref Forwarding". Vue Forum
I'd like to discuss if this functionality is something that still is not a needed feature.
@LinusBorg replied to that message saying that
$refs
acts different from React and that's fine. But what about exposing DOM elements? Maybe:or
Obviously this API is just an idea. I don't know which would be the perfect API or even if this is an interesting idea.
One benefit of this API would be for 3rd party libraries that want to offer full customization and prefer to expose the DOM element directly instead of multiple props (props for height, width, styles, classes... dunno)
This behaviour can be done right now by just using
querySelector
API but you would need to know the class names (not a big problem). If you decide to got with aquerySelector
you could end with a breaking change in the next releases if the author wants/needs to change the DOM structure.Not big problems anyway. Just an idea.
What do you all think?
The text was updated successfully, but these errors were encountered: