Words: 7887; Reading Time: 40 Minutes click to read the original article

It has been almost eight years since the first version was released on December 8th, 2013. Do you know the meaning of each version name?

The version number The name paraphrase time
V0.9 Animatrix The Matrix animated 2014.2.25
V0.10 Blade Runner Blade runner 2014.3.23
V0.11 Cowboy Bebop The star cowboy 2014.11.7
V0.12 Dragon Ball Dragon ball 2015.6.12
V1.0 Evangelion New Age Evangelion 2015.10.27
V2.0 Ghost in the Shell Ghost in the Shell 2016.9.30
V2.1 Hunter X Hunter Full-time hunter 2016.11.22
V2.2 Initial D Initial D 2017.2.26
V2.3 JoJo’s Bizarre Adventure Fantastic adventures of JoJo 2017.4.2
V2.4 Kill la Kill Cut suit girl 2017.7.13
V2.5 Level E Supernatural E contact 2017.10.13
V2.6 Macross Hyperspace Fortress 2019.2.4
V3.0 One Piece One piece 2020.9.18
V3.1 Pluto The most powerful robot on earth 2021.6.8
V3.2 Quintessential Quintuplets Five equal flowers marry 2021.8.10

The original name of each version is named after the manga, so these anime, how many have you seen?

So let’s focus on Vue3.0.

origin

When a new tool appears, it must be to solve the problem of the existing tool. We often hear that Vue is not suitable for developing large and complex projects, and one of the root reasons is that Vue’s existing apis force us to organize code by options, but sometimes it makes more sense to organize code by logical relationships. Another reason is the current lack of a concise and low-cost mechanism to extract and reuse logic between multiple components.

So let’s take a look at the 2.0 problems and how Vue3 solves them.

Defects in option-style organization code

Options-based organization code has the same function scattered in all options, resulting in the need to skip options such as Data, methods and computed during development.

Vue3 launched the CompositionApi to solve this problem. It combines the logic scattered in various options together. Let’s take a look at the following:

The problem of a Mixin

For complex functions, we might think of using mixins to extract to separate files. Mixins, however, have some usage problems, such as naming conflicts and unclear attribute origins.

Vue3 provides a way of extracting each function from Hooks, which are a separate function, so no more problems.

Poor TypeScript support

Now that TypeScript is standard for large projects, Vue’s current API has a lot of trouble integrating with TypeScript. The main reason is that Vue relies on a simple This context to expose properties. The way we use this now is subtle. (For example, the function under methods refers to the component instance, not the methods object).

In other words, Vue’s existing API wasn’t designed with type derivation in mind, which makes adapting to TypeScript complicated.

Currently, most Vue developers who use TypeScript write components as TypeScript classes (with the help of decorators) through the vue-class-Component library. It must rely on the decorator — a very unstable Stage 2 proposal with many unknowns in implementation details. It’s very risky based on that.

The solution proposed in Vue3 makes more use of common variables and functions that are naturally type-friendly, perfectly enjoys type derivation, and does not require much additional type annotation.

This also means that your JavaScript code is almost always TypeScript code. Even non-typescript developers benefit from better IDE type support.

Better responsiveness and performance

As we all know, Vue2 responds by adding getters and setters to an existing property of an Object via Object.defineProperty, so it can only listen for changes in the value of this property, but not for additions and deletions of Object attributes. In the implementation of Vue 2, there are some performance issues when the subproperties are still objects when the data is converted to responsivity during component initialization. Object.defineproperty is recursively executed to define the responsivity of the subobjects. A common problem is that modifying an array by index, adding attributes directly to an object, does not trigger reactive updates.

In Vue3, Proxy is used to implement responsiveness. It’s not that Proxy itself is better than Object.defineProperty, it’s just the opposite. So why choose Proxy?

Because a Proxy is essentially a hijack of an object, it can listen not only for changes in the value of an object property, but also for additions and deletions of object properties. And in the implementation of response, the use of delayed processing, when the nested deep object, only when its attributes are accessed will be processed attribute response, performance will be improved to some extent.

Supports the global API Treeshaking

