Before you know it, VUe3.0 has entered the beta version, and the official version is coming soon. I have read a lot of articles about Vue3.0 before, but the paper is too shallow, now I will write a handwritten demo!!

The first step is to set up the environment. Although the latest version of @vue/ CLI3 already supports the creation of VUe3.0 projects, but do it yourself. Just review the configuration of WebPack.

The source of the article I put on Github, can be obtained by themselves.

Environment set up

Initialize the

Mkdir vue3.0-demo CD vue3.0-demo // NPM init -y // git initialize git initCopy the code

Add the gitignore file

Create a gitignore file and copy it with the following contents:

.DS_Store node_modules/ /dist/ npm-debug.log* yarn-debug.log* yarn-error.log* /test/unit/coverage/ /test/e2e/reports/ /build/ selenium-debug. Log # Editor directory and file.idea.vscode *.suo *.ntvs* *.njsproj *.slnCopy the code

Install dependencies

// Install vue3.0 yarn add vue@next // Install webpack yarn add webpack webpack-cli webpack-dev-server -d // Install package and compile dependency yarn add Html-webpack-plugin clean-webpack-plugin -d // Install vue file compile dependency yarn add vue-loader@next @vue/compiler-sfc -d // Install style compile dependency yarn add css-loader style-loader less-loader -DCopy the code

Create a project directory

// Create the related project folder mkdir SRC // create the public entry folder mkdir public // create the webpack configuration file CD > webpack.config.js // Create the base file CD > SRC /main.js CD >  public/index.html cd > src/App.vueCopy the code

Write index. HTML

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Vue3.0 demo</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>
Copy the code

Configuration webpack. Config. Js

Is a very common configuration file, if you really do not understand, you can first look at the official Webpack documentation.

const path = require('path')
// Vue-loader 15.x must use this plug-in
const { VueLoaderPlugin } = require('vue-loader')
// Write style and js files to index.html via link and script
const HtmlWebpackPlugin = require('html-webpack-plugin')
// Delete the packaged files
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

const resolve = dir= > path.resolve(__dirname, dir)

module.exports = (env = {}) = > ({
  // The current operating mode
  mode: env.prod ? 'production' : 'development'.// Debug tools
  devtool: env.prod ? 'source-map' : 'inline-source-map'.// Pack the entry
  entry: resolve('./src/main.js'),
  // Package export
  output: {
    path: resolve('./dist'),
    publicPath: '/'
  },
  // Parse the module request
  resolve: {
    alias: {
      'vue': '@vue/runtime-dom'.The '@': resolve('./src')}},// Module configuration, various loader
  module: {
    rules: [{test: /\.vue$/,
        use: 'vue-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader'.'css-loader'] {},test: /\.less$/,
        use: ['style-loader'.'css-loader'.'less-loader']]}},/ / the plugin
  plugins: [
    new VueLoaderPlugin(),
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: resolve('./public/index.html'),
      filename: 'index.html'})].// Development server
  devServer: {
    publicPath: '/'.inline: true.hot: true.stats: 'minimal'.contentBase: __dirname,
    overlay: true.historyApiFallback: true}})Copy the code

Write the webpack entry file main.js

The mount mode of VUe3.0 is slightly changed

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

// Create an application
const app = createApp(App)

/ / a mount
app.mount('#app')
Copy the code

Write the app.vue file

Written as a new feature of Vue3.0, the template can directly contain multiple child nodes without the need for additional element tags

<template>
  <h1>I am a Home page</h1>New to Vue3.0, template allows multiple tag nodes without the need for an additional layer</template>

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

Copy the code

Add the script to package.json

// 添加脚本
"scripts": {
  "dev": "webpack-dev-server"
}
Copy the code

Start the project

Execute the following command:

yarn dev

// or

npm run dev
Copy the code

After successful startup, visit http://localhost:8080/ and you will see the following content in your browser.

composition API

The Composition API borrows ideas from React hooks to make business logic more cohesive. Principles aside, API engineers only care about how well the API works. An important feature of Vue3.0 is the Composition API, so let’s take a look at setup, REF, Reactive, computed, toRefs, and watchEffect with the demo. More details can be found in the draft.

setup

Features of the setup function:

  • Is the entry point to the Composition API;
  • Called before the cycle beforeCreate event is declared;
  • Returns an object whose properties are incorporated into the render context and can be used directly in the template;
  • We can return a render function like this:
    • return () => h('div', [count.value, object.foo])
  • As the first parameter, the props object is received, which can be monitored by watchEffect.
  • Take the context object as the second argument. This object contains the attrs, slots, and emit attributes.

