最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue3新特性及使用感受

    正文概述 掘金(Lavelay)   2021-07-03   822

    VUE3新特性

    1. Composition API

    • setup

      使用setup时,它接受两个参数:

      1. props: 组件传入的属性
      2. context:提供三个属性

      setup 中接受的props是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。

      const { xxx } = toRefs(props) // 可用toRefs解决
      

      setup中不能访问 Vue2 中最常用的this对象,所以context中就提供了this中最常用的三个属性:attrsslotemit,分别对应 Vue2.x 中的 $attr属性、slot插槽 和$emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。

      语法糖 script setup

      直接给script标签添加setup属性,不要像旧的语法那样在底部return一堆属性出去

      组件import后直接在template使用,不需要注册

      <template>
        <div>
       		<span>{{ num }}</span>
      	  <panel></panel>
        </div>
      </template>
      
      <script setup>
        import { ref } from 'vue'
        import Panel from '@/components/Panel.vue'
        
        const num = ref(0)
      </srcipt>
      

      script setup 相关文档

    • reactive

      <template>
        <div class="about">
          <div>
            <span>count 点击次数: </span>
            <span>{{ count }}</span>
            <button @click="addCount">点击增加</button>
          </div>
        </div>
      </template>
      <script>
      import { reactive, toRefs } from 'vue'
      export default {
        setup () {
          const state = reactive({
            count: 0
          })
          const addCount = function () {
            state.count++
          }
          return {
            // 这样展开后state property会失去响应式,因为是取值返回,不是引用
            // ...state,
            ...toRefs(state),
            addCount,
          }
        },
      }
      </script>
      
      
    • ref

      ref函数将一个普通对象转化为响应式包装对象,将一个 ref 值暴露给渲染上下文,在渲染过程中,Vue 会直接使用其内部的值,在模板中可以把 {{ num.value }} 直接写为 {{ num }} ,但是在js中还是需要通过 num.value取值和赋值

      <template>
        <div class="about">
          <div>
            <span>num 点击次数: </span>
            <span>{{ num }}</span>
            <button @click="addNum">点击增加</button>
          </div>
        </div>
      </template>
      <script>
      import { ref } from 'vue'
      export default {
        setup () {
          const num = ref(0)
      
          const addNum = function () {
            num.value++
          }
          return {
            num,
            addNum
          }
        }
      }
      </script>
      
    • toRefs

      toRefsreactive对象转换为普通对象,其中结果对象上的每个属性都是指向原始对象中相应属性的ref引用对象,这在组合函数返回响应式状态时非常有用,这样保证了开发者使用对象解构或拓展运算符不会丢失原有响应式对象的响应

    • readonly

      对于不允许写的对象,不管是普通object对象、reactive对象、ref对象,都可以通过readonly方法返回一个只读对象

      直接修改readonly对象,控制台会打印告警信息,不会报错

      const state = reactive({
        count: 0
      })
      const readonlyState = readonly(state)
      
      // 监听只读属性,state.count修改后依然会触发readonlyState.count更新
      watch(() => readonlyState.count, (newVal, oldVal) => {
        console.log('readonly state is changed!')
        setTimeout(() => {
          // 修改只读属性会打印告警信息,但是不会报错
          readonlyState.count = 666
        }, 1000)
      })
      
    • 生命周期

    setup 执行时机是在 beforeCreate 之前执行

    相对应的生命周期,3版本的执行时机总比2版本的早

    vue3新特性及使用感受
    • Computed

      接受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。

      const count = ref(1)
      const plusOne = computed(() => count.value + 1)
      
      console.log(plusOne.value) // 2
      

      或者,它也可以使用具有 getset 函数的对象来创建可写的 ref 对象。

      const count = ref(1)
      const plusOne = computed({
        get: () => count.value + 1,
        set: val => {
          count.value = val - 1
        }
      })
      
      plusOne.value = 1
      console.log(count.value) // 0
      
    • watch

    	watch(source, callback, [options])
    

    ​ 参数说明:

    ​ source: 可以支持 string,Object,Function,Array; 用于指定要侦听的响应式变量

    ​ callback: 执行的回调函数

    ​ options:支持 deep、immediate 和 flush 选项。

    侦听 reactive 定义的数据

       const state = reactive({ nickname: "lilei", age: 20 });
        // 修改age值时会触发 watch的回调
        watch(
          () => state.age,             
          (curAge, preAge) => {
            console.log("新值:", curAge, "老值:", preAge);
          }
        );
    

    侦听 ref 定义的数据

    const year = ref(0);
    watch(year, (newVal, oldVal) => {
      console.log("新值:", newVal, "老值:", oldVal);
    });
    

    侦听多个数据

    watch(
      [() => state.age, year],
      ([curAge, newVal], [preAge, oldVal]) => {
        console.log("新值:", curAge, "老值:", preAge);
        console.log("新值:", newVal, "老值:", oldVal);
      }
    );
    

    侦听复杂的嵌套对象

    const state = reactive({
      room: {
        id: 100,
        attrs: {
          size: "140平方米",
          type: "三室两厅",
        },
      },
    });
    watch(
      () => state.room,
      (newType, oldType) => {
        console.log("新值:", newType, "老值:", oldType);
      },
      { deep: true }
    );
    
    

    stop 停止监听

    我们在组件中创建的watch监听,会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()函数的返回值,操作如下:

    const stopWatchRoom = watch(() => state.room, (newType, oldType) => {
        console.log("新值:", newType, "老值:", oldType);
    }, {deep:true});
    
    setTimeout(()=>{
        // 停止监听
        stopWatchRoom()
    }, 3000)
    

    2. Fragment

    1. 可以直接写多个节点,根节点不是必要的,无需创建了,减少了节点数。

    2. Fragment节点是虚拟的,不会DOM树中呈现。

    <template>
        <div></div>
        <div></div>
    </template>
    

    3. Teleport

    例子:在子组件Header中使用到Dialog组件,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。

    即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中

    若希望 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:

    <body>
      <div id="app"></div>
      <div id="dialog"></div>
    </body>
    

    定义一个Dialog组件Dialog.vue, to 属性, 与上面的id选择器一致:

    <template>
      <teleport to="#dialog">
        <div class="dialog">
     			...
        </div>
      </teleport>
    </template>
    

    在一个子组件Header.vue中使用Dialog组件。header组件

    <div class="header">
        ...
        <navbar />
        <Dialog v-if="dialogVisible"></Dialog>
    </div>
    ...
    

    Dom 渲染效果如下:

    vue3新特性及使用感受

    使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与 <div id="app"></div> 同级,也就是在 body 下,但是 Dialog 的状态 dialogVisible 又是完全由内部 Vue 组件控制.

    4. Suspense

    vue3新特性及使用感受

    在正确渲染组件之前进行一些异步请求是很常见的事。组件通常会在本地处理这种逻辑,绝大多数情况下这是非常完美的做法。

    <suspense> 组件提供了另一个方案,允许等待整个组件树处理完毕而不是单个组件。

    <template>
      <suspense>
         <template #default>
           <async-component></async-component>
         </template>
         <template #fallback>
           <div> 
             Loading...
           </div>
         </template>
      </suspense>
    </template> 
    

    这个 <suspense> 组件有两个插槽。它们都只接收一个子节点。default 插槽里的节点会尽可能展示出来。如果不能,则展示 fallback 插槽里的节点。

    重要的是,异步组件不需要作为 <suspense> 的最近子节点。它可以在组件树任意深度的位置且不需要出现在和 <suspense> 自身相同的模板中。只有所有的后代组件都准备就绪,该内容才会被认为解析完毕。

    5. vue-router

    vue2.x使用路由选项redirect设置路由自动调整,vue3.x中移除了这个选项,将在子路由中添加一个空路径路由来匹配跳转

    // vue2.x router
    [
      {
        path: '/',
        component: Layout,
        name: 'WebHome',
        redirect: '/dashboard', // 这里写跳转
        children: [
          {
            path: 'dashboard',
            name: 'Dashboard',
            component: () => import('../views/dashboard/index.vue')
          }
        ]
      }
    ]
    
    // vue3.x router
    [
      {
        path: '/',
        component: Layout,
        name: 'WebHome',
        children: [
          { path: '', redirect: 'dashboard' }, // 这里写跳转
          {
            path: 'dashboard',
            name: 'Dashboard',
            component: () => import('../views/dashboard/index.vue')
          }
        ]
      }
    ]
    
    import { useRouter, useRoute } from 'vue-router'
    
    export default {
      setup () {
        const router = useRouter()
        const route = useRoute()
        return {
          
        }
      }
    }
    

    6. Vuex

    import { useStore } from 'vuex'
    
    export default {
      setup () {
        const store = userStore()
    
        const userId = computed(() => store.state.app.userId)
        const getUserInfo = () => store.dispatch('xxxx')
    		const setUserInfo = () => store.commit('xxx')
        return {
          userId
        }
      }
    }
    

    7. getCurrentInstance

    获取当前的组件实例

    import { getCurrentInstance } from 'vue'
    
    export default {
      setup () {
        // const { ctx } = getCurrentInstance()          ctx 只在开发环境生效,生产环境无效
        const { proxy: ctx } = getCurrentInstance()
        console.log('ccccc', ctx)
        return {
         
        }
      }
    }
    

    了解更多可查看官方文档

    使用总结

    1. 在2版本时候,当代码行数很多时,data,计算属性,watch都分布在不同区域,要来回切换,开发体验不好,新版本可以按业务逻辑放到一块

    2. 组合式Api开发起来更加灵活,逻辑复用较好

    3. 业务逻辑集中写在setup中,可能会导致代码臃肿较难维护

    ​ 组件抽离、要统一开发规范

    1. 利用 ES6 模块系统import/export,按需编译,体积比Vue2.x更小 (Tree shaking)

      import { computed, watch } from "vue";

    问题:

    watch监听reactive的数据为什么需要用函数返回?

    1.官网,data源要是返回值的getter函数或ref

    vue3新特性及使用感受

    watch源码

    export function watch<T = any>(
      source: WatchSource<T> | WatchSource<T>[],  /* getter方法  */
      cb: WatchCallback<T>,                       /* hander回调函数 */
      options?: WatchOptions                      /* watchOptions */
    ): StopHandle { 
      return doWatch(source, cb, options)
    }
    

    watch接受三个参数,分别是getter方法,回调函数,和options配置项。

    doWatch核心方法

    function doWatch(
      source: WatchSource | WatchSource[] | WatchEffect,
      cb: WatchCallback | null,
      { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
    ): StopHandle {
      /* 此时的 instance 是当前正在初始化操作的 instance  */
      const instance = currentInstance
      let getter: () => any
      if (isArray(source)) { /*  判断source 为数组 ,此时是watch情况 */
        getter = () =>
          source.map(
            s =>
              isRef(s)
                ? s.value
                : callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
          )
      /* 判断ref情况 ,此时watch api情况*/
      } else if (isRef(source)) {
        getter = () => source.value
       /* 正常watch情况,处理getter () => state.count */
      } else if (cb) { 
        getter = () =>
          callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
      } else {
        /*  watchEffect 情况 */
        getter = () => {
          if (instance && instance.isUnmounted) {
            return
          }
          if (cleanup) {
            cleanup()
          }
          return callWithErrorHandling(
            source,
            instance,
            ErrorCodes.WATCH_CALLBACK,
            [onInvalidate]
          )
        }
      }
       /* 处理深度监听逻辑 */
      if (cb && deep) {
        const baseGetter = getter
        /* 将当前 */
        getter = () => traverse(baseGetter())
      }
    
      let cleanup: () => void
      /* 清除当前watchEffect */
      const onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
        cleanup = runner.options.onStop = () => {
          callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
        }
      }
      
      let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE
    
      const applyCb = cb
        ? () => {
            if (instance && instance.isUnmounted) {
              return
            }
            const newValue = runner()
            if (deep || hasChanged(newValue, oldValue)) {
              if (cleanup) {
                cleanup()
              }
              callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
                newValue,
                oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
                onInvalidate
              ])
              oldValue = newValue
            }
          }
        : void 0
      /* TODO:  scheduler事件调度*/
      let scheduler: (job: () => any) => void
      if (flush === 'sync') { /* 同步执行 */
        scheduler = invoke
      } else if (flush === 'pre') { /* 在组件更新之前执行 */
        scheduler = job => {
          if (!instance || instance.isMounted) {
            queueJob(job)
          } else {
            job()
          }
        }
      } else {  /* 正常情况 */
        scheduler = job => queuePostRenderEffect(job, instance && instance.suspense)
      }
      const runner = effect(getter, {
        lazy: true, /* 此时 lazy 为true ,当前watchEffect不会立即执行 */
        computed: true,
        onTrack,
        onTrigger,
        scheduler: applyCb ? () => scheduler(applyCb) : scheduler
      })
    
      recordInstanceBoundEffect(runner)
      /* 执行watcherEffect函数 */
      if (applyCb) {
        if (immediate) {
          applyCb()
        } else {
          oldValue = runner()
        }
      } else {
        runner()
      }
      /* 返回函数 ,用终止当前的watchEffect */
      return () => {
        stop(runner)
        if (instance) {
          remove(instance.effects!, runner)
        }
      }
    }
    

    起源地下载网 » vue3新特性及使用感受

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元