React 手写题
1 Tab 组件
接收 items 数组,点击切换 active tab
import { useState } from 'react'
const Tab = ({ items }) => {
const [activeKey, setActiveKey] = useState(items[0].key)
return (
<div>
<div>
{items.map((item) => (
<button
key={item.key}
onClick={() => setActiveKey(item.key)}
style={{ backgroundColor: activeKey === item.key ? 'green' : 'gray' }}
>
{item.label}
</button>
))}
</div>
<div>
{items.find((item) => item.key === activeKey).children}
</div>
</div>
)
}
2 useCount 自定义 Hook
const { num, add, cut } = useCount(0)
import { useState, useCallback } from 'react'
function useCount(initialValue) {
const [num, setNum] = useState(initialValue)
// useCallback 防止每次 render 都产生新函数引用,避免子组件无效渲染
const add = useCallback(() => {
setNum(prev => prev + 1) // 函数式更新避免闭包陷阱
}, [])
const cut = useCallback(() => {
setNum(prev => prev - 1)
}, [])
return { num, add, cut }
}
3 usePrevious
返回 state 的上一个值(useRef + useEffect)
- useEffect 在渲染之后执行,所以 return 的 ref.current 还是旧值
import { useEffect, useRef } from 'react'
function usePrevious(state) {
const ref = useRef()
// useEffect 在渲染之后执行,所以 return 时 ref.current 还是旧值
useEffect(() => {
ref.current = state // 渲染完成后才更新为新值
}, [state])
return ref.current // 返回的是本次渲染前的值
}
4 输入框 + 防抖
即时 setValue + useMemo 包裹防抖函数
题干:实现一个搜索输入框组件,要求:输入时不卡顿(即时响应),但停止输入 500ms 后才发送请求(防抖)。
- 坑 1:debounce 要用
useMemo包裹,否则每次 render 都创建新函数 - 坑 2:要即时
setVal让输入框不卡,防抖只控制请求发送
import { useState, useMemo } from 'react'
const debounce = (fn, delay) => {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
function Comp() {
const [val, setVal] = useState('')
// 要用 useMemo 包裹,否则每次 setVal 都会重新执行函数组件,产生新的 fnDeb
const fnDeb = useMemo(
() => debounce((value) => {
console.log(`发请求: ${value}`)
}, 500),
[]
)
const handleChange = (e) => {
const value = e.target.value
setVal(value) // 即时 setVal,输入框不会卡住
fnDeb(value) // 防抖只控制请求发送
}
return <input onChange={handleChange} value={val} />
}
5 倒计时组件 + Hook 封装
setInterval + 函数式更新避免闭包陷阱
题干:实现一个倒计时组件 <CountDown time={60} />,传入秒数,每秒递减显示,归零停止。要求封装为 useCountDown Hook。
import { useState, useEffect } from 'react'
function useCountDown(initialTime) {
const [remainTime, setRemainTime] = useState(initialTime)
useEffect(() => {
setRemainTime(initialTime)
const timer = setInterval(() => {
// 用函数式更新 prev => ... 避免闭包陷阱(不依赖外部 remainTime)
setRemainTime(prev => {
if (prev > 0) return prev - 1
clearInterval(timer)
return 0
})
}, 1000)
return () => clearInterval(timer)
}, [initialTime])
return remainTime
}
const CountDown = ({ time }) => {
const remainTime = useCountDown(time)
return <p>剩余 {remainTime} 秒</p>
}