likes
comments
collection
share

Vue3.2.x中的小技巧以及注意事项

作者站长头像
站长
· 阅读数 9

前言

vue3在2022年的2月7号成为了vue默认版本,并且随之而来的还有vue3新文档, 并且从实际使用的角度来说, vue3确实比vue2使用起来更加的舒服,所以觉得经过一段时间的使用,来分享一下使用过程中的小技巧以及注意事项。

小技巧

关于减少.value的使用

  1. 使用watch来监听Ref数据的时候, 可以做到省略.value的使用, 例如:

      const value = ref(1);
      // 省略() => value.value
      watch(value, (v) => {
        // 省略v.value
        console.log(v);
      }, { immediate: true });
    
      setTimeout(() => {
        value.value = 2;
      }, 1000);
    
  2. 使用vue3.2.25以上版本提供的$ref, 还是跟上面的代码实现一样的功能

    该功能是一个实验性能, 需要相应的配置, 这里以vite为例, 需要在vite.config.tsvue plugin中添加一个reactivityTransform属性, 请看下面的配置。如果用的是其他工程化工具, 可以参考vue的新文档, 文档中有详细的说明。

      plugins: [
        vue({
          reactivityTransform: true,
        })
      ]
    
      const count = $ref(1);
      // 增加了() => count
      watch(
        () => count,
        (v) => {
          console.log(v);
        },
        { immediate: true }
      );
    
      setTimeout(() => {
        // 省略了count.value
        count++;
      }, 1000);
    

输出: Vue3.2.x中的小技巧以及注意事项

这里需要说明一下使用$ref需要注意的问题, 首先该功能是一个实验性性能, 需要相应的配置, 并且vue的文档中指出该方法是一个编译器宏使用时无需引入, 但为了ts和编辑器的无端报错, 个人还是喜欢显示的引入, 就像这样import { $ref } from 'vue/macros' 接着再说一下$ref的另一个很严重的问题, 就是丢失响应式, 为什么会丢失响应式呢? 其实这部分官方文档已经做出了说明, 请看下面的代码

// App.vue
import { $ref } from "vue/macros";
import { useApp } from "./App";
let count = $ref(1);

useApp(count);

setTimeout(() => {
  console.log("change");
  count++;
}, 1500);

// App.ts
import { watch } from "vue";
export const useApp = (count) => {
  watch(
    () => count,
    (c) => {
      console.log("watch", c);
    },
    { immediate: true }
  );
};

上面代码中App.ts里面的watch只会执行一次, 很明显, count丢失了响应性 如何解决这个问题呢?请看下面的代码:

// App.vue
import { $ref, $$ } from "vue/macros"; // 引入$$
import { useApp } from "./App";
let count = $ref(1);

useApp($$(count)); // useApp(count) --> useApp($$(count))

setTimeout(() => {
  console.log("change");
  count++;
}, 1500);

// App.ts
import { watch } from "vue";
export const useApp = (count) => {
  watch(
    count, // () => count --> count
    (c) => {
      console.log("watch", c);
    },
    { immediate: true }
  );
};

可以看到, 我们在传递$ref值的时候 需要用一个$$方法包裹一下, 这样就不会丢失响应性了, 具体更详细的使用方法, 还是希望大家仔细阅读一下vue的新文档

关于减少import导入语句

发现这个功能是无意间的,在使用element-plus的时候, 查看elment-plus官网 指南 快速开始, 其中提到了自动导入的功能, 文档中说的是 首先下载对应的插件npm install -D unplugin-vue-components unplugin-auto-import, 然后如果使用的是vite的话, 需要在vite.config中添加几条配置, 就像下面一样:

// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default {
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
}

因为好奇去看了这两个包的介绍, 发现不光可以自动导入组件, 还可以自动导入方法, 例如心细的小伙伴已经发现, .value那部分的代码 不管是ref还是$ref我都没有写import语句来导入, 这里就用到了这两个插件, 我们来看一下如果要自动导入vue的方法对应的配置。

// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default {
  plugins: [
    // ...
    AutoImport({
      imports: ["vue", "vue/macros"],  // 增加这一行代码
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
}

在这样的配置下就可以愉快的减少import导入了。

关于在script setup中声明组件名字

script setup的方式刚发布的时候, 我就一直在纠结这个问题, 因为项目中有很多的递归组件, 如果没有name来做标识的话, 势必会产生问题

  • 刚开始vue的issues中其他用户提出的解决方式是在.vue文件中定义两个script标签, 其中一个用来定义组件的name, 而另一个用来编写组件逻辑, 例如下面这样: Vue3.2.x中的小技巧以及注意事项 这种方式相信对于一些有强迫症或者完美主义者来说是完全不能接受的, 包括我 也不能接受, 所以在vueissues中就有一个用户开发了一个插件来解决这个问题。

  • unplugin-vue-define-options插件

下载插件npm i unplugin-vue-define-options -D 我们直接来看一下这个插件的使用方式: 在vite中使用

// vite.config.ts
import DefineOptions from 'unplugin-vue-define-options/vite'
import Vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [Vue(), DefineOptions()],
})

ts项目需要在tsconfig.json中添加一个配置

{
  "compilerOptions": {
    "types": ["unplugin-vue-define-options"]
  }
}

使用方式

<script setup lang="ts">
  defineOptions({
    name: 'App'
  })
</script>

该插件的功能远远不止定义组件的name, 还可以定义组件的propsemitsrender等,有兴趣的小伙伴可以去看一下,感觉可以利用这一特性才做一些骚操作,不过尤大大觉得这种方式不太好。

注意事项

关于响应式的问题

  • props不能使用解构的方式来使用, 例如下面的例子
  // Parent.vue
  <template>
    <ChildVue ref="childRef" v-bind="data" />
  </template>
  
  <script setup lang="ts">
    import { reactive } from 'vue';
    import ChildVue from "./views/Child.vue";
    const data = reactive({ name: 'veloma' });
    setTimeout(() => {
      data.name = 'timer';
    }, 1500);
  </script>

  // Child.vue
  <template>
    <div>{{ data.name }}</div>
  </template>

  <script lang="ts" setup>

    const props = defineProps<{
      name: string;
    }>();
    const data = reactive({ ...props });

  </script>

上面的例子在子组件中, 通过reactiveprops进行了解构, Parent组件中1.5s后更新name, 这时我们会发现Child组件中的模板并不会产生更新, 那如何来解决这个问题呢? 首先有两种解决方式:

  1. 使用3.2.25或以上的版本直接解构defineProps, 例如这样 const { name } = defineProps<{ name: string }>() 2.或者通过computed来解构, 例如 const data = computed(() => ({ ...props }))

模板循环中加不加key的问题

关于这个问题, 在vue新文档中有提到这样一句话

Vue3.2.x中的小技巧以及注意事项

只看这句话的话是没有任何问题的,但在实际的使用过程中, 举个🌰:

    // Parent.vue
    <template>
      <div>
        <ChildVue v-for="item of list" v-bind="item"></ChildVue>

        <button @click="onClick">按钮</button>
      </div>
    </template>
    <script setup lang="ts">
      import { ref } from "vue";
      import ChildVue from "./views/Child.vue";

      const list = ref([{ name: "veloma" }, { name: "timer" }, { name: "lucy" }]);

      const onClick = () => {
        console.log('点击');
        const item = { ...list.value[0] };
        item.name = "veloma1111";
 
        list.value[0] = item;
     };
    </script>
    
    // Child.vue
    <template>
      <div>{{ name }}</div>
    </template>

    <script lang="ts" setup>
      import { onMounted } from "vue";

      defineProps<{
        name: string;
      }>();

      onMounted(() => {
        console.log("mounted");
      });
    </script>

我们看上面的代码会发现功能非常的简单, 子组件接收一个name属性, 父组件循环渲染子组件, 且子组件中有一个onMounted钩子, 我们希望的是, 当点击按钮的时候触发子组件的onMounted钩子, 乍一看是没有任何问题的, 但实际是不会触发的, 看结果:

Vue3.2.x中的小技巧以及注意事项

我们发现click事件确实触发了, 而数据也确实变化了, 页面也变化了, 但就是没有触发子组件的onMounted钩子, 那这是怎么回事呢?实际上在vue处理这一步的时候 重用了之前namevelomaChild组件, 重用不会产生挂载, 也就不会触发onMounted钩子, 那要怎么解决呢?其实很简单, 只需要给Child组件一个key即可.

Vue3.2.x中的小技巧以及注意事项

Vue3.2.x中的小技巧以及注意事项

总结

到目前为止其实还有好多公司没有升级到vue3, 但是我相信 在不久的将来 甚至就是今年, vue3 + vite + typescript 一定会覆盖大部分的公司, 所以建议小伙伴们还是需要仔细认真的多看两遍vue的新文档, 系统的了解一下vue3的变化为以后的升级做好准备, 加油!