likes
comments
collection
share

小美老师,如何在开发中更加优雅的使用Vue3.0在日常开发中,Vue3.0已经用的很常见了。但是在最近接手一个多人迭代的

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

一. 前言

在日常开发中,Vue3.0已经用的很常见了。但是在最近接手一个多人迭代的“code hell”项目中,我发现一些非常匪夷所思的写法。在这里总结一下,我个人感觉比较好用的Vue3.0的特性,以及一些在ts方面更加合理的规范吧。仅作为个人日常总结。

小美老师,如何在开发中更加优雅的使用Vue3.0在日常开发中,Vue3.0已经用的很常见了。但是在最近接手一个多人迭代的

注意: 以下默认 setup的语法糖写法

二. defineProps

1. 定义

定义组件的属性。通过调用该函数,可以声明组件的属性,并且将属性的类型、默认值等信息进行声明。 在子组件内部通过props属性来接收父组件传递的属性值。

这里注意是defineProps是个函数

可以看一下在Vue源码中的定义:

    export declare function defineProps<TypeProps>(): Readonly<TypeProps>;

这里面牵涉到runtime以及一些宏函数的问题(之后有空再聊一下吧)

2. 写法

通过使用范型参数定义

    const props = defineProps<{T}>()

如下例子:

    const props = defineProps<{
        setUpPropsConfig: {
            title: string,
            value: number,
            isOK: boolean,
            list: string | number [],
            myObject: { name: string, age: number},
            func?: () => void
        }
    }>()

还可以把当前的范型参数抽出去编写成一个接口

    interface ISetUpProps{
        ····
    }
    const props = defineProps<ISetUpProps>()

这里要注意一个问题,这个接口文件不能通过import从外部文件引入,不然会报错


[@vue/compiler-sfc] type argument passed to defineProps() must be a literal type,
or a reference to an interface or literal type.

加入默认值:

withDefaults(defineProps, {})

    const props = withDefaults(defineProps<{
        title: string,
        value: number
    }>(), {
        title: 'name',
        value: 1
    })

三. defineEmits

用于定义组件的事件。通过调用该函数,可以声明组件所触发的事件,并且将事件的名称、参数等信息进行声明。

    export declare function defineEmits<TypeEmit>(): TypeEmit;

类似于defineProps也是一个函数,可以通过范型参数定义

    const emit = defineEmits<{ 
        (e: 'change', id: number): void 
        (e: 'update', array: number[]): void }
    >()

其实这两个方法本质上来说就是函数function() , 跟范型函数的写法是一致的:

    // 范型函数
    const emitFunc = <T>(args: T): void => {
    }
    
    function emitFunc2<T>(args: T): void {
    }
    
    // defineProps
    function defineProps<TypeProps>(): Readonly<TypeProps>
    
    // defineEmits
    function defineEmits<TypeEmit>(): TypeEmit

四. defineExpose

在setup语法糖下,显式暴露组件的公共属性和方法。简单来说,就是允许父组件访问子组件的特定属性或方法

这个方法之前我用的不多,最近在项目中发现还真的挺好用的,挺香🤔!!!

举个例子: 项目中有很多表单弹窗的封装。怎么封装呢?

4.1 update:modelValue

这个方法就是通过传入一个 modelValue 的prop,在需要更新值的时候,通过触发 update:modelValue, 完成组件值的更新。

简单封装一个弹窗:

代码如下:

 // myDialog.vue
<template>
  <div class="mask" v-if="visiable">
    <div class="modal-content">
      <div class="dialog-header">{{ title }}</div>
      <div class="dialog-body">{{ content }}</div>
      <div class="dialog-footer">
        <el-button @click="onConfirm" type="primary">确定</el-button>
        <el-button @click="onCancel">取消</el-button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
const props = withDefaults(
    defineProps<{
        visiable: boolean,
        title: string,
        content: string
    }>(), {
        visiable: false,
        title: '我的标题',
        content: 'xxxxxxx'
})

const emit = defineEmits<{
    (e: 'update:visiable', val: boolean): void
}>()

const onConfirm = () => {
  emit('update:visiable', false)
}

const onCancel = () => {
  emit('update:visiable', false)
}
</script>

<style scoped lang="scss">
.mask {
      width: 100%;
      height: 100%;
      position: fixed;
      top: 0;
      left: 0;
      .modal-content {
          width: 400px;
          padding: 16px;
          margin: 200px auto 0;
          position: relative;
          background: #fff;
      }
      .dialog-header {
          padding-bottom: 16px;
          color: #303133;
          line-height: 24px;
      }
      .dialog-body {
          color: 606266;
          font-size: 14px;
      }
      .dialog-footer {
          margin-top: 18px;
          text-align: right;
      }
}
</style>

在父组件里面引入一下,然后调用一下:

<template>
    <myDialog v-model:visiable="visiable" />
    <button @click="clickMe">点我</button>
</template>

<script lang="ts" setup>
    import myDialog from '../src/myDialog/index.vue'
    import { ref } from 'vue'

    const visiable = ref(false)

    const clickMe = () => {
        visiable.value = true
    }
</script>

小美老师,如何在开发中更加优雅的使用Vue3.0在日常开发中,Vue3.0已经用的很常见了。但是在最近接手一个多人迭代的

没有什么问题,出来了,不要管样式,比较丑。

4.2 defineExpose

