• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

前端Vue3的常用的使用技巧,给大家铺路

武飞扬头像
juejin
帮助278

前言

使用Vue3写项目已经一年左右了,暂且总结出一些使用技巧,给大家铺路.

一、首当其冲:使用<script setup> 代替setup()

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:

  1. 更少的样板内容,更简洁的代码。
  2. 能够使用纯 TypeScript 声明 props 和自定义事件。
  3. 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。 4.更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。

以上是官方文档给出的优点,但在我们平时开发过程中,真切的会影响到我们的使用体验,有如下弊端👇👇👇:

✨弊端1: 每增加一个变量、一个方法,都需要在return里进行返回,否则在<template>里就用不了

<script lang="ts">
  export default {
    name: 'MyComponent',
    setup() {
      // 当前组件公共变量
      const state = reactive({
        loading: false,
        //...
      })
      
      // 用户表单模块
      const userForm = ref({})
      const formValidate = () => {
        //...
      }
      const addUser = () => {
        //...
      }
      const updateUser = () => {
        //...
      }
      
      return {
        ...toRefs(state),
        userForm,
        formValidate,
        addUser,
        updateUser,
      }
    }
  }
</script>

使用<script setup>时,声明变量之后,可以直接使用,如下:

<template>
  <div>
    <button @click="addUser">添加</button>
  </div>
</template>
<script lang="ts" setup>
  const state = reactive({
    loading: false,
    //...
  })
  
  // 用户表单模块
  const userForm = ref({})
  const formValidate = () => {
    //...
  }
  const addUser = () => {
    //...
  }
  const updateUser = () => {
    //...
  }
</script>

✨弊端2: 组件已经import进来,但是还需要在 components里进行注册

<template>
  <child></child>
</template>
<script lang="ts">
  import Child from './child.vue'
  export default {
    name: 'MyComponent',
    // 注册组件
    components: {
      Child
    }
  }
</script>

使用<script setup>可以直接在tempalte中使用,无需注册,如下:

<script setup>
import MyComponent from './MyComponent.vue'
</script>

<template>
  <MyComponent />
</template>

✨弊端3: props/emits的声明和使用方式不够简便

不使用<script setup>的时候,props的声明方法和vue2.x一毛一样,但是如果需要使用props里变量,则需要使用setup函数的参数,propssetup 函数的第一个参数;如果需要向父组件传出组件触发的自定义事件,则需要使用 emits声明,触发时,需要使用setup的上下文触发对应的事件,使用方式如下:

<script lang="ts">
  export default {
    // props: {
    //   modelValue: {
    //     type: [String, Array],
    //     require: true,
    //     default: ''
    //   },
    //   placeholder: {
    //     type: String,
    //     require: true,
    //     default: '请选择'
    //   }
    // },
    props: [
      'modelValue',
      'options',
      'placeholder',
      'labelText',
      'valueText',
      'filterable',
      'clearable',
      'loading'
    ],
    emits: ['update:modelValue', 'change'],
    setup(props, { emit }) {
      const currentValue = ref(props.modelValue)

      watch(currentValue, (data) => {
        emit('update:modelValue', data)
      })
      watch(
        () => props.modelValue,
        (data) => {
          currentValue.value = data
        }
      )
      const handleChange = (val) => {
        emit('change', val)
      }
      return {
        currentValue,
        handleChange
      }
    }
  }
</script>

但是使用单文件<script setup>时,需要使用definePropsdefineEmits两个API声明propsemit,声明的props可以在<template>中直接使用,

<template>
  <div>{{msg}}</div>
</template>
<script lang="ts" setup>
export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})
const emit = defineEmits(['change'])

const handleChange = (val) => {
  emit('change', val)
}
</script>

✨弊端4: 不支持多个script,

虽然这种情况并不是非常必要,但是<script setup>可以与普通的 <script> 一起使用,我通常的使用方法是,使用普通的 <script>来声明组件名称,如下:

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

<script lang="ts">
  export default {
    name: 'MyComponent'
  }
</script>
<script lang="ts" setup>
  type Props = {
    modelValue: string
  }
  const props = defineProps<Props>()
  const emit = defineEmits(['update:modelValue'])
  
</script>

二、使用组合式函数,类似hook

随着Vue3版本的发布,Composition API(组合API)作为Vue3中最重要的一个功能,与之前的2.x版本的Options API(选项API)的对比图随处可见,相信大家并不陌生,vue3的组合式API可以使我们的代码按功能拆分的更聚集,而不是像vue2.x版本需要在data、computed、methods之间反复横跳,但是如果你只是使用setup()这一个功能的话,那就大可不必了,在功能特别多的一个组件里,这一定是一个灾难。

image (11).png 举个🌰:

<script lang="ts">
  import Child from './child.vue'
  import { useRouter } from 'vue-router'
  export default {
    name: 'MyComponent',
    components: {
      Child
    },
    emits: ['update:modelValue'],
    setup() {
      const router = useRouter()
      // 当前组件公共变量
      const state = reactive({
        loading: false,
        //...
      })
      
      // 子组件
      const childRef = ref()
      const childReload = ()=>{
        childRef.value.reload()
      }
      
      // 用户表单模块
      const userForm = ref({})
      watch(
        userForm,
        (val) => {
          emit('update:modelValue', val)
        }
      )
      const formValidate = () => {
        //...
      }
      const addUser = () => {
        //...
      }
      const getUser = () => {
        //...
      }
      const updateUser = () => {
        //...
      }
      
      //用户列表
      const userList = ref([])
      const getUserList = () => {
        //...
      }
      
      //...其他功能
  
      
      onMounted(()=>{
        getUserList()
      })
      
      return {
        ...toRefs(state),
        childRef,
        childReload,
        userForm,
        formValidate,
        addUser,
        updateUser,
        **getUser**,
        userList,
        getUserList,
      }
    }
  }
