Vue3 document parsing

Vue3 document parsing

After thinking about it, it's better to send it. After all, Vue may not be contacted for a long time.
I was thinking before, why do many people recommend reading documents when they encounter problems
Why?
Because the document is really useful (really fragrant)
This article is a personal understanding of the content in the document. After reading the document once in an intermittent week, the composition API is really fragrant, ts is really fragrant!

Add:
I don't know how to send the pictures of Typora to CSDN. If there is a need for pictures, please tell me how to upload... I will modify them as soon as possible.

Global API

createApp

When creating a project through scaffolding, you can create it in main JS:

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

createApp(App).mount('#app')

Using this function, you can provide a context application instance, and the entire component tree mounted by the application instance shares the same context.

This means that we can set a root prop when creating an instance, and all its subcomponents can get this value through the props method. Its first parameter receives a root component option object options as the first parameter. Using the second parameter, you can pass the root prop to the application, for example:

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

createApp(App,{username:"Black cat several crimson"}).mount('#app')
// App.vue
<template>
  <div id="app">
    <defineComp/>
    {{username}}
  </div>
</template>

<script>
import defineComp from './components/defineComp.vue'
export default {
  components: { defineComp },
  name: 'App',
  props:{
    username:{
      type: String,
      default: 'neko_zzz'
    }
  }
}
</script>

The final display result on the page is black cat several crimson

corrections

It seems that this second parameter can only pass props to the root component for use. For its deep-seated sub components, props cannot be seen.

h

Returns a "virtual node", usually abbreviated as VNode: a common object that contains information describing to Vue which node it should render on the page, including the description of all child nodes. It is intended for manual writing Rendering function:

render() {
  return h('h1', {}, 'Some title')
}

h is a utility for creating VNode, which is only used as the abbreviation of createVNode function, and render is only exposed to the hook that developers use createVNode.

The render function takes precedence over the render function compiled according to the template option or the HTML template in the DOM where the element is mounted.

be careful! If the Vue option contains a rendering function, the template will be ignored!

attribute

Receive three parameters: type, props and children

type
  • Type: String | Object | Function

  • Details:

    HTML tag name, component, asynchronous component or functional component. A comment is rendered using a function that returns null. This parameter is required.

props
  • Type: Object

  • Details:

    An object that corresponds to the attribute s, prop s, and events we will use in the template. Optional.

children
  • Type: String | Array | Object

  • Details:

    The descendant VNode is generated using h(), or a string is used to obtain the "text VNode", or an object with a slot. Optional.

    h('div', {}, [
      'Some text comes first.',
      h('h1', 'A headline'),
      h(MyComponent, {
        someProp: 'foobar'
      })
    ])
    

Relevant knowledge

The knowledge point of this part is reusable & Composition - > rendering function. The API details of h function are described here.

Reusable & Composite rendering function

Vue recommends using templates to create your HTML in most cases. However, in some scenarios, you really need the full programming power of JavaScript. In this case, you can use the rendering function, which is closer to the compiler than the template.

Create label VNode

For example, I now need to implement a component that can control the size of the title by passing in numbers:

import { createApp, h } from 'vue'
import App from './App.vue'
import './index.css'
const app = createApp(App)

// Register global components
app.component('word-level',{
    render(){
        return h(
            // Here is the name of the tag. It can also be component name / asynchronous component name
            'h' + this.level,
            // Here is the style and other information added to the label, such as class attribute
            {},
            // Get all data passed into the default slot through default()
            // If it is a named slot, for example, use < template #header > in the parent component
            // Then this. Is used to obtain the contents of the header slot$ slots. header()
            this.$slots.default()	 
        )
    },
    props:{
        level:{
            type: Number,
            default: 5
        }
    }
})

app.mount('#app')
<template>
  <div id="app">
    <word-level :level="1">111</word-level>
    <word-level :level="2">222</word-level>
    <word-level :level="3">333</word-level>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

Finally, you can successfully get the rendering results in the page:

[the external chain image transfer fails, and the source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-dmTi3ZKI-1643156410420)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211221105438797. PNG)]

In the previous rendering function, we returned the result of h function in render, which is not an actual DOM element. Its more accurate name may be createNodeDescription, because the information it contains will tell Vue what kind of nodes need to be rendered on the page, including the description information of its children. We describe such a node as "virtual node", which is often abbreviated as VNode.

Note that VNodes must be unique, and all VNodes in the component tree must be unique, which means that the following rendering functions are illegal:

render() {
  const myParagraphVNode = h('p', 'hi')
  return h('div', [
    // Error - duplicate Vnode!
    // The second parameter here is the descendant VNode. You can omit the attribute that should have appeared here, but you'd better set a null to represent the placeholder
    myParagraphVNode, myParagraphVNode
  ])
}

If you really need to repeat elements / components many times, you can use factory functions to implement them. For example, the following rendering function renders 20 identical paragraphs in a completely legal way:

render() {
  return h('div',
    Array.from({ length: 20 }).map(() => {
      return h('p', 'hi')
    })
  )
}
Create component VNode

To create a VNode for a component, the first parameter passed to h should be the component itself.

render() {
  return h(ButtonCounter)
}

If we need to resolve a component by name, we can call resolveComponent:

const { h, resolveComponent } = Vue

// ...

render() {
  const ButtonCounter = resolveComponent('ButtonCounter')
  return h(ButtonCounter)
}
Use JavaScript instead of template functionality

The content of this part can only be seen in the document when it is needed. You know it has this function.

Simply put, it is to replace the template functions provided by Vue through the native js. After all, as long as the operation can be easily completed in the native JavaScript, Vue's rendering function will not provide a proprietary alternative method.

https://v3.cn.vuejs.org/guide/render-function.html#%E5%88%9B%E5%BB%BA%E7%BB%84%E4%BB%B6-vnode

Review the scope slots on the way:

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-DQdi5pai-1643156410426)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211221114153299. PNG)]

Functional component

Vue provides a component type called functional component, which is used to define scenarios that have no response data and do not need any life cycle, but Vue 3 X improves the performance of stateful components, which is almost the same as that of functional components. Therefore, it is recommended to use only stateful components.

  • In Vue 3 In X, all functional components are created by ordinary functions.

  • export default exports a function with two parameters:

    • Props (because there is no this reference in the functional component, Vue will pass props as the first parameter)
    • Context: context is an object containing attrs, slot and emit attributes
import { h } from 'vue'

const DynamicHeading = (props, context) => {
  return h(`h${props.level}`, context.attrs, context.slots)
}

DynamicHeading.props = ['level']

export default DynamicHeading

defineComponent

In fact, this function is mainly used to deduce the type of ts or tsx. If you use js to write the project, just use the default export default {} directly, for example:

<script>
export default {
  name: 'App'
}
</script>

If it is a project using ts, the most important function of defineComponent is to give the component the correct parameter type inference.

Let's first look at what the document says:

This API provides a parameter. The value of the parameter can be an object with component options or a setup function. The function name will be used as the component name.

It may be difficult to understand this sentence alone, so with the help of the source code, it is only a encapsulation of the setup function and returns the options object. If we provide detailed component options when calling the function, an options object will be returned; If a setup function is provided for brevity only, an object that encapsulates only the setup function is returned.

export function defineComponent(options: unknown){
	return isFunction(options) ? { setup: options } : options
}

Receive the options component options passed back from the defineComponent package through a specific variable. In this package, you can set specific types for data / functions.

<script lang="ts">
import { defineComponent, PropType, ref, computed, reactive } from 'vue'
    
// It is recommended to use the interface when declaring reactive
interface Student{
    name: string,
    age: number
}

export default defineComponent({
    name: 'defineComp',
    props: {
        callback: {
            type: Function as PropType<()=>void>
        },
        success: {
            type: Boolean,
            default: true
        }
    },
    setup(){
        // In fact, if the specific type of ref is not given, vue3 you can deduce the type according to the initial value
        // However, when specifying complex data types, you can manually pass a generic type
        const month = ref<string| number>(9)
        const nextMonth = computed((): string => {
            return ('' + month.value + 1)
        })
        // There are three ways to define a reactive variable
        const st1: Student = reactive({name:'Black cat several crimson',age:20})
        const st2 = reactive<Student>({name:'Black cat several crimson',age:20})
        const st3 = reactive({name:'Black cat several crimson',age:20}) as Student
        return{
            month,
            nextMonth,
            st1,
            st2,
            st3
        }
    }
})
</script>

The so-called setting a setup function represents a abbreviation that can be performed when there is only setup method in the component, for example:

<script lang="ts">
import { defineComponent, ref} from 'vue'
const defineComp = defineComponent(()=>{
    const month = ref<string| number>(9)
    return{
        month
    }
})
</script>

defineAsyncComponent

You can create an asynchronous component that is loaded only when needed.

In large applications, we may need to divide the application into smaller code blocks, and load a module from the server only when necessary.

In vue3 X, the use of asynchronous components is the same as vue2 X there are three main changes:

  1. Vue3.x added this helper function to display and declare asynchronous components
  2. The component option in the asynchronous component advanced declaration method is renamed loader
  3. The component loading function bound by loader no longer accepts the resolve and reject functions, and must return a promise

In Vue 2 In X, the only way to declare an asynchronous component is as follows:

const asyncPage = () => import('./v2AsyncComp.vue')

However, at Vue 3 X the above usage is not applicable, because if you want to call an asynchronous component at this time, you need to wrap it with the defineAsyncComponent auxiliary function:

Now, in Vue 3, since functional components are defined as pure functions, async components definitions need to be explicitly defined by wrapping it in a new defineAsyncComponent helper.

For higher-level usage, defineAsyncComponent can accept an object:

The defineAsyncComponent method can also return objects in the following format:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent({
  // Factory function
  loader: () => import('./Foo.vue'),
  // Components to use when loading asynchronous components
  loadingComponent: LoadingComponent,
  // Components to use when loading fails
  errorComponent: ErrorComponent,
  // Delay before displaying loadingComponent | default value: 200 (unit: ms)
  delay: 200,
  // If timeout is provided and the time to load the component exceeds the set value, an error component will be displayed
  // Default value: Infinity (i.e. never timeout, unit: ms)
  timeout: 3000,
  // Defines whether a component can be suspended. Default value: true
  suspensible: false,
  /**
   *
   * @param {*} error Error message object
   * @param {*} retry A function that indicates whether the loader should retry when the promise loader reject s
   * @param {*} fail  A function that instructs the loader to end and exit
   * @param {*} attempts Maximum number of retries allowed
   */
  onError(error, retry, fail, attempts) {
    if (error.message.match(/fetch/) && attempts <= 3) {
      // Try again when the request has an error. You can try up to 3 times
      retry()
    } else {
      // Note that retry/fail is like resolve/reject in promise:
      // One of them must be called to continue error handling.
      fail()
    }
  }
})

Relevant knowledge

First, review the basic knowledge: component foundation - > dynamic component + deep component - > asynchronous component + deep component - > component registration

Component foundation - listen for sub component events

Sometimes it is very useful to throw a specific value with an event. For example, we might want the < blog post > component to decide how much to enlarge its text. In this case, the second parameter of $emit can be used to provide this value:

<button @click="$emit('enlargeText', 0.1)">
  Enlarge text
</button>

Then, when the parent component listens to this event, we can access the thrown value through $event:

<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>

Or, if the event handler is a method:

<blog-post ... @enlarge-text="onEnlargeText"></blog-post>

Then this value will be passed into this method as the first parameter:

methods: {
  onEnlargeText(enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

Next, try the v-model instruction on the handwriting component that often appears in online classes. In order for it to work normally, the < input > in this component must:

  • Bind its value attribute to a prop named modelValue
  • When its input event is triggered, the new value is thrown through the custom update:modelValue event

After writing the code, it is like this:

app.component('custom-input', {
  // The parent component passes a property named modelValue to the child component, which is responsible for receiving the content in the input box
  props: ['modelValue'],
  emits: ['update:modelValue'],
  template: `
    <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    >
  `
})

Now v-model can work perfectly on this component:

<custom-input v-model="searchText"></custom-input>

Another way to implement v-model in this component is to use the function of calculated property to define getter s and setter s. The get method should return modelValue property, and the set method should trigger the corresponding event.

app.component('custom-input', {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  template: `
    <input v-model="value">
  `,
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) { 
        this.$emit('update:modelValue', value)
      }
    }
  }
})
Component foundation - dynamic component

Sometimes, dynamic switching between different components is very useful. Take the document as an example. Now three components and three corresponding buttons are provided. How to switch three components in the same position? Click the button to get the information on the button, and the name of the corresponding component can be found through this information.

[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-zCBt4Jga-1643156410428)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211220163930374. PNG)]

Then you need to view a new knowledge point, the component tag:

<!-- The component will be `currentTabComponent` Change when you change -->
<component :is="currentTabComponent"></component>

In the above example, currentTabComponent can include

  • The name of the registered component, or
  • Option object of a component

Therefore, in order to realize the above functions, a calculation attribute can be used to calculate the value of currentTabComponent. When the value it represents is the same as the name of the target component, it will be replaced by the target component. However, Vue creates a new instance of currentTabComponent each time it switches to a new tag.

Moreover, the is attribute has a wonderful way to create regular html elements.

Some HTML elements, such as < UL >, < ol >, < Table > and < Select >, have strict restrictions on which elements can appear inside them. Some elements, such as < li >, < tr > and < option >, can only appear inside some other specific elements.

This leads to some problems when we use these constrained elements. For example:

<table>
  <blog-post-row></blog-post-row>
</table>

This custom component < blog post row > will be promoted to the outside as invalid content, resulting in an error in the final rendering result. We can use special is attribute As an alternative:

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

When it is used for native HTML elements, the value of is must start with Vue: before it can be interpreted as a Vue component. This is avoided and native Custom element Confusion.

Deep component - component registration
  • Global registration: using Vue createApp({...}). Component ('component name ', {options}) creation
  • Local registration: after importing components through import, they are declared in components
Deep components - Dynamic & asynchronous components

The contents of dynamic components and component foundation are basically the same. The only difference is that keep alive is used to cache data.

The asynchronous component uses the API defineasynccomponent introduced here, for example:

const { createApp, defineAsyncComponent } = Vue

const app = createApp({})

const AsyncComp = defineAsyncComponent(
  () =>
    new Promise((resolve, reject) => {
      resolve({
        template: '<div>I am async!</div>'
      })
    })
)

app.component('async-example', AsyncComp)

This method accepts a factory function that returns Promise. After retrieving the component definition from the server, Promise's resolve callback should be called. You can also call reject(reason) to indicate that the load failed.

After combining webpack 2 and above with ES2015 syntax, we can use dynamic import in this way:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)

app.component('async-component', AsyncComp)

When Register components locally You can also use defineAsyncComponent:

import { createApp, defineAsyncComponent } from 'vue'

createApp({
  // ...
  components: {
    AsyncComponent: defineAsyncComponent(() =>
      import('./components/AsyncComponent.vue')
    )
  }
})
What's new in suspend

It is an experimental new feature, and its API will be changed at any time, so we don't need to describe it in detail, so we can roughly understand the idea.

Asynchronous components are suspended by default. This means that if it has a < suspend > in the parent chain, it will be regarded as the asynchronous dependency of the < suspend >. In this case, the loading status will be controlled by < suspend >, and the loading, error, delay and timeout options of the component itself will be ignored.

By specifying suspend: false in its options, asynchronous components can exit suspend control and always control their loading state.

The < suspend > component has two slots. They all receive only one direct child node. The nodes in the default slot will be displayed as much as possible. If not, the node in the fallback slot is displayed.

Importantly, asynchronous components do not need to be direct children of < suspend >. It can appear at any depth in the component tree and does not need to appear in the same template as < suspend > itself. The content will not be considered resolved until all descendant components are ready.

template>
  <suspense>
    <template #default>
      <todo-list />
    </template>
    <template #fallback>
      <div>
        Loading...
      </div>
    </template>
  </suspense>
</template>

<script>
export default {
  components: {
    TodoList: defineAsyncComponent(() => import('./TodoList.vue'))
  }
}
</script>

defineCustomElement(3.2+)

One of the great benefits of custom elements is that they can be used with any framework, even without a framework. Custom elements are ideal when you need to distribute components to end users who may use different front-end technology stacks, or when you want to hide the implementation details of the components they use from the end application.

The method accepts and defineComponent Same parameter, but returns a native Custom element , this element can be used for any framework or not based on the framework.

Usage example:

<my-vue-element></my-vue-element>
import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
  // Here are the common Vue component options
  props: {},
  emits: {},
  template: `...`,
  // For defineCustomElement only: CSS injected into shadow root
  styles: [`/* inlined css */`]
})
// Register the custom element.
// After registration, all ` < My Vue element > 'tags on the page will be upgraded.
customElements.define('my-vue-element', MyVueElement)
// You can also initialize this element programmatically:
// (this can only be done after registration)
document.body.appendChild(
  new MyVueElement({
    // Initialized prop (optional)
  })
)

Relevant knowledge

High level guide - Vue and Web Components

There is indeed a certain degree of functional overlap between custom elements and Vue components: they both allow us to define reusable components with data transfer, event emission and life cycle management functions. However, the Web Components API is relatively low-level and simple.

By default, Vue will give priority to trying to parse a non-native HTML tag into a registered Vue component. If it fails, it will be rendered as a custom element. This behavior will cause Vue in development mode to issue a warning of "failed to resolve component".

Global configuration method for resolving warnings

The so-called Custom Element can be understood as using the browser's own API to create a reusable component. The API of modern browsers has been updated so that you can create a reusable component without using a framework. Both Custom Element and Shadow DOM allow you to create reusable components. Even, these components can be almost seamlessly connected to the framework.

Custom element

Related articles

Custom elements are simple user-defined HTML elements. They are defined by using CustomElementRegistry. To register a new element, use window A method called define in customelements is used to obtain the registered instance.

window.customElements.define('my-element', MyElement);

The first parameter indicates the name of the user-defined element label, which adopts the short horizontal line naming method;

The second parameter is responsible for executing the constructor of the element:

class MyElement extends HTMLElement {
  constructor() {
    super();
  }
  // The connectedCallback method will be triggered when the element is inserted into the DOM tree
  // It can be understood as componentDidMount in React
  connectedCallback() {
    // here the element has been inserted into the DOM
  }
}

Generally speaking, we need to set the element after connectedCallback. Because this is the only way to be sure that all attributes and child elements are already available. Constructors are generally used to initialize state and set Shadow DOM. The constructor is called when an element is created, and connectedCallback is called when an element has been inserted into the DOM.

Acquisition of custom elements

You can also call * * customelements Get ('My element ') * * to get the reference of the element constructor to construct the element. If you have passed customelement Define() to register. Then you can use new element() instead of document Createelement() to instance an element.

