Vue Composition API

Introduction to the

Why the Composition API? 🤔

👉 Limitations of Vue 2

👉 The Vue 3 solution

Limitations of Vue 2 🙈

Component readability & maintenance ⚠️

Let's see a live example 👀

export default Vue.extend({
  ...
  data() {
    return {
      searchState: {
        query: "vue",
        loading: false,
        results: []
      } as SearchState<RawRepository>,
    };
  },

  methods: {
    runSearch(): void {
      ...
      this.searchState.loading = true;
      githubAPI
        .get(`/search/repositories?q=${this.searchState.query}`)
        .then((response: AxiosResponse<RawResult<RawRepository>>) => {
          this.searchState.results = response.data.items;
        })
        .finally(() => {
          this.searchState.loading = false;
        });
    }
  },

  mounted(): void {
    this.runSearch();
  }
});
export default Vue.extend({
  ...
  data() {
    return {
      orderOptions: [
        { label: "🌡️ score", value: "score" },
        { label: "⭐ stargazers", value: "stargazers_count" },
        { label: "⚠️ issues", value: "open_issues_count" }
      ] as OrderOption[],
      orderIndex: 0
    };
  },

  computed: {
    orderedResults(): RawRepository[] {
      return orderBy(
        this.searchState.results,
        this.orderOptions[this.orderIndex].value,
        "desc"
      );
    }
  },
  ...
});
data() {
  return {
    searchState: {
      query: "vue",
      loading: false,
      results: []
    } as SearchState<RawRepository>,
    orderOptions: [
        { label: "🌡️ score", value: "score" },
        { label: "⭐ stargazers", value: "stargazers_count" },
        { label: "⚠️ issues", value: "open_issues_count" }
      ] as OrderOption[],
      orderIndex: 0
  };
},

computed: {
  orderedResults(): RawRepository[] {
    return orderBy(
      this.searchState.results,
      this.orderOptions[this.orderIndex].value,
      "desc"
    );
  }
},

methods: {
  runSearch(): void {
    ...
    this.searchState.loading = true;
    githubAPI
      .get(`/search/repositories?q=${this.searchState.query}`)
      .then((response: AxiosResponse<RawResult<RawRepository>>) => {
        this.searchState.results = response.data.items;
      })
      .finally(() => {
        this.searchState.loading = false;
      });
  }
},

mounted(): void {
  this.runSearch();
}

Features are organized by component options 🧐

Not by logical concerns...

Limitations of Vue 2 🙈

Limited code reuse patterns ⚠️

Mixins 🔎

export default function searchMixinFactory<T>({ ... }) {
  return {
    data() {
      return {
        searchState: {
          query: "vue",
          loading: false,
          results: []
        } as SearchState<T>
      };
    },
    methods: {
      runSearch(): void {
        ...
      }
    },
    mounted() {
      this.runSearch();
    }
  };
}
export default function orderMixinFactory<T>({ ... }) {
  return {
    data() {
      return {
        orderOptions: [
          { label: "🌡️ score", value: "score" },
          { label: "⭐ stargazers", value: "stargazers_count" },
          { label: "⚠️ issues", value: "open_issues_count" }
        ] as OrderOption[],
        orderIndex: 0
      };
    },
    computed: {
      orderedResults(): T[] {
        return orderBy(
          this.searchState.results,
          this.orderOptions[this.orderIndex].value,
          "desc"
        );
      }
    }
  };
}
import { RawRepository } from "@/types";
import searchMixinFactory from "@/mixins/search";
import orderMixinFactory from "@/mixins/order";

export default Vue.extend({
  mixins: [
    searchMixinFactory<RawRepository>({ ... }),
    orderMixinFactory<RawRepository>({ ... })
  ],
  ...
});

Mixins 🚨

⚔️ Can lead to conflicts

🔌 Not really reusable

🕶️ Obscure relationships

🤸‍♂️ Can't be dynamically added

Scoped slots 🔎

<template>
  <GenericSearch :get-results="getRepositories" v-slot="{ query, results, run }">
    <GenericOrder
      :input="results"
      :options="orderOptions"
      v-slot="{ orderedResults }">
      
      <input :value="query" ... />
      <SearchRepository v-for="repository of orderedResults" ... />
    
    </GenericOrder>
  </GenericSearch>
</template>

<script>
export default Vue.extend({
  ...
  methods: {
    getRepositories(query): RawRepository[] {
      return githubAPI
        .get(`/search/repositories?q=${query}`)
        .then((response: AxiosResponse<RawResult<RawRepository>>) => {
       	  return response.data.items
      	});
    }
  }
});
</script>
<template>
  <div>
    <slot v-bind="{ query, results, run }" />
  </div>
</template>

<script>
export default Vue.extend({
  props: {
    getResults: Function
  },
  data() {
    return {
      query: "",
      results: []
    };
  },
  methods: {
    run(): void {
      this.getResults(this.query).then(results => this.results = results);
    }
  }
});
</script>
<template>
  <div>
    <slot v-bind="{ orderedResults }" />
  </div>
</template>

<script>
export default Vue.extend({
  props: {
    input: Array,
    options: Array
  },
  computed: {
    orderedResults() {
      return orderBy(input, options[index].value, "desc");
    }
  }
});
</script>

Scoped slots 🚨

⚙️ Lots of configuration in the template

🤯 Hard to understand

🕵️ Decrease readability

🔒 Less flexible (props from children only available in template)

The Vue 3 solution 💥

The composition functions 🛠️

The setup function 🏠

Completely optional 💁

export default Vue.extend({
  ...
  setup() {
    // search feature
    ...
  
    // order feature
    ...
  
    // filter feature
    ...

    // pagination feature
    ...
  }
});

Should we do this? 🙈

Nope ✋👮

Use Composition Functions 😎

import useSearch from "@/use/search";
import useOrder from "@/use/order";
import useFilter from "@/use/filter";
import usePagination from "@/use/pagination";

export default Vue.extend({
  ...
  setup() {
    return {
       ...useSearch(),
       ...useOrder(),
       ...useFilter(),
       ...usePagination()
    };
  }
});

DEMO

THANK YOU! 🙏

Introduction to the Vue Composition API

By Nicolas Payot

Introduction to the Vue Composition API

  • 888