After reading the previous chapter, I believe you already know something about VUE-Next (VUE 3.0). This chapter guides you through the VUe-Next functional API, which should help you both read the source code and start studying it when it is released.

The basic example

Directly copy the following code, run to see the effect. Use a higher version of Chrome and open the F12 debugging tool.


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<script src="https://s1.zhuanstatic.com/common/js/vue-next-3.0.0-alpha.0.js"></script>
<div id="app"></div>
<script>
  const { ref, reactive, createApp, watch, effect } = Vue

  function useMouse() {
    const x = ref(0)
    const y = ref(0)

    const update = e= > {
      x.value = e.pageX
      y.value = e.pageY
    }

    Vue.onMounted((a)= > {
      window.addEventListener('mousemove', update)
    })

    Vue.onUnmounted((a)= > {
      window.removeEventListener('mousemove', update)
    })

    return { x, y }
  }

  const App = {
    props: {
      age: Number
    },
    // The entry to the Composition API
    setup(props, context){
      console.log('props.age', props.age)
      // Define response data
      const state  = reactive({name:'zhuanzhuan'});
      // Use public logic
      const {x,y} = useMouse();

      Vue.onMounted((a)= >{
        console.log('When group mount is complete')}); Vue.onUpdated((a)= >{
        console.log('Data updated')}); Vue.onUnmounted((a)= >{
        console.log('Component to be unmounted')})function changeName(){
        state.name = 'walk';
      }

      // Create the monitor and get the stop function
      const stop = watch((a)= > console.log(` watch state. Name: `, state.name))
      // Call the stop function to clear the corresponding monitoring
      // stop()

      // Observe the packing object
      watch((a)= > state.name, (value, oldValue) => console.log(`watch state.name value:${value} oldValue:${oldValue}`))

      effect((a)= > {
        console.log('effect' triggered! The name is:${state.name}Age:${props.age}`)})// Returns the context, which can be used in templates
      return {
        // State: Vue. ToRefs (state), // We can also write this to convert each attribute on state to the responsive data in the form of ref
        state,
        x,
        y,
        changeName,
      }
    },
    template:` < button @ click = "changeName" > name is: {{state. The name}} the mouse x: {{x}} the mouse: {{y}} < / button > `
  }

  createApp().mount(App, '#app', {age: 123});
</script>
</body>
</html>

Copy the code

The motivation

Logical composition and reuse

One of the core issues facing component API design is how to organize logic and how to extract and reuse logic across multiple components. Current Vue 2.x based apis have some common logic reuse patterns, but all have more or less problems. These models include:

  • Mixins
  • Higher-order Components (aka HOCs)
  • Renderless Components (Components based on Scoped slots/scope slots encapsulation logic)

There are plenty of examples of these patterns on the web, so I won’t go into details here. In general, these modes have the following problems:

  • The source of the data in the template is unclear. For example, when multiple mixins are used in a component, it can be difficult to tell from the template which mixin a property comes from. HOC has a similar problem.

  • Namespace conflict. There is no guarantee that mixins developed by different developers won’t use exactly the same properties or method names. HOC has a similar problem with the injected props.

  • Performance. Both HOC and Renderless Components require additional component instances to be nested to encapsulate logic, resulting in unnecessary performance overhead.

As you can see from the useMouse example above:

  • Attributes exposed to the template come from a clear source (returned from a function);
  • The return value can be renamed arbitrarily, so there is no namespace conflict;
  • There is no performance penalty associated with creating additional component instances.

Type inference

A major design goal of VUE-Next is to enhance TypeScript support. The Class API was expected to do this, but after discussion and prototyping, it was decided that Class was not the right way to solve this problem and that class-based apis still have type problems.

Function-based apis are naturally friendly to type derivation, because TS has complete support for function parameters, return values, and generics. What’s more, function-based apis write almost exactly the same code when using TS or native JS.

The setup () function

We will introduce a new component option, Setup (). As the name implies, this function will be where we setup our component logic, which is called after props is initialized when a component instance is created. It provides a unified entry point for using vue-Next’s new Composition API features.

Execution time

The setup function is executed after beforeCreate but before created.

state

There are several main types of state declarations.

The base type

Base types can be declared using the ref API as follows:

const App = {
    setup(props, context){
        const msg = ref('hello')

        function appendName(){
            msg.value = `hello ${props.name}`
        }

        return {appendName, msg}
    },
    template:`<div @click="appendName">{{ msg }}</div>`
}

Copy the code

We know that in JavaScript primitive value types such as string and number have only values and no references. If you return a string variable in a function, the code that receives the string only gets a value and cannot track subsequent changes to the original variable.

Therefore, the point of wrapping objects is to provide a container that allows us to pass values of any type between functions by reference. This is a bit like useRef in React Hooks — except that Vue wrapper objects are also responsive data sources. With such containers, we can pass state back to components by reference in composition functions that encapsulate logic. Components are responsible for presentation (tracking dependencies) and composite functions are responsible for managing state (triggering updates) :

setup(props, context){
    // x,y may be modified by code inside useMouse() to trigger updates
    const {x,y} = useMouse();

    return { x, y }
}
Copy the code

Wrapping objects can also wrap data that is not of primitive value types, and the nested properties of the wrapped object are traced responsively. Wrapping an object or an array with a wrapper object is not trivial: it allows us to replace the value of the entire object with, say, a filter array:

const numbers = ref([1.2.3])
// Replace the original array with the same reference
numbers.value = numbers.value.filter(n= > n > 1)
Copy the code

As a bonus, you may have noticed in the first example of the base type that although the MSG returned by setup() is a wrapper object, in the template we used a binding like {{MSG}} directly without.value. This is because when a wrapper object is exposed to a template rendering context, or nested within another reactive object, it is automatically unwrapped as an internal value.

Reference types

A reference type can be declared using ref or reactive.

const App = {
    setup(props, context){
        const state  = reactive({name:'zhuanzhuan'});

        function changeName(){
            state.name = 'walk';
        }

        return {state, changeName, msg}
    },
    template:''
}

Copy the code

Receiving props data

  • inpropsDefines the parameter names that the current component allows to be passed from outside:
props: {
    age: Number
}
Copy the code
  • throughsetupThe first parameter to the function, receivepropsData:
setup(props) {
  console.log('props.age', props.age)

  watch((a)= > props.age, (value, oldValue) => console.log(`watch props.age value:${value} oldValue:${oldValue}`))}Copy the code

In addition, the change of a prop can be observed directly through the watch method. Why is this? Because props is an object wrapped in reactive in the source code, it is responsive, so the callbacks in the Watch method automatically collect the dependencies and then call the callback logic when the age changes.

context

The second parameter to the setup function is a context object that contains some useful properties that need to be accessed through this in vue 2.x. What if I want to use this to access some built-in properties in vue2? Such as attrs or emit. We can use the second parameter of setup, in vue-next, which is accessed as follows:

const MyComponent = {
  setup(props, context) {
    context.attrs
    context.slots
    context.parent
    context.root
    context.emit
    context.refs
  }
}
Copy the code

Note: == this== is not accessible in the setup() function

Reactive () function

The Reactive () function receives a normal object and returns a reactive data object.

The basic grammar

Vue 3.x provides a reactive() function to create a reactive data object. The basic code example is as follows:

// Create a responsive data object with a state similar to that returned by data() in vue 2.x
const state  = reactive({name:'zhuanzhuan'});
Copy the code

Define reactive data for use by the Template

  1. Import reactive functions as needed:
const { reactive } = Vue
Copy the code
  1. insetup()Call from a functionreactive()Function to create a responsive data object:
const { reactive } = Vue

setup(props, context){
    const state  = reactive({name:'zhuanzhuan'});

    return state
}
Copy the code
  1. intemplateTo access responsive data:
template:
Copy the code

Value Unwrapping (automatic Unwrapping of wrapping objects)

Ref () function

The ref() function is used to create a responsive data object based on the given value. The return value of the ref() call is an object containing only a.value attribute.

The basic grammar

const { ref } = Vue

// Create a responsive data object age with an initial value of 3
const age = ref(3)

// To access the value of a reactive data object created by ref(), you must use the.value attribute
console.log(age.value) 3 / / output
// set age to +1
age.value++
// Print the age value again
console.log(age.value) / / output 4
Copy the code

Access the reactive data created by ref in the template

  1. insetup()To create responsive data:
setup() {
 const age = ref(3)

     return {
         age,
         name: ref('zhuanzhuan')}}Copy the code
  1. intemplateTo access responsive data:
template:'

Name is: {{name}}, age is {{age}}

'
Copy the code

Access reactive data created by a REF in a Reactive object

When a reactive object created by ref() is mounted to reactive(), the reactive object is automatically expanded to its original value and can be accessed directly without.value.

In other words, when a wrapper object is referenced as a property of another responsive object, it is automatically expanded. For example:

const age = ref(3)
const state = reactive({
  age
})

console.log(state.age) 3 / / output
state.age++            // There is no need to pass.value to access the original value directly
console.log(age)       / / output 4
Copy the code

The details of wrapping objects may seem complicated, but there’s only one basic rule to keep in mind: you only need to use.value to fetch the internal values of a wrapped object if you refer to them directly as variables — you don’t even need to know they exist in the template.

Note: The new ref overwrites the old ref, as shown in the following code: ==

// Create a ref and mount it to Reactive
const c1 = ref(0)
const state = reactive({
  c1
})

// Create ref again and call it c2
const c2 = ref(9)
// replace old ref c1 with new ref c2
state.c1 = c2
state.c1++

console.log(state.c1) / / output 10
console.log(c2.value) / / output 10
console.log(c1.value) / / 0
Copy the code

IsRef () function

IsRef () is used to determine whether a value is an object created by ref(). Application scenario: Expand a value that might have been created by ref(), for example:

const { isRef } = Vue

const unwrapped = isRef(foo) ? foo.value : foo
Copy the code

ToRefs () function

const { toRefs } = Vue

setup() {
    // Define reactive data objects
	const state = reactive({
      age: 3
    })

    // Define the event handlers available on the page
    const increment = (a)= > {
      state.age++
    }

    Setup returns an object for the page to use
    // This object can contain either responsive data or event handlers
    return {
      // Convert each attribute on state to reactive data in ref form. toRefs(state),// Increment event handler
      increment
    }
}
Copy the code

The response data from the setup() return can be accessed directly on the page:

template:` < div > < p > the current age value is: {{age}} < / p > < button @ click = "increment" > + 1 < / button > < / div > `
Copy the code

The computed () function

Computed () is used to create computed properties, and the return value of computed() is an instance of ref. Import on demand before using computed:

const { computed } = Vue
Copy the code

Create a read-only compute property

const { computed } = Vue

// create a ref response
const count = ref(1)

// Create a reactive calculation property plusOne based on the value of count
// It automatically evaluates and returns a new ref based on the dependent ref
const plusOne = computed((a)= > count.value + 1)

console.log(plusOne.value) 2 / / output
plusOne.value++            // error
Copy the code

Create computable properties that are readable and writable

During a call to a computed() function, passing in an object containing the get and set functions yields a readable and writable computed property, as shown in the following code:

const { computed } = Vue

// create a ref response
const count = ref(1)

// Create a computed attribute
const plusOne = computed({
  // Evaluates the function
  get: (a)= > count.value + 1.// The assignment function
  set: val= > { count.value = val - 1}})// The set function is triggered when an assignment to a calculated attribute is performed
plusOne.value = 9
// When the set function is triggered, the value of count is updated
console.log(count.value) / / output 8
Copy the code

The watch () function

The watch() function is used to monitor changes in certain data items to trigger specific actions that need to be imported on demand before use:

const { watch } = Vue
Copy the code

Basic usage

const { watch } = Vue

const count = ref(0)

// Define watch to trigger the watch callback whenever count changes
// Watch is automatically called once when it is created
watch((a)= > console.log(count.value))
/ / 0

setTimeout((a)= > {
  count.value++
  / / output 1
}, 1000)
Copy the code

Monitor the specified data source

Monitoring reactive data sources:

const { watch, reactive } = Vue

const state  = reactive({name:'zhuanzhuan'});

watch((a)= > state.name, (value, oldValue) => { / *... * / })
Copy the code

Monitor data sources of type REF:

const { watch, ref } = Vue

// Define the data source
const count = ref(0)
// Specify the data source to monitor
watch(count, (value, oldValue) => { / *... * / })
Copy the code

Monitoring multiple data sources

Monitoring reactive data sources:

const { reactive, watch, ref } = Vue

onst state = reactive({ age: 3.name: 'zhuanzhuan' })

watch(
  [(a)= > state.age, () => state.name],    // Object.values(toRefs(state)),
  ([age, name], [prevCount, prevName]) => {
    console.log(age)         // New age value
    console.log(name)          // The new name value
    console.log('-- -- -- -- -- -- -- -- -- -- -- --')
    console.log(prevCount)     // Old age value
    console.log(prevName)      // The new name value
  },
  {
    lazy: true When the watch is created, the code in the callback function is not executed
  }
)

setTimeout((a)= > {
  state.age++
  state.name = 'walk'
}, 1000)
Copy the code

Removal of monitoring

Watch monitoring, created within the setup() function, stops automatically when the current component is destroyed. If you want to explicitly stop a monitoring, you can simply call the return value of the watch() function, with the following syntax

// Create the monitor and get the stop function
const stop = watch((a)= > { / *... * / })

// Call the stop function to clear the corresponding monitoring
stop()
Copy the code

Clear invalid asynchronous tasks in Watch

Sometimes, when we expect to cleanup invalid asynchronous tasks when a value monitored by watch changes or after watch itself is stopped, we provide a cleanup registrator function in the watch callback to do the cleanup. The cleanup function is called when:

  • Watch is repeated
  • Watch was forced to stop

Example code for Template is as follows:

/* The code in template */<input type="text" v-model="keywords" />

Copy the code

Example code in Script is as follows:

// Define reactive data keywords
const keywords = ref(' ')

// Asynchronous task: prints the keywords entered by the user
const asyncPrint = val= > {
  // Prints after 1 second delay
  return setTimeout((a)= > {
    console.log(val)
  }, 1000)}// Define the watch listener
watch(
  keywords,
  (keywords, prevKeywords, onCleanup) => {
    // Execute the asynchronous task and get the timerId to close the asynchronous task
    const timerId = asyncPrint(keywords)

    // The keywords have changed or Watcher is about to be stopped.
    // Cancel asynchronous operations that have not yet completed.
    // If the watch listener is repeated, the last asynchronous task will be cleared first
    onCleanup((a)= > clearTimeout(timerId))
  },
  // Watch is not executed when it is first created
  { lazy: true})// Return the data needed in template
return {
  keywords
}
Copy the code

The reason we register the cleanup function with the passed cleanup function, rather than returning a cleanup function directly as React useEffect does, is because the return value of the Watcher callback has special use in asynchronous scenarios. We often need to use async function in the watcher callback to perform asynchronous operations:

const data = ref(null)
watch(getId, async (id) => {
  data.value = await fetchData(id)
})
Copy the code

We know that async function implicitly returns a Promise – in this case, we cannot return a cleanup function that needs to be registered immediately. In addition, the Promise returned by the callback is used internally by Vue for asynchronous error handling.

When the watch callback is invoked

By default, all Watch callbacks are called after the current renderer flush. This ensures that the DOM is always updated in the callback. If you want the callback to be triggered before the DOM update or synchronously, use the Flush option:

watch(
  (a)= > count.value + 1, () = >console.log(`count changed`),
  {
    flush: 'post'.// default, fire after renderer flush
    flush: 'pre'.// fire right before renderer flush
    flush: 'sync' // fire synchronously})Copy the code

All watch options (TS type declaration)

interface WatchOptions { lazy? : boolean deep? : boolean flush? :'pre' | 'post' | 'sync'onTrack? :(e: DebuggerEvent) = > voidonTrigger? :(e: DebuggerEvent) = > void
}

interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  key: string | symbol | undefined
  type: 'set' | 'add' | 'delete' | 'clear' | 'get' | 'has' | 'iterate'
}
Copy the code
  • Lazy is the opposite of 2.x’s immediate
  • Deep has the same behavior as 2.x
  • OnTrack and onTrigger are two hooks for debugging. They are called when watcher – traces a dependency and when a dependency changes, respectively. The parameter obtained is a Debugger event containing dependency details.

LifeCycle Hooks LifeCycle functions

All existing lifecycle hooks have corresponding onXXX functions (available only in setup()) :

const { onMounted, onUpdated, onUnmounted } = Vue

const MyComponent = {
  setup() {
    onMounted((a)= > {
      console.log('mounted! ')
    })

    onUpdated((a)= > {
      console.log('updated! ')})// Adjust to unmounted
    onUnmounted((a)= > {
      console.log('unmounted! ')}}}Copy the code

The following list maps vue 2.x’s lifecycle functions to the new Composition API:

  • beforeCreate -> setup()
  • created -> setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

provide & inject

Provide () and Inject () enable data transfer between nested components. These two functions can only be used in the setup() function. Parent components use the provide() function to pass down data; Inject () is used in sub-components to obtain data passed from the upper layer.

Sharing Common Data

App.vue root component:

<template>
  <div id="app">
    <h1>App root component</h1>
    <hr />
    <LevelOne />
  </div>
</template>

<script>
import LevelOne from './components/LevelOne'
// 1. Import provide as required
import { provide } from '@vue/composition-api'

export default {
  name: 'app',
  setup() {
    // 2. The root component of App serves as the parent component and shares data with the child component through the provide function (no limit on levels)
    // provide(' name of data to share ', shared data)
    provide('globalColor'.'red')},components: {
    LevelOne
  }
}
</script>
Copy the code

LevelOne. Vue components:

<template>
  <div>
    <! -- 4. Set font color for tag via attribute binding -->
    <h3 :style="{color: themeColor}">Level One</h3>
    <hr />
    <LevelTwo />
  </div>
</template>

<script>
import LevelTwo from './LevelTwo'
Import Inject as required
import { inject } from '@vue/composition-api'

export default {
  setup() {
    When invoking the inject function, obtain the data shared by the parent by the specified data name
    const themeColor = inject('globalColor')

    // 3. Return the received shared data to Template
    return {
      themeColor
    }
  },
  components: {
    LevelTwo
  }
}
</script>
Copy the code

LevelTwo. Vue components:

<template>
  <div>
    <! -- 4. Set font color for tag via attribute binding -->
    <h5 :style="{color: themeColor}">Level Two</h5>
  </div>
</template>

<script>
Import Inject as required
import { inject } from '@vue/composition-api'

export default {
  setup() {
    When invoking the inject function, obtain the data shared by the parent by the specified data name
    const themeColor = inject('globalColor')

    // 3. Return the received shared data to Template
    return {
      themeColor
    }
  }
}
</script>
Copy the code

Share ref responsive data

Levelone. vue and Leveltwo. vue remain unchanged:

<template>
  <div id="app">
    <h1>App root component</h1>

	<! -- Click the button in app. vue to switch the color of the text in the child component -->
    <button @click="themeColor='red'">red</button>
    <button @click="themeColor='blue'">blue</button>
    <button @click="themeColor='orange'">orange</button>

    <hr />
    <LevelOne />
  </div>
</template>

<script>
import LevelOne from './components/LevelOne'
import { provide, ref } from '@vue/composition-api'

export default {
  name: 'app',
  setup() {
    // define ref responsive data
    const themeColor = ref('red')

    // Use the REF data through the subcomponent provided by provide
    provide('globalColor', themeColor)

    // The return data in setup is used by the current component's Template
    return {
      themeColor
    }
  },
  components: {
    LevelOne
  }
}
</script>
Copy the code

template refs

Ref () can also refer to elements or components on the page.

Element reference

Example code is as follows:

<template>
  <div>
    <h3 ref="h3Ref">TemplateRefOne</h3>
  </div>
</template>

<script>
import { ref, onMounted } from '@vue/composition-api'

export default {
  setup() {
    // Create a DOM reference
    const h3Ref = ref(null)

    // The element reference is not available until the DOM is first loaded
    onMounted((a)= > {
      // Set the font color for the DOM element
      // h3ref. value is a native DOM object
      h3Ref.value.style.color = 'red'
    })

    // Return the created reference
    return {
      h3Ref
    }
  }
}
</script>
Copy the code

Component reference

Sample code in templaterefone.vue is as follows:

<template>
  <div>
    <h3>TemplateRefOne</h3>

    <! -- 4. Click the button to display the count value of the child component -->
    <button @click="showNumber">Gets the count value in TemplateRefTwo</button>

    <hr />
    <! -- add ref reference to component -->
    <TemplateRefTwo ref="comRef" />
  </div>
</template>

<script>
import { ref } from '@vue/composition-api'
import TemplateRefTwo from './TemplateRefTwo'

export default {
  setup() {
    // 1. Create a ref reference to the component
    const comRef = ref(null)

    // 5. Display the value of count in the child component
    const showNumber = (a)= > {
      console.log(comRef.value.count)
    }

    // 2. Return the created reference
    return {
      comRef,
      showNumber
    }
  },
  components: {
    TemplateRefTwo
  }
}
</script>
Copy the code

Sample code in templatereftwo.vue:

<template>
  <div>
    <h5>TemplateRefTwo --- {{count}}</h5>
    <! -- 3. Click the button to increment count by 1 -->
    <button @click="count+=1">+ 1</button>
  </div>
</template>

<script>
import { ref } from '@vue/composition-api'

export default {
  setup() {
    // 1. Define reactive data
    const count = ref(0)

    // 2. Return the response data to Template
    return {
      count
    }
  }
}
</script>
Copy the code

createComponent

This function is not necessary unless you want to integrate TypeScript’s type inference perfectly with your project development.

This function only provides type inference so that when writing code with TypeScript, you can provide full type inference for props in setup().

import { createComponent } from 'vue'

export default createComponent({
  props: {
    foo: String
  },
  setup(props) {
    props.foo // <- type: string}}Copy the code

reference

  • Early adopters vue3. New feature x – CompositionAPI:www.cnblogs.com/liulongbinb…
  • Vue Function – RFC:zhuanlan.zhihu.com/p/68477600 -based API

The above is vuE-Next (VUE 3.0) API, I believe you can use it flexibly.

So we must be very curious about the vuE-next response type of principle, the next chapter vuE-Next (VUE 3.0) with your decryption.