Vue3:新特性详解

作者: 前端辉羽 | 来源:发表于2021-02-22 11:28 被阅读0次

    本文目录:

    • 1.特性函数setup
    • 2.Ref 语法
    • 3.Reactive 函数
    • 4.Vue3 生命周期
    • 5.侦测变化 - watch
    • 6.Vue3的模块化开发
    • 7.弹窗类组件优化:Teleport
    • 8.异步组件优化:Suspense
    • 9.全局API优化

    1.特性函数setup

    1、setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 也就说在 setup函数中是无法 使用 data 和 methods 中的数据和方法的
    2、setup函数是 Composition API(组合API)的入口
    3、在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用

    setup第一个参数props会自动推论成props里面定义的类型
    第二个参数context,在setup中无法直接访问vue2最常用的this对象,context提供了vue2中最常用的三个属性
    context.attrs
    context.slots
    context.emit

    2.Ref 语法

    ref 是一个函数,它接受一个参数,返回的就是一个神奇的 响应式对象 。我们初始化的这个 0 作为参数包裹到这个对象中去,在未来可以检测到改变并作出对应的相应。

    <template>
      <h1>{{count}}</h1>
      <h1>{{double}}</h1>
      <button @click="increase">+1</button>
    </template>
    import { ref } from "vue"
    setup() {
      const count = ref(0)
      const double = computed(() => {
        return count.value * 2
      })
      const increase = () => {
        count.value++
      }
      return {
        count,
        increase,
        double
      }
    }
    

    3.Reactive 函数

    reactive的用法与ref的用法相似,也是将数据变成响应式数据,当数据发生变化时UI也会自动更新。不同的是ref用于基本数据类型,而reactive是用于复杂数据类型,比如对象和数组

    import { ref, computed, reactive, toRefs } from 'vue'
    
    interface DataProps {
      count: number;
      double: number;
      increase: () => void;
    }
    
    setup() {
      const data: DataProps  = reactive({
        count: 0,
        increase: () => { data.count++},
        double: computed(() => data.count * 2)
      })
      const refData = toRefs(data)
      return {
        ...refData
      }
    }
    

    使用 ref 还是 reactive 可以选择这样的准则
    第一,就像刚才的原生 javascript 的代码一样,像你平常写普通的 js 代码选择原始类型和对象类型一样来选择是使用 ref 还是 reactive。
    第二,所有场景都使用 reactive,但是要记得使用 toRefs 保证 reactive 对象属性保持响应性。

    4.Vue3 生命周期

    在vue3中,生命周期在hook中的变化

    • beforeCreate -> 不需要
    • created -> 不需要
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeUnmount -> onBeforeUnmount
    • unmounted -> onUnmounted
    • errorCaptured -> onErrorCaptured
    • renderTracked -> onRenderTracked
    • renderTriggered -> onRenderTriggered

    为了更好的Tree-shaking,Vue3的生命周期函数都是从 vue 中导入,再进行直接调用
    从 vue 中引入 多个生命周期函数

    import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, unMounted} from 'vue'
    export default {
      name: 'App',
      setup() {
        onBeforeMount(() => {
          // 在挂载前执行
        })
        onMounted(() => {
          // 在挂载后执行
        })
        onBeforeUpdate(() => {
          // 在更新前前执行
        })
        onUpdated(() => {
          // 在更新后执行
        })
        onBeforeUnmount(() => {
          // 在组件销毁前执行
        })
        unMounted(() => {
          // 在组件销毁后执行
        })
        return {}
      }
    }
    

    onErrorCaptured可以捕捉错误,同时捕捉错误需要返回一个布尔值,表示错误是否向上传播

    import { onErrorCaptured } from 'vue'
    setup(){
      const error = ref(null)
      onErrorCaptured ((e:any)=>{
        error.value = e
        return true
      })
    }
    

    5.侦测变化 - watch

    watch的第一个参数是一个响应式的对象,如果要监听多个对象,则第一个参数也可以是一个数组
    watch 多个值,返回的也是多个值的数组

    watch([greetings, data], (newValue, oldValue) => {
      console.log('old', oldValue)
      console.log('new', newValue)
      document.title = 'updated' + greetings.value + data.count
    })
    

    如果是打算监听data中的一个属性,比如data.count,则不能直接去watch([greetings, data.count]这样写,而是要使用 getter 的写法 watch reactive 对象中的一项

    watch([greetings, () => data.count], (newValue, oldValue) => {
      console.log('old', oldValue)
      console.log('new', newValue)
      document.title = 'updated' + greetings.value + data.count
    })
    

    6.Vue3的模块化开发

    使用composition API 的好处

    • 将相关的逻辑代码组合在一起,不要分散在代码的各个地方
    • 可以非常容易的重用代码,比mixin有更高的灵活度

    实践:记录鼠标的位置
    利用vue3进行初步的实现,将所有的代码都写在setup中

    const x = ref(0)
    const y = ref(0)
    const updateMouse = (e: MouseEvent) => {
      x.value = e.pageX
      y.value = e.pageY
    }
    onMounted(() => {
      document.addEventListener('click', updateMouse)
    })
    onUnmounted(() => {
      document.removeEventListener('click', updateMouse)
    })
    

    但是上方的代码还不能做到很方便的重用,所以接下来需要对代码进行改造,将组件内逻辑抽象成可复用的函数
    新建一个文件hooks文件夹,里面就存放我们抽离出来的所有的逻辑模块,新建一个useMousePosition.ts

    import { ref, onMounted, onUnmounted } from 'vue'
    function useMouseTracker() {
      // const positions = reactive<MousePostion>({
      //   x: 0,
      //   y: 0
      // })
      const x = ref(0)
      const y = ref(0)
      const updatePosition = (event: MouseEvent) => {
        x.value = event.clientX
        y.value = event.clientY 
      }
      onMounted(() => {
        document.addEventListener('click', updatePosition)
      })
      onUnmounted(() => {
        document.removeEventListener('click', updatePosition)
      })
      return { x, y }
    }
    export default useMouseTracker
    

    在需要使用这个逻辑的页面进行引用

    import useMousePosition from './hooks/useMousePosition'
    

    这里引用到的就是一段函数代码,然后调用函数,取其返回值就可以了

    const { x, y } = useMousePosition()
    

    vue3 这种实现方式的优点

    • 第一:它可以清楚的知道 xy 这两个值的来源,这两个参数是干什么的,他们来自 useMouseTracker 的返回,那么它们就是用来追踪鼠标位置的值。
    • 第二:我们可以xy 可以设置任何别名,这样就避免了命名冲突的风险。
    • 第三:这段逻辑可以脱离组件存在,因为它本来就和组件的实现没有任何关系,我们不需要添加任何组件实现相应的功能。只有逻辑代码在里面,不需要模版。

    将请求接口的逻辑抽离出去:

    import { ref } from 'vue'
    import axios from 'axios'
    // 添加一个参数作为要使用的 地址
    const useURLLoader = (url: string) => {
    // 声明几个ref,代表不同的状态和结果
      const result = ref(null)
      const loading = ref(true)
      const loaded = ref(false)
      const error = ref(null)
    // 发送异步请求,获得data
    // 由于 axios 都有定义,所以rawData 可以轻松知道其类型
      axios.get(url).then((rawData) => {
        loading.value = false
        loaded.value = true
        result.value = rawData.data
      }).catch((e) => {
        error.value = e
      })
      // 将这些ref 一一返回
      return {
        result,
        loading,
        error,
        loaded
      }
    }
    export default useURLLoader
    

    在要使用接口的页面进行引用:

    const { result, loading, loaded } = useURLLoader('https://dog.ceo/api/breeds/image/random')
    ...
    <h1 v-if="loading">Loading!...</h1>
    <img v-if="loaded" :src="result.message" >
    

    模块化结合typescript - 泛型改造:

    function useURLLoader<T>(url: string) {
      const result = ref<T | null>(null)
    

    在应用中的使用,可以定义不同的数据类型

    // 在应用中的使用,可以定义不同的数据类型
    interface DogResult {
      message: string;
      status: string;
    }
    interface CatResult {
      id: string;
      url: string;
      width: number;
      height: number;
    }
    const { result, loading, loaded } = useURLLoader<CatResult[]>('https://api.thecatapi.com/v1/images/search?limit=1')
    

    组件的定义和导出的都是一个普通的object

    const component = {
      name: 'HelloWorld',
      props: {
        ...
      }
    }
    
    export default component;
    

    而普通的对象在书写代码的时候自然不会获得ts的提示支持,vue3提供了defineComponent来让普通的object变为支持ts的对象

    const component = 
      defineComponent({
        name: 'HelloWorld',
        props: {
          ...
        }
    })
    
    export default component;
    

    7.弹窗类组件优化:Teleport

    Vue2在弹窗组件开发中,Dialog被包裹在其他组件之中,容易被干扰,样式也在其他组件中,容易变得非常混乱。
    vue3 新添加了一个默认的组件就叫 Teleport,我们可以拿过来直接使用,它上面有一个 to 的属性,它接受一个css query selector 作为参数,这就是代表要把这个组件渲染到哪个 dom 元素中

    <template>
      <teleport to="#modal">
        <div id="center">
          <h1>this is a modal</h1>
        </div>
      </teleport>
    </template>
    

    特别适合弹窗类的元素渲染,这样页面的所有元素就不用所有的元素都挤在一个div中了
    在index.html添加一个modal元素

    <div id-"app"></div>
    <div id-"modal"></div>
    

    实际应用中,在使用弹窗类元素的组件中需要手动控制该元素的渲染与否。
    定义一个isOpen来控制,
    子组件内部通过context.emit('close-modal')去触发根组件中定义的事件,注意在vue3中子组件需要通过emit引入该事件。

    <template>
    <teleport to="#modal">
      <div id="center" v-if="isOpen">
        <h2><slot>this is a modal</slot></h2>
        <button @click="buttonClick">Close</button>
      </div>
    </teleport>
    </template>
    <script lang="ts">
    import { defineComponent } from 'vue'
    export default defineComponent({
      props: {
        isOpen: Boolean,
      },
      emits: {
        'close-modal': null
      },
      setup(props, context) {
        const buttonClick = () => {
          context.emit('close-modal')
        }
        return {
          buttonClick
        }
      }
    })
    

    在 App.vue 组件中使用

    const modalIsOpen = ref(false)
    const openModal = () => {
      modalIsOpen.value = true
    }
    const onModalClose = () => {
      modalIsOpen.value = false
    }
    
    <button @click="openModal">Open Modal</button><br/>
    <modal :isOpen="modalIsOpen" @close-modal="onModalClose"> My Modal !!!!</modal>
    

    8.异步组件优化:Suspense

    Suspense是Vue3推出的一个内置的特殊组件
    Suspense内部默认有两个自定义的slot,刚开始的会渲染#default部分,达到某个条件之后才会去渲染#fallback的内容。
    如果要使用Suspense,组件中要返回一个Promise
    可以非常方便为异步请求的等待界面进行个性化的定制。
    定义一个异步组件,在 setup 返回一个 Promise,AsyncShow.vue

    <template>
      <h1>{{result}}</h1>
    </template>
    <script lang="ts">
    import { defineComponent } from 'vue'
    export default defineComponent({
      setup() {
        return new Promise((resolve) => {
          setTimeout(() => {
            return resolve({
              result: 42
            })
          }, 3000)
        })
      }
    })
    </script>
    

    在 App.vue 中使用

    <Suspense>
      <template #default>
        <async-show />
      </template>
      <template #fallback>
        <h1>Loading !...</h1>
      </template>
    </Suspense>
    

    default可以存放多个异步组件,这样会等到所有的异步组件都拿到结果之后才去渲染里面的内容。

    <template>
      <img :src="result && result.message">
    </template>
    
    <script lang="ts">
    import axios from 'axios'
    import { defineComponent } from 'vue'
    export default defineComponent({
      async setup() {
        const rawData = await axios.get('https://dog.ceo/api/breeds/image')
        return {
          result: rawData.data
        }
      }
    })
    </script>
    

    Suspense 中可以添加多个异步组件

    <Suspense>
      <template #default>
        <async-show />
        <dog-show />
      </template>
      <template #fallback>
        <h1>Loading !...</h1>
      </template>
    </Suspense>
    

    9.全局API优化

    vue2全局API的写法
    1.从vue中导出一个全局对象Vue

    import Vue from 'vue'
    import App from './App.vue'
    

    2.然后在Vue中进行一系列的配置

    Vue.config.ignoredElements = [/^app-/]
    Vue.use(/* ... */)
    Vue.mixin(/* ... */)
    Vue.component(/* ... */)
    Vue.directive(/* ... */)
    

    3.新建vue实例,然后调用$mount方法将其挂在到app节点上

    new Vue({
      render: h => h(App)
    }).$mount('#app')
    

    Vue2 这样写在一定程度上修改了 Vue 对象的全局状态。
    第一,在单元测试中,全局配置非常容易污染全局环境,用户需要在每次 case 之间,保存和恢复配置。有一些 api (vue use vue mixin)甚至没有方法恢复配置,这就让一些插件的测试非常的困难。
    第二,在不同的 APP 中,如果想共享一份有不同配置的 vue 对象,也变得非常困难。

    vue3新增加了createApp,先将createApp引入,然后利用createApp生成一个app实例,

    const app = createApp(App)
    

    接下来增加配置就不需要直接给Vue添加了,而是给app添加就行了

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

    现在再设置任何的配置都会是在不同的app实例上,不同的设置就不会发生冲突。
    当配置结束以后,我们再把 App 使用 mount 方法挂载到固定的 DOM 的节点上。

    app.mount(App, '#app')
    

    相关文章

      网友评论

        本文标题:Vue3:新特性详解

        本文链接:https://www.haomeiwen.com/subject/ijixfltx.html