The contents of this series

  • Vue3.0 Family Bucket Basic Guide – Quick Build (1/4)

  • Vue3.0 Family Bucket Guide – New Features of Vue3.0 (2/4)

  • Vue3.0 family bucket primer – [email protected] and [email protected] (3/4)

  • Vue3.0 Family Bucket – Other differences between 3.x and 2.x (4/4)

New features of VUe3.0

1. Instantiation

2. X uses the constructor new Vue(…) Create instance, 3.x create instance with createApp function;

2.x all attribute methods and Settings are bound to the global Vue object, 3.x is bound to the Vue instance, tightening the scope;

3.x removes vue.config. productionTip and vue.config. keyCodes configuration attributes;

// vue 2.x
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)
Vue.prototype.customProperty = () => {}
  
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})
Copy the code

// vue 3.x
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

const app = createApp(App)

app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
app.config.globalProperties.customProperty = () => {}

app.use(router).use(store).mount('#app')
Copy the code

2. Create a page

Create a new test.vue in the/SRC /views directory

<template> <div class="page-wrapper"> <span> This is a new page </span> </div> </template> <script> export default {name: 'Test', setup () { return {} } } </script>Copy the code

Create a route in/SRC /router/index.js

import { createRouter, createWebHistory } from 'vue-router' import Home from '.. /views/Home.vue' const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/test', name: 'Test', component: () => import(/* webpackChunkName: "test" */ '../views/Test.vue') } ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default routerCopy the code

3, Composition API

In vue2.x, all the data is returned in the data method defined under methods, and vue3.x is called through this. All the code logic is implemented in the setup method, including data, watch, computed, methods, hooks, And there’s no more this

The vue3.x setup method is executed only once during the component’s life cycle and is not repeated

Compared with OPTIONS configuration in vue2.x, the semantics of vue3.x based on combined API are not as clear as 2.x. In 2.x, data, methods, computed, and watch are separated by different scopes and look very clear. 3. There will be higher demands on code organization.

Vue2. X uses the Composition API to install @vue/ composition-API, which is basically the same as the Composition API and won’t be described here

① State and event binding reactive & Ref

Reactive is almost equivalent to the Vue.Observable () API in 2.x, but has been renamed to avoid confusion with the Observable in RxJS

Reactive and ref in vue3. X replace the data definition in vue2. X

As you can see from the code below, Reactive handles bidirectional binding of objects, while REF handles bidirectional binding of js base types. In fact, the principle of REF is to objectize the base type and add a __v_isRef attribute to distinguish it.

For details, see ref. Ts source code as follows