Vue3 reconstructs global and local apis, uses ESModule named export access, supports tree-shaking, packages only used functions, users only pay for the actual use of functions, and the reduction of package size also means improved performance.

// vue2
import Vue from 'vue'

Vue.nextTick(() = > {
  // Something DOM related
})

Copy the code
// vue3
import { nextTick } from 'vue'

nextTick(() = > {
  // Something DOM related
})
Copy the code

These are the major changes to Vue, so let’s take a look at what’s new.

New features and changes

Let’s focus on some major non-compatible changes:

Global API

  • Multiple application root instances are supported to prevent global configuration contamination

    // vue2
    // This affects both root instances
    Vue.mixin({
      / *... * /
    })
    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })
    Copy the code
    // vue3
    import { createApp } from 'vue'
    
    const app = createApp({})
    app.mixin({
      / *... * /
    })
    Copy the code

    See the Global Api for details of some other global Api changes.

  • The global API is refactored to treeshakingable

    import { nextTick } from 'vue'
    
    nextTick(() = > {
      // Something DOM related
    })
    // **** Affected API
    // Vue.nextTick
    Var ue. Observable (var ue. Reactive)
    // Vue.version
    Vue.compile (full build only)
    // vue.set (build only)
    // vue.delete (build only)
    
    Copy the code

Templates are related to directives

  • Better use ofv-model

Instead of the original V-model and V-bind. sync modifiers, support bidirectional binding using multiple V-Models in the form of parameters.

<! -- vue2 --> <ChildComponent v-model="pageTitle" :title.sync="title"/> <! @input="(title)=> (pageTitle=title)" :title="title" @update:title="(title)=> (title=title)"/>Copy the code
<! -- vue3 --> <ChildComponent v-model="pageTitle" v-modle:title="title"/> <! @update:modelValue="(title)=> (pageTitle=title)" :title="title" @update:title="(title)=> (title=title)"/>Copy the code
  • <template v-for>The change of the
<! -- vue2 --> <template v-for="item in list"> <div :key="'heading-' + item.id">... </div> <span :key="'content-' + item.id">... </span> </template>Copy the code
<! -- vue 3 --> <template v-for="item in list" :key="item.id"> <div>... </div> <span>... </span> </template>Copy the code
  • v-bindChange of merge order
<! -- vue2 --> <div id="red" v-bind="{ id: 'blue' }"></div> <! -- result --> <div id="red"></div>Copy the code
<! -- vue3 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<! -- result -->
<div id="blue"></div>
Copy the code
  • Remove the V-on. native modifier

    In previous versions, to add a native DOM listener to the root element of a child component, you used the.native modifier.

<! -- vue2 --> <my-component v-on:close="handleComponentEvent" v-on:click.native="handleNativeClickEvent" />Copy the code

In VUe3, all event listeners in a component that are not defined to be triggered by the component will now be added as native event listeners to the root element of the child component (unless inheritAttrs: False is set in the child component’s options).

<! -- vue3 --> <my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" /> <script> export default { emits: ['close'] } </script>Copy the code
  • Supported fragments (multiple nodes)
<! -- vue2 --> <template> <div> <header>... </header> <main>... </main> <footer>... </footer> </div> </template>Copy the code

In VUe2, components must be contained within an element, and multiple root nodes are not supported, which sometimes causes us trouble writing styles, so vue3 supports multiple root nodes.

<! -- vue3 --> <template> <header>... </header> <main v-bind="$attrs">... </main> <footer>... </footer> </template>Copy the code
  • Added Teleport portal

The core of VUE application development is component writing, which encapsulates UI and related behavior into components to build UI. But sometimes a part of a component template logically belongs to that component, and from a technical point of view, it is best to move that part of the template to a location in the DOM other than the Vue app.

For the most common modal window, we want the modal window logic to exist in the component, but on the UI, the element should be mounted to the DOM root node (such as the body) for CSS positioning.

<body>
  <div style="position: relative;">
    <h3>Tooltips with Vue 3 Teleport</h3>
    <div>
      <modal-button></modal-button>
    </div>
  </div>
</body>
Copy the code
const app = Vue.createApp({});

