sequence

Vue3 has also been released for a long time, the official version of the default has been switched to VUe3, and there is a perfect Chinese document, I wonder if you have used it?

This slag experience for a period of time, or quite silky, some development experience, I hope you can work early

Github Vue-next-analysis (to be continued at….)

Make good use of h (createVNode) and render functions

We know there’s a magical createVNode function that’s exported in Vue3 and the current function creates a VDOM, so don’t look down on vDOM, we can use it, we can do amazing things like implement a popover component

The usual idea is to write a component to reference in the project and use v-model to control its show and hide, but there is a problem with this, we need to copy and paste the cost of reuse. There is no way to improve efficiency, such as encapsulating NPM to use by calling JS.

However, createVNode and Render solved all of these problems

Const message = {setup() {const num = ref(1) return {num}}, template: ` < div > < div > {{num}} < / div > < div > this is a popup window < / div > < / div > `}Copy the code
  // Initialize the component to generate a VDOM
  const vm = createVNode(message)
  // Create a container, or use an existing one
  const container = document.createElement('div')
  //render changes from patch to dom
  render(vm, container)
// Popovers hang wherever you want to go
document.body.appendChild(container.firstElementChild)
Copy the code

After the above operation, we found that we could encapsulate it as a method and put it wherever we wanted.

To make good use of JSX/TSX composite

As stated in the documentation, Vue recommends using template syntax to build HTML in most cases. However, in some usage scenarios, we really need to use the full programming power of JavaScript. This is where the render function comes in.

Advantages of JSX versus template syntax

JSX and template syntax are both writing categories supported by VUE, but they do have different usage scenarios and styles that we can use depending on the actual situation of the current component

What is the JSX

JSX is a syntax extension of Javascript, JSX = Javascript + XML, that is, to write XML in Javascript, because of this feature of JSX, so it has the flexibility of Javascript. At the same time, it is both semantic and intuitive of HTML.

Advantages of template syntax

  • 1. Template syntax doesn’t violate rules. We write like HTML
  • Vue3 allows for more optimizations at compile time, such as static tags, blocks, cached event handlers, and so on, due to the traversability of templates
  • 3, template code logic code strictly separate, high readability
  • 4, the JS foundation is not so good, remember a few commands can quickly develop, easy to get started
  • 5. Perfect support for vUE official plug-ins, code formatting, syntax highlighting, etc

The advantage of the JSX

  • 1. Be flexible, be flexible, be flexible (say important things three times)Copy the code
  • 2. Multiple components can be written to a single fileCopy the code
  • 3, as long as the JS foundation is good, there is no need to remember so many commands, up is a outputCopy the code
  • 4, JS and JSX mixed, the method is declarative ready-to-use, logical for those who know the businessCopy the code

contrast

Due to the vue support for the JSX, communities, and debate to debate, whether to a high or low, then the slag that they had no high and low, which do you think fit, which can be use, shortcomings in the right place is his advantage We handed down from old masters to carry forward the golden mean, master, will use a combination of these, Will be able to play an invincible effect, in the chaos of bo boss favor.

In general, container components, because he may want to do a standard or kill packaging for the current display component, then JSX is the best container component

For example: now we have a requirement, we have two buttons, and now we need to do a background data to choose which button to display. We usually do this by controlling the different components in a template with v-IF

However, with JSX and functional components, we find that the logic is clearer, the code is cleaner, the quality is higher, and the X is more loaded

Let’s take a look at

First complete the two components

//btn1.vue
<template>
  <div>This is btn1{{num}}<slot></slot>
  </div>
</template>
<script>
import { ref, defineComponent } from 'vue'
export default defineComponent({
  name: 'btn1'.setup() {
      const num = ref(1)
      return { num }
  }
})
</script>
//btn2.vue
<template>
  <div>This is btn2{{num}}<slot></slot>
  </div>
</template>
<script>
import { ref, defineComponent } from 'vue'
export default defineComponent({
  name: 'btn2'.setup() {
      const num = ref(2)
      return { num }
  }
})
</script>
Copy the code

Use JSX with functional components to make a container component

