Close read Vue official documentation series πŸŽ‰


Note: This post is based more on the @vue/ composition-API library.

What is the Composition-API ?

The core purpose of composition-API is to reuse code. Composition-api gives developers access to Vue’s underlying responsive system, as opposed to the traditional Options API, which handles the objects returned by data itself. The composition-API now requires the developer to manually define reactive data in setup.

The disadvantage is that the definition of reactive data is no longer simple and convenient, while the advantage is that the timing and location of the definition of reactive data are no longer strictly limited, and can be assembled more flexibly.

The Options API splits function code based on different Options (categories), such as data in function to data, method logic to Methods, and computed properties to computed.

This arrangement is clear, but as the amount of code increases, it becomes less readable and presents a challenge for reuse of component logic. For example, mixins that still use this approach to reuse their code will cause naming conflicts and unclear data sources.

Composition APIS treat a feature as a whole. The whole itself includes options such as data, methods, computed, and life-cycle, and each function is treated as an independent part.

The Composition API now allows you to write your component logic in the same way that you would write functions in traditional JavaScript. You can see that reactive data has to be declared manually, but the benefit is that these reactive objects and functions can be detached from the component. Enabling sharing and reuse across components.

Logical concerns are grouped by color, and as an added bonus, in high-volume code scenarios, there is no need to scroll around with the mouse to navigate between different options for the same functionality.

Composition-API VS Options API

Options API Composition-API
Bad for reuse Easy code reuse, separation of concerns
Potential naming conflicts and unclear source of data sources Clear data source
Context loss Provide better context
Limited type support betterTypeScriptsupport
Supported by API type Organized by function/logic
Organized by function/logic Easy code reuse
Reactive data must be in the component’sdataDefined in the Can be used independently of Vue components

setup

Setup is a new component option that serves as an entry point to the composition-API, and the value is a function that is executed only once to establish a link between data and logic.

Setup execution time is between beforeCreated and created, so this cannot be accessed, and data, methods, computed, etc., cannot be accessed because they have not been resolved.

{
    setup(props, context){
    
        context.attrs; //Attributes
        context.slots; //slots
        context.emit; //tirgger event
        context.listeners; // events
        context.root; // root component instance
        context.parent; // parent component isntance
        context.refs; // all refs
        
        return{}; }}Copy the code

The return value of the setup method is incorporated into the context of the template to participate in the rendering of the data.

API,

getCurrentInstance

Gets the component instance that is currently executing the Setup function. Note that getCurrentInstance can only be executed in setup or a lifecycle hook.

import {getCurrentInstance} from 'composition-api';

setup(props, ctx){
  const vm = getCurrentInstace();
  onMounted(() = >{
   vm =  getCurrentInstance();
  });
}
Copy the code

ref && Ref

Define a reactive REF object with a single property named value inside.

import { ref } from 'composition-api';

setup(props, ctx){
    const title = ref('this is a title! ');
    
    setTimeout(() = >{
       title.value = 'change title text';
    },1000);

    return {title}
}
Copy the code

Type declaration

// The type structure of the ref value
interface Ref<T>{
   value:T 
}

// The type structure of the ref function
function ref<T> (value:T) :Ref<T>{}
Copy the code

Specifically, we can override the generic parameters passed by default when we call the ref() method, or we can declare an assertion directly using as ref

.

Ref needs to be unpacked in the setup method, but not in the template.

isRef

Check if a value is an object of type Ref. The ref() function already has this functionality by default, and if the value accepted is already of a ref type, nothing is processed, otherwise it is converted to a ref value.

unRef

Syntactic sugar, which functions like isRef(val)? Val. Value: val.

toRef / toRefs

A corresponding REF object is mapped based on a Property on the source responsive object. The REF object still maintains a reactive link to the corresponding property on the source reactive object.

import {reactive, toRef} from 'composition-api';

setup(props, ctx){
    const state = reactive({foo:1.bar:2});
    
    // Inject a ref object from the property of the source responsive object.
    const fooRef = toRef(state, 'foo');
    
    // The reactive link to the source reactive object remains
    fooRef.value = 2;
    console.log(state.foo);
    
    state.foo++;
    console.log (fooRef); }Copy the code

ToRef does not report an error even if the property on the source response to be mapped does not exist. Instead, a new REF object is completely created without links.

ToRefs () is a shortcut to toRef() and is used to convert all properties on a source responsive object to a REF object.

reactive

Create reactive objects that can be deconstructed into references to multiple Ref objects using the toRefs method.

setup(props, ctx){
    const userInfo = reactive({
        firstName:'shen'.lastName:'guotao'
    });
    
    return{... toRefs(userInfo)} }Copy the code

Type declaration:

function reactive<T extends object> (target: T) : UnwrapNestedRefs<T>
Copy the code

This means that the generics accepted by reactive methods must inherit object and then be used as type constraints for passing parameters, whose return value is wrapped around T with UnwrapNestedRefs generics.

Note that if refs and Reactive are used together, the reactVie method can be used to redefine the ref object, which automatically expands the original value of the ref object. Of course, this does not deconstruct the original Ref object.

