Vue3 equivalent to create component instance via new Vue() - vue.js

I have a data-modelling library that uses underlying Vue2 component instances, eg:
const Person = Vue.extend({
name: 'Person',
//- ... data, computed, watch, methods, events, etc...
})
const person = new Person({ ... })
I can't find docs to determine if there is an equivalent method of creating a new component instance detached from the DOM and OUTSIDE of the main App's context.
Certainly createApp() isn't what I'm looking for - from the Vue3 API Docs
I guess defineComponent() seems to be the way to define the component (d'uh!) ... but since there is no constructor, there seems to be no way to instantiate it outside of the Apps context (ie mounting it)
Is there such a thing? Have I perhaps been versioned-out of my sweet sweet data modelling library?!? Tell me it's not so!

Related

OOP in Svelte and its reactivity

I wanna use svelte for a little app that Im making. The app was half finished using plain html/css/js when I stumbled upon Svelte.
I was using a lot of javascript classes and aimed for object oriented programming.
Now looking at svelte, it looks like its not made for OOP at all. Am I wrong? Properties of classes wont be tracked and updated by svelte.
Maybe my approach is wrong. I basicly used a View/Model pattern, where I have a model class object that Im feeding the svelte component. Using the object's properties in html wont update obviously. (This works great with angular i.e.)
<script lang="ts">
import type { Key } from "../key";
export let key: Key
const onTrigger = () => {
key.trigger()
}
const onRelease = () => {
key.release()
}
</script>
<div
class="key"
class:black={key.note[1] == '#' || key.note[1] === 'b'}
class:pressed={key.isPressed}
on:pointerdown={onTrigger}
on:pointerup={onRelease}
on:pointerleave={onRelease}
style={key.isPressed ? 'transform: scale(1.5); transform-origin: center;' : ''}>
<div class="key-mapping">{#html key.mapping.toLocaleUpperCase() + '<br/>' }</div>
<div class="key-note">{ key.note + (key.octave ? key.octave.toString() : '') }</div>
</div>
(Key represents a piano key sitting inside a piano component, things like key.isPressed or key.octave wont update, because they are changed in the model class)
Demo here
I really dont wanna use the store for ALL properties of my classes that I use in html, because I think this is not the purpose of the store. I was hoping to save some code by using Svelte and not make it weird and complex.
I saw the trick to reassign the whole object like this
object.property = 'some value'
object = object
to trigger reactivity, but this wont work when changing the properties outside of the component.
Also using the reactive marking $: { ... } I wasnt able to update any class' property (Only when changing it directly from a html event)
Also saw a decorator function to make classes reactive to svelte, but the decorator makes the class singleton too, which makes it useless to me.
So there are a few questions I wanna ask:
Is there any proper way to update class properties in Svelte?
If not, whats the prefered coding style? Functional?
Will there be OOP support in the future?
You don't need dummy assignments as soon as you assign to a property (rather than invoking a method) and there is no issue with using classes as long as you do not "hide" changes from Svelte's compiler.
E.g. this will work just fine (inside the component):
const onTrigger = () => {
key.isPressed = true;
}
const onRelease = () => {
key.isPressed = false;
}
In general your components should be fairly specialized so they do not have to deal with deeply nested data and complex modifications which makes it easy to lose reactivity.
Ideally you just have some very simple local state via component properties rather than objects. Here your Key component should just use properties for all its state e.g. isPressed should just be a property that then can be bound on the level of the parent component.

Vue composition API store

I was wondering if it was possible with the new Vue composition API to store all the ref() in one big object and use that instead of a vuex store. It would certainly take away the need for mutations, actions, ... and probably be faster too.
So in short, is it possible to have one place for storing reactive properties that will share the same state between different components?
I know it's possible to have reusable code or functions that can be shared between different components. But they always instantiate a new object I believe. It would be great if they would depend on one single source of truth for a specific object. Maybe I'm mixing things up...
You can use reactive instead of ref.
For eg.
You can use
export const rctStore = reactive({
loading: false,
list: [],
messages: []
})
Instead of
export const loading = ref(false)
export const list = ref([])
export const messages = ref([])
Cheers!

How does the next() guard function in vue-router work?

I'm trying to understand the solution in this SO post. The solution allows the user to keep track of the previous route in the current route.
Below is the snippet of Vue code that I'm trying to understand. If I understand correctly, next accepts a callback function that receives the current component's vue instance. We then set the prevRoute data property of this vue instance to from. Is this interpretation correct? If not, what is actually happening?
If someone could also add a brief explanation as to what the Vue API is doing behind the scenes that would also be very helpful for me to actually understand the snippet of code.
...
data() {
return {
...
prevRoute: null
}
},
beforeRouteEnter(to, from, next) {
next(vm => {
vm.prevRoute = from
})
},
...
As per the documentation...
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument
So vm is the component instance assigned to the destination route.
From your question...
We then set the prevRoute data property of this vue instance to from. Is this interpretation correct?
Almost. All you're doing is setting a direct object property on the Vue component which is after all, just a JavaScript object at heart. For example
const vm = { name: 'I am totally a Vue component' }
vm.prevRoute = from
This property will not be reactive but you can certainly access it within your component via this, just as you can other non-data properties like $el, $refs, etc.

Dynamically instantiating a component in Vue.js

Following this tutorial, I'm trying to programmatically create instances of a component on my page.
The main snippet is this:
import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()
instance.$mount()
this.$refs.container.appendChild(instance.$el)
However I get two errors:
The component I'm trying to instantiate contains references to the store, and these don't work: "TypeError: Cannot read property 'state' of undefined".
For the last line of the snippet (this.$refs.container.appendChild(instance.$el)) I get this error: "Uncaught TypeError: Cannot read property 'container' of undefined"
I'm really not sure how to troubleshoot this, if anyone strong in Vue.js could give me some hint as to why I'm getting these errors and to solve them that would be terrific.
1) Since you're manually instantiating that component and it doesn't belong to your main app's component tree, the store won't be automatically injected into it from your root component. You'll have to manually provide the store to the constructor when you instantiate the component ..
import ProjectRow from "./ProjectRow.vue";
import Vue from "vue";
import store from "../store";
let ProjectRowClass = Vue.extend(ProjectRow);
let ProjectRowInstance = new ProjectRowClass({ store });
2) In a Vue Single File Component (SFC), outside of the default export this doesn't refer to the Vue instance, so you don't have access to $refs or any other Vue instance property/method. To gain access to the Vue instance you'll need to move this line this.$refs.container.appendChild(instance.$el) somewhere inside the default export, for example in the mounted hook or inside one of your methods.
See this CodeSandbox for an example of how you may go about this.
This is another way to instantiate a component in Vue.js, you can use two different root elements.
// Instantiate you main app
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
//
// Then instantiate your component dynamically
//
// Create a component or import it.
const Hello = {
props: ['text'],
template: '<div class="hello">{{ text }}</div>',
};
// Create a componentClass by Vue.
const HelloCtor = Vue.extend(Hello);
// Use componentClass to instantiate your component.
const vm = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
// then mount it to an element.
.$mount('#mount');
It works by assigning "this" to the property "parent". By setting the parent you also have access to the $store in the new instance. (Provided that "this" is another Vue instance/Component and already has access to the store, of course)
new (Vue.extend(YourNewComponent))({
parent: this,
propsData: {
whatever: 'some value',
},
}).$mount(el.querySelector('.some-id'))
If you don't need the reference to the parent, you can just leave "parent: this," out.
Important note: When mounting many (like 500+) items on the page this way you will get a huge performance hit. It is better to only give the new Component the necessary stuff via props instead of giving it the entire "this" object.
I went down this path, following all the examples above, and even this one: https://css-tricks.com/creating-vue-js-component-instances-programmatically/
While I got far, and it works (I made a lot of components this way), at least for my case, it came with drawbacks. For example I'm using Vuetify at the same time, and the dynamically added components didn't belong to the outer form, which meant that while local (per component) validation worked, the form didn't receive the overall status. Another thing that did not work was to disable the form. With more work, passing the form as parent property, some of that got working, but what about removing components. That didn't go well. While they were invisible, they were not really removed (memory leak).
So I changed to use render functions. It is actually much easier, well documented (both Vue 2 and Vue 3), and everything just works. I also had good help from this project: https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
Basically, to add a function dynamically, just implement the render() function instead of using a template. Works a bit like React. You can implement any logic in here to choose the tag, the options, everything. Just return that, and Vue will build the shadow-DOM and keep the real DOM up to date.
The methods in here seems to manipulate the DOM directly, which I'm glad I no longer have to do.

