Vue3使用技巧
前言
使用Vue3写项目已经一年左右了,暂且总结出一些使用技巧,给大家铺路,大家如果有更好的使用技巧,还请在评论区不吝赐教👏
一、首当其冲:使用<script setup>
代替setup()
<script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script>
语法,它具有更多优势:
-
更少的样板内容,更简洁的代码。 -
能够使用纯 TypeScript 声明 props 和自定义事件。 -
更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。 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函数的参数,props是setup 函数的第一个参数;如果需要向父组件传出组件触发的自定义事件,则需要使用 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>
时,需要使用defineProps 和 defineEmits两个API声明props和emit,声明的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()这一个功能的话,那就大可不必了,在功能特别多的一个组件里,这一定是一个灾难。

举个🌰:
<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. 声明一个组合式函数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>
三、借助带有 get 和 set 函数的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 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 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})
文章出自:https://juejin.cn/post/7158380747861000223
作者:我就是胖虎
原文始发于微信公众号(前端24):Vue3使用技巧
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/216497.html