// Container components
import btn1 from './btn1.vue'
import btn2 from './btn2.vue'
export const renderFn = function (props, context) {
  return props.type == 1 ? <btn1>{context.slots.default()}</btn1> : <btn2>{context.slots.default()}</btn2>
}

Copy the code

Importing business Components

// Business components
<template>
 <renderFn :type="1">1111111</renderFn>
</template>
<script>
import { renderFn } from './components'
console.log(renderFn)
export default {
 components: {
   renderFn
 },
 setup(){}};</script>
Copy the code

Provide/Inject dependency well

Before making good use of DI, let’s take a look at some concepts that will help us understand di more fully

What are IOC and DI

Inversion of Control, or IoC, is a design principle used in object-oriented programming to reduce coupling between computer code. One of the most common is called Dependency Injection, or DI, and another is called Dependency Lookup. With inversion of control, when an object is created, it is passed (injected) a reference to the object on which it depends by an external entity that regulates all objects in the system.

What is dependency injection

In plain English, dependency injection is passing instance variables into an object

Dependency injection in VUE

In VUE, we apply the concept of dependency injection, which essentially replaces global state management by declaring dependencies in parent components and injecting them into descendant component instances

Let’s look at his basic usage first

Declare provide in the parent component

//parent.vue
<template>
    <child @setColor="setColor"></child>
    <button @click="count++">add</button>
</template>

<script >
import { defineComponent, provide, ref } from "vue";
import Child from "./child.vue";
export default defineComponent({
    components: {
        Child
    },
    setup() {
        const count = ref(0);
        const color = ref('# 000')
        provide('count', count)
        provide('color', color)
        function setColor(val) {
            color.value = val
        }
        return {
            count,
            setColor
        }
    }
})
</script>


Copy the code

Into the child component

//child.vue
// Use inject
<template>
    <div>This is what is injected {{count}}</div>
    <child1 v-bind="$attrs"></child1>
</template>

<script>
import { defineComponent, inject } from "vue";
import child1 from './child1.vue'
export default defineComponent({
    components: {
        child1
    },
    setup(props, { attrs }) {
        const count = inject('count');
        console.log(count)
        console.log(attrs)
        return {
            count
        }
    }
})
</script>
 

Copy the code

Because of the dependency injection feature, we have largely replaced global state management, and we don’t want to introduce vuex at every turn

Now we have a page theme color that runs through all components and can change the theme color in some components. Our conventional solution is to install a Vuex and deliver the color value through its API. In this case, if we want to change the theme color, we need to dispatch to the Action first. Then Mutation was triggered in the Action and the state was changed in the Mutation. In this way, you found that there was a bit of an overkill, I just changed the color!

So what do we do with dependency injection

First of all, we know that vUE is a monomial data stream, that is, a child component cannot modify the content of the parent component, so we should think of using $attrs to pass methods to the parent component transparently and modify them in the component component.

Let’s look at the code

// Descendant component child1.vue
<template>
    <div :style="`color:${color}`" @click="setColor">This is the color of the injected content</div>
</template>

<script>
import { defineComponent, inject } from "vue";

export default defineComponent({
    setup(props, { emit }) {
        const color = inject('color');
        function setColor() {
            console.log(0)
            emit('setColor'.'red')}return {
            color,
            setColor
        }
    }
})
</script>
 
Copy the code

By embedding the current descendant component in child.vue, you can change the color in a neat way

Use Composition apis to extract common logic

As we all know, the biggest new feature of VUE3 is the Composition API, which will give you a competitive edge in the industry and a unique skill set

Let’s do it step by step

What is the Composition API

Organizing logic with component options (Data, computed, Methods, watch) is often effective. However, as our components start to get bigger, the list of logical concerns grows. This can lead to components that are hard to read and understand, especially for those who didn’t write them in the first place.

In VUE3, the Composition API was created to address the current pain points and avoid large projects where the code logic is scattered around the current components and becomes difficult to maintain

The Composition API is a setup function declared in the component configuration object. We can encapsulate all the logic in the setup function. Then we can achieve the same effect with vue3’s reactive API hook function, calculation property API, etc. But with clearer code and reuse at the logical level

Based on using

