疑惑类
1. 在 promise 里面不需要async 但是什么时候需要async await 呢??
Promise 本身就是一个异步对象,async await 也是为了解决异步问题,
- 函数不需要 async/await,因为它只是把 Promise 返回出去,调用方可以自己用 .then() 或 await。
- 只有在你需要在函数内部处理异步结果时,才需要用 async/await。
"在函数内部处理异步结果" 理解:比如你用 axios、fetch、hyRequest 这些方法去请求后端接口,拿到的数据不是立刻返回的,而是异步返回的,通常是一个 Promise。
易错点
2. 关于scroll滚动时,是依据 window
滚动还是 ele
元素
1. 经验类:
1.1 关于flex布局可以产生的等价的效果
目前样式:

要求样式:

解决方式1. 采用displat: flex; + width: 50%; + 换行

解决方式2. 采用 flex: 1; + justify-content: flex-end;

完整的:

详细解释: 逐个分析这些属性:
1. display: flex
-
将元素设置为弹性布局(Flexbox)容器
-
这使得其子元素可以灵活地排列和对齐
-
在这个评论组件中,它用于 .right 类,用来排列评分项
2. flex-wrap: wrap
-
允许 flex 项目在需要时换行
-
当一行放不下所有内容时,会自动换到下一行
-
在这个场景中,如果评分项太多,会自动换行显示,不会溢出容器
3. flex: 1
-
这是 flex-grow: 1 的简写
-
表示该元素会占据父容器中所有可用的剩余空间
-
在这个评论组件中,.right 区域会占据左侧评分区域之外的所有空间
4. justify-content: flex-end
- 控制 flex 项目在主轴(水平方向)上的对齐方式
- flex-end 表示将内容靠右对齐
- 在这个评论组件中,评分项会靠右显示
1.2 关于数据存储的两种形式
方式一:存储在 store
里面 (引入+store.js)
第一步要注意:获取接口方法,例如是在 services 文件夹里面进行配置接口方法

接着,就要 借助pinia
里面的 defineStore(" ", {state: ... ,actions: ...})
+ store
文件夹下面的.js文件( js 文件里面要写发送网络请求的函数)

方式二: 存储在页面里面 defineProps({})
(组件+传参)
借助组件里面的传参的形式进行书写

1.3 关于 HYRequest 里面代码的理解
1.3.1 axios.create 的作用主要是创建单独的实例:为这个实例设置专属的 baseURL、timeout、拦截器等,不影响全局的 axios 配置。