How should I handle events in Vuex?

I am used to using a global event bus to handle cross-component methods. For example:
var bus = new Vue();
...
//Component A
bus.$emit('DoSomethingInComponentB');
...
//Component B
bus.$on('DoSomethingInComponentB', function(){ this.doSomething() })
However, I am building a larger project, which requires global state management. Naturally, I want to use Vuex.
While this bus pattern works with Vuex, it seems wrong. I have seen Vuex recommended as a replacement for this pattern.
Is there a way to run methods in components from Vuex? How should I approach this?
Vuex and event bus are two different things in the sense that vuex manages central state of your application while event bus is used to communicate between different components of your app.
You can execute vuex mutation or actions from a component and also raise events from vuex's actions.
As the docs says:
Actions are similar to mutations, the difference being that:
Instead of mutating the state, actions commit mutations.
Actions can contain arbitrary asynchronous operations.
So you can raise an event via bus from actions and you can call an action from any component method.
Using a global event bus is an anti pattern because it becomes very difficult to trace it (where was this event fired from? Where else are we listening to it? etc.)
Why do you want to use a global event bus? Is there some method that you want to trigger in another component? If you use Vuex then all your actions (methods) are in one central state and you can just dispatch your action.
So for example instead of doing this..
// Component A
bus.$emit('DoSomethingInComponentB');
// Component B
bus.$on('DoSomethingInComponentB', function(){ this.doSomething() })
With Vuex you would have the doSomething() function in a central store as an action. Then just make sure to convert you local component data to a global Vuex state data and dispatch that action.
this.$store.dispatch('doSomething')
It may not be directly what you are looking for, but I use watchers to respond to state changes. I come from an Angular background where having side effects respond to actions makes sense to me. To make this work I am having a particular value in the store change and then watch for the value in the relevant component like so:
Vue.component('comp-2', {
...
watch: {
mod1Foo() {
// do something here when the store value of the getter
// mod1/getFoo changes
}
},
computed: {
...mapGetters({
mod1Foo: 'mod1/getFoo'
})
}
});
Here is a full StackBlitz example: https://stackblitz.com/edit/js-gdme1g