Vue Element+Node.js development of enterprise general management background system - Vue advanced

Keywords: Javascript node.js Vue Vue.js

Front: what did new Vue do

Before looking at the provide inject, you'd better know what new Vue has done (the case used is the provide inject)

  • Vue's true face is here. In fact, it is a class implemented by Function. We can only instantiate it through new Vue

  • In this_ Before init (options) is executed, in addition to extending the prototype prototype method, it will also extend the global static method (set, delete, nextTick... - > mount to Vue asset_types [component|direct|filter] and |base(Vue instance) - > mount to Vue.options) for Vue. This part of the code searches the initGlobalAPI. There is no example here
  • Vue initialization mainly does the following things:
  1. Merge configuration (options)
  2. initLifecycle, initEvents and initRender are before beforeCreate
  3. Initialize inject, initialize status [props, methods, data, watach] (initState), initialize provide after beforeCreate and before created
  4. Finally, call vm.$mount.

Now it will be much better to look at provide and inject

provide inject

provide inject to improve a better cross component solution

Case link

Official documents: provide / inject

provide should be an object or a function that returns an object that can inject properties into its descendants

inject should be a string array or an object (key is the local binding name)

Note: the provide and inject bindings are not responsive. Then, if you pass in a listener object, the property of the object is still responsive

<body>
  <div id="root">
    <Test></Test>
  </div>
  <script>
    function registerPlugin() {
      Vue.component('Test', {
        template: '<div>{{message}}<Test2 /></div>',
        provide() {
          return {
            elTest: this
          }
        }, // function is used to obtain the runtime environment, otherwise this will point to window
        data() {
          return {
            message: 'message from Test'
          }
        },
        methods: {
          change(component) {
            this.message = 'message from ' + component
          }
        }
      })
      Vue.component('Test2', {
        template: '<Test3 />'
      })
      Vue.component('Test3', {
        template: '<button @click="changeMessage">change</button>',
        inject: ['elTest'],
        methods: {
          changeMessage() {
            this.elTest.change(this.$options._componentTag)
          }
        }
      })
    }
    Vue.use(registerPlugin)
    new Vue({
      el: '#root'
    })
  </script>
</body>
  1. Provide its properties / methods / data... Even Vue(this) to future generations. Use provide() {return {eltest: This}} to mount the provide to vm.options first and then vm.options_ Provided

  2. inject is first mounted on vm.options, and finally on vm (VueComponent), which can be obtained directly through this

    Get component name: through this. $options_ componentTag

Front end: implementation of Vue instance mounting

Before looking at the filter, you'd better know what $mount has done (the case used is the filter)

  • At vue.prototype.com_ When init is executed, vm.$mount and Vue.$mount will be executed
  1. el is restricted. Vue cannot be mounted on root nodes such as body and html
  2. If the render method is not defined, the el or template string is converted to the render method
  3. Finally, Vue only recognizes the render function. With the render function, it will call the mount method mount.call(this, el, hydrating) (VaR mount = Vue. Prototype. $mount, and the method is mounted on the prototype $mount)

The $mount method supports passing in two parameters

  1. The first parameter is el, indicating the mounted element, which can be a string or a DOM object. If it is a string, the browser will call the query method to convert it into a DOM object
  2. The second parameter is related to server-side rendering, and it is not necessary to pass the second parameter in the browser environment

The mountComponent method is then executed

  1. Judge whether render conforms to the specification, and then execute the beforeMounted hook function
  2. The core of mountComponent is to instantiate a rendering Watcher first, and its callback function calls the updateComponent method, calling vm._ in this method. The render method generates a virtual Node and finally calls VM_ Render update DOM
  3. Finally, judge VM_ Ismounted is true, indicating that the instance has been mounted and the mounted hook function is executed at the same time

In the mountComponent method, VM_ update(vm._render(), hydrating) ,vm._ Render ultimately executes the vm.$createElement method and returns vnode (which is a virtual Node)

  • vm.$createElement is actually executing createElement, which involves virtual DOM. The code is very complex and will not be shown here

<div id="app">
  {{ message }}
</div>
<!-- Above and below are equivalent -->
<script>
render: function (createElement) {
  return createElement('div', {
     attrs: {
        id: 'app'
      },
  }, this.message)
}
</script>

Now it will be much better to look at the filter

filter

Case link

<body>
  <div id="root">
    {{message | lower}}
  </div>
  <script>
    new Vue({
      el: '#root',
      filters: {
        lower(value) {
          return value.toLowerCase()
        }
      },
      data() {
        return {
          message: 'Hello Vue'
        }
      }
    })
  </script>
