Stateful Vue.js Functional Components

Hello!

Abdelrahman Awad

Software Engineer @Baianat

Open-source contributor and creator of vee-validate

Vue.js Functional Components

  • They are stateless (no data).
  • They are instanceless (no `this`).
  • They are cheaper to render (faster).

Cases when they are useful

  • When you want access to the underlying VNode and manipulate it before rendering.
  • When you want to render other components based on some runtime logic.
  • When you want to render multiple root nodes for the same component.

Vue.observable()

Allows you to create "detached" reactive values

import Vue from 'vue';

// create reactive state.
const state = Vue.observable({
  x: 0,
  y: 0
});


// Previously if you needed to do that:
const state = new Vue({
  data: () => ({
    x: 0,
    y: 0
  })
});

Example: Mouse Position

<MousePosition v-slot="{ x, y }">
  <div>x: {{ x }}</div>
  <div>y: {{ y }}</div>
</MousePosition>

Example: Mouse Position Component

import Vue from 'vue';

// create reactive state.
const state = Vue.observable({
  x: null,
  y: null
});

// Handle position updates.
const updatePosition = e => {
  state.x = e.pageX;
  state.y = e.pageY;
};

let isDoneSetup = false;

export const MousePosition = {
  name: 'MousePosition',
  functional: true,
  render(_, { scopedSlots }) {
    if (!isDoneSetup) {
      document.addEventListener('mousemove', updatePosition);
      isDoneSetup = true;
    }

    // render the scoped slot.
    return scopedSlots.default(state);
  }
};

Demo

When should you use this?

  • When the state is a global state that is unique to the current device (eg: Mouse position, Battery Status).
  • When the functional component is a "provider" of the state via scoped slots.

Hooks in Vue 3.0

import { onMounted, onUnmounted, value, createElement as h } from "vue";

function useMouse() {
  const x = value(0);
  const y = value(0);
  const update = e => {
    x.value = e.pageX;
    y.value = e.pageY;
  };

  onMounted(() => {
    window.addEventListener("mousemove", update);
  });

  onUnmounted(() => {
    window.removeEventListener("mousemove", update);
  });

  return { x, y };
}

export default {
  setup() {
    const { x, y } = useMouse();

    return () => {
      return h("div", {}, `${x.value} ${y.value}`);
    };
  }
};

Why a "setup" function?

  • Avoids the "call order" problem.
  • Effective clean up of watchers.

Hint: JavaScript execution is single-threaded.

Links and Resources

Thanks!

Made with Slides.com