简单对上的组件进行修改一下:

    <template>
      <div class="mask" v-if="visiable">
        <div class="modal-content">
          <div class="dialog-header">{{ modalContent.title }}</div>
          <div class="dialog-body">{{ modalContent.content }}</div>
          <div class="dialog-footer">
            <el-button @click="onConfirm" type="primary">确定</el-button>
            <el-button @click="onCancel">取消</el-button>
          </div>
        </div>
      </div>
    </template>

<script setup lang="ts">
    import { ref } from 'vue'

    const visiable = ref(false)
    const modalContent = ref<{ title: string, content: string }>({title: '', content: ''})
    const init = (item: { title: string, content: string }) => {
      visiable.value = true
      modalContent.value = item ?? {}
    }

    const closeModal = () => visiable.value = false

    const onConfirm = () => {
      closeModal()
    }

    const onCancel = () => {
      closeModal()
    }

    defineExpose(
      {
        init
      }
    )
</script>

也比较容易理解,不再依靠外部传入的prop,通过内部定义的ref来控制显示,然后把控制显示的方法在这里是 init,通过defineExpose暴露出去,在父组件中调用。

在父组件中:

<template>
    <!-- <myDialog v-model:visiable="visiable" /> -->
    <defineDialog ref="defineDialogRef" />
    <button @click="clickMe">点我</button>
</template>

<script lang="ts" setup>
    import defineDialog from '../src/myDialog/defineDialog.vue'
    import { ref } from 'vue'

    const defineDialogRef = ref()

    const clickMe = () => {
        defineDialogRef.value.init({title: '新的标题', content: '这是个内容'})
    }
</script>

通过ref拿到子组件对象,然后调用init方法,同时可以传值。

可以试一下,没有什么问题。

两种方法没有什么好坏之分,就是一种写法上的不同。个人觉得第二种更好用一点,特别是只需要处理子组件上的值的时候。

五. 获取 DOM 节点

有个疑问,这么多年了,vue3.0项目中还会有人用getElementById等方法获取节点么?这不是兜兜转转还是拉了一裤子翔么?

除了一些特殊的情况外,都可以使用内置的templatereference(简称 ref)获取、操作dom元素。

如下的例子:

<template>
    <div ref="divRef" style="width: 200px; height: 400px;"></div>
    <input ref="inputRef" value="value的内容" />
</template>

<script lang="ts" setup>
import { ref, onMounted } from 'vue'

// 可以设置不同的元素类型
const divRef = ref<HTMLDivElement>()
const inputRef = ref<HTMLInputElement>()

onMounted(() => {
    console.log(divRef.value)
    // 为了严格的类型安全,这里最好通过可选链来限制。
    console.log(inputRef.value?.value)
})
</script>

ref如果用于普通 DOM 元素,引用将是元素本身;如果用于子组件,引用得到的值将是子组件的实例对象

六. pinia

合理的使用一些pinia的api可以很好的简化代码、优化代码。

6.1 基本使用

新建一个store.ts

import { defineStore } from "pinia"

type TStore = {
    counter: number
    name: string
  }
export const useStore = defineStore('myStore', {
    state: (): TStore => {
        return {
            counter: 1,
            name: '小美老师'
        }
    },
    getters: {
        addCount: (state) => {
            return state.counter++
        },
        addCount2(): number {
            return this.counter * 3;
        }
    },

    actions: {
        induce() {
            this.counter--
        }
    }
})

要注意两点:

  1. state是一个函数,所以类型推断的时候是需要按照函数的类型推断方式,推荐使用箭头函数更方便。
  2. 有两种getters的写法,但是个人更推荐通过常规函数用this访问整个store的实例。

6.2 state的获取

  1. 通过store实例访问 state,可以直接读取和写入,store.counter++。
  2. 通过 store.$reset() 方法可以将 state 重置为初始值。
  3. 通过 store.$patch() 方法可以完成state值的批量修改。
  4. 通过 store.$subscribe() 订阅 state 的变化,在 patches 修改之后订阅只会触发一次。

这个2和3其实真的很好用,但是我在项目中看到用的不多。

如上代码,我们在项目中引入一下:

   <template>
    <div>{{ name }}</div>
    <div> {{ counter }}</div>

    <button @click="patch">patch</button>

    <button @click="reset">reset</button>

</template>

<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useStore } from '../src/store'

const store = useStore()
const { name, counter } = storeToRefs(store)
const reset = () => {
  store.$reset(); // 重置成功
};

const patch = () => {
   // 直接修改state里面的值,不用在批次修改。
  store.$patch({
    counter: store.counter + 1,
    name: "啊,小美老师",
  });
}

store.$subscribe(
    () => {
        console.log("啊,小美老师发生了变化");
    },
    { detached: true }
);

</script>

这里的detached属性可能是用来指示这个监听器是否应该与组件的生命周期解绑。如果detachedtrue,那么即使组件被销毁,这个监听器也会继续监听状态变化。

这里面尤其是store.$subscribe我在项目中很少看到,都是在通过watch监听store里面值的改变。这个是可以推广使用的。

六. 总结

简单介绍了一下项目中个人比较常用的几个特性,需要的同学配合项目食用。多谢大家了!

对了,插句话,最近比较喜欢练字,有兴趣的码友们可以加我一下 小红薯758440437 ,一起打卡练字。

转载自:https://juejin.cn/post/7408037561237159947
评论
请登录