This is 10 days of my participation in the November Gwen Challenge. See details of the event: The last Gwen Challenge 2021

As components get larger, the different logical concerns become separated and difficult to maintain.

We can solve this problem using the Composition API.

The Setup function

The use of this

The setup function is called before parsing the other component options, so the this we use here does not refer to an instance of the reshuffled.

Use with templates

The contents returned by the setup function are exposed, and we can use variables or methods directly in the template.

const app = Vue.createApp({
    setup(props) {
        return {
            name: 'Jack'.handleClick() { alert('this is setup')}}},template:/*html*/ ` 
      
{{name}}
`
}) Copy the code

Responsive variable

ref

The variable returned by the following code is not reactive, that is, the name variable returned after 2 seconds remains unchanged:

setup(props) {
    let name = 'Jack';
    setTimeout(() = > {
        name = 'Joe'
    }, 2000);
    return { name }
}
Copy the code

We can use ref responsive variables. It encapsulates data through proxy. When data changes, the template and other contents are updated.

Ref deals with basic types of data, such as strings, numbers, and so on. In the following example, ref uses proxy to change ‘Jack’ to a reactive reference to proxy({value: ‘Jack’}). Use the value property to modify the value, using the variable name directly in the component:

const app = Vue.createApp({
    setup(props) {
        const { ref } = Vue;
        let name = ref('Jack');
        setTimeout(() = > {
            name.value = 'Joe'
        }, 2000);
        return { name }
    },
    template:/*html*/ ` 
      
{{name}}
`
}) Copy the code

reactive

Reactive can handle non-basic types of data, such as objects, arrays, and so on. In the following example, reactive uses proxy to change {name: ‘Jack’} to a reactive reference to proxy({name: ‘Jack’}) :

const app = Vue.createApp({
    setup(props) {
        const { reactive } = Vue;
        const nameObj = reactive({ name: 'Jack' });
        setTimeout(() = > {
            nameObj.name = 'Joe'
        }, 2000);
        return { nameObj }
    },
    template:/*html*/ ` 
      
{{nameObj.name}}
`
}) Copy the code

We can use readonly to data the object referenced responsively, returning read-only data.

const app = Vue.createApp({
    setup(props) {
        const { reactive, readonly } = Vue;
        const nameObj = reactive([123]);
        const copy = readonly(nameObj)
        setTimeout(() = > {
            nameObj[0] = '456';
            copy[0] = '456'; // Error: Copy object is read-only
        }, 2000);
        return { nameObj }
    },
    template:/*html*/ ` 
      
{{nameObj[0]}}
`
}) Copy the code

Data using the ES6 structure is not responsive because it returns a value instead of an object, i.e. Name === ‘Jack’ :

const app = Vue.createApp({
    setup(props) {
        const { reactive } = Vue;
        const nameObj = reactive({ name: 'Jack' });
        setTimeout(() = > {
            nameObj.name = 'Joe';
        }, 2000);
        const { name } = nameObj; / / ES6 deconstruction
        return { name }
    },
    template:/*html*/ ` 
      
{{name}}
`
}) Copy the code

toRefs

Using toRefs for deconstruction solves this problem:

const app = Vue.createApp({
    setup(props) {
        const { reactive, toRefs } = Vue;
        const nameObj = reactive({ name: 'Jack' });
        setTimeout(() = > {
            nameObj.name = 'Joe';
        }, 2000);
        const { name } = toRefs(nameObj);
        return { name }
    },
    template:/*html*/ ` 
      
{{name}}
`
}) Copy the code

ToRefs converts proxy({name: ‘Jack’}) to {name: proxy({value: ‘Jack’})}. Name === proxy({value: ‘Jack’})

toRef

Undefined is returned when a toRefs object is deconstructed into a non-existent property using ES6.

We can use toRef to solve this problem.

const app = Vue.createApp({
    setup(props) {
        const { reactive, toRef } = Vue;
        const data = reactive({ name: 'Jack' });
        const age = toRef(data, 'age');
        setTimeout(() = > {
            age.value = '18';
        }, 2000);
        return { age }
    },
    template:/*html*/ ` 
      
{{age}}
`
}) Copy the code

The Setup parameters

The setup function takes two arguments, props and context.

The props parameter is a responsive prop attribute passed from the parent component and cannot be deconstructed using ES6. ToRefs and toRef can be used instead.

The context parameter contains three properties, attrs, slots, and emit. These three attributes are equivalent to this.$attrs, this.$slots, and this.$emit used in the component object.

Access the component’s property

Since the component instance is not created when setup is executed, we can only access the parameters passed by the parent component, namely props, attrs, slots, emit.

Using render functions

If the setup function returns a render function, the component will use the render function directly to generate the DOM. This can be implemented with slots:

const app = Vue.createApp({
    template:/*html*/ ` 
      
       

parent

`
}) app.component('child', { setup(props, context) { const { h } = Vue; const { attrs, slots, emit } = context; return () = > h('div', {}, slots.default()) } }) Copy the code

