Reading the composition api documentation for Vue 3, I didn't quite understand how the new Composition API works. Could you please explain where the data() function has gone? And if it is no longer used what to use instead?
Updated 23.10.2021: The documentation in the link has been updated and expanded to include a mention of the data() in the Composition API introduction, so this question is now deprecated.
Under the new Composition API, all of the variables that you previously defined in data() are just returned from your setup() function as normal variables with reactive values. For example, a Vue 2.0 component that had a data function like so:
data() {
return {
foo: 1,
bar: { name: "hi" }
}
}
becomes this setup() function in Vue 3:
setup() {
const foo = ref(1);
const bar = reactive({ name: "hi" });
return { foo, bar }
}
The ref helper wraps a non-object value for reactivity, and reactive wraps an object. This is exposing the underlying principles of Vue more clearly than the old way, where the wrapping happened "magically" behind the scenes, but will behave the same otherwise. What I like about it personally is that your setup() function can build up your object on the go while keeping related things together, allowing it to tell a cohesive story and not require jumping around to different sections.
The composition is the new feature comes with Vue 3 and as a plugin for Vue 2, it doesn't replace the old option api but they could be used together in the same component.
The composition api compared to option api :
Gather the logic functionalities into reusable pieces of logic.
Use one option which the setup function which is executed before the component is created, once the props are resolved, and serves as the entry point for composition API's.
Define your old data option as ref or reactive properties
computed and watch is defined as : watch(...,()=>{...}) or computed(()=>{...})
Methods defined as plain javascript functions.
setup option used instead of created hook and it has as parameters the props and context
Hooks like mounted could be used as onMounted(()=>{...}), learn more
With script setup syntax you could declare your reactive data using ref, reactive and computed ...
<script setup >
import { ref, reactive, computed } from 'vue'
const isActive = ref(false)
const user = reactive({ firstName: 'John', lastName: 'Doe', age: 25 })
const fullName = computed(() => user.firstName + ' ' + user.lastName)
</script>
Related
The situation
In my Vue app, I have a Vue component which mounts an svg, which I have defined with a few props. These are a combination of reactive and non-reactive data.
The reactive data that we need is percData, which therefore sits in the data(){} object.
We also have colours, width, height, and scale, which are not reactive and never will be. I don't call these in the <template> block, and I don't plan for them to change. These are currently declared with const, and are not within the export defautl{} block scope.
The question(s)
Where is the best place to be for these const declarations?
What scope are these const-declared variables currently in?
More generally, how does scope work for the <script> tag in Vue in a multi-component app? Is each script/component a separate scope from the global one? And where is the global scope?
My possible understanding
As per this thread and this thread, I think the best place for my const would be in a separate file, from which I would then import them in my mySvgComponent component. Is this the correct approach?
My code
<template>
<div></div>
</template>
<script>
import { mySvgComponent} from '../mySvgComponent'
import { select } from 'd3'
const [colour1, colour2, colour3, colour4] = ['#000000', '#111111', '#222222', '#3333'];
const width = 135
const height = 135
const scale = .75
export default {
name:'mySvgComponent',
data(){
return{
percData: null
}
},
props: {
summary: Object
},
methods: {
percSetup(summary) {
return this.percData = [
{ colour: colour1, perc: +summary.colour1Percentage.toFixed(2)},
{ colour: colour2, perc: +summary.colour2Percentage.toFixed(2)},
{ colour: colour3, perc: +summary.colour3Percentage.toFixed(2)},
{ colour: colour4, perc: +summary.colour4Percentage.toFixed(2)}
]
}
},
async mounted() {
this.percSetup(this.$props.summary)
const svg =
select('div')
.append('svg')
.call(mySvgComponent(this.percData)
.width(width)
.height(height)
.scale(scale))
}
}
</script>
<style></style>
Related threads and why I don't think they answer my question:
How to set a component non-reactive data in Vue 2?, How to make a template variable non-reactive in Vue, How could I use const in vue template?. I don't call my const variables in my <template> tag, and I don't need it to be responsive.
What is the best way to create a constant, that can be accessible from entire application in VueJs ?. Maybe I don't understand this fully. Why would I need to run a method() to return my const variables?
Vue SFC is syntax sugar, it's necessary to understand what code it's compiled to in order to use it effectively.
The result of SFC script syntax is ES module with roughly the same content as the body of <script> block, with name and render options being added by the compiler. Then general modular JavaScript practices are applicable. The constants can remain in current module if they don't take much lines and aren't reused in other modules, and can be moved to a separate module otherwise. In case the constants are supposed to be used in a template, they can be returned from data or setup function.
The result of SFC script setup syntax is ES module with the whole block being moved to generated setup function, with the exception of imports. In this case it's inefficient to declare constants in this block because they will be created on each component instantiation, this may be a reason to move them to a separate module, although this can be considered preliminary optimization.
Considering the above, the scope of Vue SFC modules works exactly like it's expected from ESM because this is what they are compiled to.
Vue composition API provides markRaw function to additionally prevent constant objects from being made reactive when they are used inside reactive ones like data, in case this is unwanted. The same is done for options API with Object.freeze in linked question.
TL;DR: the code in the question is ok, it's correct to use the constants like that in this case.
In Vue.js, you can set non-reactive (i.e., constant) variables by declaring them in the data object as a function that returns an object, rather than declaring them directly as properties of the data object. This ensures that the variables are only set once during the initialisation of the component and cannot be modified later. Here's an example:
data: function () {
return {
nonReactiveConst: 'This is a non-reactive const variable'
}
}
Another way to create a non-reactive variable in Vue is to use the Vue.observable() function to create an observable object, and then assign the non-reactive variable as a property of that object.
const state = Vue.observable({
nonReactiveConst: 'This is a non-reactive const variable'
});
You can then access the non-reactive variable inside the component's template using state.nonReactiveConst.
It's important to note that using this method, you can still mutate properties of the object, but you can't reassign the whole object.
const app = createApp({
data() {
return {
some_id: 0
}
}
})
I have an autocomplete on a field.
When a label is selected, I want to pass the id to a Vue app.
onSelectItem: ({label, value}) => {
app.some_id = value;
}
This worked in an old v2 version of Vue.js.
Now, I can't even call the methods of the Vue app from other JavaScript functions.
What is the best solution?
There are certain circumstances where you may need to access and mutate, change the instance's data.
This was easier in Vue JS 2, but Vue JS 3 has become more encapsulated. However it does not mean mutating state from outside is impossible. You can read about it here
Supposing that you are using Vue with build steps, which covers most cases, you will have something like this:
const app = createApp({
data() {
return {}
},
})
.mount('#app');
Now if you head to browser console and type app, it will be null because it is limited to the scope of compiled .js files.
But if you attach app to a global object, document for example:
document.app = createApp({
data() {
return {}
},
})
.mount('#app');
Now if you type app in the console, it will no longer be null, but the Vue instance. From there, you can access the instance's data as well as mutate it via app.$data property.
Now what if the instance has components and you want to mutate their $data? In previous versions, it was possible to access children via $children property. But now in order to access children, you have to give each of them a ref, then access via their ref name. For example:
app.$refs.alertComponent.$data.message = "New message!"
Given the code below, my child component alerts trigger before any of the code in the Parent mounted function.
As a result it appears the child has already finished initialization before the data is ready and therefor won't display the data until it is reloaded.
The data itself comes back fine from the API as the raw JSON displays inside the v-card in the layout.
My question is how can I make sure the data requested in the Parent is ready BEFORE the child component loads? Anything I have found focuses on static data passed in using props, but it seems this completely fails when the data must be fetched first.
Inside the mounted() of the Parent I have this code which is retrieves the data.
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray).then(() => {
console.log('DATA ...') // fires after the log in Notes component
this.checkAttendanceForPreviousTwoWeeks().then(()=>{
this.getCurrentParticipants().then((results) => {
this.currentP = results
this.notesArr = this.notes // see getter below
})
The getter that retrieves the data in the parent
get notes() {
const newNotes = eventsModule.getNotes
return newNotes
}
My component in the parent template:
<v-card light elevation="">
{{ notes }} // Raw JSON displays correctly here
// Passing the dynamic data to the component via prop
<Notes v-if="notes.length" :notesArr="notes"/>
</v-card>
The Child component:
...
// Pickingn up prop passed to child
#Prop({ type: Array, required: true })
notesArr!: object[]
constructor()
{
super();
alert(`Notes : ${this.notesArr}`) // nothing here
this.getNotes(this.notesArr)
}
async getNotes(eventNotes){
// THIS ALERT FIRES BEFORE PROMISES IN PARENT ARE COMPLETED
alert(`Notes.getNotes CALL.. ${eventNotes}`) // eventNotes = undefined
this.eventChanges = await eventNotes.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
...
I am relatively new to Vue so forgive me if I am overlooking something basic. I have been trying to fix it for a couple of days now and can't figure it out so any help is much appreciated!
If you are new to Vue, I really recommend reading the entire documentation of it and the tools you are using - vue-class-component (which is Vue plugin adding API for declaring Vue components as classes)
Caveats of Class Component - Always use lifecycle hooks instead of constructor
So instead of using constructor() you should move your code to created() lifecycle hook
This should be enough to fix your code in this case BUT only because the usage of the Notes component is guarded by v-if="notes.length" in the Parent - the component will get created only after notes is not empty array
This is not enough in many cases!
created() lifecycle hook (and data() function/hook) is executed only once for each component. The code inside is one time initialization. So when/if parent component changes the content of notesArr prop (sometimes in the future), the eventChanges will not get updated. Even if you know that parent will never update the prop, note that for performance reasons Vue tend to reuse existing component instances when possible when rendering lists with v-for or switching between components of the same type with v-if/v-else - instead of destroying existing and creating new components, Vue just updates the props. App suddenly looks broken for no reason...
This is a mistake many unexperienced users do. You can find many questions here on SO like "my component is not reactive" or "how to force my component re-render" with many answers suggesting using :key hack or using a watcher ....which sometimes work but is almost always much more complicated then the right solution
Right solution is to write your components (if you can - sometimes it is not possible) as pure components (article is for React but the principles still apply). Very important tool for achieving this in Vue are computed propeties
So instead of introducing eventChanges data property (which might or might not be reactive - this is not clear from your code), you should make it computed property which is using notesArr prop directly:
get eventChanges() {
return this.notesArr.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
Now whenever notesArr prop is changed by the parent, eventChanges is updated and the component will re-render
Notes:
You are overusing async. Your getNotes function does not execute any asynchronous code so just remove it.
also do not mix async and then - it is confusing
Either:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray)
await this.checkAttendanceForPreviousTwoWeeks()
const results = await this.getCurrentParticipants()
this.currentP = results
this.notesArr = this.notes
or:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
Promise.all(promisesArray)
.then(() => this.checkAttendanceForPreviousTwoWeeks())
.then(() => this.getCurrentParticipants())
.then((results) => {
this.currentP = results
this.notesArr = this.notes
})
Great learning resource
Is there any way to share props between components using the composition API, or should I still resort to mixins for that?
For example, I have a "visible" prop that I want to reuse on 5 components. How can I define it in 1 common place and reuse it with the composition API?
With a mixin I would have done it the old fashioned way:
const mixin = {
props: { visibile: { type: Boolean: required: false } }
}
Used in the component:
mixins: [theMixinAbove]
How can I accomplish this using the composition API?
You can do it, but I think you'll need to implement props as-well in a similar manner, and you can't do it during setup because at that point, the props are already expected.
For example you can define a function that lives with your other function you would use during setup, and then destructure it into the rest of your props
props:{myInternalProp:String, ...withVisibilityProps()},
const app = Vue.createApp({})
app.component('my-component', {
template: '<h1>My Component is {{visiblity}}</h1>',
props:{...withVisibilityProps()},
setup(props){
return({...withVisibility(props)})
}
})
function withVisibility(props) {
return {visiblity:Vue.ref(props.visible?"visible":"not visible")};
}
function withVisibilityProps() {
return {visible:Boolean};
}
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.4/dist/vue.global.prod.js"></script>
<div id="app">
<my-component :visible="true"></my-component>
<my-component :visible="false"></my-component>
</div>
note that the setup function is used to handle the visibility variable. If you only need the prop, you can skip the withVisibility and setup
You can use script setup and use defineProps inside the composable.
https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
Based on the scaffolder mern.io I was going through the code to see what was going on. I stumbled upon a .need method which looks like something related to es6 classes. I can't seem to find any usable info anywhere, so I ask what is the .need method?
class PostContainer extends Component {
//do class setup stuff here
}
PostContainer.need = [() => { return Actions.fetchPosts(); }];
You can get the project up and running very easily with these commands.
npm install -g mern-cli
mern YourAppName
The mern documentation is pretty terse when it comes to explaining this.
fetchComponentData collects all the needs (need is an array of actions that are required to be dispatched before rendering the component) of components in the current route. It returns a promise when all the required actions are dispatched.
Reading through the code is a much clearer way of finding out what's going on here.
Overview
It's a way to specify some actions that should be dispatched before rendering the component.
This component maps the posts property from the Redux store to a prop called posts so that it can render the list of posts.
// PostContainer.jsx
function mapStateToProps(store) {
return {
posts: store.posts,
};
}
However, initially this property will be empty because the posts need to be fetched from an asynchronous API.
// reducer.js
// initial state of the store is an empty array
const initialState = { posts: [], selectedPost: null };
This component needs the posts to be available before it renders, so it dispatches the action returned from the call to Actions.fetchPosts().
// actions.js
export function fetchPosts() {
return (dispatch) => {
return fetch(`${baseURL}/api/getPosts`).
then((response) => response.json()).
then((response) => dispatch(addPosts(response.posts)));
};
}
When the action has finished dispatching, the store's data can be mapped to the connected component.
Caveat
This isn't a universal way to specify asynchronous dependencies for React components. It only works because mern has a utility method called fetchComponentData that it calls at the server side, in order to populate the Redux store before rendering.
// server.js
fetchComponentData(store.dispatch, renderProps.components, renderProps.params)
This method traverses the components from the second argument to extract the needs from each. Then it executes 'needs` and waits for all the promises to complete.
// fetchData.js
const promises = needs.map(need => dispatch(need(params)));
return Promise.all(promises);
When the promise returned by Promise.all(promise) completes, the Redux store will be populated and the components can safely render their data to be served to the client.
Syntax
You mentioned that you thought it might be related to ES6 classes, so I'll cover the syntax quickly too.
ES6 classes can't have static properties specified in the class literal, instead we have to declare them as properties on the class after it has been defined.
The needs property must be an array of functions that return promises to work with fetchComponentData. In this case we have an arrow function declared inside an array literal. It might help to look at it split up into separate variables.
const fetchPosts = () => { return Actions.fetchPosts() };
const needs = [fetchPosts];
PostContainer.need = needs;