current position:Home>Share 9 development skills related to vue3

Share 9 development skills related to vue3

2022-04-29 15:12:54Front end talent

03a8998513097899aff0603265728add.png


order

vue3 It has been released for a long time , The official will also switch the default version to vue3, And there are perfect Chinese documents [1], I wonder if comrades have used it ?

Ben has experienced it for some time , Still quite silky , A little development experience to offer , I hope you can get off work early

You can also have a look at The source code parsing github vue-next-analysis[2]( Not finished, to be continued, continuous update ....)

Make good use of h(createVNode) and render function

We know that vue3 A magical createVNode function Current function, which can create a vdom, Don't underestimate it vdom, Let's make good use of it , Can produce unexpected results For example, we need to implement a pop-up component

Our usual idea is to write a component to be referenced in the project , adopt v-model To control his display and hiding , But there's a problem with that , The cost of reuse needs to be copied and pasted . We have no way to improve efficiency , For example, package into npm By calling js To use .

However , With createVNode and render Then all the problems will be solved

//  Let's write a pop-up component first 
        const message = {
            setup() {
                const num = ref(1)
                return {
                    num
                }
            },
            template: `<div>
                        <div>{
    {num}}</div>
                        <div> This is a pop-up window </div>
                      </div>`
        }
 Copy code 
//  Initialize component generation vdom
  const vm = createVNode(message)
  //  Create a container , You can also use existing 
  const container = document.createElement('div')
  //render adopt patch  become dom
  render(vm, container)
//  Pop up windows anywhere you want to go   
document.body.appendChild(container.firstElementChild)
 Copy code 

After the above operation , We found that we can encapsulate it into a method , Put it anywhere you want .

Make good use of JSX/TSX

The document says , In the vast majority of cases ,Vue It is recommended to use template syntax to build HTML. However, in some usage scenarios , We really need to use JavaScript Complete programming ability . At this time Rendering function That comes in handy .

jsx And template syntax

jsx And template syntax are vue Supported writing categories , Then they do have different usage scenarios , And way , According to the actual situation of the current component , To use... As appropriate

What is? JSX

JSX  It's a kind of Javascript Grammar extension of ,JSX = Javascript + XML, That is to say Javascript It says in it XML, because JSX This characteristic of , So he has Javascript The flexibility of the , At the same time, it has html Of Semantic and intuitive .

The advantages of template syntax

  • 1、 Template grammar is not very inconsistent with writing , It's like we're writing html equally

  • 2、 stay vue3 Because of the traversability of the template , It can do more optimization in the compilation phase , Such as static tags 、 block block、 Cache event handlers, etc

  • 3、 Template code and logic code are strictly separated , High readability

  • 4、 Yes JS People with poor skills , Write down a few commands and you can develop quickly , Easy to get started

  • 5、vue Perfect support for official plug-ins , Code formatting , Grammar highlighting, etc

JSX The advantages of

  • 1、 flexible 、 flexible 、 flexible ( Important things are to be repeated for 3 times )
     Copy code 
  • 2、 Many files can be written by one component 
     Copy code 
  • 3、 as long as JS Good foundation , You don't have to remember so many commands , It's a one pass output 
     Copy code 
  • 4、JS and JSX A mixture of , Method is declared and used , The logic is clear for those who know the business 
     Copy code 

contrast

because vue about JSX Support for , In the community , It's also an argument to argue , In the end, it should be divided into two levels , Then Ben thought , They were not high or low , Which do you think is suitable for , Just use one , If the disadvantages are put in the right place, he is the advantage   We should carry forward the golden mean handed down by our elders , Be the master of all things , Combine the two , Can play an invincible effect , In the midst of chaos, Bo bosses favor .

Next, let's talk about my superficial understanding , We know the component type , It is divided into container type components and display type components   In general , Container component , Because he may want to make a standardization or slaughter packaging for the current display components , Then, in the container type component JSX It could be better

for instance : Now there's a need , We have two buttons , Now we need to choose which button to display through the background data , What we usually do , Is passed in a template v-if To control different components

