Skip to content

Latest commit

 

History

History
486 lines (420 loc) · 13.4 KB

vue-mastery-advanced-components.md

File metadata and controls

486 lines (420 loc) · 13.4 KB

Advanced components

Vue Mastery

Instructor: Gregg Pollack

Table of Contents

01. Introduction

Image of component-template-render cycle

02. Build a Reactivity System

  • Walking through how reactivity is implemented with vanilla JavaScript

  • It's basically a bunch of classes and functions that get called when objects change.

  • 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

Image of component-template-render cycle, updated with annotations from lesson 2

03. Evan You on Proxies

04. Reactivity in Vue.js

05. Evan You on Vue Core

06. Template Compilation

  • Component template rendering has two steps:
    1. Compilation
    2. Running of the render function
  • Vue 3 defaults to runtime only
  • Virtual DOM represents the actual DOM with JavaScript objects.
    • Template -> render function -> VNode.
  • 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.

Image showing Vue.js template compilation and lifecycle hooks

07. Evan You on the Virtual DOM

  • The Vue.js virtual DOM is essentially a fork of Snabbdom.
  • Render functions example: Vue Router view.js

08. Functional Components

  • 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

09. Evan You on Functional Components

10. The Mounting Process

  • 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

    Image of component-template-render cycle again

11. Evan You on the Mounting Process

The entry-runtime-with-compiler.js is the full vue.js that is shipped onto the CDNs.

12. Scoped Slots & Render Props

What if we have a list of items, each with a different image?

Using v-bind on the image tag

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

Scoped slots were briefly mentioned in the Vue Mastery Real-World Vue course lesson on slots.

Deprecated syntax from this course

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.

Destructuring

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>

Updated v-slot directive

Notes
  • 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 a name attribute that can be used to define additional slots.
Solution

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",
      },
    ],
  },
})

Render props

<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!!!

Vue Mastery Advanced Components course completion page