Vue 3: with the development of data, events are more and more outstanding

Time:2021-2-14

Vue 3: Data down, Events up | Vue Mastery – Thorsten Lünborg

preface

In this article, I’ll show you a pattern in which we can apply Vue 3’s new composition API to solve a specific challenge. I won’t introduce all the basics, so I’m familiar with thisBasic knowledge of new APIIt will help you.

Important tips: composition API is an additive and a new feature. It does not and will not replace the good old “options API” you know and love in Vue 1 and 2. As long as you think of this new API as another tool in your toolbox, it may come in handy when using the options API to solve some clumsy problems.


I like Vue’s new composition API. For me, Vue’s reaction system seems to have broken away from the constraints of components, and now I can use it to write any reaction code I want.

In my experience, once you get to know it, it’s a great way to create flexible, reusable code that combines well and shows you how all parts and features of a component interact.

I’ll start this article with a gadget that you can use to make it easier to use components and toolsv-model


summary:v-modelinstructions

If you’ve ever used Vue, you know itv-modelInstruction:

<input type="text" v-model="localStateProperty">

This is a great shortcut to avoid us entering complex template tags, like this:

<input 
  type="text" 
  :value="localStateProperty" 
  @change="localStateProperty = $event.target.value"
>

Best of all, we can also use it on components:

<message-editor v-model="message">

This is equivalent to doing the following:

<message-editor 
  :modelValue="message" 
  @update:modelValue="message = $event"
>

However, in order to implement the Convention of properties (prop) and events (event), our<message-editor>The component must look like this:

<template> 
  <label> 
    <input type="text" :value="modelValue", @change="(event) => $emit('update:modelValue', event.target.value)" > 
  <label>
</template>

<script> export default { 
  props: { 
    'modelValue': String, 
  } 
}
</script>

However, it seems rather lengthy.

We have to, because we can’t write directly to properties. We must issue the correct event and leave it to the parent component to decide how to handle the update we pass, because it is the data of the parent component, not the data<message-editor>Component data. Therefore, in this case, we cannot use it on inputv-model. It’s annoying.

You may know from Vue 2 that there are some patterns in the options API to deal with this problem, but today I want to see how to use the tools provided by the composition API to solve this problem in a concise way.


Challenge: reduce the template

What we want to implement is an abstraction that allows us to use the same object in the inputv-modelShortcut, even if we don’t actually want to write the local state, we want to emit the correct event. This is what we want the template to look like when it’s finished:

<template> 
  <label> 
    <input 
      type="text" 
      v-model="message" 
    /> 
  <label>
</template>

So, let’s use the composition API to achieve this:

import { computed } from 'vue'

export default { 
  props: { 
    'modelValue': String, 
  },
  setup(props, { emit }) { 
    const message = computed({ 
      get: () => props.modelValue, 
      set: (value) => emit('update:modelValue', value) 
    }) 

    return { 
      message,
    } 
  }
}

Well, it’s a new piece of code, maybe a little alien. Let’s break it down

import { computed } from 'vue'

First, let’s importcomputed()Function, which returns a reference (Ref) for evaluating properties — a wrapper for deriving values from other reactive data, such as props.

const message = computed({ 
  get: () => props.modelValue, 
  set: (event) => emit('update:modelValue')
})

In setup, we create such a calculation property, but it is a special property: we have a calculation propertygetterandsetterSo we can actually read its derived value and assign it a new value.

This is the behavior when we calculate properties in JavaScript

message.value
//= > 'this returns a string'
message.value = 'This will be emitted up'
//= > call emit (' onUpdate:ModelValue ', 'This will be fired up')

By returning this computed property from the setup () function, we expose it to the template. Now, we can use it with V-model to get a clean and beautiful template

<template> 
  <label> 
    <input 
      type="text" 
      v-model="message" 
    > 
  <label>
</template>

<script>
import { computed } from 'vue'

export default { 
  props: { 
    'modelValue': String, 
  },
  setup(props, { emit }) { 
    const message = computed({ 
      get: () => props.modelValue, 
      set: (value) => emit('update:modelValue', value) 
    }) 

    return { 
      message, 
    } 
  }
}
</script>

Now the template is very clean. But at the same time, we have to write a bunch of template files in setup () to achieve this. It looks like we just moved the template file from the template to the setup function.

So let’s extract this logic into its own function – a composition function – or “composable” for short.


Convert it to composable

Composable is just a function, we use it to abstract some code from the setup function. Composability is the advantage of this new composition API and the core principle that allows better code abstraction and composition.

This is our goal:

📄 modelWrapper.js

import { computed } from 'vue'