But there is JSX After functional components , We found the logic clearer , The code is simpler , Higher quality , Also more loaded X 了

Let's see

Start with two components

//btn1.vue
<template>
  <div>
       This is a 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 a 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 code 

use JSX Cooperate 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 code 

Introduce business components

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

Make good use of dependency injection (Provide / Inject)

Before making good use of dependency injection , Let's start with some concepts , Help us have a more comprehensive understanding of the past and present lives of dependency injection

IOC and DI What is it?

Inversion of control (Inversion of Control, Abbreviation for IoC), It's object-oriented programming [3] A design principle in , It can be used to reduce the coupling between computer codes [4]. The most common way is called Dependency injection (Dependency Injection, abbreviation DI), There's another way “ Dependency lookup ”(Dependency Lookup). Reverse by control , When an object is created , The external entity of all objects in a regulatory system , Pass a reference to the object it depends on ( Inject ) Give it .

What is dependency injection

Dependency injection   In big words : Is to pass instance variables into an object

vue Dependency injection in

stay vue in , We apply the concept of dependency injection , In fact, it is to declare dependencies in the parent component , Inject them into the descendant component instance , It can be said that it can replace Global state management The existence of

e71533f1bc81491e924a394f0afccfe1.png
image.png

Let's first look at his basic usage

Declared in parent component provide

//parent.vue
<template>
    <child @setColor="setColor"></child>
    <button @click="count++"> add to </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 code 

Inject... Into the sub components