class RefImpl<T> { private _value: Public readonly __v_isRef = true // ref constructor(private _rawValue: T, public readonly _shallow = false) { this._value = _shallow ? _rawValue : Convert (_rawValue)} get value() {// getter collects dependencies on track(toRaw(this), trackoptypes.get, 'value') return this._value} set value(newVal) {// setter triggers response if (hasChanged(toRaw(newVal), this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : convert(newVal) trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal) } } }Copy the code

Vue2. X and 3. X response writing difference:

// vue2.x
export default {
  name: 'Test',
  data () {
    return {
      count: 0,
      num: 0
    }
  },
  methods: {
    addCount () {
      this.count++
    }
    addNum() {
      this.num++
    }
  }
}
Copy the code

—-

// vue3.x <template> <div class="page-wrapper"> <div> <span>count </span> <span>{{count}}</span> <button @click="addCount"> </span> </span> <span>{{num}}</span> < button@click ="addNum"> </div> </div> </template> <script> import { reactive, ref, toRefs } from 'vue' export default { name: 'Test', setup () { const state = reactive({ count: 0 }) const num = ref(0) const addCount = function () { state.count++ } const addNum = function () { num.value++ } return {// State property becomes unresponsive because it returns a value, not a reference. state, ... toRefs(state), num, addCount, addNum } } } </script>Copy the code

Unlock the Ref

We can expose a ref value to the rendering context. During rendering, the Vue will use its internal value directly, which means that in the template you can write {{num. Value}} as {{num}}, but in JS you still need to use num.

The use of Reactive

When you use a reactive composite function, you must always keep a reference to the returned object to remain responsive. This object cannot be deconstructed or expanded; once it is, the value returned will be unresponsive.

The toRefs API is used to provide a solution to this constraint by converting each property of a reactive object to the corresponding REF.

② Read-only data readonly

The readonly method can be used to return read-only objects that are not allowed to be written, whether they are ordinary objects, Reactive objects, or Ref objects

If you modify the readOnly object directly, the console will print alarm information without error

const state = reactive({ count: 0}) const readonlyState = readonly(state) Watch (() => readonlystate. count, (newVal, oldVal) => { console.log('readonly state is changed! ') setTimeout(() => {// Changing the read-only attribute will print an alarm, but no error readonlystate.count = 666}, 1000)})Copy the code

③ Calculate attributes computed

Computed in 2.x and 3.x supports getters and setters in the same way, but in 3.x it is a combinatorial function

// vue2.x
export default {
  ...
  computed: {
    totalCount() {
      return this.count + this.num
    },
    doubleCount: {
      get() {
        return this.count * 2
      },
      set(newVal) {
        this.count = newVal / 2
      }
    }
  }
}
Copy the code

// vue3.x import { reactive, ref, toRefs, computed } from 'vue' export default { name: 'Test', setup () { const state = reactive({ count: 0, double: computed(() => { return state.count * 2 }) }) const num = ref(0) const addCount = function () { state.count++ } const addNum = function () { num.value++ } // only getter const totalCount = computed(() => state.count + num.value) // getter  & setter const doubleCount = computed({ get () { return state.count * 2 }, set (newVal) { state.count = newVal / 2 } }) return { ... toRefs(state), num, totalCount, doubleCount, addCount, addNum } } }Copy the code

④ Listen to watch & watchEffect

3. X supports immediate and deep options, as does 2.x’s watch, but does not support obj.key1.key2.

3. Watch in X supports monitoring of a single attribute or multiple attributes, which is more flexible than watch in 2.x.

3. The watchEffect method in x returns a method that stops listening;

Unlike watchEffect, which is invoked immediately after registration, watch does not, unless immediate=true is specified and watchEffect stops listening

Rendering content in the DOM is considered a “side effect” : the program changes its own state (that is, the DOM) externally. You can use the watchEffect API to apply side effects based on reactive state and reapply them automatically.

// vue2.x
export default {
  ...
  data () {
    return {
      ...
      midObj: {
        innerObj: {
          size: 0
        }
      }
    }
  },
  computed: {
    totalCount() {
      return this.count + this.num
    }
  },
  watch: {
    totalCount(newVal, oldVal) {
      console.log(`count + num = ${newVal}`)
    },
    'midObj.innerObj.size': {
      // deep: true,
      immediate: true,
      handler(newVal, oldVal) {
        console.log(`this.midObj.innerObj.size = ${newVal}`)
      }
    }
  }
}
Copy the code

// vue3.x import { reactive, ref, toRefs, computed, watch } from 'vue' export default { name: 'Test', setup () { const state = reactive({ count: 0, double: computed(() => { return state.count * 2 }), midObj: { innerObj: { size: 0 } } }) const num = ref(0) const addCount = function () { state.count++ } const addNum = function () { num.value++ } // Watch (() => totalCount.value, (newVal, oldVal) => {console.log(' count + num = ${newVal} ')}) immediate watch(() => totalCount.value, (newVal, oldVal) => { console.log(`count + num = ${newVal}, immediate=true`) }, { immediate: Deep watch(() => state.midobj, (newVal, oldVal) => { console.log(`state.midObj = ${JSON.stringify(newVal)}, deep=true`) }, { deep: True}) setTimeout () = > {state. MidObj. InnerObj. Size = 1}, 2000)/watch/monitor multiple attribute ([num, () = > totalCount. Value]. ([numVal, totalVal], [oldNumVal, OldTotalVal]) => { console.log(`num is ${numVal}, Num = ${totalVal} ')}) Let callTimes = 0 const stopEffect = watchEffect(() => {console.log('watchEffect is called! ') const div = document.createElement('div') div.textContent = `totalCount is ${totalCount.value}` Document. The body. The appendChild (div) / / call after 5 times, cancel the effect to monitor callTimes++ if (callTimes > = 5) stopEffect ()}) return {... toRefs(state), num, totalCount, addCount, addNum } } }Copy the code

4. Lifecycle hooks

2. The lifecycle hooks in X are placed under the same property as methods

In 3.x, you need to import hooks first, and then register hook callbacks in the setup method. The name of the hook remains the same as React

3. X removes beforeCreate and created hooks from 2.x and replaces them with setup methods

Compare that to React Hooks

Function-based combinatorial apis provide the same level of logic combinatorial capability as React Hooks, but they are very different: the setup() function of the combinatorial API is called only once, which means that the code using the Vue combinatorial API is:

More intuitive with idiomatic JavaScript code in general;

It can be used in conditional statements regardless of the call order; Does not repeat execution on each render to reduce garbage collection pressure; There are no inline handlers that cause child components to be updated forever, and no useCallback is required; There is no problem forgetting to record dependencies, and there is no need to “useEffect” and “useMemo” and pass in dependency arrays to catch obsolete variables. Vue’s automatic dependency tracing ensures that listeners and calculated values are always correct. We thank React Hooks for their creativity and the main inspiration for this proposal, but some of the issues mentioned above are inherent in its design, and we find that Vue’s responsive model provides a way to address them.

// vue2.x
export default {
  data () {
    return {}
  },
  methods: {
    ...
  },
  beforeCreate() {},
  created() {},
  beforeMount() {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeDestroy() {},
  destroyed() {}
}
Copy the code

// vue3.x
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

export default {
  setup() {
    onBeforeMount(() => {
      console.log('component is onBeforeMount')
    })
    onMounted(() => {
      console.log('component is onMounted')
    })
    onBeforeUpdate(() => {
      console.log('component is onBeforeUpdate')
    })
    onUpdated(() => {
      console.log('component is onUpdated')
    })
    onBeforeUnmount(() => {
      console.log('component is onBeforeUnmount')
    })
    onUnmounted(() => {
      console.log('component is onUnmounted')
    })
  }
}
Copy the code

2.x hook 3

5, fragments

2. In x, only one root node is allowed for the vue template

< react. Fragment> <> <>

// vue2.x
<template>
  <div>
    <span>hello</span>
    <span>world</span>
  </div>
</template>
Copy the code

// vue3.x
<template>
  <span>hello</span>
  <span>world</span>
</template>
Copy the code

6, Teleport

Teleport takes a leaf out of the React portal and renders elements somewhere other than the parent, such as a child element below

In VuE3,

is a built-in tag, and we usually place popovers, tooltips, and so on before the closed tag, as follows:

<body> <div id="app"> <! --main page content here--> </div> <! --modal here--> </body>Copy the code

If you follow the old thinking, you need to put the modal UI code at the bottom like this:

<body>
  <div id="app">
    <h3>Tooltips with Vue 3 Teleport</h3>
  </div>
  <div>
    <my-modal></my-modal>
  </div>
</body>
Copy the code

This is done because pop-ups, tooltips need to appear at the top of the page, parent element positioning and z-index hierarchy need to be handled properly, and the simplest solution is to put this kind of DOM at the bottom of the page. In this case, this part of logic is separated from the management of the whole project and the component App, resulting in the direct use of JavaScript and CSS to modify the UI, non-standard and unresponsive. To allow some UI fragments to be moved to other locations on the page, a new < Teleport > component was added to Vue3, and the < Teleport > component automatically clears the corresponding DOM when the component is destroyed without manual handling.

To use

, we first add an element to the page, and we render the modal content underneath that element.

The code is as follows:

<body>
  <div id="app">
    <h3>Tooltips with Vue 3 Teleport</h3>
  </div>
  <div id="endofbody"></div>
</body>
Copy the code

<template>
  <button @click="openModal">
    Click to open modal! (With teleport!)
  </button>
  <teleport to="#endofbody">
    <div v-if="isModalOpen" class="modal">
      ...
    </div>
  </teleport>
</template>

<script>
import { ref } from 'vue'

export default {
  setup() {
    const isModalOpen = ref(false)
    const openModal = function () {
      isModalOpen.value = true
    }
    return {
      isModalOpen,
      openModal
    }
  }
}
</script>
Copy the code

7, Suspense

Suspense> is a special component that will render fallback content instead of the component until a condition is met, usually for asynchronous operations occurring in component setup functionality or for asynchronous components. For example, here is a scenario where the parent component displays asynchronous child components that take a while to load and present. In this case, a component needs to handle placeholder logic or load exception logic, for example:

// vue2.x <template> <div> <div v-if="! loading"> ... </div> <div v-if="loading">Loading... </div> </div> </template>Copy the code

Or use vuE-Async-Manager in vue2.x

<template> <div> <Suspense> <div> ... </div> <div slot="fallback">Loading... </div> </Suspense> </div> </template>Copy the code

// vue3.x
<Suspense>
  <template >
    <Suspended-component />
  </template>
  <template #fallback>
    Loading...
  </template>
</Suspense>
Copy the code

In the code above, we assume that < suspension-component > is an asynchronous component that displays placeholder content until it is fully loaded and rendered: Loading. This is a simple way to use Suspense>, which, like fragments, is inspired by React

The contents of this series

  • Vue3.0 Family Bucket Basic Guide – Quick Build (1/4)

  • Vue3.0 Family Bucket Guide – New Features of Vue3.0 (2/4)

  • Vue3.0 family bucket primer – [email protected] and [email protected] (3/4)

  • Vue3.0 Family Bucket – Other differences between 3.x and 2.x (4/4)