# Vue3过渡内容

Vue2到Vue3的过渡版内容,也就是刚开始的setup函数式写法,兼容Vue2与组合式写法,学习Vue3最好直接看最新的文档,不要使用此版本。

# 声明周期变化

vue3.0生命周期最后两个周期有所改变,beforeDestroydestroyed 两个钩子变为 beforeUnmountunmounted

  • beforeUnmount: 在卸载组件实例之前调用。
  • unmounted: 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

# 组合式API

组合式API思想就是把原本分散在data、created、methods等相关的逻辑,通过setup集中到一起,这样每个代码块就划分成一个逻辑区,并且能像函数一样抽离公共setup片段,让代码更好的封装和重复使用。在setup中就像写原生js一样,通过return把要使用的数据返回给vue使用。

# 1、组合式写法

<template>
    <div @click="toggleShow">点击</div>
    <div v-if="isShow">显示内容</div>
</template>
<script>
import { defineComponent, ref } from "vue"

export default defineComponent({
    name: 'Name',
    components: {},
    setup() {
        // 通过ref定义一个响应式变量
        const isShow = ref(false)
        // 定义一个函数
        const toggleShow = () => {
            isShow.value = false
        }
        // 向父组件提交事件
        const triggerEvent = () => {
            context.emit('TRIGGLE_EVENT')
        }

        // return 出去
        return {
            isShow,
            toggleShow,
        }
    }
});
</script>

# 2、响应式式数据

在setup中声明变量只能在setup中使用,无法像在data中的数据,可以动态渲染响应,如果要定义响应式数据必须要使用 refreactive 来定义基础数据类型、数组和对象。基础类型通过ref声明,然后通过.value进行赋值操作,数组也一样,对象通过reactive来声明,赋值可以直接赋值就可以了。不论在setup中怎么操作,通过return返回后会自动解包,然后直接使用就可以了。

<template>
    <div @click="toggleShow">点击</div>
    <div v-if="isShow">显示内容</div>
</template>
<script>
import { defineComponent, ref, reactive } from "vue"

export default defineComponent({
    name: 'Name',
    components: {},
    setup() {
        // ref定义
        const bool = ref(false)
        const str = ref('')
        const num = ref(0)
        const arr = ref([])
        // 赋值 使用.value进行赋值,数组也一样,通过
        const toggleValue = () => {
            bool.value = false
            str.value = 'test'
            num.value = 18
            arr.value.push('1111')
        }

        // reactive定义
        const obj = reactive({
            name: '',
            age: 18
        })

        const changeAge = () => {
            obj.age = 20
        }

        // return 出去
        return {
            isShow,
            toggleValue,
        }
    }
});
</script>

# 3、setup参数和自定义事件

setup接收两个参数 propscontext, props为组件传入的props,props是响应式的不能使用使用ES6进行解构操作,如果使用解构的可以获取到值,但不会动态更新了。context为一个普通对象,包含attrsslotsemitexpose等属性,emit相当于$emit

在setup使用自定义事件可以使用 context.emit进行定义,在使用自定义事件前,最好在emits中先定义自定义事件。

vue3废除了$on$off等属性,所以事件总线不能直接使用了,可以使用插件 mitt 或 tiny-emitter来替代。

<script>
import { defineComponent, ref } from "vue"

export default defineComponent({
    name: 'Name',
    components: {},
    emits: ['TRIGGLE_EVENT'], // 定义自定义事件
    setup(props, context) {

        const isShow = ref(false)
        
        // 向父组件提交事件
        const triggerEvent = () => {
            context.emit('TRIGGLE_EVENT', isShow)
        }

        // return 出去
        return {
            isShow,
            toggleShow,
        }
    }
});
</script>

# 4、响应式解构

像如果对 props 参数或者 reactive 定义的对象使用ES6解构的话,会失去响应性,再次赋值就无法更新视图。我们可以使用 toRefs 把他转换成响应式的数据再赋值。

props中的值虽不能被解构,但是可以直接使用,直接props.xxx调用接收传入的值就OK了,props使用toRefs解构就是单纯的被解构使用。

一般reactive中的属性直接用 . 调用赋值就行了,除非属性又是一个对象,也可以使用toRefs解构进行动态改变值。

<script>
import { defineComponent, ref, reactive, toRefs, toRef } from "vue"

export default defineComponent({
    name: 'Name',
    components: {},
    props: {
        author: {
            type: String
        },
        age: {
            type: Number
        }
    },
    setup(props, context) {

        let { author, age } = toRefs(props)
        // 如果author是一个可选的props,可以使用toRef替代让其变为响应性的
        const author = toRef(props, 'author')
        console.log(author.value)

        const obj = reactive({
            name: '',
            title: ''
        })
        let { name, title } = toRefs(obj)
        name.value = 'changz'
        console.log(obj.name) // changz
        // return 出去
        return {
            author,
            obj,
        }
    }
});
</script>