ref

Ref creates a wrapper object that has a single reactive property value. If the object is specified as ref’s value, the object will be deeply traversed by reactive methods. Value and assignment are required through x.value in js code, but the wrapper object is handled automatically in the template and x.value is not called separately.

Take the example of a counter.

<template> counter: <span>{{ count }}</span> <button @click="countPlus">{{ plusText }}</button> <button @click="countMinus">{{ minusText }}</button> </template> <script> import { setup, ref } from 'vue' export default { name: 'Counter', Setup () {const plusText = 'add' const minusText = 'reduce' const count = ref(0) const countPlus = () => { count.value++ } const countMinus = () => { count.value-- } return { plusText, minusText, count, countPlus, countMinus } } } </script>Copy the code

reactive

Reactive objects are created by Reactive. The created reactive objects are not wrapped objects and do not need to use x.value. Reactive is equivalent to vue.Observable of Vue 2.x, which fetches a responsive proxy object for an object

computed

The ability to evaluate properties in Vue 2.x is very powerful and will definitely remain in VUe3.0, but it is now a higher-order function with the composition Api.

watchEffect

This is done automatically once under Setup to collect dependencies and fires incoming functions when dependencies change, making it a good place to put “side effects” into callback functions, such as asynchronous requests with unknown results.

Combine the three points above and give an example.