customElements.define('my-element', class extends HTMLElement {...});

...

const el = customElements.get('my-element');
const myElement = new el();  // same as document.createElement('my-element');
document.body.appendChild(myElement);
shadow DOM

I encountered shadow DOM when writing wechat applet before, and I was worried that I couldn't modify the style in a component library from the outside..

Using Shadow DOM, the HTML and CSS of custom elements are completely encapsulated in the component. This means that the element will appear in the DOM tree of the document as a single HTML tag. Its internal structure will be placed in #Shadow root. After Shadow root is created, you can use all DOM methods of document object, such as this shadowRoot. Queryselector to find elements.

In fact, some native HTML elements also use Shadow DOM. For example, you have a < video > element in another web page, which will be displayed as a separate label, but it will also display the controls for playing and pausing video. These controls are actually part of the Shadow DOM of the video element, so they are hidden by default. To display the Shadow DOM in Chrome, go to Preferences in the developer tool and select Show user agent Shadow DOM. When you view the video element again in the developer tool, you can see the Shadow DOM of the element.

Shadow DOM also provides CSS for local scope. All CSS applies only to the component itself. The element will inherit only the minimum number of CSS defined from outside the component, or even no CSS from outside. However, you can expose these CSS properties so that users can style components, for example:

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `<p>Hello world</p>`;

This defines a Shadow root with mode: open, which means that developers can find it and interact with it, configure exposed CSS properties, and listen for thrown events. Similarly, you can also define mode: closed, which will get the opposite performance.

You can use the: host selector to style the component itself The host CSS pseudo class selects the root element of the shadow DOM that contains the CSS used internally. In other words, this allows you to select a custom element from its shadow dom.

For example, custom elements use display: inline by default, so if you want to display components as block elements, you can do this:

:host {
  display: block;
}

This also allows you to style the context. For example, you want to change whether the background of the component is gray through the disabled attribute:

:host([disabled]) {
  opacity: 0.5;
}

By default, custom elements inherit some attributes from the surrounding CSS, such as color and font. If you want to clear the initial state of the component and set all CSS in the component to the default initial value, you can use:

:host {
  all: initial;
}

It is very important to note that the style defined externally in the component itself takes precedence over the style defined by the host in the Shadow DOM. If you do

my-element {
  display: inline-block;
}

It will be covered

:host {
  display: block;
}

You should not change the style of custom elements from the outside. If you want users to set some styles of components, you can expose CSS variables to achieve this effect. For example, if you want users to choose the background color of components, you can expose a CSS variable called -- background color. Suppose there is a Shadow DOM whose root node is < div id = "container" >

#container {
  background-color: var(--background-color);
}

Now the user can set its background color outside the component

my-element {
  --background-color: #ff0000;
}

You can also set a default value in the component in case the user does not set it

:host {
  --background-color: #ffffff;
}

#container {
  background-color: var(--background-color);
}
Modify the shadow DOM style (which may be very common)
  • Gets the parent node label that wraps the shadow DOM area

  • Create a style tag and write the style file manually through innerHtml

  • Insert the style into the shadow DOM through appendChild

Take a case in Ruan Yifeng's article on web component to prove:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<user-card
  image="https://semantic-ui.com/images/avatar2/large/kristy.png"
  name="User Name"
  email="yourmail@some-email.com"
  class="father"
></user-card>
  
<template id="userCardTemplate">
  <style>
   :host {
     display: flex;
     align-items: center;
     width: 450px;
     height: 180px;
     background-color: #d4d4d4;
     border: 1px solid #d5d5d5;
     box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
     border-radius: 3px;
     overflow: hidden;
     padding: 10px;
     box-sizing: border-box;
     font-family: 'Poppins', sans-serif;
   }
   .image {
     flex: 0 0 auto;
     width: 160px;
     height: 160px;
     vertical-align: middle;
     border-radius: 5px;
   }
   .container {
     box-sizing: border-box;
     padding: 20px;
     height: 160px;
   }
   .container > .name {
     font-size: 20px;
     font-weight: 600;
     line-height: 1;
     margin: 0;
     margin-bottom: 5px;
   }
   .container > .email {
     font-size: 12px;
     opacity: 0.75;
     line-height: 1;
     margin: 0;
     margin-bottom: 15px;
   }
   .container > .button {
     padding: 10px 25px;
     font-size: 12px;
     border-radius: 5px;
     text-transform: uppercase;
   }
  </style>
  
  <img class="image">
  <div class="container">
    <p class="name"></p>
    <p class="email"></p>
    <button class="button">Follow John</button>
  </div>
</template>
 <script>
    class UserCard extends HTMLElement {
        constructor() {
            super();
            var shadow = this.attachShadow( { mode: 'open' } );
            var templateElem = document.getElementById('userCardTemplate');
            var content = templateElem.content.cloneNode(true);
            content.querySelector('img').setAttribute('src', this.getAttribute('image'));
            content.querySelector('.container>.name').innerText = this.getAttribute('name');
            content.querySelector('.container>.email').innerText = this.getAttribute('email');
            shadow.appendChild(content);
        }
	}
	window.customElements.define('user-card', UserCard);
 </script>
</body>
</html>

You can see that all element nodes and styles of user card are placed in #shadow root.

[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-qdgUtkak-1643156410430)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-2021122211809281. PNG)]

Let's try the following three steps:

  • Get #shadow root's direct parent element node container
  • Manually create a style label and manually enter the specific style in the label through innerHtml
  • Insert the style label into the parent node
 const container = document.querySelector('.father')
 let style = document.createElement("style")
 style.innerHTML = 
 	" .container{ background-color: #000; font-size: 30px; color: #fff; width: 100% } "
 container.shadowRoot.appendChild(style)

You can see that the style we added manually was successfully put into shadow Dom and took effect.

[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-xQdA0USl-1643156410432)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-2021122211658128. PNG)]

However, if the mode is set to closed, the exposed elements cannot be obtained and the style cannot be modified.

var shadow = this.attachShadow( { mode: 'closed' } );
Building custom elements using Vue

Similar to the definelements in the native API, Vue supports the use of the defineCustomElement method to create custom elements, and uses an API that is completely consistent with Vue components. This method accepts the same parameters as defineComponent, but returns a custom element constructor extended from HTMLElement:

<my-vue-element></my-vue-element>
import { defineCustomElement } from 'vue'

const MyVueElement = defineCustomElement({
  // Normal Vue component options are provided here
  props: {},
  emits: {},
  template: `...`,

  // Unique feature of defineCustomElement: CSS will be injected into the shadow root
  styles: [`/* inlined css */`]
})

// Register custom elements
// After registration, all ` < My Vue element > 'tags on this page will be updated
customElements.define('my-vue-element', MyVueElement)

// You can also programmatically instantiate this element:
// (this can only be done after registration)
document.body.appendChild(
  new MyVueElement({
    // initial props (optional)
  })
)
Array.from de duplication

If you want to get multiple custom elements in the page and add new functions or some styles for them, but one element appears many times, for example:

[a,a,b,c,a,a,a,a,d] // Different symbols are used to represent different element nodes

Using the method of traversing the array to modify each item will cause a waste of time, so here is an array de duplication method to simplify the array.

Due to array The input parameter of from () is an iteratable object, so we can use its combination with Set to quickly delete duplicates from the array.

function unique(array) {
  return Array.from(new Set(array));
}

unique([1, 1, 2, 3, 3]); // => [1, 2, 3]

First, new Set(array) creates a Set containing an array, and the Set set will delete duplicates.

Because the Set set is iterative, you can use array From() converts it to a new array.

In this way, we realize array de duplication.

resolveComponent

resolveComponent can only be used in render or setup functions.

If it is available in the current application instance, it is allowed to resolve component s by name.

Returns a Component. If not found, the received parameter name is returned.

import { resolveComponent } from 'vue'
render() {
  const MyComponent = resolveComponent('MyComponent')
}

After obtaining the component, you can render the component through the h auxiliary function, which has been introduced in h.

nextTick

Delay the callback until after the next DOM update cycle. Use it immediately after modifying the data, and then wait for the DOM to update.

import { createApp, nextTick } from 'vue'

const app = createApp({
  setup() {
    const message = ref('Hello!')
    const changeMessage = async newMessage => {
      message.value = newMessage
      await nextTick()
      console.log('Now DOM is updated')
    }
  }
})

Or through the instance method:

createApp({
  // ...
  methods: {
    // ...
    example() {
      // Modify data
      this.message = 'changed'
      // DOM has not been updated
      this.$nextTick(function() {
        // The DOM is now updated
        // `this ` is bound to the current instance
        this.doSomethingElse()
      })
    }
  }
})

Well, although the document says nextTick is for DOM update, when is the so-called DOM update? In other words, why can't we get the modified data immediately after modifying the data? We have to wait for the DOM update to get the latest value?

It may be difficult to understand the concept directly, so I recommend you take a look at this first case

After reading it, you can clarify your thinking: after updating the data, Vue is not updated in real time, and DOM update takes a certain time. There is a time difference from the data update to the display page. If we operate or obtain the DOM immediately within the time difference, it is still the UN updated DOM operated and obtained, so of course, we can't obtain the updated value. That is, Vue executes asynchronously when updating the dom.

Relevant knowledge

If you want to understand nextTick api a little, you probably need to understand the rendering mechanism of the browser and the concepts of tasks, microtasks, queues and other mechanisms. There will be a lot of content extension design in this part. Here, only the general understanding ideas are recorded. For specific understanding, you can see this article article And this describe.

