← Назад к вопросам

Как реализовать свой useState?

2.0 Middle🔥 111 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация собственного useState

Реализация useState — один из лучших способов понять, как работают React hooks изнутри. Хотя полная реализация очень сложная, мы можем создать упрощённую версию, которая демонстрирует основной принцип.

Основная идея

React использует замыкания и массив для хранения состояния каждого компонента. Каждый вызов setState вызывает ре-рендер компонента, при котором индекс хука позволяет получить правильное значение из массива состояния.

Простая реализация

let componentHooks = [];
let hookIndex = 0;

function useState(initialValue) {
  const index = hookIndex;
  hookIndex++;
  
  // Инициализируем значение, если это первый рендер
  if (componentHooks[index] === undefined) {
    const initial = typeof initialValue === "function" ? initialValue() : initialValue;
    componentHooks[index] = initial;
  }
  
  // Функция для обновления состояния
  const setState = (newValue) => {
    const value = typeof newValue === "function" 
      ? newValue(componentHooks[index]) 
      : newValue;
    
    if (value !== componentHooks[index]) {
      componentHooks[index] = value;
      render(); // ре-рендер всего приложения
    }
  };
  
  return [componentHooks[index], setState];
}

// Пример использования
function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("Мир");
  
  return {
    render: () => `Привет, ${name}! Счётчик: ${count}`,
    increment: () => setCount(count + 1),
    setName: (newName) => setName(newName),
  };
}

Проблема: Эта реализация имеет критический баг — индекс хука должен быть сброшен перед каждым ре-рендером. Вот исправленная версия:

Улучшенная реализация

let components = new WeakMap(); // Сохраняем состояние для каждого компонента

function createComponent(componentFn) {
  let hooks = [];
  let hookIndex = 0;
  let isMounted = false;
  
  function useState(initialValue) {
    const index = hookIndex;
    hookIndex++;
    
    // Инициализация при первом монтировании
    if (!isMounted) {
      const initial = typeof initialValue === "function" ? initialValue() : initialValue;
      hooks[index] = initial;
    }
    
    const setState = (newValue) => {
      const value = typeof newValue === "function" 
        ? newValue(hooks[index]) 
        : newValue;
      
      // Не обновляем если значение не изменилось (оптимизация)
      if (value !== hooks[index]) {
        hooks[index] = value;
        render(); // ре-рендер
      }
    };
    
    return [hooks[index], setState];
  }
  
  function render() {
    hookIndex = 0; // Сбрасываем индекс для следующего рендера!
    isMounted = true;
    return componentFn(useState);
  }
  
  return { render, hooks };
}

// Использование
const counter = createComponent((useState) => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");
  
  return {
    display: `Count: ${count}, Text: ${text}`,
    increment: () => setCount(count + 1),
    decrement: () => setCount(count - 1),
    setText: (newText) => setText(newText),
  };
});

console.log(counter.render().display); // Count: 0, Text: 
counter.render().increment();
console.log(counter.render().display); // Count: 1, Text:

Более реалистичная реализация (с модулем)

class HookSystem {
  constructor() {
    this.currentComponent = null;
    this.hookIndex = 0;
  }
  
  useState(initialValue) {
    const component = this.currentComponent;
    const index = this.hookIndex++;
    
    // Инициализируем состояние компонента
    if (!component.hooks) {
      component.hooks = [];
    }
    
    if (component.hooks[index] === undefined) {
      const value = typeof initialValue === "function" ? initialValue() : initialValue;
      component.hooks[index] = { state: value };
    }
    
    const setState = (action) => {
      const hook = component.hooks[index];
      const newState = typeof action === "function" ? action(hook.state) : action;
      
      // Сравниваем по значению (поверхностное сравнение)
      if (!Object.is(newState, hook.state)) {
        hook.state = newState;
        this.scheduleRender(component);
      }
    };
    
    return [component.hooks[index].state, setState];
  }
  
  scheduleRender(component) {
    // В реальном React это более сложно — используется приоритет, батчинг и т.д.
    this.hookIndex = 0;
    component.render();
  }
  
  runComponent(component) {
    this.currentComponent = component;
    this.hookIndex = 0;
    return component.componentFn();
  }
}

// Использование
const hookSystem = new HookSystem();

const MyComponent = {
  componentFn: function() {
    const [count, setCount] = hookSystem.useState(0);
    const [name, setName] = hookSystem.useState("Alice");
    
    return {
      state: { count, name },
      handlers: {
        increment: () => setCount(c => c + 1),
        changeName: (n) => setName(n),
      }
    };
  },
  render() {
    const result = hookSystem.runComponent(this);
    console.log(`${result.state.name}: ${result.state.count}`);
  }
};

MyComponent.render(); // Alice: 0

Ключевые принципы, которые мы реализовали

  1. Замыкания — функция setState закрывает переменную index и hooks
  2. Сброс индекса — перед каждым ре-рендером индекс обнуляется
  3. Ленивая инициализация — значение инициализируется только при первом рендере
  4. Функциональное обновлениеsetState принимает функцию для вычисления нового значения
  5. Батчинг обновлений — всё перерисовывается при изменении

Почему реальный React сложнее

  • Fiber архитектура — React использует linked list для управления обновлениями
  • Приоритеты — одни обновления важнее других
  • Concurrent mode — рендер можно прерывать и возобновлять
  • Dependency trackinguseEffect отслеживает зависимости
  • Error boundaries — обработка ошибок при рендере
  • Strict mode — двойной вызов эффектов для отладки

Правило вызова useState

Хотя мы это не реализовали явно, важно помнить:

// ❌ НЕПРАВИЛЬНО — условный вызов useState
function BadComponent() {
  if (someCondition) {
    const [state, setState] = useState(0); // может меняться порядок!
  }
  return null;
}

// ✅ ПРАВИЛЬНО — всегда вызывай hooks на верхнем уровне
function GoodComponent() {
  const [state, setState] = useState(0);
  
  if (someCondition) {
    // используй состояние здесь
  }
  return null;
}

Резюме

Реализация собственного useState показывает, как React использует:

  • Замыкания для инкапсуляции состояния
  • Индекс хука для определения какое состояние получить
  • Сброс индекса перед каждым ре-рендером
  • Функции обновления для контролируемого изменения состояния

Это упрощённая версия, но она демонстрирует основные механизмы, которые стоят за магией React hooks.

Как реализовать свой useState? | PrepBro