Trying the composition API in Vue3
On October 4th, The Vue-3 code was made public at github.com/vuejs/vue-next
It is still in pre-alpha monorepo that you can't quite use the same way as Vue2 just yet. There is also functionality from Vue2 that hasn't reached parity yet. At the time of writing, these are SSR, kep-alive, transitions, DOM-specific transforms (v-on, v-dom, etc.)
It does allow us, however, to start playing with though.
I spent the last couple nights trying to make some sample code to work. Unfortunately, the going was a bit tough. I wanted to start by making a parent component pass a prop to a child component. It took me a lot longer than I expected, but that was mostly my fault. I'll try to describe the path I took to get it eventually to work, and some of the mistakes I've made.
Get the repo
I started by downloading the git repo with the following commands
# clone the vue-next mono repo to vue-next folder
git clone https://github.com/vuejs/vue-next.git
cd vue-next
# install dependencies
npm install
# build vue.global.js
npm run dev
this will generate a vue.global.js
file at packages\vue\dist\vue.global.js
Of course later I realized that the best place to start is here: https://github.com/vuejs/vue-next/blob/master/.github/contributing.md
The problem I had was that the file that was generated mounts Vue
as a global, so is not suited for use with bundlers like parcel
or webpack
, which as what I was trying to use it with. In the contributing link from the repo, there are further instructions for generating other builds, but I've decided to use the global package instead with a simple file server (like serve
or http-sever
), it even works on online code editors like jsfiddle, which I ended up using.
I've found a sample code from the vue-composition-api-rfc at https://vue-composition-api-rfc.netlify.com/#basic-example
<template>
<button @click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</template>
<script>
import { reactive, computed } from "vue";
export default {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
});
function increment() {
state.count++;
}
return {
state,
increment
};
}
};
</script>
First I made it available by uploading it to gitlab as a gist and generating a rawgist link for includeing in a jsfiddle
I had to make some changes to make it work with the global package, since that doesn't support Single File Components.
HTML:
<div id="app"></div>
<template id="appTemplate">
<button @click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</template>
JS:
const { reactive, computed } = Vue
const MyComponent = {
setup(props) {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
});
function increment() {
state.count++
}
return {
state,
increment
};
},
template: document.getElementById("appTemplate").innerHTML
};
const app = Vue.createApp({})
app.mount(MyComponent, "#app")
As you can see, instead of using template literals or SFC, I used the <template>
html tag and referenced it with getElementById
. Otherwise it's pretty much the same.
The next goal was to add another component and pass a prop to it.
I added the following code to the appTemplate
<my-component :count="state.count"></my-component>
and this to the script
const MyComponent = Vue.createComponent({
template: `<div>my component {{count}}<div>`,
props: {
count: Number
},
setup(props){
return {...props}
}
})
I also registered the component before mounting the app
app.component('my-component', MyComponent)
The result was that the prop was passed initially with a value of 0
, and then it didn't update any after that. So I tried copying what the app does.
const MyComponent = Vue.createComponent({
template: `<div>my component {{state.count}}<div>`,
props: {
count: Number
},
setup(props){
const state = reactive({...props})
return {
state
}
}
});
Shis still doesn't work and it's not clear to me why.
Now is the time when I frantically try a hundred different things and nothing seems to work. I could list all the things, but I'll just mention a few.
// added `onUpdated`
const { reactive, computed, onUpdated } = Vue;
const MyComponent = Vue.createComponent({
template: `<div>my component {{state.count}}<div>`,
props: {
count: Number
},
setup(props){
const state = reactive({...props})
// added onUpdated function
onUpdated(() => {
state.count = props.count
})
return {
state
}
}
});
When I console logged the state after the update, the state did change, but the template did not update. This wasn't making any sense.
Eventually after more reading and debugging I've found out two things.
The way to do it correctly from what I gather is to use reactive
AND computed
const state = reactive({
count: computed(() => props.count)
})
The other thing that I eventually noticed, was that my div
tag wasn't closed. This was causing the layout to only render the first time, and likely why I may have tried something that should have been working (like using onUpdate
) and weren't.
the working code (https://jsfiddle.net/dapo/fp34wj6z/)
<div id="app"></div>
<template id="appTemplate">
<button @click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
<pre>{{ state }}</pre>
<my-component :count="state.count"></my-component>
</template>
<template id="myComponent">
<div>
Child
<pre>{{ state }}</pre>
</div>
</template>
const {
reactive,
computed,
} = Vue;
const MyComponent = Vue.createComponent({
props: {
count: Number
},
setup(props) {
const state = reactive({
count: computed(() => props.count),
triple: computed(() => props.count * 3)
});
return {
state,
}
},
template: document.getElementById('myComponent').innerHTML
});
const App = {
setup(props) {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
});
function increment() {
state.count++;
}
return {
state,
increment
};
},
template: document.getElementById('appTemplate').innerHTML
};
const app = Vue.createApp({});
app.component('my-component', MyComponent)
app.mount(App, "#app");
TL;DR;
- I didn't watch my template syntax and missed that a tag wasn't closed, which was causing the rendering not to work properly.
- should have spent more time reading the docs than trying madly to just get it to work.
Resources :
- https://vue-composition-api-rfc.netlify.com/
- https://vue-composition-api-rfc.netlify.com/api.html
- https://github.com/vuejs/composition-api
- https://github.com/LinusBorg/composition-api-demos/
- https://github.com/vuejs/vue-next/blob/master/.github/contributing.md
Anyway, I'll be playing more with this in the future. I certainly subscribe to the benefits of the composition API, I just need to spend some more time understanding the how ref
, reactive
, computed
, and watch
all work together.
Top comments (2)
Currently
v-model
is not supported. So I've used a react-useState-style helper function to have reusable functionality (somewhat) similar to v-model.jsfiddle.net/dapo/a1fsw23n/
It's amazing what RTFMing can do for your understanding.
I have a vue 2 component that i couldn't make using defineComponent correctly, I need some help if you don't mind
how can I use existed props here into defineComponent and make things work worrectly, because I need to pass the formatter to change the datepicker format and without defineComponent it won't works