Vue逻辑复用

文章介绍了Vue的组合式API如何用于封装和复用有状态逻辑,包括无状态和有状态逻辑的示例,以及如何通过提取组合式函数改善代码结构。此外,还讨论了异步状态管理、自定义指令的创建和使用,以及插件的编写和功能注入。
摘要由CSDN通过智能技术生成

逻辑复用

组合式函数

概念

利用Vue的组合式API封装和复用有状态逻辑的函数

无状态逻辑:在接受输入后可以立刻返回期望的输出

有状态逻辑:管理会随时间而变化的状态,如跟踪当前鼠标在页面中的位置

鼠标跟踪器示例

<!-- 直接在组件中使用组合式 API 实现鼠标跟踪功能-->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const x = ref(0)
const y = ref(0)

function update(event) {
  x.value = event.pageX
  y.value = event.pageY
}

onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

当想要在多个组件中使用它时,可以把这个逻辑以函数形式提取到外部文件mouse.js:

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'

// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return { x, y }
}
//在组件中使用方式
<script setup>
	import {useMouse } from '.mouse.js'
    
    const { x, y } = useMouse()
</script>
<template>
	Mouse position is at: {{x}}, {{y}}
</template>

可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数。

例:将添加和清除DOM事件监听器的逻辑也封装进一个组合式函数中

//event.js
import { onMounted, onUnmounted } from 'vue'

export function useEventListener(target, event, callback){
    // 如果你想的话,
  // 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素(可惜我不会!>.<)
    onMounted(()=> target.addEventListener(event, callback))
    onUnmounted(()=> target.removeEventListener(event, callback))
}

//这是分割线^3^------------------------------------

//之前的useMouse()函数可简化为:
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
import {useEventListener } from '.event.js'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  useEventListener(window, 'mousemove', (event) => {
      x.value = event.pageX
      y.value = event.pageY
  })

  // 通过返回值暴露所管理的状态
  return { x, y }
}

异步状态示例

需要接受参数的组合式函数案例:做异步数据请求时,需要处理不同状态:加载中,加载成功,加载失败

<script setup>
import { ref } from 'vue'

const data = ref(null)
const error = ref(null)

fetch('...')
  .then((res) => res.json())
  .then((json) => (data.value = json))
  .catch((err) => (error.value = err))
</script>

<template>
  <div v-if="error">Oops! Error encountered: {{ error.message }}</div>
  <div v-else-if="data">
    Data loaded:
    <pre>{{ data }}</pre>
  </div>
  <div v-else>Loading...</div>
</template>

当有多个组件需要获取数据时,这样很繁琐,把它抽取成组合式函数:

//fecth.js
import { ref } from 'vue'

export function useFecth(url){
    const data = ref(null)
    const error = ref(null)
    
    fecth(url)
    	.then((res) => res.json())
    	.then((json) => (data.value = json))
    	.catch((err) => (error.value = err))
	return { data, error }
}

在组件中:

<script set up>
import { useFetch } from './fetch.js'
const {data, error} = useFetch('...')
</script>

此时fetch接收静态url,只执行一次请求就完成,如果想要在每次url变化时都能重新请求,可让它同时允许接受ref作为参数:

// fetch.js
import { ref, isRef, unref, watchEffect } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  function doFetch() {
    // 在请求之前重设状态...
    data.value = null
    error.value = null
    // unref() 解包可能为 ref 的值
    fetch(unref(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  }

  if (isRef(url)) {
    // 若输入的 URL 是一个 ref,那么启动一个响应式的请求
    watchEffect(doFetch) //立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行
  } else {
    // 否则只请求一次
    // 避免监听器的额外开销
    doFetch()
  }

  return { data, error }
}

约定和最佳实践

命名

驼峰命名法,“use”作为开头

输入参数

最好在输入参数时兼容ref,使用unref()工具函数:

import { unref } from 'vue'

function useFeature(maybeRef) {
  // 若 maybeRef 确实是一个 ref,它的 .value 会被返回
  // 否则,maybeRef 会被原样返回
  const value = unref(maybeRef)
}

如果接受ref做参数,产生响应式effect,确保使用watch()显示监听ref,或在watchEffect()中调用unref()进行正确追踪

返回值

在组合式函数中使用ref而不是reactive,在对象被解构为ref后仍具有响应性。

副作用

例如,添加DOM监听事件或请求数据

使用限制

组合式函数应当被同步调用(我的理解:import一个组合式函数后必须要调用)

目的:让Vue确定当前正在被执行的是哪个实例,从而:1.将生命周期钩子注册到该实例;2.将计算属性和监听器注册到该组件实例上,以便在卸载时停止监听,避免内存泄漏。

通过抽取组合式函数改善代码结构

组合式API有足够的灵活性,基于逻辑问题将代码拆分成更小的函数:

<script setup>
import { useFeatureA } from './featureA.js'
import { useFeatureB } from './featureB.js'
import { useFeatureC } from './featureC.js'

const { foo, bar } = useFeatureA()
const { baz } = useFeatureB(foo)
const { qux } = useFeatureC(baz)
</script>

在选项式API中使用组合式函数

组合函数必须在setup()中调用,返回的绑定必须在setup()中返回, 以便暴露给 this 及其模板

import { useMouse } from './mouse.js'
import { useFetch } from './fetch.js'

export default {
  setup() {
    const { x, y } = useMouse()
    const { data, error } = useFetch('...')
    return { x, y, data, error }
  },
  mounted() {
    // setup() 暴露的属性可以在通过 `this` 访问到
    console.log(this.x)
  }
  // ...其他选项
}

与其他模式比较(略略略)

自定义指令

介绍

回顾:两种在vue中重用代码的方式:

组件:主要的构建模块

组合式函数:侧重于有状态的逻辑

自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑

<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>

在中,任何以v开头的驼峰式命名变量都可被用作一个自定义指令;在没有的情况下,自定义指令需通过directives选项注册:

export default {
  setup() {
    /*...*/
  },
  directives: {
    // 在模板中启用 v-focus
    focus: {
      /* ... */
    }
  }
}
/*将自定义指令全局注册到应用层级*/
const app = createApp({})

//使v-focus 在所有组件可用
app.directive('focus',{
  /* ... */
})

指令钩子

//一个指令的定义对象可以提供几种钩子函数 (都是可选的)

const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}
钩子参数
  • el: 指令绑定到的元素。这可以用于直接操作 DOM
  • binding:一个对象,包含以下属性
  • value:传递给指令的值 , 例如在 v-my-directive="1 + 1" 中,值是 2
  • oldValue:之前的值,仅在 beforeUpdateupdated 中可用
  • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
  • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
  • instance:使用该指令的组件实例
  • dir:指令的定义对象
  • vnode:代表绑定元素的底层 VNode
  • prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdateupdated 钩子中可用