app.component('modal-button', {
  template: `  
       `.data() {
    return { 
      modalOpen: false}}})Copy the code

In the example above, we can see a problem — the modal box is rendered in a deeply nested div, and the position: Absolute of the modal box is referenced by the relative parent of the div. The final effect is affected by the parent position, which may not be what we want.

Teleport provides a clean way to control which parent node in the DOM renders HTML without having to resort to global state or split it into two components.

app.component('modal-button', {
  template: `  
       
        
       `.data() {
    return { 
      modalOpen: false}}})Copy the code

component

  • Functional component

In vue2 we may demand for performance and many of the root node and the use of functional components, when, in vue3 ordinary components for optimized performance, component and function performance, but also supports multiple root nodes, so the application of the functional components scenario is not very necessary, so made some adjustments for functional components:

<! --> <script> export default {functional: true, props: ['level'], render(h, { props, data, children }) { return h(`h${props.level}`, data, children) } } </script> <! <template> --> <template functional> < Component :is=" 'H ${props. Level}' "v-bind="attrs" v-on="listeners" /> </template> <script> export default { props: ['level'] } </script>Copy the code

Functional Option and functional attribute are deleted in VUe3. The two methods cannot be used in VUe3.

In VUe3, a functional component is just a normal function that takes two arguments: props and context.

// vue3
import { h } from 'vue'

const DynamicHeading = (props, context) = > {
  return h(`h${props.level}`, context.attrs, context.slots)
}

DynamicHeading.props = ['level']

export default DynamicHeading
Copy the code
  • Creating asynchronous components

It was previously possible to define an asynchronous component by returning a Promise function:

// vue2
const asyncModal = () = > import('./Modal.vue');
// Or take the configuration
const asyncModal = {
  component: () = > import('./Modal.vue'),
  delay: 200.timeout: 3000.error: ErrorComponent,
  loading: LoadingComponent
}
Copy the code

In Vue3, a new API (defineAsyncComponent) was added to show the definition of asynchronous components.

// vue3
import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

// Asynchronous components with no options
const asyncModal = defineAsyncComponent(() = > import('./Modal.vue'))

// Asynchronous components with options
const asyncModalWithOptions = defineAsyncComponent({
  loader: () = > import('./Modal.vue'),
  delay: 200.timeout: 3000.errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})
Copy the code
  • newemitsOptions to define and validate the custom events emitted
<! -- vue2 --> <template> <div> <p>{{ text }}</p> <button v-on:click="$emit('accepted')">OK</button> </div> </template> <script> export default { props: ['text'] } </script>Copy the code

Vue3 added the emits option to display custom events that define the component. Event listeners that are not declared emits are counted in the component’s $attrs and bound to the root node of the component.

<! -- vue3 --> <template> <div> <p>{{ text }}</p> <button v-on:click="$emit('accepted')">OK</button> </div> </template> <script> export default { props: ['text'], emits: ['accepted'] } </script>Copy the code

emits also supports validation of custom events by changing them to object form.

