Writing is the development and study of one’s own thoughts.

preface

The purpose of this article is to give an easy and understandable description of the concept, syntax and functions of “compound API”, as a continuation of my own learning and phased achievements.

What is a compound API?

Compound apis are new to Vue3.0, as opposed to “optional apis”, a new form of component writing. At the syntactic level, it consists mainly of setup functions and lifecycle hooks called within them, typically with some reactive apis (next article).

  • Setup function entry to the composite API, typically used as a component option.

    export default {
      props: {
        name: {
          type: String,}},setup(props,context) {
        The setup option is a function that accepts props and context
        console.log(props); // { name: '' }
        const defaultName = props.name||'Code nong Fat Sea'
        // Anything returned here can be used for the rest of the component, the template, and for refs to access the component instance
        return{ defaultName }; }};Copy the code

    Note that you should avoid using this in setup because the setup option is executed before the component is created, before the component instance is produced.

  • Lifecycle hooks can be registered by importing the onX function directly

    import { onMounted, onUpdated, onUnmounted } from 'vue'
    
    const MyComponent = {
      setup() {
        onMounted(() = > {
          console.log('mounted! ')
        })
        onUpdated(() = > {
          console.log('updated! ')
        })
        onUnmounted(() = > {
          console.log('unmounted! ')}}}Copy the code

    This is consistent with the Vue lifecycle, except for create, which is the setup phase itself.

    See the mapping between optional API lifecycle options and composite apis for more details

  • <script setup>

    The new writing of the compound API in Vue3.2 is a compile-time syntactic sugar that uses the composite API in a single file component.

    <script setup>
    console.log('hello script setup')
    </script>
    Copy the code

    The code inside is compiled into the contents of the component Setup () function, which is executed each time a component instance is created. Methods such as defineProps and defineEmits are built in to provide functions and context similar to those in the setup() function.

    All of the variables defined in the interim can be used directly in the template, including improt imported components or methods (official: top-level bindings are exposed to the template). However, when you need to access the component instance through the template ref or $parent, you need to specify the attributes to expose using defineExpose.

    <template> <div class="c-count-wrap"> <span class="c-count-num">{{ currentTime }}</span> </div> </template> <script setup> let currentTime = ref(props.initialValue); let timer = null; // timer //... Const stop = () => {clearInterval(timer); }; // Define the attribute and method defineExpose({stop,}) to expose; </script>Copy the code

    Compared to ordinary

    • Less boilerplate content, cleaner code.
    • You can declare props and throw events using pure Typescript.
    • Better runtime performance (their templates are compiled into renderers of the same scope as them, without any intermediate proxies).
    • Better IDE type inference performance (less work for the language server to extract types from code).

Second, what problem does it appear to solve?

Can help us better code logic aggregation and reuse.

There is a downside to the opt-in API approach when a component contains more functionality and becomes more complex. It leads to a distraction of logical focus, which in turn makes it difficult to understand and maintain components. This is best illustrated by an example of a large component in the official documentation (the logical concerns are grouped by color).

The compound API allows us to spread these around data, computed, methods, filters… In order to realize the aggregation and reuse of code logic. Similar to mixins, but more flexible than mixins, without variable overrides and unknown data sources.

Three, take an example

Use an example to feel the difference between “alternative” and “compound”.

Here’s an example of a countdown component that was implemented a long time ago. The code has been simplified, but the sparrow is small, all the organs are perfect for practice. The full code is available on Github

! [image-20211211172627033](/Users/zhp/Library/Application Support/typora-user-images/image-20211211172627033.png)

The template section is simple, consisting of a two-color SVG image with a number in the middle.

<template>
  <div class="c-count-wrap">
    <svg xmlns="http://www.w3.org/200/svg" height="110" width="110">
      <circle cx="55" cy="55" r="50" fill="none" stroke="#ccc" stroke-width="5" stroke-linecap="round"/>
      <circle class="c-count-process" cx="55" cy="55" r="50" fill="none" stroke="#ff9800" stroke-width="5" :stroke-dasharray="`${process},10000`"/>
    </svg>
    <span class="c-count-num">{{ currentTime }}</span>
  </div>
</template>
<style>
.c-count-wrap {
  display: inline-block;
  position: relative;
  font-size: 0;
}
.c-count-wrap .c-count-num {
  position: absolute;
  display: inline-block;
  top: 50%;
  left: 0;
  width: 100%;
  text-align: center;
  transform: translateY(-50%);
  font-size: 14px;
  white-space: nowrap;
}
.c-count-wrap .c-count-process {
  transform-origin: 55px 55px;
  transform: rotate(-90deg);
}
</style>
Copy the code

The Js part has 2 inputs, 1 calculated property, 3 methods, and 1 event.

Implementation of optional APIS

<script> export default { props: { initialValue: { type: Number, default: 10, }, autoPlay: { type: Boolean, default: true, }, }, data() { return { currentTime: 0, timer: null, }; }, computed: {// Ring progress bar process() {const totalTime = this.InitialValue; const currentPercent = parseFloat(this.currentTime / totalTime).toFixed( 2 ); const circleLength = Math.floor(2 * Math.PI * 50); return currentPercent * circleLength; }, }, created() { this.currentTime = this.initialValue; if (this.autoPlay) { this.start(); } }, methods: { start() { clearInterval(this.timer); this.timer = setInterval(() => { if (this.currentTime <= 0) { clearInterval(this.timer); $emit("turnOver"); return; } this.currentTime -= 1; }, 1000); }, stop() { clearInterval(this.timer); }, reset() { this.stop(); this.currentTime = this.initialValue; ,}}}; </script>Copy the code

Implementation of a compound API

<script>
import { ref, computed } from 'vue'
export default {
  props: {
    initialValue: {
      type: Number.default: 10,},autoPlay: {
      type: Boolean.default: true,}},setup(props,context){
    let currentTime = ref(props.initialValue)
    let timer = null / / timer

    const start = () = > {
      clearInterval(timer);
      timer = setInterval(() = > {
        if (currentTime.value <= 0) {
          clearInterval(timer);
          // Send events
          context.emit("turnOver");
          return;
        }
        currentTime.value -= 1;
      }, 1000);
    }
    const stop = () = > {
      clearInterval(timer);
    }
    const reset = () = > {
      stop()
      currentTime.value = props.initialValue;
    }
    // Loop progress bar
    const process = computed(() = >{
      const totalTime = props.initialValue;
      const currentPercent = parseFloat(currentTime.value / totalTime).toFixed(
        2
      );
      const circleLength = Math.floor(2 * Math.PI * 50);
      return currentPercent * circleLength;
    })

    if (props.autoPlay) {
      start();
    }

    return {
      currentTime,
      process,
      start,
      stop,
      reset
    }
  }
};
</script>
Copy the code

Script Setup version implementation

<script setup>
// All of the variables defined here can be used directly in the template, including the improt imported components or methods. However,
// When you need to access the component instance through the template ref or $parent, you need to specify the attributes to expose using defineExpose
import { ref, computed } from 'vue'

// Declare props using defineProps
const props = defineProps({
  initialValue: {
    type: Number.default: 10,},autoPlay: {
    type: Boolean.default: true,}});// Declare emits with defineEmits
const emit = defineEmits(["turnOver"]);

let currentTime = ref(props.initialValue);
let timer = null; / / timer

const start = () = > {
  clearInterval(timer);
  timer = setInterval(() = > {
    if (currentTime.value <= 0) {
      clearInterval(timer);
      emit("turnOver");
      return;
    }
    currentTime.value -= 1;
  }, 1000);
};
const stop = () = > {
  clearInterval(timer);
};
const reset = () = > {
  stop()
  currentTime.value = props.initialValue;
};
// Loop progress bar
const process = computed(() = > {
  const totalTime = props.initialValue;
  const currentPercent = parseFloat(currentTime.value / totalTime).toFixed(2);
  const circleLength = Math.floor(2 * Math.PI * 50);
  return currentPercent * circleLength;
});

if (props.autoPlay) {
  start();
}

// Specify the attributes to expose using defineExpose
defineExpose({
  start,
  stop,
  reset,
});
</script>
Copy the code

conclusion

The compound API is a new component writing form added to Vue3, as opposed to the “optional API”. To solve the problem of logical separation of concerns in large and complex components with optional apis. It can help us better code aggregation and reuse.

The resources

  1. V3.cn.vuejs.org/guide/compo…