preface
It’s been a while since the official release of VUE3.0, and as a technician, it’s important to keep your technology up to date. This article takes a look at how 3.0 compares to 2.x.
@TOC
First, establish the project
Vue3.0 has two ways to build scaffolding Vite
npm init vite-app hello-vue3 # OR yarn create vite-app hello-vue3
Copy the code
Scaffolding vue – cli
npm install -g @vue/cli # OR yarn global add @vue/cli
vue create hello-vue3
# select vue 3 preset
Copy the code
Set up the scaffolding using yarn create viet-app hello-vue3. After installing the dependencies using the yarn command, type yarn dev to start the project.
The project is shown in the figure below
Incompatible changes
1. V-model new grammar sugar
In 2.x, using v-Models on components is equivalent to binding value Prop and input events
<ChildComponent v-model="pageTitle"/ > <! --><ChildComponent :value="pageTitle" @input="pageTitle = $event" />
Copy the code
In 3.x, v-Models on custom components are equivalent to passing modelValue prop and receiving update:modelValue events thrown
<ChildComponent v-model="pageTitle"/ > <! --><ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
Copy the code
Allows us to use multiple V-Models on custom components
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent"/ > <! --><ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
Copy the code
2. A new global API: createApp
Vue 2.x has a number of global apis and configurations; for example, to create global components, you can use an API like Vue.com Ponent
Vue.component('button-counter', {
data: () = > ({
count: 0
}),
template: '<button @click="count++">Clicked {{ count }} times.</button>'
})
Copy the code
Global directives use vue. directive declarations
Vue.directive('focus', {
inserted: el= > el.focus()
})
Copy the code
But global configuration can easily accidentally contaminate other test cases, requiring some side effects of its own
In Vue 3 we introduced createApp, which returns an application instance
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App).mount('#app')
Copy the code
Here is a table of the current global apis and their corresponding instance apis:
2. X global API | 3. X Instance API (app) |
---|---|
Vue.config | app.config |
Vue.config.ignoredElements | app.config.isCustomElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
3. Global API Treeshaking
In Vue 3, the global and internal apis were refactored and tree-shaking support was taken into account. As a result, the global API can now only be accessed as named exports of ES module builds. Unused code does not end up in the final package. This optimizes project volume. Of course the usage needs to change accordingly:
import { nextTick } from 'vue'
nextTick(() = > {
// Something DOM related
})
Copy the code
NextTick /this.$nextTick will cause undefined is not a function error
4. Other changes
VUE3.0 also has some other changes, for example
- The Render function argument H is now imported globally instead of being passed as an argument automatically
- V-if and V-for priorities used on the same element have changed v-if always takes effect before V-for
- If an element defines both V-bind =”object” and an identical separate property, merge the last defined property
<! -- template --><div id="red" v-bind="{ id: 'blue' }"></div><! -- result --><div id="blue"></div><! -- template --><div v-bind="{ id: 'blue' }" id="red"></div><! -- result --><div id="red"></div> Copy the code
- Filters have been removed and are no longer supported
- $on, $OFF, and $once instance methods have been removed
- The use of numbers (i.e. keyCodes) as v-on modifiers is no longer supported, and config.keycodes are no longer supported
- .
More changes can be found in the VUe3 documentation: the VUe3 documentation
New features Composition API
- Prior to Vue3, writing a component was writing an “object that contains Options that describe the component,” a form called the Options API
- The Options API classifies components according to methods, computed, data, and props. When components are small, this kind of classification is obvious. However, in a large component, a component has multiple function points. When using the Options API, each function point has its own Options. If you need to modify a function point, you need to constantly switch up and down in a single file and find the corresponding function point for optimization.
As shown in the figure below, this page uses the paging function of elementUI. It is necessary to write the corresponding page-turning logic in data and methods, separated by components and Created. To optimize the paging logic, it is necessary to switch and search up and down.
The Composition API is an enhancement of the API. It is not the paradigm for vue.js 3.0 component development. If your components are simple enough, you can still use the Options API.
Look at the Composition API with the following code
<template>
<button @click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
setup() {
const state = reactive({
count: 0.double: computed(() = > state.count * 2)})function increment() {
state.count++
}
return {
state,
increment
}
}
}
</script>
Copy the code
In this code, you can see that compared to vue.js 2.x, there is a setup startup function, and options such as props, data, and computed are not defined in the component.
In the Setup function, a reactive object state is created through the Reactive API. The state object has two attributes, count and double, where count corresponds to the value of a numeric attribute. Double, on the other hand, creates a value for a computed property using computed API. In addition, increment method is defined. Finally, expose the state object and increment method externally, and use the exposed contents in template.
4. How to implement Composition API
Here is the link to the setupComponent method in VUE3 source code
The main logic of the setup launcher is during the rendering of vNode
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
// Create a component instance
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// Set the component instance
setupComponent(instance)
// Set up and run the render function
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
Copy the code
In the process of creating a component instance, we will focus on the implementation of the createComponentInstance method
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
const type = vnode.type as ConcreteComponent
// Inherits the appContext from the parent component instance, or directly from the root vNode if it is the root component.
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
// Unique id of the component
uid: uid++,
/ / component vnode
vnode,
// Parent component instance
parent,
// App context
appContext,
// VNode node type
type: vnode.type,
// Root component instance
root: null.// New component vNode
next: null.// Child node vnode
subTree: null.// Update function with side effects
update: null.// Render function
render: null.// Render context proxy
proxy: null.// Render context proxy with with block
withProxy: null.// Responsive related objects
effects: null.Dependency injection
provides: parent ? parent.provides : Object.create(appContext.provides),
// Render proxy's property access cache
accessCache: null.// Render cache
renderCache: [].// Render context
ctx: EMPTY_OBJ,
/ / data data
data: EMPTY_OBJ,
/ / props data
props: EMPTY_OBJ,
// Common attributes
attrs: EMPTY_OBJ,
// Slot related
slots: EMPTY_OBJ,
// A component or DOM ref reference
refs: EMPTY_OBJ,
// The reactive result returned by the setup function
setupState: EMPTY_OBJ,
// Setup function context data
setupContext: null.// The registered component
components: Object.create(appContext.components),
// Register the directive
directives: Object.create(appContext.directives),
/ / suspense
suspense,
// Suspense for asynchronous dependencies
asyncDep: null.Suspense // Whether asynchronous dependencies are all handled
asyncResolved: false.// Whether to mount
isMounted: false.// Whether to uninstall
isUnmounted: false.// Whether to activate
isDeactivated: false.// Life cycle, before create
bc: null.// Lifecycle, created
c: null.// Lifecycle, before mount
bm: null.// Mounted
m: null.// Life cycle, before update
bu: null.// Lifecycle, updated
u: null.// Life cycle, unmounted
um: null.// Lifecycle, before unmount
bum: null.// Lifecycle deactivated
da: null.// Lifecycle activated
a: null.// Animate animate
rtg: null.// Lifecycle render tracked
rtc: null.// Lifecycle error captured
ec: null.// Issue event methods
emit: null
}
// Initialize the render context
instance.ctx = { _: instance }
// Initialize the root component pointer
instance.root = parent ? parent.root : instance
// Initializes the dispatch event method
instance.emit = emit.bind(null, instance)
return instance
}
Copy the code
Process for setting up a component instance
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
const { props, children, shapeFlag } = instance.vnode
// Check if it is a stateful component
const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
// Initialize props
initProps(instance, props, isStateful, isSSR)
// Initialize the slot
initSlots(instance, children)
// Set stateful component instances
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
Copy the code
Next, the Setup function judges the processing and completes the component instance setup
function setupStatefulComponent(instance: ComponentInternalInstance, isSSR: boolean) {
const Component = instance.type as ComponentOptions
// 0. Create the attribute access cache for the rendering proxy
instance.accessCache = Object.create(null)
// 1. Create the render context proxy
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
// 2. Decide to handle the setup function
const { setup } = Component
if (setup) {
If the setup function takes parameters, a setupContext is created
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
currentInstance = instance
pauseTracking()
// Execute the setup function to get the result
const setupResult = callWithErrorHandling(
setup,
instance
)
// Process the setup execution result
if (isPromise(setupResult)) {
instance.asyncDep = setupResult
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
Copy the code
Next we need to know to create PublicInstanceProxyHandlers rendering context agent functions, we access the instance. The CTX rendering in the context of a property, We enter the get function, and we enter the set function when we modify properties in the instance. CTX rendering context.
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentRenderContext, key: string) {
const {
ctx,
setupState,
data,
props,
accessCache,
type,
appContext
} = instance
let normalizedProps
if (key[0]! = ='$') {
// setupState / data / props / ctx
// The render agent's properties are accessed in the cache
constn = accessCache! [key]if(n ! = =undefined) {
// If there is content in the cache, the data is fetched from the cache
switch (n) {
case AccessTypes.SETUP:
return setupState[key]
case AccessTypes.DATA:
return data[key]
case AccessTypes.CONTEXT:
return ctx[key]
case AccessTypes.PROPS:
returnprops! [key] } }else if(setupState ! == EMPTY_OBJ && hasOwn(setupState, key)) {// Fetch data from setupStateaccessCache! [key] = AccessTypes.SETUPreturn setupState[key]
} else if(data ! == EMPTY_OBJ && hasOwn(data, key)) {// Fetch data from dataaccessCache! [key] = AccessTypes.DATAreturn data[key]
} else if (
(normalizedProps = instance.propsOptions[0]) &&
hasOwn(normalizedProps, key)
) {
// Get data from propsaccessCache! [key] = AccessTypes.PROPSreturnprops! [key] }else if(ctx ! == EMPTY_OBJ && hasOwn(ctx, key)) {// Fetch data from CTXaccessCache! [key] = AccessTypes.CONTEXTreturn ctx[key]
} else if(! __FEATURE_OPTIONS_API__ || ! isInBeforeCreate) { accessCache! [key] = AccessTypes.OTHER } }const publicGetter = publicPropertiesMap[key]
let cssModule, globalProperties
// Public $XXX attribute or method
if (publicGetter) {
return publicGetter(instance)
} else if (
// The CSS module is injected when compiled by vue-loader
(cssModule = type.__cssModules) &&
(cssModule = cssModule[key])
) {
return cssModule
} else if(ctx ! == EMPTY_OBJ && hasOwn(ctx, key)) {// User-defined attributes, also starting with '$'accessCache! [key] = AccessTypes.CONTEXTreturn ctx[key]
} else if (
// Globally defined attributes
((globalProperties = appContext.config.globalProperties),
hasOwn(globalProperties, key))
) {
return globalProperties[key]
} else if( __DEV__ && currentRenderingInstance && (! isString(key) ||// #1091 avoid internal isRef/isVNode checks on component instance leading
// to infinite warning loop
key.indexOf('__v')! = =0)) {if( data ! == EMPTY_OBJ && (key[0= = ='$' || key[0= = ='_') &&
hasOwn(data, key)
) {
// If data defined in data starts with $, a warning is reported because $is a reserved character and does not act as a proxy
warn(
`Property The ${JSON.stringify(
key
)} must be accessed via $data because it starts with a reserved ` +
`character ("$" or "_") and is not proxied on the render context.`)}else {
// Warning if a variable used in a template is not defined
warn(
`Property The ${JSON.stringify(key)} was accessed during render ` +
`but is not defined on instance.`
)
}
}
},
set(
{ _: instance }: ComponentRenderContext,
key: string,
value: any
): boolean {
const { data, setupState, ctx } = instance
if(setupState ! == EMPTY_OBJ && hasOwn(setupState, key)) {// Assign setupState
setupState[key] = value
} else if(data ! == EMPTY_OBJ && hasOwn(data, key)) {// Assign a value to data
data[key] = value
} else if (key in instance.props) {
// Can't assign props directly
return false
}
if (key[0= = ='$' && key.slice(1) in instance) {
// Cannot assign a value to a reserved attribute starting with $inside Vue
return false
} else {
// User-defined data assignment
ctx[key] = value
}
return true}}Copy the code
SetupState, data, and props.
Then go back to setupStatefulComponent and determine the number of arguments to the setup function. If greater than one, createSetupContext using createSetupContext:
function createSetupContext(
instance: ComponentInternalInstance
) :SetupContext {
const expose: SetupContext['expose'] = exposed= > {
instance.exposed = proxyRefs(exposed)
}
return {
/ / property
attrs: instance.attrs,
/ / slots
slots: instance.slots,
// Send events
emit: instance.emit,
//
expose
}
}
Copy the code
Execute the setup function to get the result
function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null, type: ErrorTypes, args? : unknown[]) {
let res
try {
// Execute setup with parameters passed inres = args ? fn(... args) : fn() }catch (err) {
handleError(err, instance, type)
}
return res
}
Copy the code
Executing handleSetupResult handles the results of the setup function execution
export function handleSetupResult(instance: ComponentInternalInstance, setupResult: unknown) {
if (isFunction(setupResult)) {
instance.render = setupResult as InternalRenderFunction
} else if (isObject(setupResult)) {
instance.setupState = proxyRefs(setupResult)
}
finishComponentSetup(instance)
}
Copy the code
Next is the finishComponentSetup function, mainly to do the standard template or render function and compatible Options API
function finishComponentSetup(instance: ComponentInternalInstance, isSSR: boolean) {
const Component = instance.type as ComponentOptions
// Normalize the template or render function
if(! instance.render) {if(compile && Component.template && ! Component.render) {// Compile at runtime
Component.render = compile(Component.template, {
isCustomElement: instance.appContext.config.isCustomElement,
delimiters: Component.delimiters
})
}
// The component object's render function is assigned to instance
instance.render = (Component.render || NOOP) as InternalRenderFunction
if (instance.render._rc) {
// For run-time compiled rendering functions that use the with block, use a proxy for the new rendering context
instance.withProxy = new Proxy(
instance.ctx,
RuntimeCompiledPublicInstanceProxyHandlers
)
}
}
// Compatible with vue.js 2.x Options API
if (__FEATURE_OPTIONS_API__) {
currentInstance = instance
pauseTracking()
applyOptions(instance, Component)
resetTracking()
currentInstance = null}}Copy the code
Source code to achieve Composition API link diagram
Through the analysis of VUE3 source code we understand the component initialization process, create component instance, set up component instance. By going further, we also introduced the proxy process for rendering context. You learned when setup initiates function execution in the Composition API, and how to make a connection between the results returned by Setup and template rendering.
conclusion
This paper started from the construction of VUE3 project, listed the incompatible changes of VUE3 and VUe2. X, demonstrated the Composition API of VUE3, and some advantages compared with Options API. Finally, we implemented the Composition API. Through the source code to give you analysis of the implementation principle. I hope you can have a preliminary understanding of VUE3 and gain something from this article.