const foo = ref(' ');
const r = reactive({foo});

r.foo === foo.value;
Copy the code

But you cannot literally add a ref to a reactive object.

const foo = ref(' ');
const r = reactive({});

r.foo = foo; //bad
Copy the code

readonly

Take a reactive object or plain object and return a read-only proxy for them.

import { readonly, toRefs } from 'composition-api';

setup(props, ctx){
    const originalUserInfo = readonly(userInfo);
    
    // override reactive objects
    userInfo = originalUserInfo ;
    
    return {
        ...toRefs(userInfo)
    }
}
Copy the code

isProxy

Check whether the object is a proxy created by Reactive or Readonly.

isReactive

Check whether the object is a reactive agent created by Reactive.

Note: Reactive objects wrapped by Readonly remain true.

isReadonly

Check whether the object is a read-only proxy created by ReadOnly.

toRaw

Returns the original object of the Reactive or Readonly agent. This is an “escape pod” that can be used to temporarily read data without the overhead of proxy access/trace, or to write data without triggering changes.

// The original object
const foo = {};
//readonlyFoo
const readonyFoo = readonly(foo);

//reactiveFoo
const reactiveFoo = reactive(foo);

// Get the original object again

let orignal = toRaw(reactiveFoo);
Copy the code

It is not recommended to preserve persistent references to the original object. Use with caution.

markRaw

Marks an object so that it will never be converted to a proxy. Returns the object itself.

computed

The computed property functionality provided in composition-API is the same as the computed option provided in OptionsAPI.

import {computed} from 'composition-api';

setup(props, ctx){
    const fullName = computed(() = >{
        return userInfo.firstName + userInfo.lastName;
    });
    
    const pass = computed(() = >{
        if(userInfo.score >= 60) return 'pass';
        if(userInfo.score < 60) return 'Fail'})};Copy the code

Computed has a computational cache. But when the computed property is used (in the template), then computed function is necessarily executed once, and then if computed property in computed changes, computed function is executed again, returning the value of the most recent computed property.

watchEffect && watch

watchEffect

  • The side effect method is executed immediately. And it is reexecuted when the internally dependent reactive values change.
  • Dependencies can be collected automatically without specifying listening properties.
  • Can be achieved byonInvalidateCancel listening.
import {reactive, watchEffect, toRefs} from 'composition-api';

setup(props, ctx) {
    const data = reactive({
        num:0.count:0});const stop = watchEffect(() = >{
        // Execute immediately, output 0
        // Re-execute watchEffect every 1 second when the value changes.
        // Count, although updated every 2 seconds, does not trigger the current watchEffect because it is not a dependency of the current watchEffect.
        console.log(data.num);
        
        //nInvalidate(fn) the incoming callback is executed when watchEffect is restarted or stopped.
        onInvalidate(() = > {
            // Cancel the asynchronous API call.
            apiCall.cancel()
        })
    });
    
    setInterval(() = >{
        data.num++;
    },1000);
    
    setInterval(() = >{
        data.count++;
    },2000);
    
    return {
        ...toRefs(data),
        onStop(){stop()}
    }
}
Copy the code

Note that when a side effect function executes a function that changes the reactive data, it can cause an infinite loop.

watch

  • Has lazy execution and does not execute immediately.
  • To clarify which dependencies have changed state and trigger the listener to re-execute, it is possible to listen on multiple dependencies.
  • The ability to obtain the value before and after the state change.
  • You can stop listening manually

// Listen only on reactive objects, not on properties of reactive objects.
watch(data, (newValue, oldValue) = >{
    console.log(newValue,oldValue)
})
Copy the code

Listen to multiple data sources:

import { watch, reactive } from 'vue';
export default {
    setup () {
        const state = reactive({
            count: 0.msg: 'hello'
        })

       const stop =  watch([() = > state.count, () = > state.msg],([count, msg], [prevCount, prevMsg]) = >{
            console.log(count, msg);
            console.log('-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -');
            console.log(prevCount, prevMsg);
        })

        setTimeout(() = >{
            state.count++;
            state.msg = 'hello world';
        },1000);

        return{ state }; }};Copy the code

provide && inject

Composition-api style dependency injection:

Parent:

import { provide, ref } from 'composition-api';

setup(){
    const title = ref('learn vue');
    
    const changeTitle = () = >{ title.value = 'learn vue and typescript! ' };
    
    provide("title", title);
    
    return {changeTitle}
}
Copy the code

Son

import { inject } from 'composition-api';

setup(){
  const title = inject('title');
  
  setTimeout(() = >{title.value ='learn success! '},1000);
  
  return {title}
}
Copy the code

shallowReactive

Only responsivity (shallow responsivity) of the outermost property of the object is handled, so if the outermost property changes, the view is updated, and other layer properties change, the view is not updated.

{
    setup(){
        const obj = {
            x: {y: {z:0}}};const shallowObj = shallowReactive(obj);

        shallowObj.x.y.z=1; // Updates will not be triggered

        return {shallowObj}
    }
}
Copy the code

shallowRef

Reactive only values are processed. For reference values, reactive is not implemented.

customRef