</body>

(anonymous) the final generated render function is like this, and the render function is executed in update

  • It can be seen that the filter is a package of this state
(function anonymous() {
  with (this) {
    return _c('div', { attrs: { id: 'root' } }, [_v('\n' + _s(_f('lower')(message)) + '\n')])
  }
})

Preceding: listening properties

Before watching a watch, it's best to know what the watch does (the example used is watch)

The initialization of the listening property initWatch is in the initState function in the instance initialization phase of Vue

initWatch traverses the watch object and gets each handler (possibly array, function and ordinary object). If the handler is an array, it traverses the array and calls the createwatch method. Finally, execute the vm.$watch method

  • If the handler is an object, take the handler method in the object
  • If the handler is a string, take vm[handler] to see if there is this method

The $watch method is mounted on the Vue prototype

  • First, judge the cb type (because the vm.$watch method can be called directly on the Vue instance, and an object or function can be passed in directly)
  • If immediate is set, the callback function cb will be executed directly
  • Finally, an unwatchFn method is returned, which will call teardown method to remove the watcher

options are processed in the constructor of watcher, so there are four types of watchers: deep watcher, user watcher, computed watcher and sync watcher

  • deep watcher: if (this.deep) traverse(value) in the process of get evaluation of the watcher is actually a deep traversal of an object, because the traversal process is the access to a sub object, which will trigger their getter process, so as to collect dependencies, that is, subscribe to their changed watchers
  • User Watcher: the watch created through vm.$watcher is a user watcher. Its function is to handle errors when evaluating the watcher and executing the callback function
  • computed watcher: once the data dependent on the calculated attribute is modified, the setter process will be triggered to notify all watchers subscribing to its changes to update and execute the watcher.update() method
  • sync watcher: the default userWatcher is asynchronous (when the responsive data changes, trigger the watcher.update(), just push the watcher to a queue). The watcher callback function will be executed in nextTick. If sync: true is configured for the watch, it will be executed synchronously (making its execution order advance)

The last step is to go through the process of collecting dependencies from get. This place is complex. Skip it for the time being. It would be much better to look at the watcher now

watch

Case link

Watch usage 1: common usage (function)

In initWatch, the handler is a function, because the cb (corresponding handler) in the last $watch wants the format to be a function, so no other processing will be performed on it

<body>
  <div id="root">
    <h3>Watch Usage 1: common usage</h3>
    <input v-model="message">
    <span>{{ copyMessage }}</span>
  </div>
  <script>
    new Vue({
      el: '#root',
      watch: {
        message(value) {
          this.copyMessage = value
        }
      },
      data() {
        return {
          message: 'Hello Vue',
          copyMessage: ''
        }
      }
    })
  </script>
</body>

Watch usage 2: binding method

In initWatch, the handler is a string. You need to go to the Vue instance to find the method handler = vm[handler], and then the process is the same as that of usage 1

<body>
  <div id="root2">
    <h3>Watch Usage 2: binding method</h3>
    <input v-model="message">
    <span>{{ copyMessage }}</span>
  </div>
  <script>
    new Vue({
      el: '#root2',
      watch: {
        message: 'handleMessage'
      },
      data() {
        return {
          message: 'Hello Vue',
          copyMessage: ''
        }
      },
      methods: {
        handleMessage(value) {
          this.copyMessage = value
        }
      }
    })
  </script>
</body>

Watch usage 3: deep + handler

During the get evaluation of the watcher, if (this.deep) traverse(value) is actually a deep traversal of an object. By default, the watcher cannot monitor the changes of the internal attributes of the object. At this time, it needs to use the deep attribute to deeply monitor the object

<body>
  <div id="root3">
    <h3>Watch Usage 3: deep + handler</h3>
    <input v-model="deepMessage.a.b">
    <span>{{ copyMessage }}</span>
  </div>
  <script>
    new Vue({
      el: '#root3',
      watch: {
        deepMessage: {
          handler: 'handleDeepMessage',
          deep: true
        }
      },
      data() {
        return {
          deepMessage: {
            a: {
              b: 'Deep Message'
            }
          },
          copyMessage: ''
        }
      },
      methods: {
        handleDeepMessage(value) {
          this.copyMessage = value.a.b
        }
      }
    })
  </script>
</body>

Watch usage 4: immediate + handler

Judge immediate in $watch. If it is true, it will be executed immediately after the Watcher is created