Browser process

The browser (multi process) mainly includes the following processes:

  • Browser process (main process of browser)
  • Third party plug-in process
  • GPU process (browser rendering process), in which GPU process (multithreading) is closely related to the Web front end, mainly including the following threads:
    • GUI rendering thread
    • JS engine thread
    • Event triggered thread (closely related to EventLoop)
    • Timed trigger thread
    • Asynchronous HTTP request thread

GUI rendering thread and JS engine thread are mutually exclusive. In order to prevent inconsistency in DOM rendering, one thread will be suspended while the other thread is executing.

Among these threads, the JS engine thread and event trigger thread are closely related to Vue's nextTick.

JS engine thread and event trigger thread

After the browser page is rendered for the first time, the workflow of JS engine thread combined with event trigger thread is as follows:

  1. The synchronization task is executed on the JS engine thread (main thread) to form an Execution Context Stack.

    2. Outside the main thread, the event trigger thread manages a Task Queue. As long as the asynchronous task has the running result, an event is placed in the Task Queue.

    3. After the synchronous tasks in the execution stack are executed, the system will read the task queue. If there are asynchronous tasks to be executed, add them to the execution stack of the main thread and execute the corresponding asynchronous tasks.

Event Loop mechanism

When the main thread runs, it will generate an execution stack. When the code in the stack calls some asynchronous API s, it will add events to the task queue.

It should be clearly remembered that the main thread's execution stack calls some asynchronous API s and then adds events to the task queue, such as DOM operations, ajax requests, timers, etc.

After the code in the stack is executed, it will read the events in the task queue and execute the callback function corresponding to the event. This cycle forms an event cycle mechanism.

There are two types of tasks in JS: Micro task and macro task:

  • Macro tasks: script (main code block), setTimeout, setInterval, setImmediate, I/O, UI rendering

  • Micro task: process nextTick(Nodejs) ,promise ,Object.observe ,MutationObserver

Although it was mentioned earlier that the main thread adds events to the task queue after calling the asynchronous API, macro tasks are not all asynchronous tasks, and the main code block is a kind of macro task. Macro task is the code executed in the execution stack every time, including getting an event callback from the event queue every time and putting it into the execution stack for execution. In order to make the JS engine thread and GUI rendering thread switch orderly, the browser will re render the page after the end of the current macro task and before the execution of the next macro task (macro task > rendering > macro task >...)

Micro tasks are tasks that are executed immediately after the execution of the current macro task (tasks that are executed after the execution of the current macro task and before UI rendering). Micro tasks respond faster than setTimeout (the next macro task) because there is no need to wait for UI rendering. After the current macro task is executed, all micro tasks generated during its execution will be executed again.

The execution sequence can be roughly divided into macro tasks - > Add micro tasks to the execution stack - > check whether there is a micro task queue - > execute all micro tasks if there is one - > render UI - > execute macro tasks

Let's take an example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    .outer {
      height: 200px;
      background-color: #FF8CB0;
      padding: 10px;
    }
    .inner {
      height: 100px;
      background-color: #FFD9E4;
      margin-top: 50px;
    }
  </style>
</head>
<body>
  <div class="outer">
    <div class="inner"></div>
  </div>
</body>
<script>
let inner = document.querySelector('.inner')
let outer = document.querySelector('.outer')
// Listen for attribute changes of outer element
new MutationObserver(function() {
  console.log('mutate')
}).observe(outer, {
  attributes: true
})
// click to listen for events
function onClick() {
  console.log('click')
  setTimeout(function() {
    console.log('timeout')
  }, 0)
  Promise.resolve().then(function() {
    console.log('promise')
  })
  outer.setAttribute('data-random', Math.random())
}
inner.addEventListener('click', onClick)
</script>
</html>

Now analyze this Code:

  1. When we click on the external container, the onClick function is triggered to execute the function body from top to bottom
    1. First, output click and encounter the setTimeout timer as the second macro task
    2. Promise encountered Resolve, treat this code as a micro task
    3. In case of ui change, the function will be executed between the next round of macro task and the current round of micro task
  2. After the onclick function is executed, query whether there are micro tasks in this round. If so, execute all micro tasks
    1. Execute micro task promise The then function after resolve outputs promise
    2. Before starting the next round of macro task, execute the function to modify the ui and output mutate
  3. After the first round of macro tasks are completed, start the second round of macro tasks
    1. Execute the contents in setTimeout and output timeout

So the output order in the console is click - > promise - > mutation - > timeout

Vue. 2.x nextTick

When you set VM Somedata = 'new value', the component will not be re rendered immediately. When the queue is refreshed, the component updates the next "tick" when the event cycle queue is empty. In most cases, we don't need to care about this process, but it can be tricky if you want to do something after the DOM state is updated. Although Vue JS usually encourages developers to think in a "Data-Driven" way and avoid direct contact with DOM, but sometimes we do. In order to wait for Vue to finish updating the DOM after the data changes, you can use Vue immediately after the data changes nextTick(callback) . In this way, the callback function will be called after the DOM update is completed.

The priority of task execution is promise - > mutationobserver - > setimmediate - > setTimeout

nextTick's rendering has gone through many stages iteration , it was finally identified as a micro task in version 2.6 +, but some changes were made to the event execution. To prevent the function execution caused by the high priority of micro task in some cases found early. However, when the micro task is found in the test, the rendered DOM element nodes can be obtained.

Here is an additional one Examples , this topic explains the unexpected rendering caused by the high priority of micro tasks when two nodes with similar structures operate on a boolean type of data at the same time. The solution is to set different key values for two nodes with similar structures.

option

data

We can usually get instance objects directly through component instances, such as VM a. In fact, because the component instance proxies all the properties on the data object, it accesses VM A is equivalent to accessing VM$ data. a.

With_ Properties starting with or $will not be proxied by component instances because they may conflict with Vue's built-in properties and API methods. You can use, for example, VM$ data._ property to access these properties.

// Create an instance directly
const data = { a: 1 }

// This object will be added to the component instance
const vm = createApp({
  data() {
    return data
  }
}).mount('#app')

console.log(vm.a) // => 1

Relevant knowledge

The content of this part is about the principle of responsiveness - > in-depth responsiveness, and does not involve the basis, calculation and listening of responsiveness.

Responsiveness - deep responsive principle

As an understanding of responsiveness, we need to do the following:

  • When a value is read, it is tracked. For example, val1 + val2 will read val1 and val2 at the same time.
  • Detect when a value changes, for example, when we assign val1 = 3.
  • Rerun the code to read the original value, for example, run sum = val1 + val2 again to update the value of sum.

This code is not responsive:

let val1 = 2
let val2 = 3
let sum = val1 + val2

console.log(sum) // 5

val1 = 3

console.log(sum) // Still 5

In order to run our sum at any time when the value changes, the first thing we need to do is wrap it in a function:

const updateSum = () => {
  sum = val1 + val2
}

But how do we tell Vue this function?

Vue keeps track of the currently running functions through a side effect. A side effect is the wrapper of a function, which starts tracing before the function is called. Vue knows which side effects are running and when, and can execute them again when needed.

In fact, Promise. Com can be used for this part All to understand, we created a Promise array, which stores multiple Promise states. Here we can create a stack to perform side effects and add functions waiting for listening to the stack.

So what we need is something that can wrap the sum, like this:

createEffect(() => {
  sum = val1 + val2
})

Here we use createEffect to track and execute:

// Maintain a stack that performs side effects
const runningEffects = []

const createEffect = fn => {
  // Wrap the transmitted fn in a side effect function
  const effect = () => {
    runningEffects.push(effect)
    fn()
    runningEffects.pop()
  }

  // Automatic side effects immediately
  effect()
}

Please look carefully. Here is a side effect function called effect defined in createEffect. It is not uncommon to define functions in functions. This is the so-called closure to privatize function data. Whenever we call createEffect or effect function, the internal effect function will be called at the same time. However, the effect function cannot be accessed outside the createEffect function.

When our side effect is called, it will push itself into the runningEffects array before calling fn. This array can be used to check for side effects that are currently running. At any time, as long as something responds wonderfully to data changes, you can be sure that it has been wrapped in a side effect.

Although Vue's public API does not include any methods to directly create side effects, it does expose a function called watchEffect, which behaves much like the createEffect function in our example.

Well, in fact, I didn't have an in-depth understanding of function closures before, but I just roughly know its role, so I'll expand my knowledge of closures here. As a precondition for closures, you first need to understand the execution context and scope of js.

Execution context

You can see this in this part video

The global execution context is like a list of points. We can use it to find out where the code data is stored and correctly reference variables.

First, the code segment will generate the current execution context and point to the global execution context. At this time, two areas will be generated. First, it points to the global scope (similar to the block level scope). If the required data is not found in the scope, it will be extended to the second area, that is, the global object for data search.

  • The declarations of var and function are created in the global object
  • Variables declared by let, const and class are created in the global scope

[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-VAGw2pNe-1643156410433)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211226133122874. PNG)]

Take another look at this topic. First, add a declared by var and function object foo to the global object. It should be noted that only the function name of function foo is saved in the global object, and the specific {...} The contents of the function body are not placed in the global object.

Add a knowledge point. The function object body will store the text environment of the execution context when the function is created, which means that an environment named [[environment]] is created.