CustomRef is used to create a customRef that explicitly controls the dependency trace and trigger response, takes a factory function with track for the trace and trigger for the response, and returns an object with get and set attributes.

V-model with anti-shake function using custom REF:

<input v-model="text" />
Copy the code
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) = > {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() = > {
          value = newValue
          trigger()
        }, delay)
      },
    }
  })
}

export default {
  setup() {
    return {
      text: useDebouncedRef('hello'),}}}Copy the code

LifeCycle Hooks

Since setup() is executed before beforeCreate, created, so:

  • Not in thesetup()Function.thisBecause the component is not fully instantiated at this point.
  • Not in thesetup()Function.beforeCreate 与 createdTwo composite life cycles.

However, the following lifecycle methods can be used:

  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted
  • onErrorCaptured
  • onRenderTracked
  • onRenderTriggered
import {onMounted} from 'composition-api';

setup(props, ctx){
    onMounted(() = >{
        console.log('mounted');
    });
}
Copy the code

Best practices

ref && reactive

  1. To be able to userefUse as much as possibleref.refBecause there are.valueSo it’s a little bit more intuitiverefObject.
  2. Base type valuesrefDefinition.
  3. This parameter is recommended when the object type has multiple membersreactive.
const n = ref(0);
const data = ref([]);
const mouse = reactive({
    x:0.y:0
});
Copy the code

Ref Automatically unpacks

  1. The template is automatically unpacked.
  2. watchListening values are automatically unpacked.
  3. usereactivepackagingrefObject, automatically unpack
const counter = ref(0);
const rc = reactive({
    foo:1,
    counter
});

rc.counter; // Unpack automatically without unpacking
Copy the code
  1. unrefUnpacking method.

This method is useful when we are not sure whether the value received is of a Ref type, but expect the end result to be of a non-REF type

acceptRefParameter returns a reactive result.

function add (a: Ref<number>, b: Ref<number>) {
    return computer(() = >a.value + b.value);
}
Copy the code

Compatible with non-responsive scenarios

function add (a: Ref<number> | number, b: Ref<number> | number) {
    return computer(() = > unref(a) + unref(b));
}
Copy the code

isRef() && ref()

The ref function has built-in judgment, which is useful when writing indeterminate types.

isRef(foo) ? foo : ref(foo) ==== ref(foo);
Copy the code

It is more useful to return an object made of ref members

It is more useful to return an object made of ref members:

const data = {
    x: ref(0),
    y: ref(1),
    z: ref(2)}Copy the code

When using Es6 deconstruction:

const {x, y ,z} = data;
x.value = 1;
Copy the code

Used by object references, and wrapped by Reactive ().

const rcData = reactive(data);
rcData.x = 1;
Copy the code

Automatic clearance side effects

Use the onUnmounted hook from our encapsulated use method to automatically clean up dependencies, such as event untying and dependency cleanup.

Provide/inject type security

Declare type-safe keys for provide and Inject in a shared module. For example, declare a key in a shared context.ts module.

//context.ts
import {InjectionKey} from '@vue/composition-api'

interface UserInfo {
  name:string;
  id:number;
}

export default const InjectionKeyUser : InjectionKey<UserInfo>  = Symbol(a);Copy the code

2:

import {InjectionKeyUser} from './context';
{
    setup(){
        provide(InjectionKeyUser, {name:'zhangsan'.id:10001})}} {setup(){
        const user = inject(InjectionKeyUser);

        if(user){
            console.log(user.name); }}}Copy the code

State sharing

States can be created and used independently of components. However, the most common method does not support SSR. In order to support SSR, we should share state based on provide/inject.

//context.ts

//....
export default const InjectionKeyState : InjectionKey<State> = Symbol(a);Copy the code
// useState.ts
export function createState () {
    const state = { / * * / };

    return {
        install(app:App){ app.provide(InjectionKeyState, state); }}}export function useState () :State  {
    const {inject} = '@vue/composition-api';
    returninject(InjectionKeyState)! ; }Copy the code

Get the DOM node through ref

<img src="demo.jpg" ref="domRef" />
Copy the code
{
    setup(){
        const domRef = ref(null);
        onMounted(() = >{
            console.log(domRef.value)
        })
        return {domRef}
    }
}
Copy the code

mayBeRef

export type mayBeRef<T> = Ref<T> | T;
Copy the code

Safely deconstruct a Reactive object.

Using ES6 to deconstruct a reactive object defined by a Reactive () method will destroy its reactive characteristics. A good way to do this is to use toRefs() for structure.

const rc = reactive({
    x:0.y:1
});

//bad
const {x, y} = rc;
isRef(x); //false

//good;
const {x, y} = toRefs(rc);
Copy the code

Props cannot be deconstructed using ES6

The methods for setup(props) are a proxy object and cannot be deconstructed directly using ES6.

Use $nextTick and so on in setup

export default {
  setup(props, { root }) {
    
    const { $nextTick } = root;
    console.log($nextTick); }};Copy the code

reference

  • www.yuque.com/vueconf/mkw…
  • Juejin. Cn/post / 685041…
  • Juejin. Cn/post / 689054…