1. useEffect和uselayoutEffect区别
Hook | 触发时机 | 是否阻塞渲染 |
---|---|---|
useEffect |
浏览器完成渲染(绘制)之后 才执行,属于 异步 调用,不会阻塞页面的绘制。 | ❌ 不阻塞 |
useLayoutEffect |
DOM 更新后、浏览器绘制前 执行,属于 同步 调用,会在页面绘制前完成执行,因此可能阻塞渲染。 | ✅ 阻塞 |
使用场景建议
useEffect
适合:
- 数据请求
- 事件监听/解绑
- 日志记录
- 不影响页面首屏渲染的逻辑
useLayoutEffect
适合:
- 需要立即 测量 DOM 尺寸/位置(如
getBoundingClientRect
) - 需要阻止页面闪烁的布局调整
- 同步修改 DOM 样式(如动画的初始位置)
✅ 记住口诀
useEffect
:绘制后执行 → 异步 → 不影响渲染
useLayoutEffect
:绘制前执行 → 同步 → 会阻塞渲染
2.useEffect
的 return
(清理函数)和 依赖数组 的执行时机梳理成一个精确的时序表
基本规则
scss
ts
复制编辑
useEffect(() => {
// 副作用逻辑
return () => {
// 清理逻辑
};
}, [dep1, dep2]);
执行顺序:
- 组件首次渲染 → 执行副作用函数(
// 副作用逻辑
)。 - 依赖变化 → 先执行上一次的
return
清理逻辑,再执行新的副作用逻辑。 - 组件卸载 → 执行最后一次的
return
清理逻辑。
3.taiwindcss如何选择组件内的子元素
1. 用 space-*
/ divide-*
处理直接子元素间距或边框
这种方式最适合「兄弟元素」场景,不需要自己写选择器。
css
html
复制编辑
<div class="space-y-4">
<p>第一段</p>
<p>第二段</p>
<p>第三段</p>
</div>
space-y-4
→ 给所有 直接子元素 添加垂直间距space-x-4
→ 给所有 直接子元素 添加水平间距divide-y
/divide-x
→ 给直接子元素之间添加分隔线
2. 用 group
+ group-*
选择子元素
group
是 Tailwind 内置的父级状态类,可以配合伪类选择子元素(常用在 hover、focus 场景)。
ini
html
复制编辑
<div class="group p-4 border">
<p class="group-hover:text-red-500">鼠标移到父元素时变红</p>
</div>
- 父元素加
.group
- 子元素用
group-hover:
、group-focus:
等前缀响应父元素状态
3. 用 @layer
写自定义选择器
如果需要像普通 CSS 那样 .parent > .child
,可以借助 Tailwind 的 @layer
功能扩展样式:
scss
css
复制编辑
@layer components {
.card > .title {
@apply text-lg font-bold text-blue-500;
}
}
HTML:
ini
html
复制编辑
<div class="card">
<div class="title">标题</div>
</div>
- 这样 Tailwind 编译时会保留
.card > .title
规则 - 适合复用性强的子元素样式
4. 使用 &
嵌套选择器(在 Tailwind 的 SCSS/Sass/ PostCSS 中)
如果项目用了 Sass 或 PostCSS Nesting,可以直接写嵌套语法:
scss
css
复制编辑
@layer components {
.parent {
@apply p-4 bg-gray-100;
& > .child {
@apply text-red-500;
}
}
}
编译后会生成:
css
css
复制编辑
.parent { padding: 1rem; background-color: #f7fafc; }
.parent > .child { color: #ef4444; }
5.实现一个es6的proxy
js
function MyProxy(target, handler) {
const proxy = {};
Object.keys(target).forEach(key => {
Object.defineProperty(proxy, key, {
get() {
if (handler.get) {
return handler.get(target, key, proxy);
}
return target[key];
},
set(value) {
if (handler.set) {
const res = handler.set(target, key, value, proxy);
if (res) target[key] = value;
return res;
}
target[key] = value;
return true;
}
});
});
return proxy;
}
// 测试
const obj = { a: 1, b: 2 };
const p = MyProxy(obj, {
get(target, prop) {
console.log(`get ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`set ${prop} = ${value}`);
target[prop] = value;
return true;
}
});
console.log(p.a); // get a -> 1
p.b = 10; // set b = 10
6.JavaScript 事件循环(Event Loop) 、主线程(Main Thread) 、消息队列(Message Queue / Task Queue) 和 调用栈(Call Stack) 的运作过程
2. 执行流程
假设代码:
javascript
js
复制编辑
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
执行过程:
- 主线程执行同步代码 →
console.log("A")
输出 → 压栈/弹栈 - 遇到
setTimeout
→ 定时器到点后回调放入 宏任务队列 - 遇到
Promise.then
→ 回调放入 微任务队列 - 继续执行
console.log("D")
- 执行所有微任务队列 (输出
C
) - 执行一个宏任务队列的任务(输出
B
)
最终输出:
css
css
复制编辑
A
D
C
B
可视化顺序图
javascript
javascript
复制编辑
┌───────────────────┐
│ Call Stack │
└───────┬───────────┘
│
▼
同步任务先执行(A、D)
│
▼
微任务队列(Promise.then)
│
▼
宏任务队列(setTimeout)
事件循环流程:
css
css
复制编辑
[执行栈为空] →
执行微任务队列全部任务 →
执行一个宏任务 →
执行微任务队列全部任务 →
再取一个宏任务 →
循环...