多组件联动与高阶组件解决方案–list联动search和navigationBar

csdn推荐

目录

01: 前言

到目前为止,我们已经实现了首页的 search、navigationBar、list 模块。只不过目前三个模块完全是独立的,没有任何的关联性。

接下来要处理的就是让它们三个可以联动起来。也就是标题所说的:list 联动 search 和 navigationBar。

在这样的联动之中,我们应该注意哪些事情?联动的数据又应该如何进行处理?如何做可以让我们的逻辑更加清晰?高阶组件指的又是什么?如何创建和使用高阶组件?

02: 多组件联动注意事项与整体逻辑分析

在我们的实际开发中,经常会遇到多个组件之间互相进行联动的场景。这样的场景我们应该怎么进行处理呢?

所谓的多组件联动,其实更准确一点来说,是指:多个组件之间,存在一个或者多个共享的数据。当数据发生改变时,执行对应的逻辑。

把这句话拆开来看,就是两部分:

1. 多组件之间需要共享数据。

2. 监听数据变化,并执行对应逻辑。

多组件之间需要共享数据

多组件之间共享数据,通常有三种方式:

1. 组件之间的数据传递 -- 常见于层级关系比较清晰的多组件之中。

1. 父传子。

2. 子传父。

3. ……

2. 依赖注入:Provide / Inject -- 嵌套层级比较深,并且子组件只需要父组件的部分内容。

3. 全局状态管理工具:vuex -- 以上两种情况都不适用的情况下。

针对于我们这里的场景,层级关系比较复杂,并且需要进行复杂的逻辑操作。此时,我们在多组件之间共享数据的策略就需要通过 vuex 来实现。

监听数据变化的方式

当组件之间共享的数据发生变化时,我们需要执行对应的逻辑操作。首先我们就需要监听到数据的变化。

在 vue 中监听数据变化的方式,首推就是 watch。

在刚才我们已经确定了共享的数据需要保存到 vuex 中,所以我们就需要通过 watch 监听到 vuex 中共享数据的变化。在监听到变化时,执行对应的业务逻辑。

整体逻辑分析

依据我们以上所说的内容,整体的实现逻辑应该为:

1. 创建共享数据对应的 vuex modules 模块。

2. 在 getters 中建立对应的快捷访问计算属性。

3. 在对应的业务组件中,监听 getters,并执行对应逻辑。

03: 简单联动处理:navigationBar 对应 list

关键点:共享数据发生变化 引起 逻辑数据发生变化。

- src/store/modules
- - app.js

// src/store/modules/app.js
import { ALL_CATEGORY_ITEM } from '@/constants'
export default {
  namespaced: true,
  state: () => ({
    // 当前选中的分类
    currentCategory: ALL_CATEGORY_ITEM,
  }),
  mutations: {
    /**
     * 切换选中分类
     */
    changeCurrentCategory(state, newCategory) {
      state.currentCategory = newCategory
    },
  }
}

// src/store/getters.js
export default {
  ……
  /**
   * category选中项
   */
  currentCategory: (state) => state.app.currentCategory,
  /**
   * category选中项下标
   */
  currentCategoryIndex: (state, getters) => {
    return getters.categorys.findIndex(
      (item) => item.id === getters.currentCategory.id
    )
  },
  ……
}

// src/views/main/components/list/index.vue

    /**
     * 构建数据请求
     */
    let query = {
      page: 1,
      size: 20,
      categoryId: '',
      searchText: ''
    }
    /**
     * 通过此方法修改 query 请求参数,重新发起请求
     */
    const resetQuery = (newQuery) => {
      query = { ...query, ...newQuery }
      // 重置状态
      isFinished.value = false
      pexelsList.value = []
      // 数据为空,“加载”icon出现在屏幕中,会触发onLoad事件。秒呀!!!
    }
    /**
     * 监听 currentCategory 的变化
     */
    watch(
      () => store.getters.currentCategory,
      (currentCategory) => {
        // 重置请求参数
        resetQuery({
          page: 1,
          categoryId: currentCategory.id
        })
      }
    )

04: 明确 searchBar 对应 list 处理流程

对于 searchBar 区域,我们目前还缺少三部分内容要处理:

1. 搜索提示

2. 搜索历史

3. 推荐主题

需要先把 searchBar 区域的内容开发完成,然后再处理对应的联动。

05: searchBar:搜索提示初步实现