Next, create an execution context of function foo, and then form a linked list of foo execution context - > foo related global scope - > foo related global objects. Next, let's look at the internal definition of foo function, console Log is not considered first. This is what the runtime considers; When the sentence let a is encountered, an uninitialized variable a is created in the global scope.

This relational linked list will finally point to the context text environment saved by the function object.

Finally, the foo function is invoked, and it is found that the a that needs to print is uninitialized data, so it will eventually be wrong.

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-VaCUa4yR-1643156410434)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211226145120116. PNG)]

Take another classic topic as an example. First fill in the global object of the global context with liList: [], and then go down to the loop part.

As in the previous processing of the function body, for(;) The part of and the specific loop statements should be discussed separately.

  • First, create a data with i=0 in the text environment of the for loop () (because the let is used, it cannot be put into the global execution context)
  • Next, create a function context object for liList[0], which is responsible for storing the context when the function is created
  • Then define the function object content of liList[0], which points to the context when it is created

The part of viewing function and data definitions has been completed. Next, run liList[0] ().

  • First, create a runtime object before execution, which stores the function body console log(i)
  • It is found that there is no i this data at runtime, so it looks up for its function object
  • In his function object, i=0 data is found in the environment of the function running context, and the data is returned and printed successfully
  • And so on, finally print 1, 2, 3, 4, 5

[the external chain image transfer fails. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-gTWCqVgp-1643156410435)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211226150249277. PNG)]

When using let, it will normally output 1, 2, 3, 4 and 5. But what if var is used in the loop body?

  • First create the global object liList: [] and i:0
  • Create a function context object for liList[i], which stores the environment when the function is defined, so it points to the global execution context
  • Define the content of the function object of liList[0], which points to the context when it is created
  • When liList[i] () is run, a runtime object is created, which stores the function body console log(i)
  • It is found that there is no i this data at runtime, so it looks up for its function object
  • In his function object, i=5 data is found in the environment of the function running context, and the data is returned and printed successfully
  • And so on, finally print 1, 2, 3, 4, 5

As for why i=5 in the found context, it is because i is only stored in the global object of the global execution context, and the context environment pointed to by each function object all points to the global object. Therefore, in the end, each function object all references i with the value of 5 in the global object.

[the external chain image transfer fails. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-P8rJgGLZ-1643156410436)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211226152038369. PNG)]

closure

After understanding the execution context, you can explain closures.

Although the word closure sounds ethereal, in fact, you only need to remember this sentence:

The variable of external superior scope can be accessed inside the function because of the scope chain. The variables inside the function that can be accessed from outside the function are closures.

Therefore, closure is actually the existence of free variables in a function that can be accessed externally. Closure is "function + free variables"

You can understand free variables with the help of private data in object-oriented. Closure is a process that exposes this private data to the outside world and cannot be modified from the outside.

How Vue tracks changes

In Vue, the redistribution of local variables cannot be tracked as in the previous example, and there is no such mechanism in JavaScript. What we can track is the change of object property.

When we return a normal JavaScript object from the data function of a component, Vue wraps the object in a package with get and set handlers Proxy Yes. Proxy is introduced in ES6, which enables Vue 3 to avoid some responsiveness problems in previous versions of Vue.

Remember the previous form? Now we have an answer to how Vue implements these key steps:

  • Track when a value is read: the track function in the get processing function of proxy records the property and the current side effects.

  • Detect when a value changes: call the set handler function on the proxy.

  • Rerun the code to read the original value: the trigger function looks for which side effects depend on the property and executes them

If we want to rewrite our original example with a component, we can do this:

const vm = createApp({
  data() {
    return {
      val1: 2,
      val2: 3
    }
  },
  computed: {
    sum() {
      return this.val1 + this.val2
    }
  }
}).mount('#app')

console.log(vm.sum) // 5

vm.val1 = 3

console.log(vm.sum) // 6

The object returned by data will be wrapped in a responsive proxy and stored as this$ data. Property this.val1 and this Val2 is this$ data. Val1 and this$ data. Val2 alias, so they pass through the same proxy.

Vue will wrap sum's function in a side effect. When we try to access this Sum, it will run the side effect to calculate the value. The responsive agent wrapping $data will track that property val1 and val2 are read when the side effects run.

How to make rendering responsive

The template of a component is compiled into a render Function. The rendering function creates VNodes and describes how the component should be rendered. It is wrapped in a side effect that allows Vue to track "touched" properties at run time.

A render function is conceptually very similar to a computed property. Vue doesn't exactly track how dependencies are used. It only knows that these dependencies are used at a certain point in time when the function runs. If any of these properties subsequently change, it will trigger a side effect to run again, rerunning the render function to generate new VNodes. These actions are then used to make the necessary changes to the DOM.

props

Example:

const app = createApp({})

// Simple grammar
app.component('props-demo-simple', {
  props: ['size', 'myMessage']
})

// Object syntax, providing validation
app.component('props-demo-advanced', {
  props: {
    // Type check
    // Multiple possible types
    height: [Number, String],
    // Type check + other verification
    age: {
      type: Number,
      default: 0,
      required: true,
      validator: value => {
        return value >= 0
      },
      default: 100
    },
    // Objects with default values
    propE: {
      type: Object,
      // The default value of an object or array must be returned from a factory function 		//  new
      default() {
        return { message: 'hello' }
      }
    },
    // Functions with default values
    propG: {
      type: Function,
      // Unlike the default value of an object or array, this is not a factory function -- it is a function used as the default value 		//  new
      default() {
        return 'Default function'
      }
    }
  }
})

There is not much content in this part. The document is mainly introduced in the in-depth component - > props

Relevant knowledge

Deep component Props

Here are some props that are easy to pass errors.

Pass in a number

When we pass a number, whether it is static or not, we need to pass it by v-bind, for example:

<!-- even if `42` It's static. We still need to pass `v-bind` To tell Vue     -->
<!-- This is a JavaScript Expression instead of a string.             -->
<blog-post :likes="42"></blog-post>

<!-- Dynamic assignment with a variable.-->
<blog-post :likes="post.likes"></blog-post>
Pass in a Boolean value

When we set type: Boolean for a transfer attribute, if the specific transfer value is true, the following value part can be omitted

If you want to transfer specific values, you need to pass them through the v-bind method, for example:

<!-- Include this prop Including the case of no value, it means `true`.           -->
<!-- If not props Middle handle is-published The type of is set to Boolean,
Then the value here is an empty string instead of“ true".  -->
<blog-post is-published></blog-post>

<!-- even if `false` It's static. We still need to pass `v-bind` To tell Vue  -->
<!-- This is a JavaScript Expression instead of a string.             -->
<blog-post :is-published="false"></blog-post>

<!-- Dynamic assignment with a variable.                                -->
<blog-post :is-published="post.isPublished"></blog-post>
Pass in all properties of an object

If you want to pass in all the properties of an object as prop, you can use v-bind without parameters (replace: prop name with v-bind). This is a bit similar to the scope slot v-slot:obj="objName", which directly sends an object to the parent component as the content in the scope slot.

For example, for a given object, post:

post: {
  id: 1,
  title: 'My Journey with Vue'
}

The following template:

<blog-post v-bind="post"></blog-post>

Equivalent to:

<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>
Avoid sub components modifying Prop

Although we all know that there is a one-way data flow between parent and child components. In JavaScript, objects and arrays are passed in by reference, so for an array or object type prop, changing the object or array itself in the sub component will affect the state of the parent component. However, there are two common situations that try to change prop:

  1. This prop is used to pass an initial value; Next, this sub component wants to use it as a local prop data.

    In this case, it is better to define a local data property and take this prop as its initial value:

props: ['initialCounter'],
data() {
  return {
    counter: this.initialCounter
  }
}

​ 2. This prop is passed in as an original value and needs to be converted. In this case, it is best to use the value of this prop to define a calculation attribute:

props: ['size'],
computed: {
  normalizedSize() {
    return this.size.trim().toLowerCase()
  }
}
Prop case naming

attribute names in HTML are case insensitive, so browsers interpret all uppercase characters as lowercase characters.

Therefore, when passing prop, it should be named with dash separator in html and hump in javascript.

const app = Vue.createApp({})

app.component('blog-post', {
  // Using camelCase in JavaScript
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- stay HTML Used in kebab-case -->
<blog-post post-title="hello!"></blog-post>
Disable Attribute inheritance

When the component returns a single root node, non prop attributes (as well as class and id) will be automatically added to the root node's attributes.

However, the events listed in the emits option will not inherit from the root element of the component and will also be removed from the $attrs property.

If we want the attribute to be applied to elements other than the root node, we can set the inheritattributes option to false, and then bind the target element to $attributes through the v-bind method.

If you do not want to bind to the root node, you must add inheritattrs to change it manually, for example:

app.component('date-picker', {
  inheritAttrs: false,
  template: `
    <div class="date-picker">
      <input type="datetime-local" v-bind="$attrs" />
    </div>
  `
})

computed + watch

computed

Calculated properties are cached based on their response dependencies. Computed properties are only re evaluated when the relevant responsive dependency changes. This means that as long as the data object on which he depends has not changed, when the set calculation property is accessed multiple times, the calculation property will immediately return the previous calculation result without executing the function again.

const app = createApp({
	data(){
		return{
			a: 1
		}
	},
	computed:{
		 // Read only
        aDouble() {
        	return this.a * 2
        },
        // Reading and setting
        aPlus: {
        	get() {
            	return this.a + 1
        	},
            set(v) {
            	this.a = v - 1
          	}
        }
	}
})

It should be noted that it is said here to cache the corresponding dependencies, so it will not be updated for a non responsive data, for example:

computed: {
  now() {
    return Date.now()
  }
}

The above describes how to implement the computed method through attribute configuration in the options syntax. Now we will introduce how to use it in v3.

The specific understanding is consistent with that used in v2:

const count = ref(1)
// By default, only one getter function is received
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2

// Alternatively, accept an object with get and set functions to create a writable ref object
// Therefore, in the calculated function, the parameter is an object, and the object members are get function and set function
const plusOne2 = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})
plusOne2.value = 1
console.log(count.value) // 0

