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
- in
props
Defines the parameter names that the current component allows to be passed from outside:
props: {
age: Number
}
Copy the code
- through
setup
The first parameter to the function, receiveprops
Data:
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
- Import reactive functions as needed:
const { reactive } = Vue
Copy the code
- in
setup()
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
- in
template
To 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
- in
setup()
To create responsive data:
setup() {
const age = ref(3)
return {
age,
name: ref('zhuanzhuan')}}Copy the code
- in
template
To 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.