- src/views/components/header/header-search
- - hint.vue


  
const EMITS_ITEM_CLICK = 'itemClick' import { getHint } from '@/api/pexels' import { ref, watch } from 'vue' import { watchDebounced } from '@vueuse/core' /** * 接收搜索数据 */ const props = defineProps({ searchText: { type: String, required: true } }) /** * item 被点击触发事件 */ const emits = defineEmits([EMITS_ITEM_CLICK]) /** * 处理搜索提示数据获取 */ const hintData = ref([]) const getHintData = async () => { if (!props.searchText) return const { result } = await getHint(props.searchText) hintData.value = result } /** * 监听搜索文本的变化,并获取对应提示数据 */ watchDebounced(() => props.searchText, getHintData, { immediate: true, // 每次事件触发时,延迟的时间 debounce: 500 }) /** * 处理关键字高亮 */ const highlightText = (text) => { // 生成高亮标签 const highlightStr = `${props.searchText}` // 构建正则表达式,从《显示文本中》找出与《用户输入文本相同的内容》, // 使用《高亮标签》进行替换。 const reg = new RegExp(props.searchText, 'gi') // 替换 return text.replace(reg, highlightStr) } /** * item 点击事件处理 */ const onItemClick = (item) => { emits(EMITS_ITEM_CLICK, item) }

// 使用

06: searchBar:处理防抖功能

所谓防抖指的是:当触发一个事件时,不去立刻执行。而是延迟一段时间,该事件变为等待执行事件。如果在这段时间之内,该事件被再次触发,则上次等待执行的事件取消。本次触发的事件变为等待执行事件。循环往复,直到某一个等待事件被执行为止。

英文:debounce。

vueuse 中提供了watchDebounced,可以使用这个 API 实现防抖的 watch。

07: searchBar:提示关键字高亮处理

核心逻辑:

正则替换,把原先的正常文本 替换成 带有 html 标签的文本。最后通过 v-html 进行富文本渲染

08: searchBar:搜索历史处理

- src/store/modules
- - search.js

export default {
  namespaced: true,
  state: () => ({
    historys: []
  }),
  mutations: {
    /**
     * 1. 新增的历史记录位于头部
     * 2. 不可出现重复的记录
     */
    addHistory(state, newHistory) {
      const isFindIndex = state.historys.findIndex(
        (item) => item === newHistory
      )
      // 剔除旧数据
      if (isFindIndex !== -1) {
        state.historys.splice(isFindIndex, 1)
      }
      // 新增记录
      state.historys.unshift(newHistory)
    },
    /**
     * 删除指定数据
     */
    deleteHistory(state, index) {
      state.historys.splice(index, 1)
    },
    /**
     * 删除所有历史记录
     */
    deleteAllHistory(state) {
      state.historys = []
    }
  }
}

// 有了 modules 之后,注意在 index.js 中注册、缓存,并声明 getters。
// 代码省略。

- src/views/layout/components/header/header-search
- - history.vue


  
最近搜索
{{ item }}
const EMITS_ITEM_CLICK = 'itemClick' import { useStore } from 'vuex' import { confirm } from '@/libs' const emits = defineEmits([EMITS_ITEM_CLICK]) const store = useStore() /** * 删除所有记录 */ const onDeleteAllClick = () => { confirm('要删除所有历史记录吗?').then(() => { store.commit('search/deleteAllHistory') }) } /** * 删除单个记录 */ const onDeleteClick = (index) => { store.commit('search/deleteHistory', index) } /** * item 点击触发事件 */ const onItemClick = (item) => { emits(EMITS_ITEM_CLICK, item) }

09: 通用组件:confirm 应用场景

目前当我们点击 删除全部 历史记录时,会直接删除。这样的体验并不好。我们期望的是能够给用户一个 提示,也就是 confirm。

期望:构建一个 confirm 组件。

对于 confirm 这类组件而言,我们不希望它通过标签的形式进行使用。而是期望可以像 element-plus 中的 confirm 一样,可以直接通过方法的形式进行调用,这样就太爽了。

10: 通用组件:vnode+h函数+render函数 明确confirm构建思路

想要搞明白这一点,我们就需要了解一些比较冷僻的知识点,那就是 渲染函数。在渲染函数中,我们需要了解如下概念:

1. 虚拟 dom:通过 js 来描述 dom。

2. vnode 虚拟节点:告诉 vue 页面上需要渲染什么样子的节点。

3. h 函数:用来创建 vnode 的函数,接收三个参数(要渲染的 dom、attrs 对象、子元素)。

4. render 函数:可以根据 vnode 来渲染 dom。

根据以上所说我们知道:通过 h 函数可以生成一个 vnode,该 vnode 可以通过 render 函数被渲染。

据此我们就可以得出 confirm 组件的实现思路:

1. 创建一个 confirm 组件。

2. 创建一个 confirm.js 模块,在该模块中返回一个 promise。

3. 同时利用 h 函数生成 confirm.vue 的 vnode。

4. 最后利用 render 函数,渲染 vnode 到 body 中。

依据此思路,即可实现对应的 confirm 渲染。

11: 通用组件:构建 confirm 组件

- src/libs
- - confirm
- - - index.vue
- - - index.js


  
{{ title }}
{{ content }}
{{ cancelText }} {{ confirmText }}
// confirm 组件会以方法形式调用,自动全局注册的组件无法在其中使用。 // 在方法调用的组件中,需要主动导入组件。 import mButton from '../button/index.vue' import { ref, onMounted } from 'vue' const props = defineProps({ // 标题 title: { type: String }, // 描述 content: { type: String, required: true }, // 取消按钮文本 cancelText: { type: String, default: '取消' }, // 确定按钮文本 confirmText: { type: String, default: '确定' }, // 取消按钮事件 cancelHandler: { type: Function }, // 确定按钮事件 confirmHandler: { type: Function }, // 关闭 confirm 的回调 close: { type: Function } }) // 控制显示处理 const isVisable = ref(false) /** * confirm 展示 */ const show = () => { isVisable.value = true } /** * render 函数的渲染,会直接进行,无动画效果。 * 页面构建完成之后,再执行动画。保留动画效果。 */ onMounted(() => { show() }) // 关闭动画执行时间。0.5s 和css transition 写法保持一致。 const duration = '0.5s' /** * confirm 关闭,保留动画执行时长 */ const close = () => { isVisable.value = false setTimeout(() => { if (props.close) { props.close() } }, parseInt(duration.replace('0.', '').replace('s', '')) * 100) } /** * 取消按钮点击事件 */ const onCancelClick = () => { if (props.cancelHandler) { props.cancelHandler() } close() } /** * 确定按钮点击事件 */ const onConfirmClick = () => { if (props.confirmHandler) { props.confirmHandler() } close() } .fade-enter-active, .fade-leave-active { transition: all v-bind(duration); } .fade-enter-from, .fade-leave-to { opacity: 0; } .up-enter-active, .up-leave-active { transition: all v-bind(duration); } .up-enter-from, .up-leave-to { opacity: 0; transform: translate3d(-50%, 100px, 0); }

注意:

使用概念绑定响应式数据到 css 中。

confirm 组件会以方法形式调用,自动全局注册的组件无法在其中使用。在方法调用的组件中,需要主动导入组件。 例如:confirm 组件要主动导入 mButton 组件,mButton 也要主动导入m-svg-icon。否则会报警。

12. 通用组件:函数调用 confirm组件

// src/libs/confirm/index.js
import { h, render } from 'vue'
import confirmComponent from './index.vue'
/**
 *
 * @param {*} title 标题
 * @param {*} content 文本
 * @param {*} cancelText 取消按钮文本
 * @param {*} confirmText 确定按钮文本
 * @returns
 */
export const confirm = (
  title,
  content,
  cancelText = '取消',
  confirmText = '确定'
) => {
  return new Promise((resolve, reject) => {
    // 允许只传递 content
    if (title && !content) {
      content = title
      title = ''
    }
    // 关闭弹层事件
    const close = () => {
      render(null, document.body)
    }
    // 取消按钮事件
    const cancelHandler = () => {
      reject(new Error('取消按钮点击'))
    }
    // 确定按钮事件
    const confirmHandler = () => {
      resolve()
    }
    // 1. vnode
    const vnode = h(confirmComponent, {
      title,
      content,
      cancelText,
      confirmText,
      confirmHandler,
      cancelHandler,
      close
    })
    // 2. render
    render(vnode, document.body)
  })
}

// src/libs/index.js
export { confirm } from './confirm'

使用:

import { confirm } from '@/libs'
confirm('要删除所有历史记录吗?').then(() => {
  store.commit('search/deleteAllHistory')
})

13: searchBar:热门精选模块构建

- src/views/layout/components/header/header-search
- - theme.vue


  
热门精选

# {{ themeData.big.title }}

# {{ item.title }}

import { ref } from 'vue' import { getThemes } from '@/api/pexels' import { randomRGB } from '@/utils/color' // 处理主题数据 const themeData = ref({ big: {}, list: [] }) const getThemeData = async () => { const { themes } = await getThemes() themeData.value = { big: themes[0], list: themes.splice(1, themes.length) } } getThemeData()

CSS知识点:

1.CSS object-fit 属性 | 菜鸟教程

2.CSS backdrop-filter | 菜鸟教程

14. searchBar 联动 list

// src/store/modules/app.js
export default {
  namespaced: true,
  state: () => ({
    // 搜索的文本
    searchText: '',
  }),
  mutations: {
    /**
     * 修改 searchText
     */
    changeSearchText(state, newSearchText) {
      state.searchText = newSearchText
    },
  }
}

// 创建 getters。
// 在 index modules 中注册。

// src/views/layout/components/headr/header-search/index.vue
// 触发 searchText 变化
store.commit('app/changeSearchText', val)

// src/views/main/components/list/index.vue
/**
 * 监听搜索内容项的变化
 */
watch(
  () => store.getters.searchText,
  (val) => {
    // 重置请求参数
    resetQuery({
      page: 1,
      searchText: val
    })
  }
)

15. 总结

本篇文章核心内容包含两部分:

1. 多组件联动逻辑

2. confirm 通用组件

1. vnode

2. h 函数

3. render 函数

文章来源:https://blog.csdn.net/lambert00001/article/details/139042701



微信扫描下方的二维码阅读本文

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容