watch

For the watch part, here are a few configuration items to remember.

  • Parameters:

    • {string | Function} source

    • {Function | Object} callback

    • {Object} [options]

      • {boolean} deep: in order to find the change of the internal value of the object, you can specify deep: true in the option parameter. The same applies to listening for array changes. (Note: when changing (not replacing) an object or array and using the deep option, the old value will be the same as the new value because their references point to the same object / array. Vue does not keep a copy of the value before the change.)

      • {boolean} immediate: specifying immediate: true in the option parameter will immediately trigger the callback with the current value of the expression.

      • {string} flush: the flush option can better control the callback time. It can be set to 'pre', 'post', or 'sync'. The default value is' pre ', and the specified callback should be called before rendering. It allows the callback to update other values before the template runs. " The 'post' value can be used to delay the callback until after rendering. This value is used if the callback needs to access the updated DOM or subcomponent through $refs. For 'pre' and 'post', callbacks are buffered using queues. Callbacks are added to the queue only once, even if the observation changes multiple times. Intermediate changes in the value will be skipped and will not be passed to the callback. For more information about flush, see Side effects refresh timing.

        ---->Buffering callbacks can not only improve performance, but also help ensure data consistency. The listener will not be triggered until the code that performs the data update is completed.

  • Return: {Function} unwatch

Finally, the method of passing in the callback array probably means that multiple functions can be executed simultaneously when listening for a responsive property.

watch: {
    // Listen for top-level properties
    // Like computed, if you use the shorthand method, write a function called the listening object
    // If you need to add a configuration in it, it is represented by an object
    a(val, oldVal) {
      console.log(`new: ${val}, old: ${oldVal}`)
    },
   	
    // The callback will be called when the property of any object being listened on changes, no matter how deep it is nested
    c: {
      handler(val, oldVal) {
        console.log('c changed')
      },
      deep: true
    },
    
    // Listen for a single nested property
    'c.d': function (val, oldVal) {
      // do something
    },
    // In fact, there is another way to write nested attributes (this part should be placed outside the watch configuration here for reference only)
    this.$watch(
      () => this.c.d,
      (newVal, oldVal) => {
        // Do something
      }
    )
    
    // This callback will be called immediately after listening starts
    e: {
      handler(val, oldVal) {
        console.log('e changed')
      },
      immediate: true
    },
    
    // You can pass in the callback array, and they will be called one by one
    // Note the format here
    // When you use handler to represent a handler function, you need to wrap it in curly braces
    f: [
      'handle1',
      function handle2(val, oldVal) {
        console.log('handle2 triggered')
      },
      {
        handler: function handle3(val, oldVal) {
          console.log('handle3 triggered')
        }
        /* ... */
      }
    ]
}
Relevant knowledge

The symbol feature in es2015 often appears when looking at the documents related to watch, so we will extend the study in this section.

es2015 symbol

In addition to Number, String, Boolean, Object, null and undefined, ES6 data types also add symbols.

symbol is actually a tool used to distinguish variable names. For example, when adding Apple phones and apple fruits to the shopping cart, they can be named apple, which will cause variable naming conflict. However, in general, we will distinguish by defining a semantic variable name, such as phone apple and fruit apple.

Using symbol can actually treat it as a string that will never be repeated.

let user1 = {
  name: "Li Si",
  key: Symbol(),
};
let user2 = {
  name: "Li Si",
  key: Symbol(),
};
let grade_conflict = {
  [user1.name]: { js: 99, css: 89 },
  [user2.name]: { js: 56, css: 100 },
};

let grade = {
  [user1.key]: { js: 99, css: 89 },
  [user2.key]: { js: 56, css: 100 },
};
console.log(grade_conflict); // Li Si: css: 100 js: 56
console.log(grade); // Symbol(): css: 89 js: 99;Symbol(): css: 100 js: 56

In the above example, if the name in the object is repeated and the value of name is saved as the attribute name in grade, the following data will overwrite the previous data. Using symbol type to define a unique value can avoid the problem of overwriting.

Just looking at the above example, you may feel that using Symbol is not as convenient and effective as adding prefix manually, so take a look at the following example:

class Cache {
  static data = {};
  static set(name, value) {
    this.data[name] = value;
  }
  static get(name) {
    return this.data[name];
  }
}

let user = {
  name: "liziz",
  key: Symbol(),
};

let cart = {
  name: "liziz",
  key: Symbol(),
};

Cache.set(user.key, user);
Cache.set(cart.key, cart);
console.log(Cache.get(user.key));

Now we have created a Cache class to simulate the data buffer. In the project of separating the front end from the back end, the data buffer is essential. Now suppose there are users and shopping carts named liziz, and the background needs to store the data in the corresponding database. How to save it? If the name is saved according to the name with duplicate names, it is bound to cause data conflict in the background. Therefore, we can manually add a key value with the value of Symbol for the data object. When saving according to the unique key value, there will be no conflict. In fact, it can also be understood as uuid.

Please don't think that adding a prefix name manually can solve such a conflict. After all, the project is completed by multiple people. You can't ensure the naming habits of others. Maybe when you name Apple phone, your colleagues also name Apple phone.

The difference between the two

If you want to compare the difference between the calculated attribute and the listener, the calculated attribute can calculate the relationship between multiple attributes, and the listener can only detect a certain attribute one by one, such as calculating a fullname value. The calculated attribute can be spliced through lastname and firstname, and the listener can only listen to these two sub attributes respectively, The value of fullname is calculated during listening.

watch: {
    firstName(val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName(val) {
      this.fullName = this.firstName + ' ' + val
    }
},
computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
}

methods

A declared function is triggered by an event.

Chuan Shen

When calling the function, we can not only pass the data parameter, but also pass a $event attribute as the last parameter, which is responsible for manipulating the global DOM elements, for example:

<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>
// ...
methods: {
  warn(message, event) {
    // Native events are now accessible
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

There are also cases where parameters do not need to be passed. At this time, there is no need to manually declare $event in the template. You can directly use the event parameter in the function set by methods, for example:

<div id="event-with-method">
  <!-- `greet` Is the method name defined below -->
  <button @click="greet">Greet</button>
</div>
methods: {
    greet(event) {
      // `Event ` is a native DOM event
      if (event) {
        alert(event.target.tagName)
      }
    }
}

When looking at the document, I also found that multiple functions can be triggered at the same time in a click event!

<!-- these two items. one() and two() The button click event will be executed -->
<button @click="one($event), two($event)">
  Submit
</button>

Event modifier

Only some common modifiers are introduced here.

.stop

The stop modifier prevents bubbling, which is executed by default.

<div @click="clickEvent(2)" style="width:300px;height:100px;background:red">
    <button @click.stop="clickEvent(1)">click</button>
</div>

methods: {
    clickEvent(num) {
        // Click the button without stop to output 1 2
        // Add stop and click the button to output 1
        console.log(num)
    }
}

.capture

The function of the capture modifier and stop, in turn, set to capture from outside to inside, not prohibit.

<div @click.capture="clickEvent(2)" style="width:300px;height:100px;background:red">
    <button @click="clickEvent(1)">click</button>
</div>

methods: {
    clickEvent(num) {
        No capture Click the button to output 1 2
        Add capture Click the button to output 2 1
        console.log(num)
    }
}

If it is to prevent bubbling, set it for the innermost element stop modifier; If you set capture manually, set capture for the outermost element capture.

.self

The self modifier is used to trigger the event only when the event binding itself is clicked

<div @click.self="clickEvent(2)" style="width:300px;height:100px;background:red">
    <button @click="clickEvent(1)">click</button>
</div>

methods: {
    clickEvent(num) {
        // Click the button to output 1 2 without self
        // Add self, click the button to output 1, and click div to output 2
        console.log(num)
    }
}

The. self modifier may also be used to prevent bubbling. If you click an internal element, it will not bubble upward, but if you click an external element itself, it can trigger an event.

.prevent

The function of the prevent modifier is to prevent default events (such as the jump of a tag)

<a href="#"@ click. Prevent =" clickevent (1) "> Click me</a>
<!-- Submitting an event no longer reloads the page -->
<form @submit.prevent="onSubmit"></form>

methods: {
    clickEvent(num) {
        // Click the a tab without prevent to jump first and then output 1
        // With prevent added, clicking the a tag will not jump, only 1 will be output
        console.log(num)
    }
}

When using modifiers, order is important; The corresponding code will be generated in the same order. Therefore, use v-on: click prevent. Self will block all default event clicks, while v-on: click self. Prevent only blocks clicking on the default event of the element itself.

.passive

When we listen to the element scrolling event, we will always trigger the onscroll event. There is no problem on the pc side, but on the mobile side, it will make our web page card. Therefore, when we use this modifier, it is equivalent to setting an onscroll event lazy modifier.

<!-- Default behavior for scrolling events (Rolling behavior) Will trigger immediately,   -->
<!-- Without waiting `onScroll` Done,                    -->
<!-- To prevent it from containing `event.preventDefault()` Situation  -->
<div @scroll.passive="onScroll">...</div>
.exact

The. exact modifier allows you to control events triggered by a precise combination of system modifiers.

<!-- even if Alt or Shift It is also triggered when pressed together -->
<button @click.ctrl="onClick">A</button>

<!-- Yes and only Ctrl Triggered when pressed -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- Triggered when no system modifier is pressed -->
<button @click.exact="onClick">A</button>
.sync

When the parent component passes a value into the child component and the child component wants to change this value, you can do this:

In parent component
<children :foo="bar" @update:foo="val => bar = val"></children>

In sub assembly
this.$emit('update:foo', newValue)
Copy code

The sync modifier is used to abbreviate:

In parent component
<children :foo.sync="bar"></children>

In sub assembly
this.$emit('update:foo', newValue)

When using sync, the event name passed by the sub component must be update:value, where value must be exactly the same as the name declared in props in the sub component.

This modifier is commonly used in ui components, such as controlling the synchronous modification of pop-up box display content through sync.

emits

When I used emits before, I always used the first method introduced in the document, that is, array syntax:

// Array syntax
app.component('todo-item', {
  emits: ['check'],
  created() {
    this.$emit('check')
  }
})

Only when I read the document did I know that it can also customize the configuration in the form of objects. Yes, after all, this series belongs to Vue's option configuration, which should be customizable.

// Object syntax
app.component('reply-form', {
  emits: {
    // No validation function
    click: null,

    // With validation function
    submit: payload => {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }
})

In vue3 In X, due to the limitation of the setup function, the current instance is not pointed to in setup(), so the context parameter needs to be used.

<script>
import { defineComponent } from 'vue'
export default defineComponent({
  emits: {
      'on-change': null
  }
  setup (props, ctx) {
    const clickBtn = () => {
      ctx.emit("on-change", "hi~");
    };
    return { clickBtn }
  }
})
</script>

In the component, all the emit events should be configured in the emits option. When using the object mode, you can configure the emit event with verification. When it is null, it means no verification. During verification, the parameters of the emit event will be transferred to the parameters of the verification function. When the verification function fails and returns false, the console will issue a warning, but the emit event will continue to execute.

Generally speaking, the use of emits, whether array or object, will eventually pass the event. The use of array or object is only to record the emit event in the instance or verify the parameters in the event, and will not cancel the event transmission because the verification fails.

expose(3.2+)

This API will serve the setup syntax sugar of version 3.2 +. The components using < script setup > are closed by default, that is, the public instance of the component obtained through the template ref or $parent chain will not expose any binding declared in < script setup >.

The expose option limits the properties that public instances can access.

export default {
  // increment will be exposed,
  // But count can only be accessed internally
  expose: ['increment'],

  data() {
    return {
      count: 0
    }
  },

  methods: {
    increment() {
      this.count++
    }
  }
}

Returning a render function will prevent us from returning anything else. Internally, this should not be a problem, but it is different when we want to expose the method of this component to the parent component through the template ref.

We can solve this problem by calling expose and passing it an object in which the defined property can be accessed by external component instances:

import { h, ref } from 'vue'
export default {
  setup(props, { expose }) {
    const count = ref(0)
    const increment = () => ++count.value

    expose({
      increment
    })

    return () => h('div', count.value)
  }
}

The increment method will now be accessible through the template ref of the parent component.

To specify the attributes to be exposed in the < script setup > component, use the defineExpose compiler macro:

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

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

When the parent component obtains the instance of the current component through the template ref, the obtained instance will be like this {A: number, B: number} (Ref will be unpacked automatically as in ordinary instances).

Life cycle hook

There are two ways to use the declaration cycle hook: one is to use the configuration item method, and the other is to use the import method to introduce the hook function.

For vue2 X version and vue3 The introduction of X can be seen in this article article , very detailed.

In vue3 X adds two hook functions for debugging: onRenderTracked and onrendertrigged, which are also introduced in the above article.

These lifecycle hook registration functions can only be used in setup() Period synchronization is used because they rely on the internal global state to locate the currently active instance.

The basic format of hook function is as follows:

onxxxxx(()=>{
	// ,,,
})

Here are some important things to remember when reading the document:

  1. Mounted and updated do not guarantee that all sub components are also mounted. If you want to wait until the whole view is rendered, you can use VM inside mounted or updated$ nextTick. Moreover, the hook is not called during server-side rendering.

    mounted() {
      this.$nextTick(function () {
        // Code that runs only after the entire view is rendered
      })
    }
    
  2. beforeUpdate is called before the DOM is updated after the data changes. This is suitable for accessing the existing DOM before it will be updated, such as removing manually added event listeners.

directives

Declare a set of instructions that can be used in a component instance.

const app = createApp({})
app.component('focused-input', {
  // Declare directives configuration item
  directives: {
    focus: {
      // In fact, the declared periodic function can be invoked in the configuration.
      // And the parameter can receive the instance object el
      mounted(el) {
        el.focus()
      }
    }
  },
  template: `<input v-focus>`
})

Relevant knowledge

Reusable & combined custom instruction

Let's take autofocus in the above input box as an example, but this time we define a global instruction:

const app = Vue.createApp({})
// Register a global custom instruction ` v-focus`
app.directive('focus', {
  // When the bound element is mounted in the DOM
  mounted(el) {
    // Focus element
    el.focus()
  }
})
Hook function of instruction

There are also lifecycles in custom directives

// register
app.directive('my-directive', {
  // Instructions are hooks with a set of lifecycles:
  // Called before the attribute of the binding element or the event listener is applied.
  created() {},
  // Call before the parent component of the binding element is mounted.
  beforeMount() {},
    
  // Called when the parent component of the binding element is mounted
  // At this time, the el instance element can be obtained
  mounted() {},
    
  // Call before updating the VNode with components.
  beforeUpdate() {},
  // After the VNode** update of the VNode containing the component and its subcomponents, it is called.
  updated() {},
    
  // Calls before the parent component of the binding element is uninstalled
  beforeUnmount() {},
  // Called when the parent component of a binding element is unloaded
  unmounted() {}
})

Note:

When we use the life cycle, it is used as a function of the instruction configuration item. According to the previous knowledge, since it can be realized through the method of object configuration item, it can also be realized through the abbreviation of function method:

// You may want to trigger the same behavior when mounted and updated, regardless of other hook functions.
// Then you can pass this callback function to the instruction:
app.directive('pin', (el, binding) => {
  el.style.position = 'fixed'
  const s = binding.arg || 'top'
  el.style[s] = binding.value + 'px'
})

The parameters of the custom instruction hook function are: el, binding, vnode, preVnode.

  • The element to which the el instruction is bound. This can be used to manipulate DOM directly.
  • binding is an object that contains the following properties.
    • Instance: the instance of the component that uses the instruction.
    • Value: the value passed to the instruction. For example, in v-my-directive="1 + 1", the value is 2.
    • oldValue: previous value, available only in beforeUpdate and updated. Whether the value has changed or not is available.
    • Arg: parameter passed to instruction (if any). For example, in v-my-directive:foo, arg is "foo".
    • Modifiers: objects that contain modifiers, if any. For example, in v-my-directive foo. In bar, the modifier object is {foo: true, bar: true}.
    • dir: an object that is passed as a parameter when registering an instruction. For example, in the following instructions
Dynamic instruction parameters

If we want to transfer data to a component using instructions, we can transfer data through assignment on the component, for example:

<p v-pin="200">Stick me 200px from the top of the page</p>

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.value is the value we pass to the instruction -- in this case, 200
    el.style.top = binding.value + 'px'
  }
})

Through this setting, we can fix the element bound by the instruction at a position 200px away from the top. If we want to manually change the position of the final rendering and the offset of the position through input, we can manually pass the parameter to dynamically set the style through this parameter:

<p v-pin:[direction]="pinPadding">I am pinned onto the page at 200px to the left.</p>
const app = Vue.createApp({
  data() {
    return {
      direction: 'right',
      pinPadding: 200
    }
  }
})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.arg is the parameter we pass to the instruction, which is one of the attribute values of the binding parameter
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})

mixins

Mixin is a flexible blending function. A mixin object can contain any component options. It can use the component's life cycle, methods and other functions like components.

A function very similar to mixin is called extensions. The usage is the same as mixin. Extensions takes precedence over mixin.

When the component uses this blend:

  • If the two options do not have the same name, the mixed life cycle hook shall be executed first, and then the code part of the component shall be executed:

  • const myMixin = {
      created() {
        console.log('mixin Object's hook is called')
      }
    }
    
    const app = Vue.createApp({
      mixins: [myMixin],
      created() {
        console.log('Component hook called')
      }
    })
    
    // =>"Hook of mixin object called"
    // =>"Component hook called"
    
  • If the two options have the same name, these options will be merged in an appropriate way. In case of conflict between the properties of the data, the data of the component itself will prevail:

  • const myMixin = {
      data() {
        return {
          message: 'hello',
          foo: 'abc'
        }
      }
    }
    
    const app = Vue.createApp({
      mixins: [myMixin],
      data() {
        return {
          message: 'goodbye',
          bar: 'def'
        }
      },
      created() {
        console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" }
      }
    })
    

In Vue 2, mixin is the main tool for abstracting some component logic into reusable blocks. However, they have several problems:

  • Mixins are prone to conflict: because the properties of each mixin are merged into the same component, you still need to know every other feature in order to avoid property name conflict.
  • Reusability is limited: we cannot pass any parameters to mixin to change its logic, which reduces their flexibility in abstract logic.
  • The custom option of global mixin is recommended as plug-in unit Publish, because the global will affect each sub component created later.

Relevant knowledge

In order to solve the defect of mixing, while Vue 3 continues to support mixin, Combined API Is a more recommended way to share code between components. Here we introduce reusable & combined - > plug-in, reusable & combined - > combined API

Reusable & combined - combined API

When creating components, we assume that there will be three variables a, b and c in data. In methods, there may be methods with different operations in the order of a, b, a and c. in computed, there may be calculation attributes related to the order of a, a, c and b. in watch, there will be the listening order of b, c and a. At this time, the list of logical concerns will grow. For those who did not write components at the beginning, the method order when viewing the code is not in the order of a, b and c, but in a disorderly order. They often jump to different functions everywhere, which will make the components difficult to read and understand.

[the external chain image transfer fails. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-XztyvMtl-1643156410443)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211230110729382. PNG)]

