前言
- 常网IT源码上线啦!
- 本篇录入技术选型专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
- 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
- 接下来想分享一些自己在项目中遇到的技术选型以及问题场景。
鸿鸟只思羽翼齐,点翅飞腾千万里
“只有羽翼齐备才能飞翔千万里
只有准备充分、全面发展
才能取得成功”
破碎的,也能成为艺术品。
一、前言
vue3出了Hooks的概念。
Vue3的Hooks(更准确称为Composition API)是逻辑复用机制的革命性创新。它允许开发者将组件逻辑拆分为可复用的函数单元,彻底改变了传统的Options API组织方式。
-
逻辑关注点分离:将相关代码组织在一起(而非按data/methods分块)
-
无this的编程模型:避免上下文绑定问题
-
类型推导友好:天然支持TypeScript
-
可测试性:纯函数逻辑更易单元测试
直入正文。
二、高级Hooks模式
异步状态管理
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
async function fetchData() {
loading.value = true
try {
const response = await fetch(url)
data.value = await response.json()
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
return { data, error, loading, fetchData }
}
resize
import { onMounted, onUpdated, onUnmounted } from 'vue'
export function useWindowResize() {
const width = ref(window.innerWidth)
function updateWidth() {
width.value = window.innerWidth
}
onMounted(() => window.addEventListener('resize', updateWidth))
onUnmounted(() => window.removeEventListener('resize', updateWidth))
return { width }
}
状态共享Hook
import { reactive, readonly } from 'vue'
// 全局状态管理
const globalState = reactive({
theme: 'light',
locale: 'en-US'
})
export function useGlobalState() {
function setTheme(theme) {
globalState.theme = theme
}
return {
state: readonly(globalState), // 只读访问
setTheme
}
}
Hooks开发的时候,我一般会遵循几个原则:
-
单一职责原则:每个Hook只解决一个问题
-
命名约定:
useXxx
格式(如useMousePosition
) -
依赖注入:通过参数接收外部依赖
-
返回值标准化:返回ref/reactive对象
拖拽Hook
import { ref, onMounted, onUnmounted } from 'vue'
export function useDrag(elementRef) {
const x = ref(0)
const y = ref(0)
const isDragging = ref(false)
let startX = 0
let startY = 0
function onMouseDown(e) {
isDragging.value = true
startX = e.clientX - x.value
startY = e.clientY - y.value
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
}
function onMouseMove(e) {
x.value = e.clientX - startX
y.value = e.clientY - startY
}
function onMouseUp() {
isDragging.value = false
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
onMounted(() => {
elementRef.value.addEventListener('mousedown', onMouseDown)
})
onUnmounted(() => {
elementRef.value.removeEventListener('mousedown', onMouseDown)
})
return { x, y, isDragging }
}
使用
<template>
<div ref="draggable" :style="{ left: `${x}px`, top: `${y}px` }">
拖拽我 ({{ isDragging ? '拖动中' : '静止' }})
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useDrag } from './useDrag'
const draggable = ref(null)
const { x, y, isDragging } = useDrag(draggable)
</script>
性能优化
// 1. 惰性计算:使用computed
const expensiveValue = computed(() =>
heavyCalculation(state.data)
)
// 2. 事件节流
import { throttle } from 'lodash-es'
export function useScrollPosition() {
const scrollY = ref(0)
const updateScroll = throttle(() => {
scrollY.value = window.scrollY
}, 100)
onMounted(() => window.addEventListener('scroll', updateScroll))
onUnmounted(() => window.removeEventListener('scroll', updateScroll))
return { scrollY }
}
// 3. 依赖收集优化
watch([userId, params], () => fetchData(), { deep: false })
测试用例
// 使用Vitest测试Hook
import { test, expect } from 'vitest'
import { ref } from 'vue'
import { useCounter } from './useCounter'
test('useCounter hook', () => {
// 创建测试上下文
const { count, increment } = useCounter(5)
expect(count.value).toBe(5)
increment()
expect(count.value).toBe(6)
increment(4)
expect(count.value).toBe(10)
})
三、设计模式分类
模式 | 描述 | 示例 |
---|---|---|
状态Hook | 封装响应式状态 | useState , useToggle |
副作用Hook | 处理生命周期和外部交互 | useFetch , useEvent |
上下文Hook | 访问组件上下文 | useRouter , useStore |
工具Hook | 提供通用功能 | useClipboard , useDebounce |
组合Hook | 组合多个基础Hook | useUserDashboard |
小而专:每个Hook解决单一问题
参数设计:使用配置对象提高扩展性
function useFetch(url, { immediate = true, timeout = 5000 } = {})
返回值规范:返回响应式对象+方法集合
文档注释:使用TSDoc规范注释
/**
* 获取鼠标位置
* @returns { x: Ref<number>, y: Ref<number> }
*/
错误处理:提供错误状态和重试机制
四、React Hooks
自定义Hooks命名:以use
开头(如useAuth
)
类组件 vs 函数组件+Hooks
// 类组件 vs 函数组件+Hooks
class Counter extends React.Component {
state = { count: 0 }
increment = () => {
this.setState({ count: this.state.count + 1 })
}
render() {
return (
<button onClick={this.increment}>
Count: {this.state.count}
</button>
)
}
}
// 使用Hooks的函数组件
function Counter() {
const [count, setCount] = useState(0)
const increment = () => setCount(count + 1)
return (
<button onClick={increment}>
Count: {count}
</button>
)
}
useState实现原理
// 简化的useState实现
let state = []
let setters = []
let stateIndex = 0
function useState(initialValue) {
const currentIndex = stateIndex
state[currentIndex] = state[currentIndex] || initialValue
function setState(newValue) {
state[currentIndex] = newValue
render() // 触发重新渲染
}
setters.push(setState)
stateIndex++
return [state[currentIndex], setState]
}
React内部使用链表结构管理Hooks
// React内部使用链表结构管理Hooks
type Hook = {
memoizedState: any, // 当前状态
next: Hook | null, // 下一个Hook
};
function updateWorkInProgressHook() {
// 基于调用顺序遍历Hook链表
const hook = nextWorkInProgressHook;
nextWorkInProgressHook = hook.next;
return hook;
}
useEffect:副作用管理
有点像vue的onMounted + watch结合体
function Timer() {
const [seconds, setSeconds] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1)
}, 1000)
// 清理函数
return () => clearInterval(interval)
}, []) // 空依赖数组表示只运行一次
return <div>Seconds: {seconds}</div>
}
useReducer:复杂状态管理
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
throw new Error()
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
)
}
useCallback & useMemo:性能优化
useMemo有点像计算属性了。
function Parent() {
const [count, setCount] = useState(0)
// 使用useCallback避免函数重建
const increment = useCallback(() => {
setCount(c => c + 1)
}, [])
// 使用useMemo缓存计算结果
const doubleCount = useMemo(() => {
return count * 2
}, [count])
return (
<div>
<Child onClick={increment} />
<p>Count: {count}, Double: {doubleCount}</p>
</div>
)
}
useCallback解决什么问题?
问题场景:
function Parent() {
const [count, setCount] = useState(0);
// 每次渲染创建新函数
const handleClick = () => {
console.log('Click handled');
};
return <Child onClick={handleClick} />;
}
// Child组件
const Child = React.memo(({ onClick }) => {
// 即使props没变,因onClick引用不同仍会重渲染
return <button onClick={onClick}>Click me</button>;
});
正确使用姿势
function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 只有count变化时重建函数
const handleClick = useCallback(() => {
console.log(`Count: ${count}`);
}, [count]); // ✅ 正确声明依赖
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<Child onClick={handleClick} />
</>
);
}
何时使用useCallback:
-
函数作为useEffect依赖时
-
函数作为子组件prop时(配合React.memo)
-
函数被其他Hook依赖时
何时使用useMemo:
-
计算成本高昂的操作(>1ms)
-
引用类型(对象/数组)作为依赖或prop时
-
稳定引用需要传递给子组件时
闭包陷阱
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
// 闭包陷阱:始终使用初始count值
setCount(count + 1)
}, 1000)
return () => clearInterval(interval)
}, []) // 缺少count依赖
// 正确方案1:使用函数式更新
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1) // 使用最新值
}, 1000)
return () => clearInterval(interval)
}, [])
// 正确方案2:使用ref保存值
const countRef = useRef(count)
countRef.current = count
useEffect(() => {
const interval = setInterval(() => {
setCount(countRef.current + 1)
}, 1000)
return () => clearInterval(interval)
}, [])
return <div>{count}</div>
}
至此撒花~
后记
我发现react的写法也很不错。
我们在实际项目中或多或少遇到一些奇奇怪怪的问题。
自己也会对一些写法的思考,为什么不行🤔,又为什么行了?
最后,祝君能拿下满意的offer。
我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车
👍 如果对您有帮助,您的点赞是我前进的润滑剂。
以往推荐
vue2和Vue3和React的diff算法展开说说:从原理到优化策略