<body>
  <div id="root">
  <div id="root4">
    <h3>Watch Usage 4: immediate</h3>
    <input v-model="message">
    <span>{{ copyMessage }}</span>
  </div>
  <script>
    new Vue({
      el: '#root4',
      watch: {
        message: {
          handler: 'handleMessage',
          immediate: true,
        }
      },
      data() {
        return {
          message: 'Hello Vue',
          copyMessage: ''
        }
      },
      methods: {
        handleMessage(value) {
          this.copyMessage = value
        }
      }
    })
  </script>
</body>

Watch usage 5: bind multiple handler s

  • userWatch is almost always asynchronous, and it executes sequentially
  • If the handler is an array, it will traverse the array and call the createWatcher method. Finally, execute the vm.$watch method
<body>
  <div id="root5">
    <h3>Watch Usage 5: bind multiple handler</h3>
    <input v-model="message">
    <span>{{ copyMessage }}</span>
  </div>
  <script>
    new Vue({
      el: '#root5',
      watch: {
        message: [{
          handler: 'handleMessage',
        },
        'handleMessage2',
        function(value) {
          this.copyMessage = this.copyMessage + '...'
        }]
      },
      data() {
        return {
          message: 'Hello Vue',
          copyMessage: ''
        }
      },
      methods: {
        handleMessage(value) {
          this.copyMessage = value
        },
        handleMessage2(value) {
          this.copyMessage = this.copyMessage + '*'
        }
      }
    })
  </script>
</body>

Watch usage 6: listening object properties

This method of directly listening to a property without using deep: true will reduce the performance overhead

<body>
  <div id="root6">
    <h3>Watch Usage 6: listening object properties</h3>
    <input v-model="deepMessage.a.b">
    <span>{{copyMessage}}</span>
  </div>
    
  <script>
    new Vue({
      el: '#root6',
      watch: {
        'deepMessage.a.b': 'handleMessage'
      },
      data() {
        return {
          deepMessage: { a: { b: 'Hello Vue' } },
          copyMessage: ''
        }
      },
      methods: {
        handleMessage(value) {
          this.copyMessage = value
        }
      }
    })
  </script>
</body>

class style

Case link

The official website Class is bound to Style

<body>
  <h2 class="title">
    <a href="../index.html" style="text-decoration: none; color: #2c3e50">>back</a>
  </h2>
  <div id="root">
    <div :class="['active', 'normal']">Array binding multiple class</div>
    <div :class="[{active: isActive}, 'normal']">Array contains object bindings class</div>
    <div :class="[showWarning(), 'normal']">Array contains method bindings class</div>
    <div :style="[warning, bold]">Array binding multiple style</div>
    <div :style="[warning, mix()]">Array contains method bindings style</div>
    <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">style Multiple value</div>
  </div>

  <script>
    new Vue({
      el: '#root',
      data() {
        return {
          isActive: true,
          warning: {
            color: 'orange'
          },
          bold: {
            fontWeight: 'bold'
          }
        }
      },
      methods: {
        showWarning() {
          return 'warning'
        },
        mix() {
          return {
            ...this.bold,
            fontSize: 20
          }
        }
      }
    })
  </script>
</body>

Array binding object / expression

/* Finally, show the effect */
class="active normal" class="normal"
/* Implementation mode */
:class="[{active: isActive} 'normal']"
:class="(isActive ? 'active': '') + ' normal'"

Vue2.3 + new features

<!-- From back to front, which is compatible with which -->
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

Vue.observe

Case link

Official website Vue.observable()

Usage: make an object responsive. Vue uses it internally to handle the objects returned by the data function. The returned objects can be used directly in rendering functions and calculation properties, and the corresponding updates will be triggered when changes occur. It can also be used as a minimized cross component state memory for simple scenarios (vuex can be replaced in simple scenarios)

<body>
  <h2 class="title">
    <a href="../index.html" style="text-decoration: none; color: #2c3e50">>back</a>
  </h2>
  <div id="root">
    {{message}}
    <button @click="change">Change</button>
  </div>
  <script>
    const state = Vue.observable({ message: 'Vue 2.6' })
    const mutation = {
      setMessage(value) {
        state.message = value
      }
    }
    new Vue({
      el: '#root',
      computed: {
        message() {
          return state.message
        }
      },
      methods: {
        change() {
          mutation.setMessage('Vue 3.0')
        }
      }
    })
  </script>
</body>

During Vue initialization, in this_ Before init (options) is executed, in addition to extending its prototype prototype method, it will also extend the global static method initGlobalAPI for Vue, in which Vue.observable initialization is located

The parameter passed in by observe must at least satisfy an object. Function of observe method: add an Observer to non VNode object types. If it has been added, it will be returned directly. Otherwise, an Observer object instance will be instantiated if certain conditions are met