//child.vue
// Use inject  Inject 
<template>
    <div> This is the content of the injection {
    { 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 code 

Because of the characteristics of dependency injection , We largely replaced Global state management , I believe no one wants to introduce that cumbersome vuex Well

Let's give you an example , Now we don't have a page theme color , He runs through all the components , And you can change the theme color in some components , In our conventional solution , Just pretend vuex Then through his api Issue color value , At this time, if you want to change , First of all, we should launch dispatch To Action , And then in Action Medium trigger Mutation And then Mutation And then change it state, In this way , Did you find something Ox knife for killing chicken 了 , I just changed the color !

Let's look at dependency injection What to do with

First we know that vue It's a single data stream , That is, the child component cannot modify the content of the parent component , So we should think of using $attrs Use it to transparently pass methods to ancestor components , You can modify it in the component .

So let's look at the code

// Children's components 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 code 

Embed current component into child.vue In the middle , You can modify the color in a concise way

Make good use of Composition API Pull away from general logic

as everyone knows ,vue3 The biggest new feature , when Composition API  Also called combination api , Use him well , Is your competitiveness in the industry , You have it, too Not born Skills

Let's analyze... Step by step

What is? Composition API

Use (datacomputedmethodswatch) Component options are often effective to organize logic . However , When our components start getting bigger , Logical concerns Your list will grow . Especially for those who didn't write these components in the first place , This can make components difficult to read and understand .

So in vue3 In order to solve the current pain point , Avoid code logic fragmentation in large projects , Scattered in every corner of the current component , So it's hard to maintain ,Composition API Born in the sky

So-called Composition API  Is to declare... In the component configuration object  setup function , We can encapsulate all the logic in setup Function , And then in cooperation with vue3 The response formula provided in API Hook function 、 Compute properties API etc. , We can achieve and conventional Optional The same effect , But it has 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 at first 
    setup(props, { attrs, emit, slots, expose }) {

        //  Get page elements 
        const composition = ref(null)
        //  Dependency injection 
        const count = inject('foo', '1')
        //  Responsive combination 
        const num = ref(0)
        // Hook function 
        onMounted(() => {
            console.log(' This is a hook ')
        })
        //  Compute properties 
        computed(() => num.value + 1)
        //  Change of monitoring value 
        watch(count, (count, prevCount) => {
            console.log(' This value has changed ')
        })
        return {
            num,
            count
        }

    }
}
</script>
 
 Copy code 

From the above code we can see , One setup Function we did in Traditional option Everything in , However, this is not the best , Through these api The combination of can realize logical reuse , In this way, we can encapsulate a lot of general logic , Realize reuse , Get off work early

for instance : Everyone has used the function of copying clipboard , In general , utilize navigator.clipboard.writeText Method to write the copied content to the clipboard . However , Careful you will find , In fact, the assignment clipboard is a general function , such as : You do b End business , The management system is full of replication id、 Copy copy and other functions .

therefore Composition API The logic reuse ability of comes 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 // The previous value is null、undefined, Then take the following value , Otherwise, take the previous value 
}
export function tryOnScopeDispose(fn) {
    //  If there are active effect
    if (getCurrentScope()) {
        // In the currently active  effect  Register a processing callback on the scope . The callback will be in the relevant  effect  Called after the scope ends 
        // Can replace onUmounted
        onScopeDispose(fn)
        return true
    }
    return false
}
// With controls setTimeout Wrappers .
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 in pending  state 
        isPending.value = true
        //  Restart the timer 
        timer = setTimeout(() => {
            //  When the timer executes, it ends pending state 
            isPending.value = false
            //  Initialize the timer id
            timer = null
            //  Execution callback 
            cb(...args)
        }, unref(interval))
    }
    if (immediate) {
        isPending.value = true

        start()
    }

    tryOnScopeDispose(stop)

    return {
        isPending,
        start,
        stop,
    }
}
// Easy to use EventListener. Use... For installation addEventListener register , Automatically remove when uninstalling EventListener.
export function useEventListener(...args) {
    let target
    let event
    let listener
    let options
    //  If the first parameter is a string 
    if (isString(args[0])) {
        // Structure content 
        [event, listener, options] = args
        target = window
    }
    else {
        [target, event, listener, options] = args
    }
    let cleanup = noop
    const stopWatch = watch(
        () => unrefElement(target),//  monitor dom
        (el) => {
            cleanup() //  Execute the default function 
            if (!el)
                return
            //  The binding event el If there is no incoming, bind as window
            el.addEventListener(event, listener, options)
            //  Rewrite the function to facilitate unloading when changing 
            cleanup = () => {
                el.removeEventListener(event, listener, options)
                cleanup = noop
            }
        },
        //flush: 'post'  Template reference listening 
        { immediate: true, flush: 'post' },
    )
    //  uninstall 
    const stop = () => {
        stopWatch()
        cleanup()
    }

    tryOnScopeDispose(stop)

    return stop
}

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

    function updateText() {
        // Parse the text content of the system clipboard and return a Promise
        navigator.clipboard.readText().then((value) => {
            text.value = value
        })
    }

    if (isSupported && read) {
        //  The binding event 
        for (const event of events)
            useEventListener(event, updateText)
    }
    //  Copy the clipboard method 
    //navigator.clipboard.writeText  Method is to asynchronously return a promise
    async function copy(value = unref(source)) {
        if (isSupported && value != null) {
            await navigator.clipboard.writeText(value)
            //  The value of the response , It is convenient for external users to dynamically obtain 
            text.value = value
            copied.value = true
            timeout.start()// copied.value = false 
        }
    }

    return {
        isSupported,
        text,
        copied,
        copy,
    }
}
 Copy code 

At this time, we reuse the replication logic , The following code can be directly introduced and used in the template

<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> Copying !</span>
        </button>
    </div>
    <p v-else> Your browser does not support 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 content 
console.log(isSupported)//  Whether to support copying cutting boards api 
console.log(copied)// Whether copy completion is delayed 
console.log(copy) //  Replication method 
</script>


 Copy code 

The above code reference vue Version of Composition API For all complete versions of the library, please refer to [5]

Good at using getCurrentInstance Get component instance

getCurrentInstance  Support access to internal component instances , Usually he is placed in setup Get component instances from , however  getCurrentInstance  Only exposed to high-level usage scenarios , Typically, for example, in a library .

Strongly opposed to the use of... In application code  getCurrentInstance. please Don't Think of it as a combination API In order to get  this  An alternative to using .

What's his role then ?

