Skip to content

手写题集锦 - 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. 总结

手写题集锦的核心要点:

  1. 基础Hooks: useState、useEffect、useMemo、useCallback等
  2. 高级Hooks: useIntersectionObserver、useFetch、useAsync等
  3. 基础组件: Modal、Tabs、Form等
  4. 高级组件: VirtualList、InfiniteScroll、ErrorBoundary等
  5. 核心实现: createElement、render、Diff、Fiber等
  6. 状态管理: createStore、combineReducers、简易Zustand等
  7. 性能优化: React.memo、批量更新等

手写代码是检验理解深度的最佳方式,掌握这些实现有助于更好地理解React内部机制。