emits: {
    // There is no validation function
    click: null.// with validation functions
    submit: payload= > {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload! `)
        return false}}}Copy the code

It is strongly recommended that emits be used to log all events that are triggered by each component, with code hints for logged events.

Rendering function

  • Unified Slot API

    Previously, there were two different apis to fetch slots from components (this.$scopedSlots and this.$slots). Now we use this.

  • Integrate $listeners, class, and style into $attrs

With VUe2, we can access attributes and event listeners as follows:

<! -- vue3 --> <template> <label> <input type="text" v-bind="$attrs" v-on="$listeners" /> </label> </template> <script> export default { inheritAttrs: false } </script>Copy the code

In the virtual DOM of Vue 3, event listeners are now just attributes prefixed with on, which makes them part of the $attrs object, so $Listeners have been removed.

<! -- vue3 --> <template> <label> <input type="text" v-bind="$attrs" /> </label> </template> <script> export default { inheritAttrs: false } </script>Copy the code

There are special treatments for class and style attributes in Vue 2’s virtual DOM implementation. Therefore, they are not included in $attrs, which Vue3 simplifies by including all attributes, including class and style, in $attrs.

Custom elements

  • Only in the<component>Element.isprop

The IS attribute cannot be used in vuE3 for normal components and elements, only for component built-in components.

other

  • Lifecycle change

    • destroyedThe lifecycle option has been renamed tounmounted
    • beforeDestroyThe lifecycle option has been renamed tobeforeUnmount
  • Custom directive lifecycle tuning, and component lifecycle alignment

    • Created – New! Called before an element’s attribute or event listener is applied.
    • The bind – beforeMount
    • He inserted – mounted
    • BeforeUpdate: New! This is called before the element itself is updated, much like a component lifecycle hook.
    • Update → Remove! There are too many similarities to update, so this is redundant, please use it insteadupdated.
    • ComponentUpdated – updated
    • BeforeUnmount: New! Similar to a component lifecycle hook, it is called before an element is unloaded.
    • unbind -> unmounted
  • Mixins merge behavior changes

When data() from a component and its Mixin or extends base classes are merged, the merge is now performed ata shallow level.

  • Transition class name change

The name of the transition class v-enter was changed to v-enter-from, and the name of the transition class v-leave was changed to v-leave-from.

  • VNode life cycle events changed

    <! -- vue2 --> <template> <child-component @hook:updated="onUpdated"> </template>Copy the code
    <! -- vue3 --> <template> <child-component @vnode-updated="onUpdated"> </template>Copy the code

Abandoned the API

  • keyCodeAs av-onAndconfig.keyCodesConfiguration.
<! - key code version (abandoned) -- - > < input v - on: keyup. 13 = "submit" / > <! <input V-on :keyup.enter="submit" /> <script> vue.config. keyCodes = {// Void f1:112} </script>Copy the code
  • $on.$off$onceInstance methods have been removed and component instances no longer implement event-firing interfaces

In VUe2 we can implement component communication via EventBus:

// eventBus.js
const eventBus = new Vue()
export default eventBus
Copy the code
// ChildComponent.vue
import eventBus from './eventBus'
export default {
  mounted() {
    // Add an eventBus listener
    eventBus.$on('custom-event'.() = > {
      console.log('Custom event triggered! ')})},beforeDestroy() {
    // Remove the eventBus listener
    eventBus.$off('custom-event')}}Copy the code
// ParentComponent.vue
import eventBus from './eventBus'
export default {
  methods: {
    callGlobalCustomEvent() {
      eventBus.$emit('custom-event') // When ChildComponent is mounted, a message is displayed in the console}}}Copy the code

In VUe3, this approach is no longer valid because the $on, $off, and $once methods are completely removed. If necessary, you can use some external library that implements the event trigger interface, or you can use Provide, which can be complicated by directly accessing Vuex.

  • Filters are no longer supported
<! -- vue2 --> <template> <h1>Bank Account Balance</h1> <p>{{ accountBalance | currencyUSD }}</p> </template> <script> export default { props: { accountBalance: { type: Number, required: true } }, filters: { currencyUSD(value) { return '$' + value } } } </script>Copy the code

In VUe3, methods or computed properties can be used instead:

<! -- vue3 --> <template> <h1>Bank Account Balance</h1> <p>{{ accountInUSD }}</p> </template> <script> export default { props: { accountBalance: { type: Number, required: true } }, computed: { accountInUSD() { return '$' + this.accountBalance } } } </script>Copy the code
  • Delete $childrenproperty

    $Children Property has been removed and is no longer supported. If you need access to child component instances, we recommend using $refs.

  • The global functions set and delete and the instance methods $set and $delete. They are no longer required for agent-based change detection.

Of course, that’s just the appetizer, but what follows is the most noteworthy new feature.

Modular Api

In order to solve the problems of logic reuse and code organization mentioned earlier, VUe3 introduced a new way of writing code. This is the most important feature of VUE3 and the main trend of writing VUE in the future.

Here is a view that displays a list of warehouses for a user, with search and filter functions, with pseudocode as follows:

// src/components/UserRepositories.vue

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { 
      type: String.required: true
    }
  },
  data () {
    return {
      repositories: []./ / 1
      filters: {... },/ / 3
      searchQuery: ' ' / / 2}},computed: {
    filteredRepositories () { ... }, / / 3
    repositoriesMatchingSearchQuery () { ... }, / / 2
  },
  watch: {
    user: 'getUserRepositories' / / 1
  },
  methods: {
    getUserRepositories () {
      // Use 'this.user' to get the user repository
    }, / / 1
    updateFilters () { ... }, / / 3
  },
  mounted () {
    this.getUserRepositories() / / 1}}Copy the code

It can be seen that the code is organized according to option, and the functional logic points are fragmented and scattered among various component options. Especially for some components with a lot of content, it is necessary to jump repeatedly among various options. Reading and writing the code will be a very painful thing, which greatly reduces the maintainability of components.

In fact, when developing and reading component code, we pay more attention to function points than to the options used, which is the problem solved by composite API.

// src/composables/useUserRepositories.js

import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'

export default function useUserRepositories(user) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)
  watch(user, getUserRepositories)

  return {
    repositories,
    getUserRepositories
  }
}
Copy the code
// src/composables/useRepositoryNameSearch.js

import { ref, computed } from 'vue'

export default function useRepositoryNameSearch(repositories) {
  const searchQuery = ref(' ')
  const repositoriesMatchingSearchQuery = computed(() = > {
    return repositories.value.filter(repository= > {
      return repository.name.includes(searchQuery.value)
    })
  })

  return {
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}
Copy the code
// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: {
      type: String.required: true}},setup(props) {
    const { user } = toRefs(props)

    const { repositories, getUserRepositories } = useUserRepositories(user)

    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)

    const {
      filters,
      updateFilters,
      filteredRepositories
    } = useRepositoryFilters(repositoriesMatchingSearchQuery)

    return {
      // Because we don't care about unfiltered warehouses
      // We can expose the filtered results under the name 'Repositories'
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}
Copy the code

Composite apis separate the logical concerns of components, making them more organized and the code more readable and maintainable. Moreover, the reusable logic can be isolated into Hooks for better reusability.

Because of the special nature of composite apis, new apis need to be used, and we’ll take a look at those apis.

setup

Setup is the entry point to the composite API where everything needs to be included, and it is executed only once before the component is created, so this does not point to the current component instance.

setup(props,context){
  const { attrs, slots, emit } = context;
	// ...
}
Copy the code

parameter

  • {Data} props: The props data received is responsive.
  • {SetupContext} context: An object containing context information required by the component, containingattrs,slots,emit.

The return value

  • If an object is returned, the object’s property is passed tosetuppropsThe property in the parameter can be accessed in the template.
<! -- MyBook.vue --> <template> <div>{{ collectionName }}: {{ readersNumber }} {{ book.title }}</div> </template> <script> import { ref, reactive } from 'vue' export default { props: { collectionName: String }, setup(props) { const readersNumber = ref(0) const book = reactive({ title: 'Vue 3 Guide'}) // Exposed to template return {readersNumber, book}}} </script>Copy the code
  • If a render function is returned, the function can directly use the reactive state declared in the same scope.
// MyBook.vue

import { h, ref, reactive } from 'vue'

export default {
  setup() {
    const readersNumber = ref(0)
    const book = reactive({ title: 'Vue 3 Guide' })
    // Note that we need to explicitly call the value of ref
    return () = > h('div', [readersNumber.value, book.title])
  }
}
Copy the code

Lifecycle hook

To make the composite API as functional as the optional API, we also need a way to register the lifecycle hooks in setup. The lifecycle hooks on the composite API have the same name as the optional API, but are prefixed with ON: Mounted looks like onMounted.

import { onMounted, onUpdated, onUnmounted } from 'vue'

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

Setup replaces beforeCreate and created.

Option type API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated

responsive

In VUe3, a Proxy was used instead of Object.defineProperty, allowing Vue 3 to avoid some of the responsiveness problems of earlier versions of Vue.

When we return a normal JavaScript object from a component’s data function, Vue wraps the object in a Proxy with get and SET handlers.

An 🌰 :

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, property, receiver) { / / trap
    track(target, property)  // Trace property reads to collect dependencies
    return Reflect.get(... arguments)// Reflect binds this to Proxy
  },
  set(target, property, value, receiver) {
    trigger(target, property) // Execute the side effect dependency
    return Reflect.set(... arguments) } }const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// tacos
Copy the code
  1. Trace when a value is read: the proxygetIn the processing functiontrackThe function records the property and the current side effect.
  2. Detect when a value changes: Invoked on proxysetProcessing functions.
  3. Rerun the code to read the original value:triggerThe function finds which side effects depend on the property and executes them.

The proxied objects are not visible to the user, but internally, they enable Vue to do dependency tracking and change notification in case the value of the property is accessed or modified.

So how does a component make a render respond to data changes?

The component’s template is compiled into a render function, which creates VNodes, describing how the component should be rendered. This Render function is wrapped in a side effect that allows Vue to keep track of the property being “touched” at run time and perform the corresponding side effect when the property changes to render rerender. Of course, the rendering will not be the whole re-rendering, there are some optimization methods, online information is a lot, not here to expand on.

Let’s take a look at a few commonly used reactive apis.

ref

interface Ref<T> {
  value: T
}
function ref<T> (value: T) :Ref<T>
Copy the code

Takes an internal value and returns a reactive and mutable REF object. The ref object has a single property.value pointing to an internal value.

import { ref } from 'vue'

const counter = ref<number>(0)

console.log(counter) // { value: 0 }
console.log(counter.value) / / 0

counter.value++
console.log(counter.value) / / 1
Copy the code

Because in JavaScript, primitive types such as Number or String are passed by value rather than by reference, there is a wrapper object around any value so that we can safely pass it across the application without worrying about losing its responsiveness somewhere.

Note: Refs nested in reactive objects (such as Reactive, readonly) or used in templates will be unpacked automatically.

reactive

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

Returns a reactive copy of the object, a proxy object that is a deep recursive transformation.

import { reactive } from 'vue'
interface IState{
  count:number
}
// state is now a reactive state
const state = reactive<IState>({
  count: 0,})Copy the code

Ref and reactive:

  • In general, base data types use REF and objects use Reactive
  • If you assign an object to a REF value, the reactive method makes that object highly responsive.

readonly

A read-only proxy that accepts an object (reactive or pure) or ref and returns the original object. A read-only proxy is deep: any nested property accessed is also read-only.

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() = > {
  // For responsiveness tracing
  console.log(copy.count)
})

// Changing original triggers listeners that depend on replicas
original.count++

// Changing copies will fail and cause a warning
copy.count++ / / warning!
Copy the code

unref

If the argument is a ref, the internal value is returned, otherwise the argument itself is returned. This is val = isRef(val), right? Val. value: The syntactic sugar function of val.

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // Unwrapped must now be a numeric type
}
Copy the code

toRef

You can use it to create a new REF for a property on a source responsive object, which maintains a reactive connection to its source property.

const state = reactive({
  foo: 1.bar: 2
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) / / 2

state.foo++
console.log(fooRef.value) / / 3
Copy the code

toRefs

Converts a reactive object to a normal object, where each property of the resulting object is a REF pointing to the corresponding property of the original object.

function useFeatureX() {
  const state = reactive({
    foo: 1.bar: 2
  })

  // Operate on the logic of state

  // Convert to ref on return
  return toRefs(state)
}

export default {
  setup() {
    // Can be destructed without losing responsiveness
    const { foo, bar } = useFeatureX()

    return {
      foo,
      bar
    }
  }
}
Copy the code

To identify whether data has been processed using these apis, you can use these apis: isRef, isProxy, isReactive, and isReadonly.

computed

/ / read-only
function computed<T> (getter: () => T, debuggerOptions? : DebuggerOptions) :Readonly<Ref<Readonly<T>>> // writablefunction computed<T> (
  options: {
    get: () => T
    set: (value: T) => void}, debuggerOptions? : DebuggerOptions) :Ref<T>
interface DebuggerOptions {
  onTrack? : (event: DebuggerEvent) = >void
  onTrigger? : (event: DebuggerEvent) = >void
}
interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}
Copy the code
  • Takes a getter function and returns an immutable reactive ref object based on the return value of the getter.
const count = ref(1)
const plusOne = computed(() = > count.value + 1)

console.log(plusOne.value) / / 2

plusOne.value++ / / error
Copy the code
  • Accept a person withgetsetFunction to create a writable ref object.
const count = ref(1)
const plusOne = computed({
  get: () = > count.value + 1.set: val= > {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) / / 0
Copy the code

watchEffect

function watchEffect(
  effect: (onInvalidate: InvalidateCbRegistrator) => void, options? : WatchEffectOptions) :StopHandle

interface WatchEffectOptions { flush? :'pre' | 'post' | 'sync' // Default: 'pre'onTrack? :(event: DebuggerEvent) = > voidonTrigger? :(event: DebuggerEvent) = > void
}

interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

type InvalidateCbRegistrator = (invalidate: () => void) = > void

type StopHandle = () = > void
Copy the code

Execute a function passed in immediately, trace its dependencies responsively, and re-run the function when its dependencies change.

const count = ref(0)

watchEffect(() = > console.log(count.value))
// -> logs 0

setTimeout(() = > {
  count.value++
  // -> logs 1
}, 100)
Copy the code
  • Stop listening

When watchEffect is called on a component’s setup() function or lifecycle hook, the listener is linked to the component’s lifecycle and stops automatically when the component is uninstalled. It is also possible to explicitly call the return value to stop listening:

const stop = watchEffect(() = > {
  / *... * /
})
// later
stop()
Copy the code
  • Clearance side effect

Sometimes side effects functions perform asynchronous side effects that need to be cleared when they fail. So a function that listens for incoming side effects can take an onInvalidate function as an input parameter to register a callback in the event of a cleanup failure. This invalidation callback is triggered when:

  1. When the side effect is about to be re-executed

  2. The listener is stopped (when the component is unloaded if watchEffect is used in setup() or the lifecycle hook function)

watchEffect(onInvalidate= > {
  const token = performAsyncOperation(id.value)
  onInvalidate(() = > {
    // id has changed or watcher is stopped.
    // invalidate previously pending async operation
    token.cancel()
  })
})
Copy the code

You can also use Flush Option or watchPostEffect and watchSyncEffect to adjust the flush timing.

watch

// Listen on a single source
function watch<T> (
  source: WatcherSource<T>,
  callback: (
    value: T,
    oldValue: T,
    onInvalidate: InvalidateCbRegistrator
  ) => void, options? : WatchOptions) :StopHandle// Listen on multiple sourcesfunction watch<T extends WatcherSource<unknown> > [] (
  sources: T
  callback: (
    values: MapSources<T>,
    oldValues: MapSources<T>,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options? : WatchOptions
) :StopHandle

type WatcherSource<T> = Ref<T> | (() => T)

type MapSources<T> = {[K in keyof T] :T[K] extends WatcherSource<infer V>?V : never} // see 'watchEffect'type declaration of the share optioninterface WatchOptions extends WatchEffectOptions { immediate? :boolean // Default: falsedeep? :boolean
}
Copy the code

Watch needs to listen to specific data sources and perform side effects in a separate callback function. By default, it is also lazy — that is, the callback is invoked only when the listening source changes.

  • withwatchEffectCompared to thewatchAllow us to:
    • Lazy execution of side effects;
    • Be more specific about the state in which the listener should be triggered to restart;
    • Accesses the previous and current values of the monitored state.
// Listen for a getter
const state = reactive({ count: 0 })
watch(
  () = > state.count,
  (count, prevCount) = > {
    / *... * /})// Listen directly on a ref
const count = ref(0)
watch(count, (count, prevCount) = > {
  / *... * /
})
Copy the code
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) = > {
  / *... * /
})
Copy the code

Of course, I’ve just covered common apis; see more reactive apis.

disadvantages

Of course, composite apis are not a silver bullet, at least not yet, and there are still some problems.

  • Ref’s mental burden

Read and write refs must have. Value, syntax redundancy, there is no definite solution to solve this problem. However, refSuger2 has been proposed by THE UNIVERSITY, and it will be up to the community to see how it is received.

<script setup>
  // declaring a variable that compiles to a ref
  let count = $ref(1)

  console.log(count) / / 1

  function inc() {
    // the variable can be used like a plain value
    count++
  }
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>
Copy the code
  • Ugly long return statements

The setup() return statement becomes verbose and repetitive, and the code still skips up and down.

With the ScriptSetup syntax sugar provided in vue3.2, this problem is eliminated.

  • More self-restraint is required

While the composite API provides more flexibility in code organization, it also requires more developer self-restraint to “get it right.” Some people also worry that the API will let inexperienced people write interface bar code. In other words, while the composite API raises the upper limit of code quality, it also lowers the lower limit.

We need to give more thought to how to organize the code properly, and it is recommended to organize the program into functions and modules based on logical concerns.

ScriptSetup

< Script Setup > is a compile-time syntactic sugar for using composite apis in single-file components (SFC). It has many advantages over plain

  • Less boilerplate content, cleaner code.
  • You can declare props and emit 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).
Capitalize > import import {capitalize} from './helpers' // import MyComponent from './ myComponent.vue const msg = 'Hello! Function log() {console.log(MSG)}</ script> <template> <div @click="log">{{MSG}}</div> <div>{{ capitalize('hello') }}</div> <MyComponent /> </template>Copy the code

The code in

Before continuing, let’s look at a word compiler macro that does not need to be imported and will be compiled away when handling

- defineProps
- defineEmits
- defineExpose
- withDefaults
Copy the code

Let’s look at the API unique to < Script Setup > :

  • definePropsDeclare Props, acceptpropsOptions with the same value
const props = defineProps({
  foo: {
    type:String.default:' '}})Copy the code

If you use TypeScript, you can also use pure type declarations to declare Props.

/ / ordinary
const props = defineProps<{
  foo: stringbar? :number} > ()/ / the default value
interfaceProps { msg? :stringlabels? :string[]}const props = withDefaults(defineProps<Props>(), {
  msg: 'hello'.labels: () = > ['one'.'two']})Copy the code
  • defineEmitsDeclare emits, acceptemitsOptions with the same value
/ / ordinary
const emit = defineEmits(['change'.'delete'])
// TS type declaration
const emit = defineEmits<{
  (e: 'change'.id: number) :void
  (e: 'update'.value: string) :void} > ()Copy the code
  • defineExposeDeclare exposed bindings

Components that use < Script Setup > are turned off by default, that is, public instances of components retrieved from the template ref or $parent chain do not expose any bindings declared in < Script Setup >. The developer needs to explicitly declare the exposed properties.

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>
Copy the code
  • useSlotsuseAttrscorrespondingsetupContext.slotssetupContext.attrs, can also be used in normal composite apis.
<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>
Copy the code

There are still some things that

<script> // Ordinary <script>, executed within the scope of the module (only executed once) runSideEffectOnce() // Declare additional options export default {inheritAttrs: False, customOptions: {}} </script> <script setup> // execute in the setup() scope (for each instance) </script>Copy the code

Check out ScriptSetup for more.

other

  • Style of new features

    • The selector
    /* Depth selector */
    .a :deep(.b) {
      /* ... */
    }
    
    /* Slot selector */
    :slotted(div) {
      color: red;
    }
    
    /* Global selector */
    :global(.red) {
      color: red;
    }
    Copy the code
    • <style module>
<template>
  <p :class="$style.red">
    This should be red
  </p>
</template>

<style module>
.red {
  color: red;
}
</style>
Copy the code

You can also customize the name of the injection:

<template>
  <p :class="classes.red">red</p>
</template>

<style module="classes">
.red {
  color: red;
}
</style>
Copy the code

We can use this in the composition API using the useCssModule:

// By default, classes in 
useCssModule()


useCssModule('classes')
Copy the code

Using state-driven dynamic CSS:

<script setup>
const theme = {
  color: 'red'
}
</script>

<template>
  <p>hello</p>
</template>

<style scoped>
p {
  color: v-bind('theme.color');
}
</style>
Copy the code
  • Focus on RFCS, look back on history, and look into the future