Preface:

Just get to the point and watch it.

                                            

Some useful addresses

Vue 3 is currently in pre-alpha. There should be Alpha, Beta, etc.

Source code here

API document here, of course, should be written by foreign friends in English version.

Vue 3 changes

  1. Use the Typescript
  2. Abandon class in favor of the function-based API
  3. option API => Composition API
  4. Refactoring complier
  5. Refactoring virtual DOM
  6. New responsive mechanisms

This article focuses on the usage of Composition API and some considerations in point 3, and demonstrates how to use Provider + Inject instead of VUex through demo

The basic example

import { value, computed, watch, onMounted } from 'vue'

const App = {
  template: `
    <div>
      <span>count is {{ count }}</span>
      <span>plusOne is {{ plusOne }}</span>
      <button @click="increment">count++</button>
    </div>
  `,
  setup() {
    // reactive state
    const count = value(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}
Copy the code

The setup () – API entry

Setup is a new component option and a gateway to other apis. That is, all your operations will be defined and performed inside the setup function, and Vue3.0 will replace the class of vue2.x with a function, namely new Vue().

Setup is called after props is initialized when a component instance is created, replacing careted and beforeCreate of Vue2. X.

The first parameter to setup is props, which is the same as props in Vue2. X. More on that later.

The setup second argument provides a context object that provides a list of optional properties, the same as the list of properties mounted on this in Vue2. X.

For example: the second parameter is called content if you want to get the route object

content.$route

const MyComponent = { props: { name: String }, setup(props) { console.log(props.name) return { msg: `hello ${props.name}! ` } } template: `<div>{{ msg }}</div>`}Copy the code

Setup returns an object whose properties are directly exposed to the template rendering context. In Ve2. X, the attributes you define are unconditionally exposed to the template rendering context inside Vue.

Reactive – A data listener function

Reactive takes an object as an argument and returns a reactive proxy for that object, equivalent to vue.Observable () of vue2.x.

Usage:

setup() {
        let reactiveData = reactive({name: 'lin', age: 20})
        return {
          reactiveData 
        }
      }
Copy the code

Equivalent to Vue2. X:

data(){
    return {
        reactiveData :{name: 'lin', age: 20}    }
}
Copy the code

In contrast to Observable () in Vue2.x, the component instance initializes the entire data Object into an observable, recursively adding getters and settters to each Key using Object.defineProperty. If it is an array, override the seven methods of the proxy array object. Despite the convenience, the performance overhead can be significant on large projects. Vue3.0 will no longer actively listen to all data, but will give you the option, and instances will no longer need to recurse to data objects during initialization, thus reducing component instantiation time.

Composition API

ref

Usage:

Ref receives a raw value and returns a wrapper object with a.value attribute. The value is accessed via.value.

import {ref} from vue

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
Copy the code

Access ref in the render context

Used in a rendering context, Vue helps you expand automatically without.value access.

<template>
  <div>{{ count }}</div>
</template>

<script>
export default {
  setup() {
    return {
      count: ref(0)
    }
  }
}
</script>
Copy the code

Access ref as an object

If accessed as an object, the value defined by ref is automatically expanded, without.value access.

Count = ref(0) const state = reactive({count}) console.log(state.count Console. log(count.value) // still needs to be accessed through.value as a valueCopy the code

isRef

Check that an object is an object wrapped by ref

const unwrapped = isRef(foo) ? foo.value : foo
Copy the code

Ref and Reactive

In fact, ref is the younger brother of Reactive, and it is implemented by Reactive behind it. The only difference is that ref returns wrapped objects

Const count = reactive({value:0})Copy the code

Why does ref return a wrapper object?

If you don’t know what a wrapped object is, please see here

We know that in JavaScript primitive value types such as string and number have only values and no references. If you return a string variable in a function, the code that receives the string only gets a value and cannot track subsequent changes to the original variable. Therefore, the point of wrapping objects is to provide a container that allows us to pass values of any type between functions by reference. This is a bit like useRef in React Hooks — except that Vue wrapper objects are also responsive data sources. With such containers, we can pass state back to components by reference in composition functions that encapsulate logic. Components are responsible for presentation (tracking dependencies) and composite functions are responsible for managing state (triggering updates) : custom hooks like an act

Setup () {const valueA = useLogicA() // valueA may be updated by code inside useLogicA() const valueB = useLogicB() return {valueA,  valueB } }Copy the code

Ref and Reactive:

If ref and Reactive are returned via a structure in the Setup function, their reactive changes are not captured in the template rendering context. Because deconstructing them means copying their references. So try not to use deconstruction to return data that you expect to respond to

var App = { template: ` <div class="container"> <div> {{name1}} {{name2}} </div> <button @click="add1"> add count</button> </div>`, Setup () {const name1 = ref({name1:' name'}) const name2 = reactive({name2:'aa'}) const add1 = () => { console.log(name1.value.name1 = 'test') console.log(name2.name2 = 'test') } return { count, add1, ... pop,... name1.value,... name2 } }}Copy the code

If you must use deconstruction, you can use toRefs()

return{ ... toRefs({name:'name'}) }Copy the code

Props 

Props vs. Vue2. X main points to note

  1. In Vue2. X, props is bound to this, and we can get the corresponding value from this.props. PropsName. It’s like an act…

  2. When defining a type, it is still possible to support TS as well as Vue2. X.

  3. The PROPS object is reactive — it can be viewed as a source of data and updated synchronously within the framework when subsequent props changes. For user code, however, it is not modifiable (resulting in warnings).

    interface IProps{ name:string } const MyComponent = { setup(props:IProps) { return { msg: `hello ${props.name}! ` } }, template: `<div>{{ msg }}</div>` }Copy the code

The computed to calculate

Definition:

Computed values behave the same as computed properties: they are recalculated only when dependencies change. Type useCallback useMemo of an act

Computed () returns a wrapper object, which can be returned in setup() just like a normal wrapper object and expanded automatically in the render context.

Usage:

Computed allows transmission of two parameters

By default, if the user attempts to modify a read-only wrapper object, a warning will be triggered. In other words, you can only get but not set

The second way: pass an object, the object contains the get function and set function.

In general these two points are the same as Vue2 2.x.

Import {computed,reactive} from vue setup(){const count = reactive({count:0}) // first const computedCount1 = Computed (()=>count.count++}) // The second const computedCount2 = computed({get: ()=> count.value + 1, set: val => { count.value = val - 1 } computedCount2.value = 1 console.log(computedCount1.value) // 0 }) }Copy the code

The only difference is that in 3.0, computed is taken as an API and fetched directly from VUE, whereas in Vue2. X, computed is an object in which one computed is defined

Vue2. X var vm = new Vue({data: {a: 1}, computed: {// Only read aDouble: Function () {return this.a * 2}, set: Function (v) {this.a = v-1}}}}) Vue3.0 import {ref,computed} from Vue setup(){const a = ref(0) const b = ref(1) const a_computed = computed(()=>a.value++) const b_computed = computed({ get(){return a.value}, set(val){return a.value+val } ) }Copy the code

readonly

Receives a REF or Reactive wrapper object and returns a read-only reactive object.

const original = reactive({ count: 0 })

const copy = readonly(original)

watch(() => {
  // works for reactivity tracking
  console.log(copy.count)
})

// mutating original will trigger watchers relying on the copy
original.count++

// mutating the copy will fail and result in a warning
copy.count++ // warning!
Copy the code

watch

The Watch () API provides the ability to perform side effects based on observing changes in state.

The first argument received by watch() is called a “data source”, which can be:

  • A function that returns an arbitrary value
  • A wrapper object
  • An array containing both data sources

The second argument is the callback function. The callback function is triggered only when the data source changes:

Here are some things to watch out for:

1. If you pass a callback function instead of a data source, Vue passively listens for every responsive data you use in the callback function.

2. If you do not pass the data source, the callback function argument does not listen for the current value of the function and the last value of the change

Const count = ref(0) const count1 = ref(1) watch(()=> console.log(count.value)) Console. log(count.value) console.log(count1.value)}) // Listen for count and count1Copy the code
  • A function that returns an arbitrary value

    Const value = ref(0) watch((newValue, oldValue)=>value.value,() => {// Listen on value console.log(value.value, 'value')})Copy the code
  • A wrapper object

One thing to note here is that if you are listening to the data wrapped by Reactive, you should not use this method because Reactive does not return a wrapped object. You can do it the first way

const count = reactive({count:0}) watch(()=>count.count,()=>{.... }) const value = ref(0) watch(value,() => {// Listen on value console.log(value.value, 'value')})Copy the code
  • An array containing both data sources

In this case, a change to any of the data sources triggers a callback, and the callback receives an array of values as arguments:

 const count = ref(0)
 const test = ref(0)
 watch([value,count,()=>test.value],([newValue,newCount,newTest],[oldValue,oldCount,oldTest]) => {    console.log(value.value, 'value') })
Copy the code

Stop watch

Watch is automatically linked to the component’s life cycle and stops when the component is uninstalled. The difference is that the Watch function of Vue3.0 returns a stop function for you to manually stop the watch

`const value = ref(0)`

`const stop = watch(value,(val,old)=>{….. }) `

stop()

Cleaning side effects

In fact, the callback function has a third parameter, which is used to register the function that cleans up the side effects. People familiar with React useEffect know that useEffect can return a function to clean up its side effects, while Vue3.0 takes the form of parameters. Normally, side effects will be automatically cleaned up during the lifecycle destruction phase or if you stop the listener manually, but sometimes, when the data source changes, we may need to perform some asynchronous operations, such as setTimeOut,fetch, and before these asynchronous operations are complete, When the monitored data source changes again, we might want to undo the previous operation that we are still waiting on, such as clearTimeOut. To handle this, the third argument watcher’s callback receives is a function that registers the cleanup operation. Call this function to register a cleanup function. The cleanup function is called in subordinate cases:
Watch (value, (val, oldVal, onCleanup) => {const token = setTimeout(() => console.log(val, 'I update '), 2000) onCleanup(() => {// id has changed, or watcher is about to be stopped. // Cancel pending asynchronous operations. Console. log(' I'm a cleanup function ') clearTimeout(token)})})Copy the code
So why doesn’t Vue return a function that cleans up side effects, as React does, instead of using arguments?

That’s because we might write watch like this:

Watch (value, async (val, oldVal, onCleanup) => {const token = await setTimeout(() => console.log(val, 'I updated '), 2000) onCleanup(() => {// id has changed, or watcher is about to be stopped. // Cancel pending asynchronous operations. Console. log(' I'm a cleanup function ') clearTimeout(token)})})Copy the code

Async implicitly returns a promise, so we cannot return a cleanup function that needs to be registered immediately

Control the call back time of watch

By default, watch is called after the component is updated. If you want to call it before the component is updated, you can pass a third argument,

The third argument is an object with several options

Flush indicates the timing of the callback

Post default value after component update

Pre component before update

Sync Call

Deep depth monitoring

Type: Boolean default :false

watch(
  () => state.foo,
  foo => console.log('foo is ' + foo),
  {    flush: 'post', // default, fire after renderer flush
    flush: 'pre', // fire right before renderer flush
    flush: 'sync' // fire synchronously
  })
Copy the code

The behavior of Vue2. X is the same as that of Vue2

   const count1 = reactive({count:{count:0}})    watch(()=>count1.count, (val,oldVal)=>{      console.log(count1,"count1")    },{      deep:true    })    const add1 = ()=>{      count1.count.count = Math.random()    }
Copy the code

Lazy – Is the opposite of Vue2. X **immediate**

type:Boolean, default:false

  const count1 = reactive({count:{count:0}})    watch(()=>count1.count, (val,oldVal)=>{      console.log(count1,"count1")    },{      lazy:true    })
Copy the code

OnTrack and onTrigger

Debugger hook function, called when dependency tracing and dependency changes, respectively.

The life cycle

All existing lifecycle hooks have corresponding onXXX functions (available only in setup()) that remove created and beforeCreate.

import { onMounted, onUpdated, onUnmounted } from 'vue' const MyComponent = { setup() { onMounted(() => { console.log('mounted! ') }) onUpdated(() => { console.log('updated! ')}) // destroyed onUnmounted(() => {console.log('unmounted! ')})}}Copy the code

provide,inject

Dependency injection, and provide and Inject types of Vue2. With dependency injection, we can share data across groups, and you can even achieve global state sharing without VueX. If you’re familiar with React, this is similar to the React context. I think it’s better than context.

Dojo.provide usage:

Provide takes two parameters. The first parameter is a unique name for provide, preferably Symbol to avoid duplication. The second parameter is the data you want to expose

 provide(ThemeSymbol, 'dark')
Copy the code

Inject usage:

Inject receives two parameters, the first is the provide name and the second is the default data. If the provider does not expose its own data, inject default data.

Provide and Inject are only available in the setup function.

 const ThemeSymbol= Symbol(); const Ancestor = {  setup() {
    provide(ThemeSymbol, 'dark')
  }
}

const Descendent = {
  setup() {
    const theme = inject(ThemeSymbol, 'light' /* optional default value */)
    return {
      theme
    }
  }
}
Copy the code

Dojo.provide + replace Vuex inject

React Context + useReducer can replace Redux to some extent. In a Vue project, if you don’t want to introduce Vuex, consider providing + Inject instead.

Here’s what to learn from the article:

But Vue3 added hooks, and one of the features of hooks is that you can write custom hooks outside of components, so we can use vue capabilities not only inside.vue components, but also in any file (context.ts).

If we are in context.ts

  1. Customize and export a hook called useProvide, and useProvide in this hook and register some global states.

  2. UseInject to return the global state provided by this hook.

  3. UseProvide is then called in the setup function of the root component.

  4. These global states can be shared in any child component.

The project of actual combat

Next is a todolist demo using Vue’s latest library.

1. To-do list

2. Completed events

3 Cancelled (deleted) events

Create a project

Create projects using vuE-CLI and introduce Vue3’s latest library vue-composition-API

import VueCompositionApi from '@vue/composition-api';
Vue.use(VueCompositionApi);
Copy the code

routing

The route is created as before.

import Vue from 'vue'; import VueRouter from 'vue-router'; const routes = [ { name: 'todo', path: '/todo', component: Todo, children: [ { name: 'list', path: 'list', component: List }, { name: 'finished', path: 'finished', component: Finished }, { name: 'cancle', path: 'cancle', component: Cancle }, ] },]; const router = new VueRouter({ routes,}); export default router;Copy the code

context

Create the Context folder where we will create todo.ts and write our useProvide and useInject

1. The introduction of the API

import { provide, inject, computed, ref, Ref } from '@vue/composition-api';
Copy the code

2. Define data types

Vue3.0 incorporates TS, so we define data types first.

List: an array of to-do events

type list = listItem[]
Copy the code

ListItem: To-do event object

type listItem = {
  title: string,  context: string,  status: string,  id: number
}
Copy the code

3. Create useListProvide and export it

Export const useListProvide = () => {// All events const list = ref<list>([{title: 'first todo event ', context: Id: 0, status: 'todo'}, {title: 'first todo event ', context:' Front-end Vue conposition-API ', id: 1, status: 'todo' }, ] ); Const changeStatus = (id: number,status:string) => {if(! status){ throw new Error(`can not find status`); } const removeIndex = list.value.findIndex((listItem: listItem) => listItem.id === id) console.log(removeIndex) if (removeIndex ! == -1) { list.value[removeIndex].status = status } }; provide(listymbol, { list, changeStatus, })}Copy the code

4. Write useListInject

export const useListInject = () => { const listContext = inject<ListContext>(listymbol); if (! listContext) { throw new Error(`useListInject must be used after useListProvide`); } return listContext };Copy the code

5. There must be more than one module in the global state, so do the unified export under context/index.ts

import { useListInject,useListProvide } from './todo'
export { useListInject };
export const useProvider = () => {
  useListProvide()
};
Copy the code

6. Then use provide in the root component of main.ts to inject global state in the uppermost component.

new Vue({
  router,
  setup() {
    useProvider();
    return {};
  },
  render: h => h(App),
}).$mount('#app');
Copy the code

Provide + Inject now that we’re done, how do we use it

In the View/Todo List. In the vue

import { useListInject } from "@/context"; UseListInject setup() {const form = reactive({title: "", context: "",}); const { list, changeStatus } = useListInject(); List,changeStatus console.log(list.value, "list"); const handleAddList = () => { console.log(form, "form"); list.value.push({ title: form.title, context: form.context, id: Math.random(), status: "todo" }); form.context = '' form.title = '' dialogFormVisible.value = false }; return { list, changeStatus } }Copy the code

Final code:

<template> <div> <el-card class="box-card"> <div slot="header" class="clearfix"> <span> All pending events </span> <el-button </el-button> <el-button class="button" type="text" @click="dialogFormVisible =" </el-button </el-button> </div> <div v-for="item in list" :key="item.index" class="listItem"> < listItem v-if="item.status" === 'todo'" :item="item" style="margin:18px 0"></ListItem> </div> </el-card> <el-dialog title=" new to-do" Sync ="dialogFormVisible"> <el-form :model="form"> <el-form-item label=" event name ":label-width="formLabelWidth"> <el-input V-model ="form.title" auto-complete="off" size="medium"></el-input> </el-form-item> <el-form-item label=" event content" :label-width="formLabelWidth"> <el-input v-model="form.context" auto-complete="off" size="medium"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false" </el-button> <el-button type="primary" @click="handleAddList"> </el-button> </div> </div> </template> <script lang="ts"> import ListItem from "./components/listItem.vue"; import { computed, createComponent, ref, reactive } from "@vue/composition-api"; import { listItem as listItemType } from ".. /.. /types/index"; import { useListInject } from "@/context"; export default createComponent({ components: { ListItem }, setup() { const form = reactive({ title: "", context: ""}); const dialogFormVisible = ref(false); const formLabelWidth = ref("120px"); const { list, addList, changeStatus } = useListInject(); console.log(list.value, "list"); const handleAddList = () => { console.log(form, "form"); list.value.push({ title: form.title, context: form.context, id: Math.random(), status: "todo" }); form.context = '' form.title = '' dialogFormVisible.value = false }; return { list, form, formLabelWidth, dialogFormVisible, handleAddList, changeStatus }; }}); </script> <style> .text { font-size: 14px; } .item { margin-bottom: 18px; } .clearfix:before, .clearfix:after { display: table; content: ""; } .clearfix:after { clear: both; } .box-card { } </style>Copy the code

Overall, after Vue3.0

Advantages:

1. Incorporate TS to better write your data types.

2. Rewrite some options of Vue2. X in the form of API, so that we can better extract and reuse related logic and get closer to React Hook form.

3. The new state sharing mode can replace Vuex to some extent.

Disadvantages: 1. Easy to write spaghetti code.

The above is Vue3.0 API some new usage and attention pit, hope to bring help to you.