Emit can be used in the setup function:

const app = Vue.createApp({
    methods: { handleChange() { alert('change'); }},template:/*html*/ `<child @change='handleChange'></child>`
})

app.component('child', {
    setup(props, context) {
        const { attrs, slots, emit } = context;
        function handleClick() { emit('change')};return { handleClick }
    },
    template:/*html*/`<div @click='handleClick'>child</div>`
})
Copy the code

Thus, the syntax in the setup function can replace the traditional syntax.

TodoList

The following code implements a TodoList using the setup function:

const app = Vue.createApp({
    setup(props) {
        const { ref, reactive } = Vue;
        const inputValue = ref(' ');
        const list = reactive([]);
        const handleChange = (e) = > {
            inputValue.value = e.target.value
        }
        const handleSubmit = () = > {
            list.push(inputValue.value)
        }
        return {
            inputValue,
            handleChange,
            handleSubmit,
            list
        }
    },
    template:/*html*/ ` 
      
  • {{item}}
`
}) Copy the code

However, we found that the code that implements the input function and the code that implements the commit function were combined, and we needed to separate them to make the code structure clearer.

// Encapsulates the operations on the list
const listRelativeEffect = () = > {
    const { reactive } = Vue;
    const list = reactive([]);
    const handleSubmit = (item) = > {
        list.push(item)
    }
    return {
        list,
        handleSubmit
    }
}

// Encapsulates the operation on inputValue
const inputRelativeEffect = () = > {
    const { ref } = Vue;
    const inputValue = ref(' ');
    const handleChange = (e) = > {
        inputValue.value = e.target.value
    }
    return {
        inputValue,
        handleChange
    }
}

const app = Vue.createApp({
    setup(props) {
        // Process scheduling transfer
        const { list, handleSubmit } = listRelativeEffect();
        const { inputValue, handleChange } = inputRelativeEffect();
        return {
            inputValue, handleChange,
            handleSubmit, list
        }
    },
    template:/*html*/ ` 
      
  • {{item}}
`
}) Copy the code

This improves the maintainability of the code.

Computed properties

We can use a computed method imported from Vue to pass a parameter, which is a getter-like callback function, to get a read-only responsive reference. To access the value of the newly created computed variable, we need to use.value property like ref.

const app = Vue.createApp({
    setup(props) {
        const { ref, computed } = Vue;
        const count = ref(0);
        const handleClick = () = > { count.value += 1; };
        const countAdd5 = computed(() = > { return count.value + 5; })
        return { count, handleClick, countAdd5 }
    },
    template:/*html*/ ` 
      
{{count}}--{{countAdd5}}
`
}) Copy the code

A calculated property can also accept setters and getters as arguments in the form of objects:

const app = Vue.createApp({
    setup(props) {
        const { ref, computed } = Vue;
        const count = ref(0);
        const handleClick = () = > { count.value += 1; };
        const countAdd5 = computed({
            get: () = > {
                return count.value + 5;
            },
            set: (param) = > {
                count.value = param - 5; }});setTimeout(() = > {
            countAdd5.value = 100;
        }, 1000);
        return { count, handleClick, countAdd5 }
    },
    template:/*html*/ ` 
      
{{count}}--{{countAdd5}}
`
}) Copy the code

Watch changes in response

The Watch function takes three parameters: a reactive reference or getter function that you want to listen for, a callback, and optional configuration options.

const app = Vue.createApp({
    setup(props) {
        const { ref, watch } = Vue;
        const name = ref('Jack');
        watch(name, (newValue, oldValue) = > {
            console.log(newValue, oldValue)
        })
        return { name }
    },
    template:/*html*/`<input v-model='name'>`
})
Copy the code

When the first parameter of watch is a property on Reactive, we need to convert it to a getter, otherwise it is not a reactive reference:

const app = Vue.createApp({
    setup(props) {
        const { reactive, watch, toRefs } = Vue;
        const nameObj = reactive({ name: 'Jack' });
        // The first argument is () => nameobj.name
        watch(() = > nameObj.name, (newValue, oldValue) = > {
            console.log(newValue, oldValue)
        })
        const { name } = toRefs(nameObj);
        return { name }
    },
    template:/*html*/`<input v-model='name'>`
})
Copy the code

Passing arguments as an array allows a listener to listen for multiple arguments at the same time:

const app = Vue.createApp({
    setup(props) {
        const { reactive, watch, toRefs } = Vue;
        const nameObj = reactive({ name: 'Jack'.age: '18' });
        watch(
            [() = > nameObj.name, () = > nameObj.age],
            ([newName, newAge], [oldName, oldAge]) = > {
                console.log(newName, newAge, The '-', oldName, oldAge)
            }
        )
        const { name, age } = toRefs(nameObj);
        return { name, age }
    },
    template:/*html*/` name:  age:  `
})
Copy the code

