Instructor: Gregg Pollack
- 01. Introduction
- 02. Build a Reactivity System
- 03. Evan You on Proxies
- 04. Reactivity in Vue.js
- 05. Evan You on Vue Core
- 06. Template Compilation
- 07. Evan You on the Virtual DOM
- 08. Functional Components
- 09. Evan You on Functional Components
- 10. The Mounting Process
- 11. Evan You on the Mounting Process
- 12. Scoped Slots & Render Props
-
Walking through how reactivity is implemented with vanilla JavaScript
-
It's basically a bunch of classes and functions that get called when objects change.
- Getters
- Setters
- Watchers: See Vue.js docs | Computed properties and watchers.
-
Final JavaScript code: ./src/reactivity.js. Run with Node CLI:
~ ❯ node > .load ./src/reactivity.js > total 10 > salePrice 4.5 > data.price = 20 20 > total 40 > salePrice 18 > data.quantity = 10 10 > total 200
- Lesson resources
- Vue.js 2.x-next will rewrite the reactivity system with Proxies
- MDN | Proxy API
- There are many
handler
options, also called "traps" or "interceptors".
- There are many
- Investigating where reactivity is implemented in the Vue.js source code. You can walk through the call stack in the browser dev tools, debugger pane.
- Vue.js data options
- src/core/instance/index.js
- src/core/instance/init.js
- We dive into
initState(vm)
. It's in src/core/instance/state.js.
- We dive into
- src/core/observer/index.js
- We see
defineReactive
.
- We see
- Component template rendering has two steps:
- Compilation
- Running of the render function
- Vue 3 defaults to runtime only
- Virtual DOM represents the actual DOM with JavaScript objects.
- Template -> render function ->
VNode
.
- Template -> render function ->
- We can use
h('div')
to render DOM elements.h
is short-hand for createElement.render(h) { return h('div', { attrs: { id: 'people' }, class: 'sideBar' }, "Gregg and Chase") }
- We can also use render functions with JSX.
- The Vue.js virtual DOM is essentially a fork of Snabbdom.
- Render functions example: Vue Router view.js
-
functional: true
-
Functional components improve performance because they are stateless, and don't have their own component instance. It's sort of like a snippet of JavaScript.
-
router-view is a functional wrapper component.
-
Destructuring context
render(h, { props, data, children }) { if (props.items.length > 0 ) { return h(NormalTable, data, children) } else { return h(EmptyTable, data, children) } }
-
See Udacity notes on ES6 destructuring
const point = [10, 25, -34] const x = point[0] const y = point[1] const z = point[2] console.log(x, y, z) // output: 10, 25 -34 const point2 = [10, 25, -34] const [a, b, c] = point console.log(a, b, c) // output: 10, 25 -34
-
Where
mountComponent
is called- Called in /src/platforms/web/runtime.js
- Exported from /src/core/instance/lifecycle.js
-
Where a template gets compiled into a render function
-
What
_render
does -
What
_update
does
The entry-runtime-with-compiler.js is the full vue.js that is shipped onto the CDNs.
What if we have a list of items, each with a different image?
We can bind the img
element to the image for each product.
<div id="app">
<products-list :products="products"></products-list>
<products-list :products="products" :show-image="true"></products-list>
</div>
<script src="vue.js"></script>
<script>
Vue.component("products-list", {
props: {
products: {
type: Array,
required: true,
},
showImage: {
type: Boolean,
default: false,
},
},
template: `
<ul>
<li v-for="product in products">
<img v-show="showImage" :src="product.image" />
{{ product.name }}
</li>
</ul>
`,
})
new Vue({
el: "#app",
data: {
products: [
{
name: "Magnifying Glass",
image: "magnify.png",
},
{
name: "Light Bulb",
image: "bulb.png",
},
],
},
})
</script>
The Problem with this Implementation
What happens when sometimes we need our products-list component to also sometimes display price, sale price, add to cart buttons, different styles, or different bullets? What might result is a very brittle component full of v-ifs and v-shows. There has to be a better way!
Scoped slots were briefly mentioned in the Vue Mastery Real-World Vue course lesson on slots.
See the Vue.js docs on the deprecated slot-scope syntax.
<div id="app">
<products-list :products="products"></products-list>
<products-list :products="products">
<template slot="product" slot-scope="slotProps">
<img :src="slotProps.product.image" /> {{
slotProps.product.name.toUpperCase() }}
</template>
</products-list>
</div>
<script src="vue.js"></script>
<script>
Vue.component("products-list", {
props: {
products: {
type: Array,
required: true,
},
},
template: `
<ul>
<li v-for="product in products">
<slot name="product" :product="product" >
{{ product.name }}
</slot>
</li>
</ul>`,
})
new Vue({
// Same as above ...
})
</script>
- When adding the child component to the parent component's template, add
<template slot="product" slot-scope="slotProps">
inside the child component template element. - The
slotProps
are basically any of the props that the child component has. - See the Vue.js docs on scoped slots.
- Scoped slots are used by vue-multiselect.
See Vue.js docs on destructuring slot props.
<div id="app">
<products-list :products="products"></products-list>
<products-list :products="products">
<template slot="product" slot-scope="{{ product }}">
<img :src="product.image" /> {{ product.name.toUpperCase() }}
</template>
</products-list>
</div>
<script src="vue.js"></script>
<script>
Vue.component("products-list", {
props: {
products: {
type: Array,
required: true,
},
},
template: `
<ul>
<li v-for="product in products">
<slot name="product" :product="product" >
{{ product.name }}
</slot>
</li>
</ul>`,
})
new Vue({
// Same as above ...
})
</script>
- The
v-slot
directive was introduced in Vue.js 2.6.0. - The
v-slot
directive syntax is typically only added to<template>
elements. - Named slots are useful when there are multiple slots in the same component template. The
slot
element has aname
attribute that can be used to define additional slots.
Got this working in CodePen.
<div class="nav-bar"></div>
<div id="app">
<products-list :products="products">
<template v-slot:name></template>
<template v-slot:image></template>
</products-list>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
/* Adapted from Vue Mastery Intro lesson 11 on tabs */
body {
font-family: tahoma;
color: #282828;
margin: 0px;
}
.nav-bar {
background: linear-gradient(-90deg, #84cf6a, #16c0b0);
height: 60px;
margin-bottom: 15px;
}
img {
border: 1px solid #d8d8d8;
width: 25%;
margin: 40px;
box-shadow: 0px 0.5px 1px #d8d8d8;
}
Vue.component("products-list", {
props: {
products: {
type: Array,
required: true,
},
},
template: `
<ul>
<li v-for="product in products">
<slot name="name">
<h3>{{ product.name.toUpperCase() }}</h3>
</slot>
<slot name="image">
<img :src="product.image" />
</slot>
</li>
</ul>`,
})
new Vue({
el: "#app",
data: {
products: [
{
name: "Magnifying Glass",
image:
"https://upload.wikimedia.org/wikipedia/commons/c/c6/Mag_glass_request.jpg",
},
{
name: "Light Bulb",
image:
"https://upload.wikimedia.org/wikipedia/commons/5/5c/Led_light_bulb_-_led_lamp_1.png",
},
],
},
})
- Also see 06. Template Compilation.
- We send in a render function as a prop.
- When demonstrating render props, Gregg uses a Storybook to run Vue-Autosuggest.
- I found this pretty awkward. I much prefer the
v-slot
directive.
<div class="nav-bar"></div>
<div id="app">
<products-list
:products="products"
:product-renderer="imageRenderer"
></products-list>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
/* Adapted from Vue Mastery Intro lesson 11 on tabs */
body {
font-family: tahoma;
color: #282828;
margin: 0px;
}
.nav-bar {
background: linear-gradient(-90deg, #84cf6a, #16c0b0);
height: 60px;
margin-bottom: 15px;
}
img {
border: 1px solid #d8d8d8;
width: 25%;
margin: 40px;
box-shadow: 0px 0.5px 1px #d8d8d8;
}
Vue.component("products-list", {
props: {
products: {
type: Array,
required: true,
},
productRenderer: {
// <-- Here's our new prop
type: Function,
default(h, product) {
// <-- By default just print the name
return product.name
},
},
},
render(h) {
return h("ul", [
this.products.map(
(product) => h("li", [this.productRenderer(h, product)]) // use our new prop
),
])
},
})
new Vue({
el: "#app",
data: {
products: [
{
name: "Magnifying Glass",
image:
"https://upload.wikimedia.org/wikipedia/commons/c/c6/Mag_glass_request.jpg",
},
{
name: "Light Bulb",
image:
"https://upload.wikimedia.org/wikipedia/commons/5/5c/Led_light_bulb_-_led_lamp_1.png",
},
],
imageRenderer(h, product) {
// <-- The imageRenderer I'm passing in
return [
h("img", {
attrs: {
src: product.image,
},
}),
" ",
product.name.toUpperCase(),
]
},
},
})
COURSE COMPLETE!!! I RULE!!!