除了 el 外,其他参数都是只读的

简化形式

对于自定义指令来说 ,有时 仅仅需要在 mountedupdated 上实现相同的行为,除此之外并不需要其他钩子 ,则 可以直接用一个函数来定义指令 :

<div v-color="color"></div>
app.directive('color', (el, binding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value
})

对象字面量

如果指令需要多个值,可以向它传递一个 JavaScript 对象字面量

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})

在组件上使用

<MyComponent v-demo="test" />
<!-- MyComponent 的模板 -->

<!-- 这是分割线--------------------------- -->

<!-- MyComponent 的模板 -->
<div> <!-- v-demo 指令会被应用在此处 -->
  <span>My component content</span>
</div>

组件可能含有多个根节点。当应用到一个多根组件时,指令将会被忽略且抛出一个警告。和 attribute 不同,指令不能通过 v-bind="$attrs" 来传递给一个不同的元素。总的来说,推荐在组件上使用自定义指令

插件

介绍

能为Vue添加全局功能的工具代码,安装示例:

import { createApp } from 'vue'

const app = createApp({})

app.use(myPlugin, {
  /* 可选的选项 */
})

一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身 , 安装函数会接收到安装它的应用实例和传递给 app.use() 的额外选项作为参数

const maPlugin = {
    install(app, options){
        //配置此应用
    }
}

插件发挥作用的常见场景 :

  1. 通过 app.component()app.directive() 注册一到多个全局组件或自定义指令。
  2. 通过 app.provide() 使一个资源可被注入进整个应用。
  3. app.config.globalProperties 中添加一些全局实例属性或方法
  4. 一个可能上述三种都包含了的功能库 (例如 vue-router)

编写一个插件

//从设置插件对象开始
// plugin/i18n.js
export default{
    install:(app, options) =>{
        //编写插件代码
  }
}

希望有一个翻译函数,这个函数接收一个以 . 作为分隔符的 key 字符串,用来在用户提供的翻译字典中查找对应语言的文本。期望的使用方式 :

<h1>{{ $translate('greetings.hello') }}</h1>

这个函数应当能够在任意模板中被全局调用。这一点可以通过在插件中将它添加到 app.config.globalProperties 上来实现 :

// plugin/i18n.js
export default{
    install: (app, options) =>{
        // 注入一个全局可用的 $translate() 方法
        app.config.globalProperties.$translate = (key) => {
            // 获取 `options` 对象的深层属性
            // 使用 `key` 作为索引
            return key.split('.').reduce((o, i) => {
                if(o) return o[i]
            }, options)
        }
  }
}

用于查找的翻译字典对象则应当在插件被安装时作为 app.use() 的额外参数被传入:

import i18nPlugin from './plugins/i18n'

app.use(i18nPlugin, {
  greetings: {
    hello: 'Bonjour!'
  }
})

这样,一开始的表达式 $translate('greetings.hello') 就会在运行时被替换为 Bonjour! 了 !

插件中的Provide / Inject

可以将插件接收到的 options 参数提供给整个应用,让任何组件都能使用这个翻译字典对象

// plugins/i18n.js
export default {
  install: (app, options) => {
    app.config.globalProperties.$translate = (key) => {
      return key.split('.').reduce((o, i) => {
        if (o) return o[i]
      }, options)
    }

    app.provide('i18n', options)
  }
}

插件用户就可以在他们的组件中以 i18n 为 key 注入并访问插件的选项对象了

<script setup>
import { inject } from 'vue'

const i18n = inject('i18n')

console.log(i18n.greetings.hello)
</script>

ect

可以将插件接收到的 options 参数提供给整个应用,让任何组件都能使用这个翻译字典对象

// plugins/i18n.js
export default {
  install: (app, options) => {
    app.config.globalProperties.$translate = (key) => {
      return key.split('.').reduce((o, i) => {
        if (o) return o[i]
      }, options)
    }

    app.provide('i18n', options)
  }
}

插件用户就可以在他们的组件中以 i18n 为 key 注入并访问插件的选项对象了

<script setup>
import { inject } from 'vue'

const i18n = inject('i18n')

console.log(i18n.greetings.hello)
</script>

总结

今天进一步深入vue学习,了解了通过组合式函数,插件,自定义指令(好像不常用)进一步改善代码,但对于vue逻辑还不是很熟悉,在后续学习中要通过实战操作巩固,学习怎样对网站美化,路由的原理(不知道会不会用到计算机网络),还要能接入后端接口。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值