Preface 🍊

I go to work every day writing repetitive code, I’m not productive, and I feel like I’m not getting any better. How to finish the work at hand faster, improve their own development efficiency, xiaobian sorted out some Vue development skills, you work overtime first, I will go shopping with goddess after work.

HookEvent, originally can listen to the component lifecycle 🍊

Internally listen for life cycle functions

Today, the product manager sent me another requirement to develop a chart. I got the requirement and took a look at it. Then I went to the Echarts website to copy the sample code

<template>
  <div class="echarts"></div>
</template>
<script> export default {  mounted() {  this.chart = echarts.init(this.$el)  // Request data, assign data, etc.  // The listener window changes, resize the component  window.addEventListener('resize'.this.$_handleResizeChart)  },  updated() {  // Have done a lot of work  },  created() {  // Have done a lot of work  },  beforeDestroy() {  // When the component is destroyed, the listener event is destroyed  window.removeEventListener('resize'.this.$_handleResizeChart)  },  methods: {  $_handleResizeChart() {  this.chart.resize()  },  // A bunch of other methods  } } Copy the code

After writing the function, I was happy to test it. There was no problem in the test. The product manager said it was great. However, during code review, the tech guy said, this is a problem.

Big man: this writing is not very good, should be monitored`resize`Events and Destruction`resize`Together, the two pieces of code are now separated by hundreds of lines of code, making them less readable
Me: So I switch the two lifecycle hook functions and put them together?
Bosses:`hook`Heard yet? I:` Vue3.0 `Oh, yeah. What? We're upgrading`Vue`? Copy the code

Then the tech guy ignored me and threw a piece of code at me

export default {
  mounted() {
    this.chart = echarts.init(this.$el)
    // Request data, assign data, etc.
    
 // The listener window changes, resize the component  window.addEventListener('resize'.this.$_handleResizeChart)  // The hook listener destroys the hook function and cancels the listener event  this.$once('hook:beforeDestroy', () = > { window.removeEventListener('resize'.this.$_handleResizeChart)  })  },  updated() {},  created() {},  methods: {  $_handleResizeChart() {  // this.chart.resize()  }  } } Copy the code

After reading the code, I realized that Vue can also listen to life cycle functions in this way.

$on('hook:updated', () => {}) {$on('hook:updated', () => {})

Externally listen for life cycle functions

Today, my colleague asked me in the company group, is there any way to listen to the component’s life cycle function externally?

My colleague uses a third-party component and needs to monitor data changes of the third-party component. However, the component does not provide the change event, so my colleague has no way out. So I decided to monitor the updated hook function of the component externally. After a look, Vue supports listening externally to the component’s lifecycle hook functions.

<template>
  <! -- Listen for the component's updated hook function via @hook:updated
  <! All lifecycle hooks of a component can be listened for by @hook: hook function name.
  <custom-select @hook:updated="$_handleSelectUpdated" />
</template>
<script> import CustomSelect from '.. /components/custom-select' export default {  components: {  CustomSelect  },  methods: {  $_handleSelectUpdated() {  console.log('Custom-select component's updated hook function is fired')  }  } } </script> Copy the code

Vuex for small projects? Write a state manager using vue. Observable 🍊

In the front-end project, there are a lot of data need to convey Shared between the various components, by this time then you need a state management tools, in general, we will use Vuex, but for small projects, like Vuex’s official website said: “if you don’t intend to develop large single-page applications, using Vuex may be onerous redundancy. That’s true — if your application is simple, you’d better not use Vuex.” This allows you to manually build a Vuex using Vue2.6’s new API vue. Observable

Create the store

import Vue from 'vue'

// Create a respondable object with vue.Observable
export const store = Vue.observable({
  userInfo: {},
 roleIds: [] })  // define mutations, modify attributes export const mutations = {  setUserInfo(userInfo) {  store.userInfo = userInfo  },  setRoleIds(roleIds) {  store.roleIds = roleIds  } }Copy the code

Reference in the component

<template>
  <div>
    {{ userInfo.name }}
  </div>
</template>
<script> import { store, mutations } from '.. /store' export default {  computed: {  userInfo() {  return store.userInfo  }  },  created() {  mutations.setUserInfo({  name: 'zijun'  })  } } </script> Copy the code

To develop global components, you may want to look at vue.extend 🍊

Vue.extend is a global Api. It is rarely used in business development. However, when we want to develop some global components such as Loading,Notify,Message, etc., we can use vue. extend.

Students might write this in code when using elemental-UI loading

/ / display loading
const loading = this.$loading()
/ / close the loading
loading.close()
Copy the code

It might not be unusual to write it this way, but if you write it this way, right

const loading = this.$loading()
const loading1 = this.$loading()
setTimeout((a)= > {
  loading.close()
}, 1000 * 3)
Copy the code

You will notice that I called loading twice, but only one loading occurred, and I only turned loading off, but loading1 was also turned off. How does this work? We now use vue. extend + singleton mode to implement a loading

Developing loading components

<template>
  <transition name="custom-loading-fade">
    <! - loading mask - >
    <div v-show="visible" class="custom-loading-mask">
 <! -- Loading icon -->  <div class="custom-loading-spinner">  <i class="custom-spinner-icon"></i>  <! --loading the text shown above -->  <p class="custom-loading-text">{{ text }}</p>  </div>  </div>  </transition> </template> <script> export default {  props: {  // Whether to display loading  visible: {  type: Boolean. default: false  },  // loading the display text above  text: {  type: String. default: ' '  }  } } </script> Copy the code

Once the loading component is developed, if you need to use it directly, you need to use it this way

<template>
  <div class="component-code">
    <! -- Other code -->
    <custom-loading :visible="visible" text="Loading" />
 </div> </template> <script> export default {  data() {  return {  visible: false  }  } } </script> Copy the code

But that’s not what we need

  1. The closure can be displayed by calling the method directly through JS
  2. Loading can mask the entire page

The component is converted to a global component by vue.extend

  1. Change the loading component to props and data
export default {
  data() {
    return {
      text: ' '.      visible: false
 }  } } Copy the code
  1. Modify components with vue.extend
// loading/index.js
import Vue from 'vue'
import LoadingComponent from './loading.vue'

// Wrap the component as a subclass by vue.extend
const LoadingConstructor = Vue.extend(LoadingComponent)  let loading = undefined  LoadingConstructor.prototype.close = function() {  // If Loading has a reference, remove the reference  if (loading) {  loading = undefined  }  // Hide the component first  this.visible = false  // Wait 300 ms for loading to close the animation and then destroy the component  setTimeout((a)= > {  // Remove the mounted DOM element  if (this.$el && this.$el.parentNode) {  this.$el.parentNode.removeChild(this.$el)  }  // Call the component's $destroy method for component destruction  this.$destroy()  }, 300) }  const Loading = (options = {}) = > {  // If the component is already rendered, return it  if (loading) {  return loading  }  // The element to mount  const parent = document.body  // Component properties  const opts = {  text: ' '.. options }  // Initializing a component via a constructor is equivalent to new Vue()  const instance = new LoadingConstructor({  el: document.createElement('div'),  data: opts  })  // Attach the loading element to parent  parent.appendChild(instance.$el)  / / display loading  Vue.nextTick((a)= > {  instance.visible = true  })  // Assign the component instance to Loading  loading = instance  return instance }  export default Loading Copy the code
  1. Loading is used on the page
import Loading from './loading/index.js'
export default {
  created() {
    const loading = Loading({ text: 'Loading... ' })
    // Close in 3 seconds
 setTimeout((a)= > {  loading.close()  }, 3000)  } } Copy the code

Loading is now available globally, but if you want to mount it to vue. prototype like element-ui, you’ll need to do it with this.$loading

Mount the component to Vue.prototype

Vue.prototype.$loading = Loading
// Bind the Loading method before export
export default Loading

// Use within the component
this.$loading() Copy the code

Custom instructions to solve problems from the bottom 🍊

What is a directive? An order is when your girlfriend points at you and says, “Over there washboard, get down on your knees, that’s an order!” . Just kidding. Programmers don’t have girlfriends.

In the last section, we developed a loading component. After the development, other developers put forward two requirements when using it

  1. Can beloadingMounted to an element, now can only be used in full screen with 2. You can mount the specified element using the commandloadingIf you need it, we’ll do it

Develop the V-loading instruction

import Vue from 'vue'
import LoadingComponent from './loading'
// Construct a component subclass using vue.extend
const LoadingContructor = Vue.extend(LoadingComponent)

// Define a directive named loading Vue.directive('loading', {  / * ** Only called once, when the directive is first bound to an element, where you can do some initialization* @param {*} the element to bind to the el directive{name:' bind ', value: 'bind ',arg:' v-bind:text '}* /  bind(el, binding) {  const instance = new LoadingContructor({  el: document.createElement('div'),  data: {}  })  el.appendChild(instance.$el)  el.instance = instance  Vue.nextTick((a)= > {  el.instance.visible = binding.value  })  },  / * ** called when the VNode of the component is updated * @param {*} el  * @param {*} binding * /  update(el, binding) {  // Check whether loading is displayed by changing the ratio  if(binding.oldValue ! == binding.value) { el.instance.visible = binding.value  }  },  / * ** Only called once, when directives are unbound from elements * @param {*} el * /  unbind(el) {  const mask = el.instance.$el  if (mask.parentNode) {  mask.parentNode.removeChild(mask)  }  el.instance.$destroy()  el.instance = undefined  } }) Copy the code

Use directives on elements

<template>
  <div v-loading="visible"></div>
</template>
<script> export default {  data() {  return {  visible: false  }  },  created() {  this.visible = true  fetch().then((a)= > {  this.visible = false  })  } } </script> Copy the code

Which scenarios in the project can be customized with directives

  1. Add a componentloadingThe effect
  2. Button level permission controlv-permission
  3. Code burial points, which define instructions according to operation types
  4. inputThe input box automatically gets focus
  5. And so on…

Deep watch and watch trigger the callback immediately, I can monitor your every move 🍊

In the development of Vue project, we often use Watch to monitor the changes of data, and then do a series of operations after the changes.

Basic usage

For example, on a list page, we hope that the search can be triggered automatically when the user enters the search keyword in the search box. At this time, in addition to monitoring the change event of the search box, we can also monitor the change of the search keyword through watch

<template>
  <! This example uses element-ui-->
  <div>
    <div>
 <span>search</span>  <input v-model="searchValue" />  </div>  <! -- list, code omitted -->  </div> </template> <script> export default {  data() {  return {  searchValue: ' '  }  },  watch: {  // Reload the data after the value changes  searchValue(newValue, oldValue) {  // Judge the search  if(newValue ! == oldValue) { this.$_loadData()  }  }  },  methods: {  $_loadData() {  // Reload the data  }  } } </script> Copy the code

Immediately triggered

It is now possible to load data when the value changes, but to load data when the page is initialized, we need to call the $_loadData method again in the Created or Mounted lifecycle hook. Instead of writing this, you can configure the immediate trigger property for Watch to do just that

/ / watch
export default {
  watch: {
    // Reload the data after the value changes
    searchValue: {
 // Use handler to listen for property changes. The first call to newValue is an empty string and oldValue is undefined  handler(newValue, oldValue) {  if(newValue ! == oldValue) { this.$_loadData()  }  },  // Configure immediate execution of properties  immediate: true  }  } } Copy the code

Deep listening (I can see your inner workings)

A form page that needs to be changed to the modified state after the user modifies any item in the form. If we follow the writing method of watch in the above example, then we need to monitor every attribute of the form, which is too troublesome. In this case, we need to use the deep monitor of Watch

export default {
  data() {
    return {
      formData: {
        name: ' '. sex: ' '. age: 0. deptId: ' '  }  }  },  watch: {  // Reload the data after the value changes  formData: {  // Note that newValue and oldValue are always equal because of object references  handler(newValue, oldValue) {  // Mark the page edit status here  },  // By setting the deep property to true, watch listens for every change in the value of the object  deep: true  }  } } Copy the code

Functional components, functions are components, right? 🍊

What is a functional component? A functional component is a function that is a component, which feels like a word game. Those of you who have used React will be familiar with functional components. Functional components, we can think of as having no internal state, no lifecycle hook functions, and no this(components that do not need to be instantiated).

In the process of daily writing bug, often develop some pure display business components, such as some details page, list interface, etc., they have a common characteristic is just outside into the data show, don’t need to have internal state, don’t need in the life cycle hook function do processing, you can consider to use functional components.

Let’s start with the code for a functional component

export default {
  // Specify components as functional by configuring the functional property
  functional: true.  // External properties received by the component
  props: {
 avatar: {  type: String  }  },  / * ** Render function * @param {*} h * @param {*} context function components don't have this, props, slots, etc hanging on the context* /  render(h, context) {  const { props } = context  if (props.avatar) {  return <img src={props.avatar}></img>  }  return <img src="default-avatar.png"></img>  } } Copy the code

In the example above, we define an avatar component that displays the incoming avatar if it is passed in from the outside, and the default avatar otherwise. In the above code you can see that there is a render function, this is Vue using JSX writing, about JSX, small series will be in the subsequent article will be a detailed tutorial to use.

Why use functional components

  1. The main and key reason is that functional components do not require instantiation, are stateless, and have no life cycle, so render performance is better than normal components
  2. The functional component structure is simpler and the code structure is clearer

Differences between functional components and normal components

  1. Functional components need to specify functional in the declared component
  2. Functional components do not need to be instantiated, so there is no this, which is replaced by the second argument to the Render function
  3. Functional components have no lifecycle hook functions, cannot use computed properties, watches, and so on
  4. Functional components cannot expose events via $emit. Calling events can only call external events via context.listeners. Click
  5. Because functional components are not instantiated, when a component is referenced externally via ref, HTMLElement is actually referenced
  6. Functions components do not need to be declared, so properties that are not declared in props are automatically resolved to prop, while all undeclared properties in normal components are resolved to $attrs and automatically mounted to the component root element.

I don’t want to use JSX, can I use functional components?

Prior to Vue2.5, functional components could only be used through JSX; after that, functional components could be generated through template syntax

<! -- Add functional property to template --><template functional>
  <img :src="avatar ? avatar : 'default-avatar.png'" />
</template>
<! -- according to section 6 of the previous section, the statement props can be omitted. Copy the code

Reprint 🍊

6 Vue Combat Tips you Probably never knew