</script>

如上,使用setup()的弊端显而易见,每增加一个变量、一个方法,都需要先找到对应的“方法块”,虽然我们根据每一个功能都进行了整合,但是仍然不能一眼就看到某个变量在哪声明的会在哪个方法中用到。

✨✨试想:如果这个组件要处理的逻辑特别多,变量特别多,尽管右侧的代码和逻辑已经足够分明了,但是你作为一个刚接手这个项目的新人,看到这么一大推代码,一定也会头疼吧,再有甚者,有的开发者变量和方法的声明随意乱放,各个函数直接反复使用,你确定这不是一坨屎?

还好Vue3为我们提供了更优的解决方案,那就是使用组合式函数,用这些函数只做一件事,你只需要在组件内使用这些函数就行。

什么是“组合式函数”? 在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。 当构建前端应用时,我们常常需要复用公共任务的逻辑。例如为了在不同地方格式化时间,我们可能会抽取一个可复用的日期格式化函数。这个函数封装了无状态的逻辑:它在接收一些输入后立刻返回所期望的输出。复用无状态逻辑的库有很多,比如你可能已经用过的 lodash 或是 date-fns。 相比之下,有状态逻辑负责管理会随时间而变化的状态。

以上是官网给出的解释,总而言之,组合式函数就是一个函数,这个函数封装了有状态的逻辑,函数自身只处理跟自己相关的事情。

上面的例子,可以通过这种方式进行整合,

  1. 先分模块,把所有的变量和方法整合的足够内聚,与外部的变量足够解耦。
  2. 封装各自的函数
  3. 哪里使用就哪里搬

经过这个方法,我对上面的例子进行整合,就会变成如下:

1. 声明一个组合式函数useUser并提取到外部文件中,用于处理user 相关的逻辑,

//useUser.ts

export default function () {

  const userForm = ref({})

  const isEdit = computed<boolean>(() => !!$route.params.id)
  const userId = computed<string>(() => $route.params.id)

  const updateUser = (): void => {
    // fetch...
  }
  const addUser = (): void => {
    // fetch...
  }
  const getUser = (id: string): void => {
    // fetch...
  }

  onMounted(() => {
    if (isEdit.value) {
      getUser(userId.value)
    }
  })
  return { isEdit, userForm, updateUser, addUser }
}

2. 在组件中使用:

<template>
  <div>
    <button @click="save"></button>
  </div>
</template>

<script lang="ts">
  export default {
    name: 'MyComponent'
  }
</script>
<script lang="ts" setup>
import useUser from './hooks/useUser'
const { isEdit, userForm, updateUser, addUser } = useUser()

const save = ()=>{
  isEdit ? updateUser() : addUser()
}
</script>

三、借助带有 getset 函数的computed()

在平时开发的过程中,父子组件通信的场景很多,你是否经常用这种方式处理组件间的通信,如下:

//父组件

<template>
  <childForm v-model="childVal"></childForm>
  
  <button @click="setChild">给child赋值</button>
  <button @click="getChild">给child赋值</button>
</template>
<script lang="ts" setup>
import ChildForm from './child-form.vue'

const childVal = ref('')
const getChild = ()=>{
  console.log('childVal:',childVal.value)
}
const setChild = ()=>{
  childVal.value = 'hello'
}
</script>
// 子组件
<template>
  <input v-model="currentVal"/>
</template>

<script lang="ts">
  export default {
    name: 'Child'
  }
</script>
<script lang="ts" setup>
  type Props = {
    modelValue: string
  }
  const props = defineProps<Props>()
  const emit = defineEmits(['update:modelValue'])

  // 默认接收来自父组件的值
  const currentVal = ref(props.modelValue)
  
  // 监听来自父组件的modelValue,用于给子组件赋值
  watch(
    () => props.modelValue,
    (val) => {
      currentVal.value = val
    }
  )
  // 监听子组件值的变化,用于及时响应给父组件
  watch(
    currentVal,
    (val) => {
      emit('update:modelValue', val)
    }
  )
</script>

如上,每次父子组件直接的通信,都需要写两个 watch,同时还需要把父组件传来的值默认赋给currentVal

computed()接受一个 getter 函数,返回一个只读的响应式ref对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。

借助 computed()的这一属性,我们可以修改子组件的通信方式为如下:

// 子组件
<template>
  <input v-model="currentVal"/>
</template>

<script lang="ts">
  export default {
    name: 'Child'
  }
</script>
<script lang="ts" setup>
  type Props = {
    modelValue: string
  }
  const props = defineProps<Props>()
  const emit = defineEmits(['update:modelValue'])

  const currentVal = computed({
    get: () => props.modelValue,
    set: (val) => {
      emit('update:modelValue', val)
    }
  })
</script>

四、使用 immediate watcher

watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。而watch的第三个参数设置为{immedidate:true},代表在侦听器创建时立即触发回调。第一次调用时旧值是 undefined

可以替代如下场景:

const inputVal = ref('')

watch(inputVal,()=>{
  fetchUserList()
})

onMounted(()=>{
  fetchUserList()
})

使用 immediate watcher:

const inputVal = ref('')

watch(inputVal,()=>{
  fetchUserList()
},{immedidate:true})

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/taneghk
系列文章
更多 icon
同类精品
更多 icon
继续加载