still Logical extraction , Used in place of Mixin, This is in complex components , For the maintainability of the whole code , Extracting general logic is something that must be done , We can see element-plus[6]  in table Reuse logic , In logic extraction, because it involves obtaining props、proxy、emit  And the relationship between parent and child components can be obtained through the current component , here getCurrentInstance There is no substitute for

as follows element-plus Use... In your code getCurrentInstance Get parent component parent Data in , Save to different variables , We just need to call the current useMapState You get the data

//  Logical encapsulation of stored 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 code 

Make good use of $attrs

$attrs  Now it includes _ all _ Passed to the component attribute, Include  class  and  style.

$attrs What is the use in our development ?

Through him , We can make components Events and props transparent transmission

First, there is a standardized component , Generally, it is the components of the component library, etc

//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 code 

Next, there is a packaging component , Standardize the current component , So that the result becomes a component that meets our expectations

//parent.vue
 <template>
    <div> This layer needs a separate package </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 code 

We found that... Is used in the current packaging component $attrs, Pass it to standardized components through him , thus , We can do things like element UI Enhance and package the components in the , And there is no need to change the logic of the original component .

Elegant skills for registering global components

vue3 Components are usually used vue Provided component  Method to complete the registration of 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 code 

When using

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

However, after the big man's Ingenious Development , We found it possible to use registration vue Plug in way , It can also complete component registration , And elegant !

vue Plug in registration

Plug in format

//plugins/index.js
export default {
  install: (app, options) => {
      //  This is the content of the plug-in 
  }
}
 Copy 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 code 

In fact, the essence of plug-ins , Is in the use Invoke the plug-in in the install Method , So in this way , We can be in install Method .

index.js Throw a component plug-in

// index.js
import component from './Cmponent.vue'
const component = {
    install:function(Vue){
        Vue.component('component-name',component)
    }  //'component-name' This is the name of the component that can be used later ,install Is the default method  component-name  Is custom , We can define our own names according to specific needs 
}
//  Export the component 
export default component
 Copy code 

Component registration

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

In the above case , It is a simple and elegant way of component registration , You can find that including element-plus、vant  And other components are registered in this way .

Make good use of </script setup>

<script setup>  Is in a single file component (SFC) Chinese envoy   Compile time syntax . Compared with ordinary  <script>  grammar , It has more advantages :

  • Less template content , Simpler code .

  • Can use pure Typescript Statement props And throw events .

  • Better runtime performance ( Its template will be compiled into a rendering function with the same scope , There are no intermediaries ).

  • better IDE Type inference performance ( Reduce the amount of work that language servers do to pull types away from code ).

It can replace most setup What the function expresses , Specific usage , Please look at the step-by-step document [7]

But because of setup Function, which can return the characteristics of the rendering function , It cannot be shown in the current grammar , So I searched for information , 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 code 

In this way , We can return the rendering function in the syntax sugar

v-model The latest usage of

We know that vue2 Want to simulate v-model, The subcomponent must accept a value props  Spit out one It's called input Of emit

However, in vue3 He was upgraded in

Use... In the parent component v-model

<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 a v-model')
        return {
            pageTitle
        }
    }
})
</script>
 
 Copy code 

Use... In subcomponents  title Of props  And prescribed spitting update:title Of 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 code 

With the above grammar sugar , When we package components , You can do whatever you want , For example, I encapsulate the components that can control the display and hiding, and we can use v-model:visible Individually control the display and hiding of components . Use normal v-model  Control other logic inside the component , To have Use more concise logic , Express the same function

Last

The experience summarized in the current development is shared here , The error of , Please point out !

Then on vue Source code interested bosses , You can read this article   To Xiaobai ( own ) Of vue3 Source guide [8]

You can also directly see the source code analysis of this slag github vue-next-analysis[9]

Which includes vue Source code execution mind map , Code comments in the source code , The structure of the whole source code , Separate disassembly of each function, etc . Please point out the mistakes !

About this article

author : OK, study

https://juejin.cn/post/7080875763162939429

copyright notice
author[Front end talent],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/04/202204291512497809.html

Random recommended