The watch function is lazy and will not be executed immediately after rendering the DOM.

WatchEffect is non-lazy and executes immediately after rendering the DOM. The function automatically detects external dependencies in the code and re-executes the entire code when changes occur. You don’t need to pass many arguments, just the callback function. WatchEffect cannot retrieve previous data.

const app = Vue.createApp({
    setup(props) {
        const { reactive, toRefs, watchEffect } = Vue;
        const nameObj = reactive({ name: 'Jack'.age: '18' });
        watchEffect(() = > {
            console.log(nameObj.name);
        });
        const { name, age } = toRefs(nameObj);
        return { name, age }
    },
    template:/*html*/` name:  age:  `
})
Copy the code

Watch and watchEffect can be saved into a function object that can be called to cancel listening.

const stop = watch(
    [() = > nameObj.name, () = > nameObj.age],
    ([newName, newAge], [oldName, oldAge]) = > {
        console.log(newName, newAge, The '-', oldName, oldAge);
        setTimeout(() = > {
            stop();
        }, 2000); })Copy the code

Watch accepts the third parameter as a configurable option, which can be set to a non-lazy function here:

watch(
    [() = > nameObj.name, () = > nameObj.age],
    ([newName, newAge], [oldName, oldAge]) = > {
        console.log(newName, newAge, The '-', oldName, oldAge);
    },
    { immediate: true})Copy the code

Declare the cycle hook

Various periodic functions are available in the optional API. Since the setup function is a function that executes before the component instance is fully initialized, the beforeCreate and Created hooks are not required. The code in these hooks can be written directly in setup.

You can access a component’s lifecycle hook by prefixing it with “on”. For example, onMounted indicates the mounted hook of the component. These hooks accept a callback function as an argument.

const app = Vue.createApp({
    setup(props) {
        const { onBeforeMount } = Vue;
        onBeforeMount(() = > {
            console.log('beforeMount')});return{}},template:/*html*/` 
      
Hello World
`
}) Copy the code

The renderTracked hook in the option API recollects responsive dependencies after each DOM rendering, and renderTriggerd executes automatically each time the page fires.

Provide / Inject

Provide and inject can be used in setup like this:

const app = Vue.createApp({
    setup(props) {
        const { provide } = Vue;
        provide('name'.'Jack')
        return{}},template:/*html*/` 
       `
})

app.component('child', {
    setup(props) {
        const { inject } = Vue;
        const name = inject('name');
        return { name }
    },
    template:/*html*/` 
      
{{name}}
`
}) Copy the code

Using dojo.provide

The Provide function takes two arguments, one key and one value. The key value is passed as a string, and the value can be a string or an object.

If you want to pass multiple values, you can refactor:

provide('location'.'North Pole')
provide('geolocation', {
  longitude: 90.latitude: 135
})
Copy the code

Use the Inject

The Inject function takes two parameters, one is the key it wants to receive and the other is the default value. The second argument returns the value of the second argument if the key cannot be found. The second parameter is optional.

responsiveness

We can use refs or Reactive to add responsiveness to values while providing them.

const app = Vue.createApp({
    setup(props) {
        const { provide, ref } = Vue;
        provide('name', ref('Jack'))
        return{}},template:/*html*/`<child/>`
})

app.component('child', {
    setup(props) {
        const { inject } = Vue;
        const name = inject('name');
        const handleClick = () = > {name.value = 'Joe'}
        return { name, handleClick }
    },
    template:/*html*/`<div @click='handleClick'>{{name}}</div>`
})
Copy the code

However, for the sake of data stability, changes to reactive data are generally limited to components that are provided.

const app = Vue.createApp({
    setup(props) {
        const { provide, ref } = Vue;
        const name = ref('Jack');
        provide('name', name);
        provide('changeName'.(value) = > { name.value = value; })
        return{}},template:/*html*/`<child/>`
})

app.component('child', {
    setup(props) {
        const { inject } = Vue;
        const name = inject('name');
        const changeName = inject('changeName')
        const handleClick = () = > { changeName('Joe')}return { name, handleClick }
    },
    template:/*html*/`<div @click='handleClick'>{{name}}</div>`
})
Copy the code

To ensure that the provided data does not change, we can use readonly for the provided data:

const app = Vue.createApp({
    setup(props) {
        const { provide, ref, readonly } = Vue;
        const name = ref('Jack');
        provide('name', readonly(name));
        provide('changeName'.(value) = > { name.value = value; })
        return{}},template:/*html*/`<child/>`
})
Copy the code

The template reference

In a composite API, you can use ref like this:

const app = Vue.createApp({
    setup(props) {
        const { ref, onMounted } = Vue;
        const hello = ref(null);
        onMounted(() = > {
            console.log(hello.value) // <div>Hello World</div>
        });
        return { hello }
    },
    template:/*html*/`<div ref='hello'>Hello World</div>`
})
Copy the code

Here we expose Hello in the render context and bind it to the div as its ref with ref=’hello’.