Sobes.tech
Junior

Кои са хуковете в React?

sobes.tech AI

Отговор от AI

В React, започвайки от версия 16.8, hook-овете предоставят възможност на функционалните компоненти да управляват състоянието и да използват жизнените цикли, които преди това бяха достъпни само за класовите компоненти.

Ето основните вградени hook-ове:

  • useState: Позволява добавяне на вътрешно състояние към функционален компонент. Връща двойка: текущото състояние и функция за неговото обновяване.

    // Пример useState
    import React, { useState } from 'react';
    
    function Counter() {
      const [count, setCount] = useState(0); // Инициализация на състоянието с 0
    
      return (
        <div>
          <p>Натиснахте {count} пъти</p>
          <button onClick={() => setCount(count + 1)}>
            Натисни ме
          </button>
        </div>
      );
    }
    
  • useEffect: Позволява изпълнение на странични ефекти във функционалните компоненти. Подобен е на методите за жизнения цикъл на класовите компоненти (componentDidMount, componentDidUpdate, componentWillUnmount).

    // Пример useEffect
    import React, { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      // Изпълнява се след всеки рендер
      useEffect(() => {
        document.title = `Натиснахте ${count} пъти`;
      });
    
      // Изпълнява се само при първия рендер (празен масив от зависимости)
      useEffect(() => {
        console.log('Компонентът е монтиран');
        return () => {
          console.log('Компонентът е демонтиран'); // Функция за почистване
        };
      }, []);
    
      // Изпълнява се при промяна на count
      useEffect(() => {
        console.log('Count се промени:', count);
      }, [count]);
    
      return (
        <div>
          <p>Натиснахте {count} пъти</p>
          <button onClick={() => setCount(count + 1)}>
            Натисни ме
          </button>
        </div>
      );
    }
    
  • useContext: Позволява абониране за промени в React контекста. Приема обект на контекст, създаден чрез React.createContext().

    // Пример useContext
    import React, { useContext } from 'react';
    
    const ThemeContext = React.createContext('light'); // Създаване на контекст
    
    function ThemedButton() {
      const theme = useContext(ThemeContext); // Използване на контекста
    
      return (
        <button className={theme}>
          Бутон в тема {theme}
        </button>
      );
    }
    
    function App() {
      return (
        <ThemedButton />
      );
    }
    
  • useReducer: Алтернатива на useState за управление на по-сложно състояние, което включва няколко под-стойности или когато следващото състояние зависи от предишното. Подобен е на редуисерите в Redux.

    // Пример useReducer
    import React, { useReducer } from 'react';
    
    const initialState = { count: 0 };
    
    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, initialState);
    
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({ type: 'increment' })}>+</button>
          <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
        </>
      );
    }
    
  • useCallback: Мемоизира функция за обратно извикване. Връща мемоизирана версия на колбека, която се променя само при промяна на една от зависимостите. Полезно за предотвратяване на ненужни повторни рендери на дъщерни компоненти при предаване на колбэкове.

    // Пример useCallback
    import React, { useState, useCallback } from 'react';
    
    function Parent({ addTodo }) {
      const [count, setCount] = useState(0);
    
      const handleAddTodo = useCallback(() => {
        addTodo();
      }, [addTodo]); // зависимост от addTodo
    
      return (
        <>
          <button onClick={() => setCount(count + 1)}>
            Увеличи брояча
          </button>
          <Child onAddItem={handleAddTodo} />
        </>
      );
    }
    
    function Child({ onAddItem }) {
      console.log('Child се рендерира'); // Ще се рендерира само при промяна на onAddItem
      return <button onClick={onAddItem}>Добави елемент</button>;
    }
    
  • useMemo: Мемоизира изчисляемата стойност. Връща мемоизирана стойност, която се пресмята само при промяна на една от зависимостите. Полезно за производителност при тежки изчисления.

    // Пример useMemo
    import React, { useMemo, useState } from 'react';
    
    function expensiveCalculation(num) {
      console.log('Извършва се тежко изчисление...');
      for (let i = 0; i < 1000000; i++) {
        num += 1;
      }
      return num;
    }
    
    function Example() {
      const [count, setCount] = useState(0);
      const [todos, setTodos] = useState([]);
    
      const calculation = useMemo(() => expensiveCalculation(count), [count]); // зависимост от count
    
      const addTodo = () => {
        setTodos(t => [...t, "Нов списък"]);
      };
    
      return (
        <div>
          <div>
            <h2>Моите задачи</h2>
            {todos.map((todo, index) => (
              <p key={index}>{todo}</p>
            ))}
            <button onClick={addTodo}>Добави задача</button>
          </div>
          <hr />
          <div>
            Брой: {count}
            <button onClick={() => setCount(c => c + 1)}>+</button>
            <h2>Тежко изчисление</h2>
            {calculation}
          </div>
        </div>
      );
    }
    
  • useRef: Позволява създаване на мутабелен реф, който се запазва през целия жизнен цикъл на компонента. Често се използва за достъп до DOM елементи или за съхраняване на всяка мутабелна стойност, която не предизвиква повторно рендериране при промяна.

    // Пример useRef
    import React, { useRef } from 'react';
    
    function TextInputWithFocusButton() {
      const inputEl = useRef(null); // създаване на реф
    
      const onButtonClick = () => {
        inputEl.current.focus(); // фокусиране върху DOM елемента
      };
    
      return (
        <>
          <input ref={inputEl} type="text" /> // свързване на реф
          <button onClick={onButtonClick}>Фокус върху полето</button>
        </>
      );
    }
    
  • useImperativeHandle: Настройва дескриптор на инстанция, който се предоставя на родителския компонент при използване на ref. Позволява на дъщерния компонент да разкрива определени методи на родителя. Използва се заедно с forwardRef.

    // Пример useImperativeHandle
    import React, { useRef, useImperativeHandle, forwardRef } from 'react';
    
    const MyInput = forwardRef((props, ref) => {
      const inputRef = useRef();
    
      useImperativeHandle(ref, () => ({
        focusInput: () => {
          inputRef.current.focus();
        }
      }));
    
      return <input ref={inputRef} {...props} />;
    });
    
    function ParentComponent() {
      const inputRef = useRef();
    
      const handleFocus = () => {
        inputRef.current.focusInput(); // метод от дъщерния компонент
      };
    
      return (
        <div>
          <MyInput ref={inputRef} />
          <button onClick={handleFocus}>Фокус чрез Parent</button>
        </div>
      );
    }
    
  • useLayoutEffect: Подобен на useEffect, но се изпълнява синхронно след всички мутации в DOM. Полезен за четене на данни от DOM или за извършване на мутации, които трябва да се случат преди браузърът да изрисува екрана. Може да блокира рендерирането.

    // Пример useLayoutEffect
    import React, { useLayoutEffect, useRef } from 'react';
    
    function Tooltip({ children, text }) {
      const ref = useRef(null);
    
      useLayoutEffect(() => {
        if (ref.current) {
          const { top, height } = ref.current.getBoundingClientRect();
          console.log(`Елементът е на ${top} с височина ${height}`);
        }
      }, [children]);
    
      return (
        <span ref={ref} title={text}>
          {children}
        </span>
      );
    }
    
  • useDebugValue: Показва потребителска метка за hook-ове в React DevTools. Използва се при създаване на собствени hook-ове за отстраняване на грешки.

    // Пример useDebugValue
    import React, { useState, useDebugValue } from 'react';
    
    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      // Показва "Friend Status: Offline" или "Friend Status: Online" в DevTools
      useDebugValue(isOnline === true ? 'Online' : 'Offline');
    
      // ... логика за получаване на статуса на приятеля
    
      return isOnline;
    }
    
    function FriendListItem({ friend }) {
      const isOnline = useFriendStatus(friend.id);
    
      return (
        <li style={{ color: isOnline ? 'зелен' : 'черен' }}>
          {friend.name} - {isOnline ? 'Онлайн' : 'Изключен'}
        </li>
      );
    }
    

Освен вградени hook-ове, съществуват и потребителски hook-ове (Custom Hooks). Това са JavaScript функции, чиито имена започват с "use". Те позволяват повторно използване на логика за състояние между компоненти. Потребителският hook може да извиква други hook-ове.

// Пример потребителски hook
import { useState, useEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState([window.innerWidth, window.innerHeight]);

  useEffect(() => {
    const handleResize = () => {
      setSize([window.innerWidth, window.innerHeight]);
    };
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return size;
}

function MyComponent() {
  const [width, height] = useWindowSize(); // използване на потребителски hook

  return (
    <p>Размер на прозореца: {width}x{height}</p>
  );
}

Важно е да се спазват правилата за hook-ове:

  1. Извиквайте hook-ове само на върховото ниво на React компонента или на потребителски hook. Не ги извиквайте вътре в цикли, условия или вложени функции.
  2. Извиквайте hook-ове само от React функционални компоненти или потребителски hook-ове. Не ги извиквайте от обикновени JavaScript функции или методи на класови компоненти.