1.4
2. 技巧类
2.1 关于导出
-
当使用 export default 时(就像当前的代码):
javascript// useScroll.js export default function useScroll() { ... } // detail.vue import useScroll from "@/hooks/useScroll"; // 不需要 {}
-
当使用普通 export 时:
javascript
// useScroll.js
export function useScroll() { ... }
// detail.vue
import { useScroll } from "@/hooks/useScroll"; // 需要 {}
这是因为:
- export default 表示这个模块的默认导出,一个模块只能有一个默认导出
- 普通 export 可以导出多个内容,所以需要用 {} 来指定要导入的具体内容
2.2
3. 知识点
3.1 关于页面滚动中的clientHeight、scrollTop和scrollHeight的区别和关系
clientHeight
指的是元素内部的可视区域高度,包括padding
但不包括边框、滚动条和margin
。
scrollTop
则是元素顶部被隐藏的滚动距离,也就是已经滚动的部分。而
scrollHeight
是整个内容的高度,包括被隐藏的部分,所以它代表实际内容的总高度。
通常,当判断是否滚动到底部时,会用到scrollTop + clientHeight >= scrollHeight
. 这时候需要确保这三个值都是针对同一个元素的,比如document.documentElement
或某个具体的容器元素。
1. 核心概念
① clientHeight
(可视区域高度)
-
含义 :元素内部可见部分的高度(不含滚动条,但包含 padding)。
-
计算方式:
jsclientHeight = 可视区域高度 + 上下padding
-
特点 :如果元素没有滚动条,
clientHeight
约等于内容高度(当内容不溢出时)。
② scrollTop
(已滚动距离)
-
含义 :元素内容顶部 到可视区域顶部的滚动距离(被隐藏的高度)。
-
单位:像素(px),可读写属性。
-
特点:
scrollTop = 0
表示未滚动(内容顶部与可视区域顶部对齐)。scrollTop
最大值是scrollHeight - clientHeight
。
③ scrollHeight
(内容总高度)
-
含义 :元素全部内容的高度(包括被隐藏的部分和 padding,不含滚动条)。
-
计算方式:
jsscrollHeight = 实际内容高度 + 上下padding
-
特点 :即使没有滚动条,
scrollHeight
也始终存在(可能等于clientHeight
)。
2. 三者的关系
公式
js
// 判断是否滚动到底部
if (element.scrollTop + element.clientHeight >= element.scrollHeight) {
// 触发加载更多等操作
}
-
解释:
scrollTop + clientHeight
:已滚动的高度 + 可视区域高度。- 当这个值大于等于
scrollHeight
时,说明已经滚动到底部。
3. 图解示例
lua
|-------------------------------|
| 可视区域 (clientHeight) |
| |
| ↑ scrollTop |
| ------------------------- |
| | 已隐藏的内容 | |
| ------------------------- |
| | 可见的内容 | |
| | | |
| | | |
| | | |
| ------------------------- |
| |
| |
|-------------------------------|
- 总内容高度 (
scrollHeight
) = 已隐藏内容高度 + 可见内容高度。 - 已滚动距离 (
scrollTop
) = 已隐藏内容的高度。 - 可视区域 (
clientHeight
) = 可见内容的高度。
4. 实际应用场景
① 判断页面滚动到底部
js
window.addEventListener("scroll", () => {
const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 10) { // 留10px容错
console.log("滚动到底部了!");
}
});
② 滚动到指定位置
js
// 滚动到元素中间
const element = document.getElementById("target");
element.scrollTop = element.scrollHeight / 2 - element.clientHeight / 2;
5. 注意事项
-
浏览器兼容性:
- 在标准模式下,使用
document.documentElement
获取属性。 - 在怪异模式(Quirks Mode)下,使用
document.body
。
- 在标准模式下,使用
-
小数像素问题:
-
某些浏览器可能返回小数,建议用
Math.ceil()
处理:jsMath.ceil(element.scrollTop + element.clientHeight) >= element.scrollHeight
-
-
滚动条的影响:
- 如果容器有横向滚动条,
clientHeight
会减去滚动条高度(通常约15-20px)。
- 如果容器有横向滚动条,
总结
clientHeight
:你能看到的内容高度。scrollTop
:你已经滚动了多少。scrollHeight
:全部内容的总高度。- 滚动到底部的条件 :
scrollTop + clientHeight >= scrollHeight
。
3.2 defineEmits 的作用
defineEmits 是 Vue3 中用于定义组件可以触发的自定义事件的 API。
- 基本用法
js
// 定义组件可以触发的事件
const emit = defineEmits(['tabItemClick'])
// 在需要的时候触发事件
emit('tabItemClick', index)
- 在你的代码中的应用
js
<script setup>
import { ref } from 'vue'
defineProps({
titles: {
type: Array,
default: () => []
}
})
// 定义组件可以触发的事件
const emit = defineEmits(['tabItemClick'])
const currentIndexMy = ref(0);
const itemClickMy = (index) => {
currentIndexMy.value = index;
// 触发自定义事件,把 index 传给父组件
emit('tabItemClick', index)
}
</script>
- 在父组件中如何使用
js
<template>
<tab-control
:titles="titles"
@tabItemClick="handleTabClick"
/>
</template>
<script setup>
const handleTabClick = (index) => {
console.log('父组件收到点击事件,索引是:', index)
// 在这里处理点击事件
}
</script>
- 为什么要用 defineEmits?
- 组件通信:子组件可以通过 emit 向父组件传递信息
- 解耦:子组件不需要知道父组件如何处理事件
- 可维护性:明确声明组件可以触发哪些事件
- 完整示例
js
<!-- 子组件 tab-control.vue -->
<template>
<div class="tabControl">
<template v-for="(item, index) in titles" :key="index">
<div class="decoration"
:class="{ active: currentIndexMy === index }"
@click="itemClickMy(index)">
<span>{{ item }}</span>
</div>
</template>
</div>
</template>
<script setup>
import { ref } from 'vue'
defineProps({
titles: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['tabItemClick'])
const currentIndexMy = ref(0);
const itemClickMy = (index) => {
currentIndexMy.value = index;
emit('tabItemClick', index) // 触发事件,把 index 传给父组件
}
</script>
<!-- 父组件 -->
<template>
<tab-control
:titles="['选项1', '选项2', '选项3']"
@tabItemClick="handleTabClick"
/>
</template>
<script setup>
const handleTabClick = (index) => {
console.log('父组件收到点击事件,索引是:', index)
// 处理点击事件
}
</script>
- 总结
- defineEmits 用于定义组件可以触发的事件
- 通过 emit('事件名', 参数) 触发事件
- 父组件通过 @事件名="处理函数" 监听事件
- 这是 Vue3 中组件通信的重要方式之一
3.3 关于 ref 和 conmputed 的区别和联系
- ref:
-
ref是Vue 3中引入的一个响应式API,用于创建一个响应式的引用。它可以用来包装基本类型(如字符串、数字等)或对象。
-
在模板中,ref会自动解包,所以在模板中不需要使用
.value
来访问值;但在JavaScript中,我们需要通过.value
来访问或修改它的值。 -
ref通常用于定义响应式数据,这些数据可能会被直接修改,并且可能不依赖于其他数据。
- computed:
-
computed用于定义计算属性。计算属性是基于它们的依赖进行缓存的响应式数据。
-
计算属性是一个函数,返回一个值。这个值会根据其依赖的其他响应式数据的变化而变化,并且只有当依赖发生变化时,计算属性才会重新计算。
-
计算属性适合用于需要根据其他数据派生出来的复杂逻辑,避免在模板中写过多逻辑。
区别:
-
使用目的不同:ref用于创建响应式数据,而computed用于创建依赖于其他响应式数据的计算属性。
-
缓存:computed具有缓存,只要依赖不变,多次访问计算属性不会重新计算;而ref每次访问都会得到当前值,但没有缓存机制(除非你自己用computed包装)。
-
可写性:ref创建的数据是可读可写的(可以直接修改.value);而computed默认是只读的,但也可以定义成可写的(通过提供get和set函数)。
核心区别
特性 | ref | computed |
---|---|---|
用途 | 创建响应式数据(基本类型/对象) | 创建基于依赖动态计算的值(派生数据) |
返回值 | 返回一个响应式对象(通过 .value 访问) |
返回一个只读 的响应式引用(自动解包,无需 .value ) |
是否可写 | ✅ 可直接修改 .value |
❌ 默认只读(需定义 set() 才能可写) |
缓存机制 | ❌ 无缓存 | ✅ 自动缓存(依赖不变时复用结果) |
适用场景 | 存储可变状态(如计数器、输入值) | 依赖其他数据的复杂计算(如过滤列表、格式化数据) |
如何选择?
-
需要存储原始数据? → 选
ref
(例:
const inputText = ref("")
) -
需要基于其他数据动态计算? → 选
computed
(例:
const sortedList = computed(() => [...list].sort())
) -
需要可写计算属性? → 用
computed
的set()
javascriptconst writableComp = computed({ get: () => count.value * 2, set: (val) => { count.value = val / 2 } // 修改依赖 });
一句话总结
ref
是"数据容器",computed
是"自动计算的衍生值"两者互补而非替代:
computed
内部常依赖ref
工作!
3.4 在vue 中,获取元素的方法
Vue 中获取元素有几种主要方式
-
使用 ref 获取元素(推荐方式):
javascript<template> <div ref="myRef">内容</div> </template> <script setup> import { ref, onMounted } from 'vue' const myRef = ref(null) onMounted(() => { console.log(myRef.value) // 获取到元素 }) </script>
-
使用 template ref(用于获取模板中的元素):
javascript<template> <div ref="myRef">内容</div> </template> <script setup> import { ref } from 'vue' const myRef = ref(null) </script>
-
使用 querySelector(不推荐,但在某些场景下可用):
javascript<script setup> import { onMounted } from 'vue' onMounted(() => { const element = document.querySelector('.my-class') console.log(element) }) </script>
-
使用 getElementById(不推荐,但在某些场景下可用):
javascript<script setup> import { onMounted } from 'vue' onMounted(() => { const element = document.getElementById('my-id') console.log(element) }) </script>