Vue 3 et son API de composition
Repo:
1
tag git : step1
Repo des démos
Quels problèmes cheche-t-on à résoudre?
Comment créer des composants web possédant des comportements réutilisables et composables entre eux
Composant
- Isolation
- Réutilisabilité
- Entrées/sorties
- Comportements
Custom Element
Le standard
let template = `<div id="root"></div>`;
class MyElement extends HTMLElement {
static get observedAttributes() { return ['name']; }
get name() {
return this._name
}
set name(name) {
this._name = name
}
constructor() {
super();
this.attachShadow({mode:'open'});
this._name = "";
}
connectedCallback() {
this.shadowRoot.innerHTML = template;
this.shadowRoot.querySelector('#root').innerHTML = this.name;
}
attributeChangedCallback(attrName, oldValue, newValue) {
if (newValue !== oldValue) {
this[attrName] = newValue;
}
}
}
customElements.define("my-element", MyElement);
<html>
<body>
<my-element name="RMN"</my-element>
<script>
document.addEventListener("DOMContentLoaded", function() {
// code précédent
});
</script>
</body>
</html>
Vue
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
<my-element name="RMN"></my-element>
</div>
<script>
Vue.component('my-element', {
template : `<div>{{name}}</div>`,
props: ["name"]
});
new Vue({
el: '#root',
})
</script>
</body>
</html>
<template>
<div>{{name}}</div>
</template>
<script>
export default {
name: 'MyElement',
props: ['name']
};
</script>
Single File Component (SFC)
<template>
<button @click="handleClick">Inc {{count}}</button>
</template>
<script>
export default {
name: 'MyElement',
props: ['value'],
data() {
return {
count: 0
}
},
mounted() {
this.count = this.value ?? 0
},
methods: {
handleClick() {
this.count++
}
}
};
</script>
<template>
<div>
<my-element :value="12"></my-element>
<my-element :value="24"></my-element>
</div>
</template>
1
Les mixins
Un moyen de réutiliser les comportements
// counter.js
export default {
props: ['value'],
data() {
return {
count: 0
}
},
mounted() {
this.count = this.value ?? 0
},
methods: {
handleClick() {
this.count++
}
}
}
<template>
<button @click="handleClick">Inc {{count}}</button>
</template>
<script>
import Counter from './mixins/counter'
export default {
name: 'MyElement',
mixins: [Counter]
};
</script>
2
// counter.js
export default {
props: ['value'],
data() {
return {
count: 0
}
},
mounted() {
console.log("mount counter")
this.count = this.value ?? 0
},
methods: {
handleClick() {
console.log("inc");
this.count++
}
}
}
<script>
import Counter from './mixins/counter';
import CountDown from './mixins/countDown';
export default {
name: 'MyElement',
mixins: [Counter, CountDown]
};
</script>
// countDown.js
export default {
props: ['value'],
data() {
return {
count: 0
}
},
mounted() {
console.log("mount countdown")
this.count = this.value ?? 0
},
methods: {
handleClick() {
console.log("decr");
this.count--
}
}
}
<template>
<div>
{{count}}
<button @click="handleClick">+</button>
<button @click="handleClick">-</button>
</div>
</template>
3
On va fixer ça
(enfin à peu prêt ... )
// counter.js
export default {
...
methods: {
handleClickInc() {
console.log("inc");
this.count++
}
}
}
<template>
<div>
{{count}}
<button @click="handleClickInc">+</button>
<button @click="handleClickDecr">-</button>
</div>
</template>
<script>
import Counter from './mixins/counter';
import CountDown from './mixins/countDown';
export default {
name: 'MyElement',
mixins: [Counter, CountDown]
};
</script>
// countDown.js
export default {
...
methods: {
handleClickDecr() {
console.log("decr");
this.count--
}
}
}
4
Mais c'est pas bien 👎
On a modifié l'intérieur pour faire correspondre notre extérieur
Vue3
Le petit nouveau
<template>
</template>
<script>
export default {
name: 'App',
setup() {
}
};
</script>
La méthode setup
5
<template>
</template>
<script>
export default {
name: 'App',
setup() {
console.log("setup")
},
beforeCreate() {
console.log("beforeCreate")
}
};
</script>
Cycle de vie
setup
beforeCreate
6
<template>
{{name}}
</template>
<script>
export default {
setup() {
return {
name: "toto"
}
},
};
</script>
Afficher une variable
<template>
{{name}}
</template>
<script>
export default {
data() {
return {
name: "toto"
}
},
};
</script>
7
<template>
<button @click="inc">Inc {{count}}</button>
</template>
<script>
export default {
name: 'App',
setup() {
let count = 0;
function inc() {
console.log("inc:" + count)
count++
}
return {
count,
inc
}
}
};
</script>
Déclarer une fonction
8
Il manque la réactivité
<script>
import {reactive} from "vue";
export default {
name: 'App',
setup() {
let state = reactive({
count: 0
});
function inc() {
console.log("inc: "+state.count)
state.count++
}
function decr() {
console.log("decr: "+state.count)
state.count--
}
return {
state,
inc,
decr
}
}
};
</script>
Store réactif local
10
<template>
<div>
{{state.count}}
<button @click="inc">+</button>
<button @click="decr">-</button>
</div>
</template>
10
<script>
import {ref} from "vue";
export default {
name: 'App',
setup() {
let count = ref(0)
function inc() {
console.log("inc: "+count)
count.value++
}
function decr() {
console.log("decr: "+count.value)
count.value--
}
return {
count,
inc,
decr
}
}
};
</script>
Proxy réactif
11
<template>
<div>
{{count}}
<button @click="inc">+</button>
<button @click="decr">-</button>
</div>
</template>
11
Composition !
// behaviors/inc.js
export default function (count) {
function handleClick() {
console.log("inc "+count)
count.value++
}
return {
handleClick
}
}
// behaviors/decr.js
export default function (count) {
function handleClick() {
console.log("decr "+count)
count.value--
}
return {
handleClick
}
}
<script>
import {ref} from "vue";
import incBehavior from './behaviors/inc'
import decrBehavior from './behaviors/decr'
export default {
name: "Counter",
setup() {
let count = ref(0);
const decr = decrBehavior(count);
const inc = incBehavior(count);
return {
inc,
decr,
count
}
}
};
</script>
<template>
<div>
{{count}}
<button @click="inc.handleClick">+</button>
<button @click="decr.handleClick">-</button>
</div>
</template>
12
// Counter.vue
<script>
import {ref} from "vue";
import incBehavior from './behaviors/inc'
import decrBehavior from './behaviors/decr'
export default {
name: "Counter",
props: ['value'],
setup(props) {
let count = ref(props.value ?? 0);
const decr = decrBehavior(count);
const inc = incBehavior(count);
return {
inc,
decr,
count
}
}
};
</script>
// App.vue
<template>
<counter></counter>
<counter :value="22"></counter>
</template>
13
Pour aller plus loin
RFC Vue3:
Article de blog:
Merci de m'avoir écouté 😀
Vue3 et API de composition
By akanoa
Vue3 et API de composition
- 317