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
微信扫描下方的二维码阅读本文
暂无评论内容