<template>
    <div ref="composition">Test compositionApi</div>
</template>
<script>
import { inject, ref, onMounted, computed, watch } from "vue";
export default {
    / / setup hands
    setup(props, { attrs, emit, slots, expose }) {

        // Get the page element
        const composition = ref(null)
        // dependency injection
        const count = inject('foo'.'1')
        // Reactive combination
        const num = ref(0)
        // The hook function
        onMounted(() = > {
            console.log('It's a hook.')})// Calculate attributes
        computed(() = > num.value + 1)
        // Listen for changes in values
        watch(count, (count, prevCount) = > {
            console.log('This value has changed.')})return {
            num,
            count
        }

    }
}
</script>
 
Copy the code

As you can see from the above code, one setup function does everything you would do in a traditional option, but that’s not the end of the story. The combination of these apis allows you to reuse logic, so you can encapsulate a lot of common logic, reuse it, and leave work early

For example: everyone is used to copy the clipboard function, under normal circumstances, using the navigator. Clipboard. WriteText method could copy the content into the clipboard. However, if you are careful, you will find that assigning clipboard is actually a general function. For example, if you do b-side business, the management system is full of replication ID, replication copy and other functions.

This is where the logic reuse capabilities of the Composition API come in handy


import { watch, getCurrentScope, onScopeDispose, unref, ref } from "vue"
export const isString = (val) = > typeof val === 'string'
export const noop = () = >{}export function unrefElement(elRef) {
    const plain = unref(elRef)// Get the original value
    return (plain).$el ?? plain // if the first value is null, the last value is undefined; otherwise, the first value is used
}
export function tryOnScopeDispose(fn) {
    // If there is active effect
    if (getCurrentScope()) {
        // Register a processing callback on the currently active effect scope. This callback is invoked after the relevant effect scope ends
        // Can replace onUmounted
        onScopeDispose(fn)
        return true
    }
    return false
}
// A setTimeout wrapper with controls.
export function useTimeoutFn(
    cb,/ / callback
    interval,/ / time
    options = {},
) {
    const {
        immediate = true,
    } = options

    const isPending = ref(false)

    let timer

    function clear() {
        if (timer) {
            clearTimeout(timer)
            timer = null}}function stop() {
        isPending.value = false
        clear()
    }

    function start(. args) {
        // Clear the last timer
        clear()
        // Whether the status is pending
        isPending.value = true
        // Restart the timer
        timer = setTimeout(() = > {
            // Terminates the pending state when the timer is executed
            isPending.value = false
            // Initializes the timer ID
            timer = null
            // Perform the callbackcb(... args) }, unref(interval)) }if (immediate) {
        isPending.value = true

        start()
    }

    tryOnScopeDispose(stop)

    return {
        isPending,
        start,
        stop,
    }
}
// Use EventListener easily. AddEventListener is registered during installation and EventListener is automatically removed during uninstallation.
export function useEventListener(. args) {
    let target
    let event
    let listener
    let options
    // If the first argument is a string
    if (isString(args[0]) {// Structure the content
        [event, listener, options] = args
        target = window
    }
    else {
        [target, event, listener, options] = args
    }
    let cleanup = noop
    const stopWatch = watch(
        () = > unrefElement(target),/ / to monitor the dom
        (el) = > {
            cleanup() // Execute the default function
            if(! el)return
            // Bind the event el to window if it is not passed
            el.addEventListener(event, listener, options)
            // Rewrite the function to make it easier to unload when changing
            cleanup = () = > {
                el.removeEventListener(event, listener, options)
                cleanup = noop
            }
        },
        // Flush: 'POST' template reference listening
        { immediate: true.flush: 'post'},)/ / unloading
    const stop = () = > {
        stopWatch()
        cleanup()
    }

    tryOnScopeDispose(stop)

    return stop
}

export function useClipboard(options = {}) {
    // Get the configuration
    const {
        navigator = window.navigator,
        read = false,
        source,
        copiedDuring = 1500,
    } = options
    // Event type
    const events = ['copy'.'cut']
    // Check whether the current browser supports clipboard
    const isSupported = Boolean(navigator && 'clipboard' in navigator)
    // Exported text
    const text = ref(' ')
    // Exported copied
    const copied = ref(false)
    // Use the timer hook
    const timeout = useTimeoutFn(() = > copied.value = false, copiedDuring)

    function updateText() {
        Parsing the text content of the system clipboard returns a Promise
        navigator.clipboard.readText().then((value) = > {
            text.value = value
        })
    }

    if (isSupported && read) {
        // Bind events
        for (const event of events)
            useEventListener(event, updateText)
    }
    // Copy the clipboard method
    / / the navigator. Clipboard. WriteText is asynchronous () method returns a promise
    async function copy(value = unref(source)) {
        if(isSupported && value ! =null) {
            await navigator.clipboard.writeText(value)
            // A reactive value for external dynamic access
            text.value = value
            copied.value = true
            timeout.start()// copied.value = false }}return {
        isSupported,
        text,
        copied,
        copy,
    }
}
Copy the code

At this point we reuse the copy logic, the following code directly into the template can be used



<template>
    <div v-if="isSupported">
        <p>
            <code>{{text | | 'empty'}}</code>
        </p>
        <input v-model="input" type="text" />
        <button @click="copy(input)">
            <span v-if=! "" copied">copy</span>
            <span v-else>The copy!</span>
        </button>
    </div>
    <p v-else>Your browser does not support the Clipboard API</p>
</template>
<script setup>
import { ref, getCurrentScope } from 'vue'
import { useClipboard } from './copy.js'
const input = ref(' ')
const { text, isSupported, copied, copy } = useClipboard()
console.log(text)// Copy the content
console.log(isSupported)// Whether the copy clipboard API is supported
console.log(copied)// Whether replication completion is delayed
console.log(copy) // Copy the method
</script>


Copy the code

Refer to the vue version of the Composition API library for the full version of the above code

Use getCurrentInstance to get component instances

GetCurrentInstance supports access to internal component instances. Normally, it is placed in setup to retrieve component instances, but getCurrentInstance is only exposed for higher-order use scenarios, typically in libraries.

The use of getCurrentInstance in application code is strongly discouraged. Don’t use it as an alternative to getting this in a composite API.

So what’s his role?

Or logical extraction, instead of mixins, which is extracting common logic from complex components in order to maintain the code as a whole. That’s what you have to do. Look at the table reuse logic in Element-Plus. In logical extraction, getCurrentInstance is indispensable because it involves getting props, proxy, EMIT, and the parent-child relationship from the current component

The following element-plus code uses getCurrentInstance to retrieve data from the parent component and store it in different variables. We simply call the current useMapState to retrieve the data

// Save logical encapsulation of data
function useMapState<T> () {
  const instance = getCurrentInstance()
  const table = instance.parent as Table<T>
  const store = table.store
  const leftFixedLeafCount = computed(() = > {
    return store.states.fixedLeafColumnsLength.value
  })
  const rightFixedLeafCount = computed(() = > {
    return store.states.rightFixedColumns.value.length
  })
  const columnsCount = computed(() = > {
    return store.states.columns.value.length
  })
  const leftFixedCount = computed(() = > {
    return store.states.fixedColumns.value.length
  })
  const rightFixedCount = computed(() = > {
    return store.states.rightFixedColumns.value.length
  })

  return {
    leftFixedLeafCount,
    rightFixedLeafCount,
    columnsCount,
    leftFixedCount,
    rightFixedCount,
    columns: store.states.columns,
  }
}

Copy the code

Use $attrs

$attrs now contains all attributes passed to the component, including class and style.

What exactly is the use of $attrs in our development?

Through it, we can do component events and props pass-through

First there is a standardized component, usually a component in a component library, and so on

//child.vue
<template>
    <div>This is a standardized component</div>
    <input type="text" :value="num" @input="setInput" />
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
    props: ['num'].emits: ['edit'].setup(props, { emit }) {
        function setInput(val) {
            emit('edit', val.target.value)
        }
        return {
            setInput
        }
    }
})
</script>
 
Copy the code

Next, there is a wrapper component that modifies the current standardized component so that the result is a component that meets our expectations

//parent.vue
 <template>
    <div>Make a separate wrapper for this layer</div>
    <child v-bind="$attrs" @edit="edit"></child>
</template>

<script>
import { defineComponent } from "vue";
import child from './child.vue'
export default defineComponent({
    components: {
        child
    },
    setup(props, { emit }) {
        function edit(val) {
            // Wrap the returned value
            emit('edit'.`${val}time`)}return {
            edit
        }
    }
})
</script>
 
Copy the code

We found that $attrs was used in the current wrapper component, and by passing it through to the standardized component, we could enhance and wrap components such as those in element’s UI without changing the logic of the original component.

Tips for registering global components gracefully

Vue3 components typically use the Component method provided by VUE to register global components

The code is as follows:


const app = Vue.createApp({})

app.component('component-a', {
  / *... * /
})
app.component('component-b', {
  / *... * /
})
app.component('component-c', {
  / *... * /
})

app.mount('#app')
Copy the code

When using

<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>
Copy the code

However, after some clever development, we found that it is possible to register components in the same way as registering vue plug-ins, and it is elegant!

Vue plug-in registration

Plug-in format

//plugins/index.js
export default {
  install: (app, options) = > {
      // This is the content of the plugin}}Copy the code

Use of plug-ins

import { createApp } from 'vue'
import Plugin from './plugins/index.js'
const app = createApp(Root)
app.use(Plugin)
app.mount('#app')
Copy the code

The essence of a plug-in is to call the install method in the use method, so that we can register the component in the install method.

A component plug-in is thrown in index.js

// index.js
import component from './Cmponent.vue'
const component = {
    install:function(Vue){
        Vue.component('component-name',component)
    }  //'component-name' is the name of the component that can be used later. Install is the default method. Component-name is custom
}
// Export the component
export default component
Copy the code

The component registration

// Import components
import install from './index.js'// globally mount utils
Vue.use(install);
Copy the code

In the example above, this is a simple and elegant way to register components. You can see that components like Element-Plus and Vant are registered this way.

Use the < / script setup >

  • Less boilerplate content, cleaner code.
  • You can declare props and throw 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).

It can replace most of the setup functions. For details on how to use it, please refer to the following documentation

However, the setup function, which can return render functions, cannot be shown in the current syntax sugar, so I searched through the data and found a compromise

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

const msg = ref('Hello World! ')
const dynode = () = > h('div',msg.value);

</script>

<template>
    <dynode />
  <input v-model="msg">
</template>
Copy the code

This way, we can return the render function in the syntactic sugar

The latest use of the V-Model

We know that to simulate the V-Model in VUe2, the child component must accept a value props to emit an emit called input

However, in VUE3 he upgraded

The V-Model is used in the parent component


 <template>
    <child v-model:title="pageTitle"></child>
</template>

<script>
import { defineComponent, ref } from "vue";
import child from './child.vue'
export default defineComponent({
    components: {
        child
    },
    setup(props, { emit }) {
        const pageTitle = ref('this is v - model')
        return {
            pageTitle
        }
    }
})
</script>
 
Copy the code

The sub component uses the title props and provides to emit update:title emit

<template>
    <div>{{ title }}</div>
    <input type="text" @input="setInput" />
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
    props: ['title'].emits: ['update:title'].setup(props, { emit }) {
        function setInput(val) {
            emit('update:title', val.target.value)
        }
        return {
            setInput
        }
    }
})
</script>
 
Copy the code

With the syntax sugar above, we can encapsulate components as much as we want. For example, I can encapsulate components that show and hide by myself. We can use V-Model: Visible to show and hide components individually. Use the normal V-Model to control the other logic inside the component, thus having the same functionality expressed using more concise logic

The last

At present, the experience summarized in the development is shared here, the mistake, please big guy pointed out!

Then interested in the vUE source code, you can read the article to xiaobai (own) vue3 source guide

Github Vue-next-analysis github Vue-next-analysis github Vue-next-analysis github Vue-next-analysis

Which contains the VUE source code execution mind map, code annotations in the source code, the structure of the entire source code, the separate disassembly of each function, etc.. Wrong place please big guy point out!