new Observer(value). Finally, the walk method will be executed. This method traverses the object key and calls the defineReactive method to turn it into a responsive object

The method def encapsulates the Object.defineProperty method

The purpose is to add value__ ob__ This attribute, and the attribute value points to the current Observer instance

  • After the first definition, function observe will return directly to the same object next time__ ob__
  • The second is to prevent its traversal in the walk method__ ob__ , Because the fourth parameter enumerable is not passed, it is converted to Boolean and false, so__ ob__ It can't be enumerated. If you want to write this: value__ ob__ = This, it will__ ob__ Traverse

The defineReactive function initializes the Dep object instance at the beginning, then takes the obj property descriptor and recursively calls the observe method on the sub object, so that we can access or modify a deeply nested property in obj and trigger getter s and setter s

  • What get does is rely on collection to get the value and return it directly
  • What set does is distribute updates (reactive update dep.notify())

if (Dep.target) dep.depend(), collect the Watcher currently being calculated and take the Watcher as the subscriber. Dep is actually a management of Watcher

Eventually obj, there will be__ ob__ Such an Observe instance becomes a responsive object (getter logic is triggered when accessing and setter logic is triggered when modifying)

slot

Case link

Official website slot

Case 1:

  • The slot with the name attribute name="header" is a named slot. The slot is bound with two variables user - > obj and section - > header

  • It is an anonymous slot without name attribute (default slot). This slot is bound with two variables user - > obj and section - > body

    v-slot used to be written in the form of slot scoped

<body>
  <div id="root">
    <div>Case 1: slot Basic usage of</div>
    <Test>
      <template v-slot:header="{user}">
        <div>custom header({{user.a}})</div>
      </template>
      <template v-slot="{user}">
        <div>custom body({{user.b}})</div>
      </template>
    </Test>
  </div>
  <script>
    Vue.component('Test', {
      template: 
        '<div>' +
          '<slot name="header" :user="obj" :section="\'header\'">' +
            '<div>default header</div>' +
          '</slot>' +
          '<slot :user="obj" :section="\'body\'">default body</slot>' +
        '</div>',
      data() {
        return {
          obj: { a: 1, b: 2 }
        }
      }
    })
    new Vue({ el: '#root' })
  </script>
</body>

<!-- Render results -->
<div id="root">
  <div>Case 1: slot Basic usage of</div> 
  <div>
    <div>custom header(1)</div>
    <div>custom body(2)</div>
  </div>
</div>

Case 2:

  • v-slot:[section]="{section}" is a dynamic slot, section - > 'header'
  • Click the button to call the change method to switch the value of section
<body>
  <div id="root2">
    <div>Case 2: Vue2.6 New features - dynamic slot</div>
    <Test>
      <template v-slot:[section]="{section}">
        <div>this is {{section}}</div>
      </template>
    </Test>
    <button @click="change">switch header and body</button>
  </div>
  <script>
    Vue.component('Test', {
      template: 
        '<div>' +
          '<slot name="header" :user="obj" :section="\'header\'">' +
            '<div>default header</div>' +
          '</slot>' +
          '<slot :user="obj" :section="\'body\'">default body</slot>' +
        '</div>',
      data() {
        return {
          obj: { a: 1, b: 2 }
        }
      }
    })
    new Vue({ 
      el: '#root2',
      data() {
        return {
          section: 'header'
        }
      },
      methods: {
        change() {
          this.section === 'header' ?
            this.section = 'default' :
            this.section = 'header'
        }
      }
    })
  </script>
</body>

<!-- Render results -->
<div id="root2">
  <div>Case 2: Vue2.6 New features - dynamic slot</div> 
  <div>
    <div>this is header</div>
      default body
    </div> 
  <button>switch header and body</button>
</div>

Simple reading of source code

In the parse phase, processSlotContent will be executed to process slots. When the slot attribute is parsed on the tag, the slotTarget attribute will be added to the corresponding AST element node (if there is a slot, use the corresponding attribute - > named slot, if there is no slot, use the default attribute - > Default slot)

Slot scope is a new syntax in Vue2.5 +. Slot scope - > scope slot needs to be used on the template and can accept the prop passed to the slot

  • Usage: < template slot = "XXX" > < div slot scope = "XXX" >

Vue 2.6 + adds v-slot. v-slot can only be used on component (components are also packaged with template) and template. v-slot can be abbreviated as#

Breakpoint debugging article

Chrome developer tools

chrome developer tool -- breakpoint debugging

Posted by mkr on Sun, 26 Sep 2021 18:45:23 -0700