Appearance
手写题集锦 - React手写代码面试题库
1. 手写Hooks
1.1 useState
typescript
let state = [];
let setters = [];
let cursor = 0;
function useState(initialValue) {
const currentCursor = cursor;
state[currentCursor] = state[currentCursor] !== undefined
? state[currentCursor]
: initialValue;
const setState = (newValue) => {
state[currentCursor] = typeof newValue === 'function'
? newValue(state[currentCursor])
: newValue;
render();
};
cursor++;
return [state[currentCursor], setState];
}
function render() {
cursor = 0;
// 触发组件渲染
}1.2 useEffect
typescript
let effectCursor = 0;
let effects = [];
function useEffect(callback, deps) {
const currentCursor = effectCursor;
const hasNoDeps = !deps;
const prevDeps = effects[currentCursor] ? effects[currentCursor].deps : undefined;
const hasChangedDeps = prevDeps
? !deps.every((dep, i) => Object.is(dep, prevDeps[i]))
: true;
if (hasNoDeps || hasChangedDeps) {
// 执行cleanup
if (effects[currentCursor] && effects[currentCursor].cleanup) {
effects[currentCursor].cleanup();
}
// 异步执行effect
Promise.resolve().then(() => {
const cleanup = callback();
effects[currentCursor] = { deps, cleanup };
});
}
effectCursor++;
}1.3 useReducer
typescript
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
const dispatch = (action) => {
const newState = reducer(state, action);
setState(newState);
};
return [state, dispatch];
}1.4 useMemo
typescript
let memoCursor = 0;
let memoValues = [];
function useMemo(factory, deps) {
const currentCursor = memoCursor;
const hasNoDeps = !deps;
const prevDeps = memoValues[currentCursor]
? memoValues[currentCursor][1]
: undefined;
const hasChangedDeps = prevDeps
? !deps.every((dep, i) => Object.is(dep, prevDeps[i]))
: true;
if (hasNoDeps || hasChangedDeps) {
const value = factory();
memoValues[currentCursor] = [value, deps];
memoCursor++;
return value;
}
memoCursor++;
return memoValues[currentCursor][0];
}1.5 useCallback
typescript
function useCallback(callback, deps) {
return useMemo(() => callback, deps);
}1.6 useRef
typescript
function useRef(initialValue) {
return useMemo(() => ({ current: initialValue }), []);
}2. 手写组件
2.1 手写Modal
typescript
function Modal({ isOpen, onClose, children }) {
useEffect(() => {
if (!isOpen) return;
const handleEscape = (e) => {
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', handleEscape);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = '';
};
}, [isOpen, onClose]);
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<button onClick={onClose}>×</button>
{children}
</div>
</div>,
document.body
);
}2.2 手写Tabs
typescript
function Tabs({ children }) {
const [activeIndex, setActiveIndex] = useState(0);
return (
<div>
<div className="tab-headers">
{React.Children.map(children, (child, index) => (
<button
key={index}
className={activeIndex === index ? 'active' : ''}
onClick={() => setActiveIndex(index)}
>
{child.props.title}
</button>
))}
</div>
<div className="tab-content">
{React.Children.toArray(children)[activeIndex]}
</div>
</div>
);
}
function Tab({ title, children }) {
return <div>{children}</div>;
}
// 使用
<Tabs>
<Tab title="Tab 1">Content 1</Tab>
<Tab title="Tab 2">Content 2</Tab>
<Tab title="Tab 3">Content 3</Tab>
</Tabs>2.3 手写Form
typescript
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = (name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
};
const handleBlur = (name) => {
setTouched(prev => ({ ...prev, [name]: true }));
if (validate) {
const fieldErrors = validate({ ...values, [name]: values[name] });
setErrors(prev => ({ ...prev, [name]: fieldErrors[name] }));
}
};
const handleSubmit = (onSubmit) => async (e) => {
e.preventDefault();
const validationErrors = validate ? validate(values) : {};
setErrors(validationErrors);
if (Object.keys(validationErrors).length === 0) {
await onSubmit(values);
}
};
return {
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit
};
}3. 手写工具函数
3.1 createElement
typescript
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === 'object' ? child : createTextElement(child)
)
}
};
}
function createTextElement(text) {
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: []
}
};
}3.2 render
typescript
function render(vdom, container) {
const dom = vdom.type === 'TEXT_ELEMENT'
? document.createTextNode(vdom.props.nodeValue)
: document.createElement(vdom.type);
// 设置属性
Object.keys(vdom.props)
.filter(key => key !== 'children')
.forEach(name => {
dom[name] = vdom.props[name];
});
// 递归渲染子节点
vdom.props.children.forEach(child => render(child, dom));
container.appendChild(dom);
}4. 手写自定义Hooks
4.1 useDebounce
typescript
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}4.2 useLocalStorage
typescript
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}4.3 useInterval
typescript
function useInterval(callback, delay) {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) return;
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
}4.4 usePrevious
typescript
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}4.5 useToggle
typescript
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(v => !v);
}, []);
return [value, toggle];
}5. 手写Redux
5.1 createStore
typescript
function createStore(reducer, initialState) {
let state = initialState;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
return action;
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
};
dispatch({ type: '@@INIT' });
return { getState, dispatch, subscribe };
}5.2 combineReducers
typescript
function combineReducers(reducers) {
return (state = {}, action) => {
return Object.keys(reducers).reduce((nextState, key) => {
nextState[key] = reducers[key](state[key], action);
return nextState;
}, {});
};
}5.3 applyMiddleware
typescript
function applyMiddleware(...middlewares) {
return (createStore) => (reducer, initialState) => {
const store = createStore(reducer, initialState);
let dispatch = store.dispatch;
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return { ...store, dispatch };
};
}
function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}6. 手写高级Hooks
6.1 useIntersectionObserver
typescript
function useIntersectionObserver(
elementRef,
{ threshold = 0, root = null, rootMargin = '0%' }
) {
const [entry, setEntry] = useState();
useEffect(() => {
const node = elementRef?.current;
if (!node) return;
const observer = new IntersectionObserver(
([entry]) => setEntry(entry),
{ threshold, root, rootMargin }
);
observer.observe(node);
return () => {
observer.disconnect();
};
}, [elementRef, threshold, root, rootMargin]);
return entry;
}
// 使用
function LazyImage({ src }) {
const ref = useRef();
const entry = useIntersectionObserver(ref, { threshold: 0.1 });
const isVisible = entry?.isIntersecting;
return (
<img ref={ref} src={isVisible ? src : placeholder} />
);
}6.2 useMediaQuery
typescript
function useMediaQuery(query) {
const [matches, setMatches] = useState(
() => window.matchMedia(query).matches
);
useEffect(() => {
const mediaQuery = window.matchMedia(query);
const handleChange = (e) => {
setMatches(e.matches);
};
mediaQuery.addEventListener('change', handleChange);
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, [query]);
return matches;
}
// 使用
function ResponsiveComponent() {
const isMobile = useMediaQuery('(max-width: 768px)');
return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>;
}6.3 useClickOutside
typescript
function useClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]);
}
// 使用
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef();
useClickOutside(ref, () => setIsOpen(false));
return (
<div ref={ref}>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && <div>Dropdown Content</div>}
</div>
);
}6.4 useFetch
typescript
function useFetch(url, options) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const abortControllerRef = useRef(null);
useEffect(() => {
const fetchData = async () => {
abortControllerRef.current?.abort();
abortControllerRef.current = new AbortController();
setLoading(true);
try {
const response = await fetch(url, {
...options,
signal: abortControllerRef.current.signal
});
if (!response.ok) {
throw new Error(response.statusText);
}
const json = await response.json();
setData(json);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(error);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
abortControllerRef.current?.abort();
};
}, [url]);
return { data, error, loading };
}6.5 useAsync
typescript
function useAsync(asyncFunction, immediate = true) {
const [status, setStatus] = useState('idle');
const [value, setValue] = useState(null);
const [error, setError] = useState(null);
const execute = useCallback(() => {
setStatus('pending');
setValue(null);
setError(null);
return asyncFunction()
.then(response => {
setValue(response);
setStatus('success');
})
.catch(error => {
setError(error);
setStatus('error');
});
}, [asyncFunction]);
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { execute, status, value, error };
}6.6 useHover
typescript
function useHover() {
const [hovered, setHovered] = useState(false);
const ref = useRef(null);
const handleMouseEnter = () => setHovered(true);
const handleMouseLeave = () => setHovered(false);
useEffect(() => {
const node = ref.current;
if (node) {
node.addEventListener('mouseenter', handleMouseEnter);
node.addEventListener('mouseleave', handleMouseLeave);
return () => {
node.removeEventListener('mouseenter', handleMouseEnter);
node.removeEventListener('mouseleave', handleMouseLeave);
};
}
}, [ref.current]);
return [ref, hovered];
}
// 使用
function HoverExample() {
const [hoverRef, isHovered] = useHover();
return (
<div ref={hoverRef}>
{isHovered ? 'Hovered!' : 'Hover over me!'}
</div>
);
}6.7 useWindowSize
typescript
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}6.8 useEventListener
typescript
function useEventListener(eventName, handler, element = window) {
const savedHandler = useRef();
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) return;
const eventListener = (event) => savedHandler.current(event);
element.addEventListener(eventName, eventListener);
return () => {
element.removeEventListener(eventName, eventListener);
};
}, [eventName, element]);
}6.9 useWhyDidYouUpdate
typescript
function useWhyDidYouUpdate(name, props) {
const previousProps = useRef();
useEffect(() => {
if (previousProps.current) {
const allKeys = Object.keys({ ...previousProps.current, ...props });
const changesObj = {};
allKeys.forEach(key => {
if (previousProps.current[key] !== props[key]) {
changesObj[key] = {
from: previousProps.current[key],
to: props[key]
};
}
});
if (Object.keys(changesObj).length) {
console.log('[why-did-you-update]', name, changesObj);
}
}
previousProps.current = props;
});
}6.10 useCopyToClipboard
typescript
function useCopyToClipboard() {
const [copiedText, setCopiedText] = useState(null);
const copy = async (text) => {
if (!navigator?.clipboard) {
console.warn('Clipboard not supported');
return false;
}
try {
await navigator.clipboard.writeText(text);
setCopiedText(text);
return true;
} catch (error) {
console.warn('Copy failed', error);
setCopiedText(null);
return false;
}
};
return [copiedText, copy];
}7. 手写高级组件
7.1 手写虚拟滚动
typescript
function VirtualList({ items, itemHeight, height }) {
const [scrollTop, setScrollTop] = useState(0);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(height / itemHeight),
items.length
);
const visibleItems = items.slice(startIndex, endIndex);
const offsetY = startIndex * itemHeight;
const totalHeight = items.length * itemHeight;
return (
<div
style={{ height, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div key={startIndex + index} style={{ height: itemHeight }}>
{item}
</div>
))}
</div>
</div>
</div>
);
}7.2 手写InfiniteScroll
typescript
function InfiniteScroll({ loadMore, hasMore, loader, children }) {
const observerTarget = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting && hasMore) {
loadMore();
}
},
{ threshold: 1 }
);
if (observerTarget.current) {
observer.observe(observerTarget.current);
}
return () => {
if (observerTarget.current) {
observer.unobserve(observerTarget.current);
}
};
}, [loadMore, hasMore]);
return (
<div>
{children}
<div ref={observerTarget}>
{hasMore && loader}
</div>
</div>
);
}7.3 手写ErrorBoundary
typescript
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('ErrorBoundary caught:', error, errorInfo);
// 可以上报错误
// logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div>
<h1>Something went wrong.</h1>
<details>
{this.state.error && this.state.error.toString()}
</details>
</div>
);
}
return this.props.children;
}
}7.4 手写Portal
typescript
function Portal({ children, container = document.body }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
return mounted ? ReactDOM.createPortal(children, container) : null;
}
// 使用
function ModalWithPortal() {
return (
<Portal>
<div className="modal">Modal Content</div>
</Portal>
);
}7.5 手写Tooltip
typescript
function Tooltip({ children, content, position = 'top' }) {
const [visible, setVisible] = useState(false);
const [coords, setCoords] = useState({ x: 0, y: 0 });
const targetRef = useRef(null);
const showTooltip = (e) => {
const rect = e.target.getBoundingClientRect();
let x, y;
switch (position) {
case 'top':
x = rect.left + rect.width / 2;
y = rect.top;
break;
case 'bottom':
x = rect.left + rect.width / 2;
y = rect.bottom;
break;
// ... other positions
}
setCoords({ x, y });
setVisible(true);
};
const hideTooltip = () => setVisible(false);
return (
<>
<div
ref={targetRef}
onMouseEnter={showTooltip}
onMouseLeave={hideTooltip}
>
{children}
</div>
{visible && (
<Portal>
<div
className="tooltip"
style={{
position: 'absolute',
left: coords.x,
top: coords.y
}}
>
{content}
</div>
</Portal>
)}
</>
);
}7.6 手写Carousel
typescript
function Carousel({ images, autoPlay = true, interval = 3000 }) {
const [current, setCurrent] = useState(0);
const length = images.length;
const nextSlide = useCallback(() => {
setCurrent(current === length - 1 ? 0 : current + 1);
}, [current, length]);
const prevSlide = () => {
setCurrent(current === 0 ? length - 1 : current - 1);
};
useInterval(() => {
if (autoPlay) {
nextSlide();
}
}, interval);
return (
<div className="carousel">
<button onClick={prevSlide}>←</button>
<div className="slides">
{images.map((image, index) => (
<div
key={index}
className={index === current ? 'slide active' : 'slide'}
>
{index === current && <img src={image} alt={`Slide ${index}`} />}
</div>
))}
</div>
<button onClick={nextSlide}>→</button>
<div className="dots">
{images.map((_, index) => (
<span
key={index}
className={index === current ? 'dot active' : 'dot'}
onClick={() => setCurrent(index)}
/>
))}
</div>
</div>
);
}7.7 手写Accordion
typescript
function Accordion({ items }) {
const [activeIndex, setActiveIndex] = useState(null);
const toggle = (index) => {
setActiveIndex(activeIndex === index ? null : index);
};
return (
<div className="accordion">
{items.map((item, index) => (
<div key={index} className="accordion-item">
<div
className="accordion-header"
onClick={() => toggle(index)}
>
{item.title}
<span>{activeIndex === index ? '−' : '+'}</span>
</div>
<div
className={`accordion-content ${
activeIndex === index ? 'active' : ''
}`}
>
{item.content}
</div>
</div>
))}
</div>
);
}8. 手写React核心
8.1 手写Diff算法
typescript
function diff(oldVNode, newVNode) {
// 新节点不存在,删除
if (!newVNode) {
return { type: 'REMOVE' };
}
// 旧节点不存在,创建
if (!oldVNode) {
return { type: 'CREATE', newVNode };
}
// 类型不同,替换
if (oldVNode.type !== newVNode.type) {
return { type: 'REPLACE', newVNode };
}
// 文本节点
if (typeof newVNode === 'string') {
if (oldVNode !== newVNode) {
return { type: 'TEXT', newVNode };
}
return null;
}
// 对比props
const propsPatches = diffProps(oldVNode.props, newVNode.props);
// 对比children
const childrenPatches = diffChildren(
oldVNode.props.children,
newVNode.props.children
);
if (propsPatches || childrenPatches.length) {
return {
type: 'UPDATE',
propsPatches,
childrenPatches
};
}
return null;
}
function diffProps(oldProps, newProps) {
const patches = {};
// 找出变化和新增的props
for (let key in newProps) {
if (oldProps[key] !== newProps[key]) {
patches[key] = newProps[key];
}
}
// 找出删除的props
for (let key in oldProps) {
if (!(key in newProps)) {
patches[key] = undefined;
}
}
return Object.keys(patches).length ? patches : null;
}
function diffChildren(oldChildren, newChildren) {
const patches = [];
const length = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < length; i++) {
patches.push(diff(oldChildren[i], newChildren[i]));
}
return patches;
}8.2 手写Fiber调度
typescript
let nextUnitOfWork = null;
let currentRoot = null;
let wipRoot = null;
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
function performUnitOfWork(fiber) {
// 1. 添加DOM节点
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
// 2. 为children创建fiber
const elements = fiber.props.children;
reconcileChildren(fiber, elements);
// 3. 返回下一个工作单元
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
function reconcileChildren(wipFiber, elements) {
let index = 0;
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
while (index < elements.length || oldFiber != null) {
const element = elements[index];
let newFiber = null;
const sameType = oldFiber && element && element.type === oldFiber.type;
if (sameType) {
// 更新
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: 'UPDATE'
};
}
if (element && !sameType) {
// 新增
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: wipFiber,
alternate: null,
effectTag: 'PLACEMENT'
};
}
if (oldFiber && !sameType) {
// 删除
oldFiber.effectTag = 'DELETION';
deletions.push(oldFiber);
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
if (index === 0) {
wipFiber.child = newFiber;
} else if (element) {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
}
requestIdleCallback(workLoop);8.3 手写Reconciler
typescript
function reconcile(parentDom, oldFiber, elements) {
let index = 0;
let prevSibling = null;
while (index < elements.length) {
const element = elements[index];
const newFiber = {
type: element.type,
props: element.props,
parent: parentDom,
dom: null
};
if (index === 0) {
parentDom.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
}9. 手写状态管理
9.1 手写简单Zustand
typescript
function create(createState) {
let state;
const listeners = new Set();
const setState = (partial) => {
const nextState = typeof partial === 'function'
? partial(state)
: partial;
if (nextState !== state) {
state = Object.assign({}, state, nextState);
listeners.forEach(listener => listener());
}
};
const getState = () => state;
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const api = { setState, getState, subscribe };
state = createState(setState, getState, api);
return api;
}
// Hook
function useStore(api, selector = api.getState) {
const [, forceUpdate] = useReducer(c => c + 1, 0);
useEffect(() => {
return api.subscribe(forceUpdate);
}, []);
return selector(api.getState());
}9.2 手写Context
typescript
function createContext(defaultValue) {
const context = {
_currentValue: defaultValue,
Provider: null,
Consumer: null
};
context.Provider = function Provider({ value, children }) {
useEffect(() => {
context._currentValue = value;
}, [value]);
return children;
};
context.Consumer = function Consumer({ children }) {
return children(context._currentValue);
};
return context;
}
function useContext(context) {
return context._currentValue;
}10. 手写性能优化
10.1 手写React.memo
typescript
function memo(Component, areEqual) {
return function MemoizedComponent(props) {
const prevPropsRef = useRef();
const memoizedResultRef = useRef();
const shouldUpdate = !prevPropsRef.current ||
(areEqual
? !areEqual(prevPropsRef.current, props)
: !shallowEqual(prevPropsRef.current, props)
);
if (shouldUpdate) {
memoizedResultRef.current = Component(props);
prevPropsRef.current = props;
}
return memoizedResultRef.current;
};
}
function shallowEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || obj1 === null ||
typeof obj2 !== 'object' || obj2 === null) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}10.2 手写批量更新
typescript
let isBatchingUpdates = false;
let batchedUpdates = [];
function batchedUpdate(fn) {
if (isBatchingUpdates) {
batchedUpdates.push(fn);
} else {
isBatchingUpdates = true;
fn();
flushBatchedUpdates();
isBatchingUpdates = false;
}
}
function flushBatchedUpdates() {
batchedUpdates.forEach(fn => fn());
batchedUpdates = [];
}
// 使用
function handleClick() {
batchedUpdate(() => {
setState1(value1);
setState2(value2);
setState3(value3);
});
}11. 总结
手写题集锦的核心要点:
- 基础Hooks: useState、useEffect、useMemo、useCallback等
- 高级Hooks: useIntersectionObserver、useFetch、useAsync等
- 基础组件: Modal、Tabs、Form等
- 高级组件: VirtualList、InfiniteScroll、ErrorBoundary等
- 核心实现: createElement、render、Diff、Fiber等
- 状态管理: createStore、combineReducers、简易Zustand等
- 性能优化: React.memo、批量更新等
手写代码是检验理解深度的最佳方式,掌握这些实现有助于更好地理解React内部机制。