This is an example of a large component where logical concerns are grouped by color.

This fragmentation makes it difficult to understand and maintain complex components. The separation of options masks potential logical problems. In addition, when dealing with a single logical concern, we must constantly "jump" to the option block of the relevant code.

It would be better if you could collect the code related to the same logical concern. And that's what the composite API enables us to do.

In Vue components, a place where you can actually use the composite API is called setup.

Simply put, it is to introduce the previous calculated, watch and other methods as a function API, and then use these methods near a variable declared in setup. This can ensure that a variable in a logical concern corresponds to a piece of logic.

However, isn't this moving the code to setup? Won't it become very bloated for this function? Therefore, before proceeding with other tasks, we need to extract the code into multiple independent composite functions and encapsulate all relevant operations on different attributes in one js file. If the component needs to use these methods, it can be viewed directly in a js file. For example:

// src/composables/useUserRepositories.js

import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'

export default function useUserRepositories(user) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)
  watch(user, getUserRepositories)

  return {
    repositories,
    getUserRepositories
  }
}

The above method encapsulates the methods related to warehouse information in useuserrepositories JS file, the function is directly introduced into the component, and can be used normally as before:

// src/components/UserRepositories.vue
import useUserRepositories from '@/composables/useUserRepositories'
import { toRefs } from 'vue'

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: {
      type: String,
      required: true
    }
  },
  setup (props) {
    const { user } = toRefs(props)
    const { repositories, getUserRepositories } = useUserRepositories(user)
    return {
      repositories
      getUserRepositories
    }
  }
}

Look! After using the composite API in this way, whether the content in the component is greatly reduced! Let's take another look at the encapsulated results if multiple data attributes are used:

// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: {
      type: String,
      required: true
    }
  },
  setup(props) {
    const { user } = toRefs(props)
    const { repositories, getUserRepositories } = useUserRepositories(user)
    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)
    const {
      filters,
      updateFilters,
      filteredRepositories
    } = useRepositoryFilters(repositoriesMatchingSearchQuery)
    return {
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}

When we need to use any variable later, we can directly find the details through the combined API related to the variable.

setup

For props, the first parameter of setup, the received parameter is responsive. You cannot obtain a specific parameter through structure assignment. If you do so, it will lose its responsiveness. However, you can also use the toRefs method to wrap props and assign the structure, for example:

 const { title } = toRefs(props)

If title is an optional prop, there may be no title in the incoming props. In this case, toRefs will not create a ref for the title. You need to replace it with toRef:

// MyBook.vue
import { toRef } from 'vue'
setup(props) {
  const title = toRef(props, 'title')
  console.log(title.value)
}

The second parameter passed to the setup function is context. Context is an ordinary JavaScript object that exposes other values that may be useful in setup:

// MyBook.vue
export default {
  setup(props, context) {
    // Attribute (non responsive object, equivalent to $attrs)
    console.log(context.attrs)

    // Slot (non responsive object, equivalent to $slots)
    console.log(context.slots)

    // Trigger event (method, equivalent to $emit)
    console.log(context.emit)

    // Expose public property (function)
    console.log(context.expose)
  }
}

Note that unlike props, the properties of attrs and slots are non responsive.

If you plan to apply side effects based on changes in attrs or slots, you should do so in the onBeforeUpdate lifecycle hook.

Declare periodic hook function

[the external chain image transfer fails. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (IMG anujwu79-1643156410444) (C: \ users \ neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20211231085339510. PNG)]

Provide / Inject

In the options syntax, we can provide the attribute to be used in the form of an object, and then all its descendant components can inject the attribute, for example:

<script>
import MyMarker from './MyMarker.vue'

export default {
  data() {
    return {
      todos: ['Feed a cat', 'Buy tickets']
    }
  },
  components: {
    MyMarker
  },
  // Pass ordinary property
  provide: {
    location: 'North Pole',
    geolocation: {
      longitude: 90,
      latitude: 135
    }
  // When passing the component instance property, we need to convert provide into a function that returns an object
  provide() {
    return {
      todoLength: this.todos.length
    }
  }    
}
</script>
<script>
export default {
  inject: ['location', 'geolocation'],
  // Inject can also be made optional by setting the default value
  inject: {
    foo: { default: 'foo' }
  },
  // If it needs to be injected from a property with a different name, use from to represent its source property
  inject: {
    foo: {
      from: 'bar',
      default: 'foo'
    }
  }
}
</script>

In the composite API, we need to manually introduce the provide function, which has two parameters: name:String and value.

Let's refactor the previous example:

import { provide } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    provide('location', 'North Pole')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
  }
}

Similarly, when using the exposed attributes, you also need to manually introduce a function called inject, which has two parameters: name and default value (optional)

import { inject } from 'vue'

export default {
  setup() {
    // If you want to obtain the location, but the ancestor component does not provide it, you can use the default value
    const userLocation = inject('location', 'The Universe')
    // Directly obtain the property provided by the ancestor component
    const userGeolocation = inject('geolocation')

    return {
      userLocation,
      userGeolocation
    }
  }
}

provide and inject bindings are not responsive. This is deliberate.

However, if you pass in a responsive object, the property of the object is still responsive.

According to the principle that data and data modification operations are placed in the parent component, if the descendant component wants to modify data, it will inevitably affect the parent component (because the transmitted data can be responsive data), so it is recommended to limit all modifications to the responsive property within the component defining provide as far as possible, Provide a method to change the responsive property to solve the problem of updating the inject data inside the injected component. For example:

import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })
    
    const updateLocation = () => {
      location.value = 'South Pole'
    }
    
    provide('location', location)
    provide('geolocation', geolocation)
    provide('updateLocation', updateLocation)
  }
}
Template reference

And this. In options syntax$ refs. XXX can get the specific node consistency. We can declare ref as usual and return from setup():

<template> 
  <div ref="root">This is a root element</div>
</template>

<script>
  import { ref, onMounted } from 'vue'

  export default {
    setup() {
      // This variable is the same as the node name bound by ref 
      const root = ref(null)

      onMounted(() => {
        // DOM elements are assigned to ref s after the initial rendering
        console.log(root.value) // <div>This is a root element</div>
      })

      return {
        root
      }
    }
  }
</script>

This function can also be used in v-for to dynamically use function references to perform custom processing:

<template>
  <!--Use function binding dynamically ref,Every time a node is encountered, a data is inserted into the node number group-->
  <div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
    {{ item }}
  </div>
</template>

<script>
  import { ref, reactive, onBeforeUpdate } from 'vue'

  export default {
    setup() {
      const list = reactive([1, 2, 3])
      const divs = ref([])

      // Make sure to reset ref before each update
      onBeforeUpdate(() => {
        divs.value = []
      })

      return {
        list,
        divs
      }
    }
  }
</script>

Changes to the listening template reference can replace the life cycle hook demonstrated in the previous example.

But a key difference from the lifecycle hook is that watch() and watchEffect() run side effects before the DOM is mounted or updated, so the template reference has not been updated when the listener runs.

Therefore, listeners that use template references should be defined with the flush: 'post' option, which will run side effects after the DOM is updated, ensuring that the template reference is synchronized with the DOM and references the correct elements.

<template>
  <div ref="root">This is a root element</div>
</template>

<script>
  import { ref, watchEffect } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      watchEffect(() => {
        console.log(root.value) // => <div>This is a root element</div>
      }, 
      {
        flush: 'post'
      })

      return {
        root
      }
    }
  }
</script>
Reusable & Composite plug-in (not seen)

I haven't touched the content of this part. I'll introduce it later after I've written it myself. reference resources file

globalProperties

Add a global property that can be accessed in any component instance of the application. The property of the component has priority in case of naming conflict.

app.config.globalProperties.foo = 'bar'

app.component('child-component', {
  mounted() {
    console.log(this.foo) // 'bar'
  }
})

This can replace Vue 2 Vue of X Prototype extension:

// Before (Vue 2.x)
Vue.prototype.$http = () => {}

// After (Vue 3.x)
const app = createApp({})
app.config.globalProperties.$http = () => {}

Access discussed when adding a water group_ Token and refresh_token long token problem:

[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-2wxsIbNZ-1643156410445)(C:\Users\neko_ sauce \ appdata \ roaming \ typora \ typora user images \ image-20220101164842467. PNG)]

Keywords: Front-end TypeScript Vue Vue.js

Added by digitalmarks on Wed, 26 Jan 2022 16:26:56 +0200