# 5、在setup获取this

在setup中无法使用 this 的,所以要使用挂载在vue实例上的对象,比如UI插件的全局方法,我们可以通过 getCurrentInstance 方法获取

import { defineComponent, getCurrentInstance } from "vue"

export default defineComponent({
    name: 'Name',
    components: {},
    setup(props, context) {

        const golbal = getCurrentInstance().appContext.config.globalProperties
        golbal.$message.success('操作成功')
    }
});

# 6、使用生命周期

在setup中没有 created 钩子,因为setup就相当于created一样,所有写在生命周期的函数都应该写在setup中,只是一些特殊情况才用生命周期钩子。

beforeMount	--> onBeforeMount
mounted -->	onMounted
beforeUpdate --> onBeforeUpdate
updated -->	onUpdated
beforeUnmount --> onBeforeUnmount
unmounted --> onUnmounted
errorCaptured --> onErrorCaptured
renderTracked --> onRenderTracked
renderTriggered -->	onRenderTriggered
activated --> onActivated
deactivated --> onDeactivated

# 7、监听和计算属性

computed 计算属性和vue2一样返回的值不是响应性的,计算好的值最好不要再参与计算。

vue3在setup中监听数据一般使用watchEffect

// 计算
const count = ref(1)
const test = computed(() => count * 2)
const data = reactive({
    count: computed(() => count * 2)
})

// 监听value
watch(value, (new, old) => {
    /* ... */ 
})
// 监听多个值
watch([firstName, lastName], (newValues, prevValues) => {
  console.log(newValues, prevValues)
})

// 监听某个值,执行。。。
watchEffect(value, getValue)
const getValue = () => {
    /* ... */ 
}

// 停止监听
onst stop = watchEffect(() => {
  /* ... */
})
// later
stop()

# 9、provide/inject的使用

提供:provide(name, value) <名称, 值> 接收:inject(name, value) <接收值的名称, 默认值>

通过提供响应性数据,在父组件提供的数据被改变,同样在子组件接收的数据也会被改变。

在父组件中使用provide

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    const updateLocation = () => {
      location.value = 'South Pole'
    }
    // provide提供响应性值或者函数
    provide('location', location)
    provide('geolocation', geolocation)
    provide('updateLocation', updateLocation)
  }
}
</script>

在子组件中接收inject

<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')
    const updateUserLocation = inject('updateLocation')

    return {
      userLocation,
      userGeolocation,
      updateUserLocation
    }
  }
}
</script>

# ref/reactive的使用

在setup中一般都把数据放在一个reactive对象中,通过toRefs转换多个数据在dom使用,ref一般用来返回dom上的ref。这样reactive里的对象也能全部赋值过去了。

setup() {
    const nameRef = ref(null) // dom上的ref
    const passRef = ref(null)
    // 响应式状态
    const state = reactive({
        filterInfo: {
            limit: '',
            size: 10
        }
        tableData: [],
        count: 0,
        rules: {}
    })

    return {
        ...toRefs(state),
        nameRef,
        passRef
    }
}

# 路由vue-router4

vue3使用路由4.x版本,除了创建的方法变了,其他用法基本一样。

安装

npm install vue-router@4

创建

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
    {
        path: '/',
        name: 'index',
        component: () => import(/* webpackChunkName: "home" */ '@/views/home/index.vue')
    },
    {
        path: '/about',
        name: 'about',
        component: () => import(/* webpackChunkName: "about" */ '@/views/about/index.vue')
    },
    {
        path: '/empower',
        name: 'empower',
        component: () => import(/* webpackChunkName: "empower" */ '@/views/empower/index.vue')
    },
]

const router = createRouter({
    history: createWebHistory(process.env.BASE_URL),
    routes: routes
})

export default router

在setup中使用router/route

import { useRouter, useRoute } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    const route = useRoute()

    const pushWithQuery = (query) => {
      router.push({
        name: 'about',
        query: {
          ...route.query,
        },
      })
    }
  },
}

在setup中使用导航卫士

import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

export default {
  setup() {
    // same as beforeRouteLeave option with no access to `this`
    onBeforeRouteLeave((to, from) => {
      const answer = window.confirm(
        'Do you really want to leave? you have unsaved changes!'
      )
      // cancel the navigation and stay on the same page
      if (!answer) return false
    })

    const userData = ref()

    // same as beforeRouteUpdate option with no access to `this`
    onBeforeRouteUpdate(async (to, from) => {
      // only fetch the user if the id changed as maybe only the query or the hash changed
      if (to.params.id !== from.params.id) {
        userData.value = await fetchUser(to.params.id)
      }
    })
  },
}

# 状态管理器vuex4

安装

npm install vuex@next --save

创建

import { createStore } from 'vuex'