export function useModelWrapper(props) { 
  /*Let's not discuss implementation for now*/
}

📄 MessageEditor.vue

import { useModelWrapper } from '../utils/modelWrapper'

export default { 
  props: { 
    'modelValue': String, 
  } 
  setup(props, { emit }) { 
    return { 
      message: useModelWrapper(props), 
    } 
  }
}

Attention, oursetup()How can the code in a function be reduced to a single line of code (if we assume that we are working in a good editor, it can be added automatically for us)useModelWrapperFor example, vscode).

How do we do that? In fact, all we have to do is tosetupCopy and paste the code in to this new function! This is what it looks like:

📄 modelWrapper.js

import { computed } from 'vue'

export function useModelWrapper(props) { 
  return computed({ 
    get: () => props.modelValue, 
    set: (value) => emit('update:modelValue', value) 
  })
}

OK, it’s easy, but can we do better? Yes, we can!

But to understand how we can do it, we’re going to go around a little bitv-modelHow to work on components

In Vue 3,v-modelIt can be applied to themodelValueOn the other hand. If a component wants to expose multiple properties asv-modelThis is very useful.

v-model:argument

<message-editor 
  v-model="message" 
  v-model:draft="isDraft" 
/>

The second V-model can be implemented as follows:

<input
  type="checkbox" 
  :checked="draft",
  @change="(event) => $emit('update:modelValue', event.target.checked)"
 >

Repeat the lengthy template code. It’s time to adjust the new composition function so that we can reuse it in this example as well.

To do this, we need to add a second parameter to the function that specifies the name of the property we actually want to encapsulate. becausemodelValueyesv-modelWe can also use it as the default name of the second parameter of the wrapper

📄 modelWrapper.js

import { computed } from 'vue'

export function useModelWrapper(props, name = 'modelValue') { 
  return computed({ 
    get: () => props[name], 
    set: (value) => emit(`update:${name}`, value) 
  })
}

this is it. Now we can do anythingv-modelProperty uses this wrapper.

So the final component looks like this:

<template> 
  <label> 
    <input type="text" v-model="message" > 
    <label> <label> 
    <input type="checkbox" v-model="isDraft"> Draft 
  </label>
</template>

<script>
import { useModelWrapper } from '../utils/modelWrapper'

export default { 
  props: { 
    modelValue: String, 
    draft: Boolean
  },
  setup(props, { emit }) { 
    return { 
      message: useModelWrapper(props, 'modelValue'), 
      isDraft: useModelWrapper(props, 'draft') 
    }
  }
}
</script>

next step

This composability is not only in the hope that we willmodelValueProperties are useful when mapping to input in a template, and can also be used to pass calculated property references to other assignable combinations that need to be referenced.

By packaging first, like abovemodelValueSecondly, the combination function may not know the fact that we are not actually dealing with the local state. We’re in a small group that can be combineduseModelWrapperImplementation details are abstracted in, so other composable components can treat it as local state.


“Fast input, otherwise lost”

As an accepted example of stupidity, we have auseMessageResetComposable components of. When you stop typing for five seconds, it will reset your message to an empty string. It’s like this:

function useMessageReset(message) {
  let timeoutId
  const reset = () => {
    if (timeoutId) {
      clearTimeout(timeoutId)
    }
    timeoutId = setTimeout(() => (message.value = ''), 5000)
    watch(message, () => message.value !== '' && reset())
  }
}

This can be used in combinationwatch(), which is another function of the composition API.

  • The callback function in the second parameter runs whenever the message changes.
  • If the message is not empty, it will (restart) time out and reset the message to null after 5 seconds.

Note that this function expects to receive and assign values to a reference that it can monitor.

We’re using itmodelValueProperty because we can’t write it directly. But usinguseModelWrapper, we can provide a writable calculation property reference to this combination:

import { useModelWrapper } from '../utils/modelWrapper'
import { useMessageReset } from '../utils/messageReset'

export default {
  props: { modelValue: Boolean },
  setup(props, { emit }) {
    const message = useModelWrapper(props)
    useMessageReset(message)
    return { message }
  }
}

Notice how this composable component doesn’t knowmessageIt’s actually a parent componentv-modelTo send out an event. It’s like we’re going through a normalref()Again, it can only be assigned to.value

Also note how the rest of our functions are not affected by this. We can still use it in templatesmessage, or use it in other ways.


My last thoughts

The composition API is a great and flexible tool for every Vue developer who wants to use it. It’s just the tip of the iceberg.

This work adoptsCC agreementReprint must indicate the author and the link of this article

I’m a novice and know little about it. I hope many knowledgeable people can give me more advice.