<template> < button@click ="mulitple"> I was clicked {{state.count}} times </button> value is {{result}} </template> <script> import {setup,  ref, reactive, computed, watchEffect } from 'vue' export default { name: 'Multiplier', setup() { const state = reactive({ count: 0}) const result = ref(1) const mulitple = () => {state.count++} Actually, it's better to use computed watchEffect(() => {result.value *= (state.count + 1)}) return {state, result, mulitple } } } </script>Copy the code

toRefs

ToRefs transforms reactive objects into normal objects, where each attribute on the resulting object is a ref reference object that points to the corresponding attribute in the original object. This is useful when composite functions return reactive state, ensuring that the response of the original reactive object is not lost when the object is deconstructed or extended by the operator.

Such as their own custom hook for reuse.

// usePosition.js
import { reactive, toRefs, onMounted, onUnmounted } from 'vue'

const usePosition = () = > {
  const position = reactive({
    x: 0.y: 0
  })

  const updatePosition = (e) = > {
    position.x = e.pageX
    position.y = e.pageY
  }

  onMounted(() = > {
    window.addEventListener('mousemove', updatePosition)
  })

  onUnmounted(() = > {
    window.removeEventListener('mousemove', updatePosition)
  })
  
  return position
}

export default usePosition


// MousePosition.vueX: {{x}},y: {{y}} </template><script>
import usePosition from '@/hooks/usePosition'

export default {
  name: 'MousePosition'.setup() {
    // Method 1: unresponsive
    // const {x, y} = usePosition()
    // return { x, y }

    // Method two: unresponsive
    // return { ... usePosition() }

    // Method 3: The only way to remain responsive without using toRefs
    return {
      pos: usePosition()
    }
  }
}
</script>
Copy the code

If toRefs is used to solve this problem, methods 1 and 2 above can be used.

// usePosition.js
import { reactive, toRefs, onMounted, onUnmounted } from 'vue'

const usePosition = () = > {
  // ...
  return toRefs(position)
}

export default usePosition
Copy the code

Lifecycle hook functions

The second major change of VUe3.0 is the addition of lifecycle hook functions. The mapping between vue2.x and VUe2.x is shown in the following table:

vue2.x vue3.0 instructions
beforeCreate setup Before component creation
created setup Component creation complete
beforeMount onBeforeMount Components before mounting
mounted onMounted Components are mounted successfully
beforeUpdate onBeforeUpdate Data update before view update
updated onUpdated Data update, view update render complete
beforeDestroy onBeforeUnmount Before component destruction
destroyed onUnmounted Component destruction completed

As you can see from the table above, the new lifecycle hook functions are basically the same as the original, with slightly different destruction and creation phases. Adding the on prefix along with the setup function makes it more intuitive.

These new lifecycle hook functions are used as follows:

<template>
  <button @click="add">{{ count }}</button>
</template>

<script>
import { setup, ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

export default {
  name: 'LifeCycle'.beforeCreate() {
    console.log('beforeCreate')},created() {
    console.log('created')},setup() {
    console.log('setup')

    const count = ref(0)

    const add = () = > {
      count.value++
    }

    onBeforeMount(() = > {
      console.log('beforeMounted')
    })
    onMounted(() = > {
      console.log('onMounted')
    })
    onBeforeUpdate(() = > {
      console.log('onBeforeUpdate')
    })
    onUpdated(() = > {
      console.log('onUpdated')
    })
    onBeforeUnmount(() = > {
      console.log('beforeMounted')
    })
    onUnmounted(() = > {
      console.log('onUnmounted')})return {
      count,
      add
    }
  }
}
</script>
Copy the code

As you can see from the figure, the setup hook function is triggered before the beforeCreate, and the old lifecycle hook function is still available

Bidirectional binding V-Model

In vue3.0, we use the sync modifier to create multiple bidirectional bindings on a component. Vue3.0 adds the v-model: XXX modifier to the v-model syntax to implement the bidirectional binding of multiple attributes.

<! -- MyInput.vue -->
<template>The Numbers:<input type="number" :value="number" @input="numberInput" />Text:<input type="text" :value="text" @input="textInput" />
</template>

<script>
import { setup } from 'vue'
export default {
  name: 'MyInput'.props: {
    number: Number.text: String
  },

  setup(props, ctx) {
    console.log(ctx)
    const numberInput = e= > {
      ctx.emit('update:number', e.target.value)
    }

    const textInput = e= > {
      ctx.emit('update:text', e.target.value)
    }

    return {
      numberInput,
      textInput
    }
  }
}
</script>


<! -- MyForm.vue -->
<template>
  <my-input v-model:number="number" v-model:text="text" />
  <p>The Numbers: {{number}}</p>
  <p>Text: {{text}}</p>
</template>

<script>
import MyInput from '@/components/MyInput.vue'
import { setup, ref } from 'vue'
export default {
  name: 'MyForm'.components: {
    MyInput
  },

  setup() {
    const text = ref(' ')
    const number = ref(0)

    return {
      text,
      number
    }
  }
}
</script>

Copy the code

The effects are as follows:

vue-router

With all the examples above, let’s make it a one-page app and roll up our sleeves.

Create folders and files as follows:

Then install vue-Router

npm install vue-router@next -D

// or

yarn add vue-router@next -D
Copy the code

In the SRC /router/index.js file, write the following:

import {
  createRouter,
  createWebHistory, 
  createWebHashHistory
} from 'vue-router'
import Home from '@/views/Home.vue'

const routes = [
  {
    path: '/'.component: Home,
    name: 'Home'.meta: {
      title: 'home'}}, {path: '/counter'.component: () = > import('@/views/Counter.vue'),
    name: 'counter'.meta: {
      title: 'counter -ref API'}}, {path: '/mousePosition'.component: () = > import('@/views/MousePosition.vue'),
    name: 'mousePosition'.meta: {
      title: 'Mouse Coordinate - Reactive API'}}, {path: '/multiplier'.component: () = > import('@/views/Multiplier.vue'),
    name: 'multiplier'.meta: {
      title: 'Multiplier -watchEffect API'}}, {path: '/lifeCycle'.component: () = > import('@/views/LifeCycle.vue'),
    name: 'lifeCycle'.meta: {
      title: 'Life cycle'}}, {path: '/form'.component: () = > import('@/views/MyForm.vue'),
    name: 'form'.meta: {
      title: 'Multi-valued bidirectional binding V-model'}}]const router = createRouter({
  // Route using hash mode, url marked with #
  history: createWebHashHistory(),
  // Routes in history mode, like normal urls, need background configuration
  // history: createWebHistory(),
  routes
})

export default router
Copy the code

Then we create a menu component, the logic is very simple, get the route written above, simple processing output into the menu.

<! -- src/components/Menu.vue -->
<template>
  <ul>
    <li v-for="menu in menus" :key="menu.path">
      <router-link :to="menu.path">{{ menu.name }}</router-link>
    </li>
  </ul>
</template>

<script>
import router from '@/router'
import { setup, computed } from 'vue'

export default {
  name: 'Menu'.setup() {
    // Calculate attributes
    const menus = computed(() = > {
      // Get all routing information
      const routes = router.getRoutes()

      const res = []
      routes.forEach(route= > {
        res.push({
          name: route.meta.title,
          path: route.path
        })
      })

      return res
    })

    return {
      menus
    }
  }
}
</script>

Copy the code

Finally, boot it up again and experience the joy of Vue3.0.

conclusion

I had a preliminary experience with VUe3.0. In terms of style, I could obviously feel that it was close to React, but it could not get rid of the writing style of “object form”, giving people a strange feeling that it could not get up or down. However, after the first step, there will definitely be a better second step.