export default createStore({
    state: {
        token: '', // token
    },
    mutations: {
        // 设置token
        setToken(state, token) {
            state.token = token
        },
    },
    actions: {
        // 获取用户信息
        GetInfo({ commit }) {
            return new Promise((resolve, reject) => {
                infoApi().then(res => {
                    commit('setToken', res.token);
                    resolve(res)
                }).catch(error => {
                    reject(error)
                })
            })
        },
    },
    getters: {
        tokenLength(state) {
            return state.token.length
        }
    }
    modules: {
    }
})

在setup中使用

import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    // state和getters
    const token = ref(store.state.token)
    const count = ref(store.getters.tokenLength)

    // mutations
    const increment = () => {
        store.commit('increment')
    }
    // actions
    const asyncIncrement = () => {
        store.dispatch('asyncIncrement')
    }

    return {
        token,
        count,
        increment,
        asyncIncrement
    }
  }
}

# Teleport

有时候我们封装了一个modal弹框组件,把它放在父组件中引用,但是我们希望它的浮动不只是根据父元素来定位,我们希望它可以在我们想要的祖先元素上定位,或者直接定位在body上。平时需要全局状态或者拆分组件来解决,现在我们可以通过 Teleport 来解决,直接控制在哪个父节点渲染HTML。

// to 可以是DOM元素,或是class和id 
<teleport to="body"></teleport>
<teleport to=".box"></teleport>
<teleport to="#box"></teleport>

app.component('modal-button', {
  data() {
    return { 
      modalOpen: false
    }
  },
  template: `
    <button @click="modalOpen = true">
        Open full screen modal! (With teleport!)
    </button>

    <teleport to="body">
      <div v-if="modalOpen" class="modal">
        <div>
          I'm a teleported modal! 
          (My parent is "body")
          <button @click="modalOpen = false">
            Close
          </button>
        </div>
      </div>
    </teleport>
  `
})

# 函数渲染

vue2函数渲染

// vue2使用createElement创建虚拟DOM VNode
Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h1', 
      this.blogTitle,
      {
        // 事件
        on: {
          '!click': this.clickHandler
        },
      }
    )
  }
})

vue3直接使用 h 函数创建

import { h } from "vue"
Vue.component('anchored-heading', {
  render() {
    return h(
      'h1', 
      {
        onClick: this.clickHandler,
        // 修饰符
        capture: true
      }, 
      this.blogTitle // 内容或者子元素
      )
  }
})

# 全局创建和组件局部创建

# 1、组件的创建

// 全局创建,组件内直接使用
const app = Vue.createApp({...})

app.component('my-component-name', {
  /* ... */
})

// 或者
import myComponentName from './myComponentName'
app.component('my-component-name', myComponentName)
// 局部注册
import { defineComponent } from "vue"
import myComponentName from './myComponentName'

export default defineComponent({
    components: {
        myComponentName
    },
})

# 2、函数式组件

/**
 * render.js
 * 函数式渲染组件 
 * 
 * */ 

import { h } from "vue"

// 动态渲染h标签
export const TitleLevel = {
    render() {
        return h('h'+this.level)
    },
    props: {
        level: {
            type: Number,
            required: true
        }
    }
}
// 全局注册
import { createApp, h } from 'vue'
const app = createApp({})

// 直接在组件中使用<title-level></title-level>
app.component('title-level', {
    render() {
        return h('h'+this.level)
    },
    props: {
        level: {
            type: Number,
            required: true
        }
    }
})

// 或者
import { TitleLevel } from '@/utils/titleLevel.js'
app.component('title-level', TitleLevel)
// 组件内注册
const TitleLevel = {
    render() {
        return h('h'+this.level)
    },
    props: {
        level: {
            type: Number,
            required: true
        }
    }
}

export default defineComponent({
    components: {
        TitleLevel
    },
})

// 或者
import { TitleLevel } from '@/utils/titleLevel.js'
export default defineComponent({
    components: {
        TitleLevel
    },
})

# 3、自定义指令

/**
 * directives.js
 * 自定义指令
 * */ 

const directives = {
    install(app){
        // 自动获取焦点
        app.directive('focus', {
            mounted(el) {
                el.focus()
            }
        })
        // app.directive('xxxx', {})
    }
}

export default directives
// 全局注册

const app = createApp(App);
app.directive('focus', {
    // 当被绑定的元素挂载到 DOM 中时……
    mounted(el) {
        // 聚焦元素
        el.focus()
    }
})

// 或者
import directives from '@/utils/directives.js' // 自定义指令
const app = createApp(App);
app.use(directives);
<!-- 局部注册 -->
<input type="text" v-focus>

<script>
    export default defineComponent({
        directives: {
            focus: {
                // 指令的定义
                mounted(el) {
                el.focus()
                }
            }
        }
    })

</script>