Как передать пропсы React компоненту

9 месяцев назад·5 мин. на чтение

Компоненты React используют пропсы (props) для связи друг с другом. Каждый родительский компонент может передавать некоторую информацию своим дочерним компонентам, предоставляя им пропсы. Пропсы могут напоминать атрибуты HTML, но вы можете передавать через них любое значение JavaScript, включая объекты, массивы и функции.

Содержание туториала по React Компоненты React используют пропсы (props) для связи друг с другом. Каждый родительский компонент может передавать некоторую информацию своим дочерним компонентам, предоставляя им пропсы. Пропсы могут напоминать атрибуты HTML, но вы можете передавать через них любое значение JavaScript, включая объекты, массивы и функции.

Известные пропсы

Пропсы — это информация, которую вы передаете тегу JSX. Например, className, src, alt, width и height — вот некоторые пропсы, которые вы можете передать <img>:
function Avatar() {
  return (
    <img
      className="avatar"
      src="https://example.com/userpic.jpg"
      alt="Userpic"
      width={100}
      height={100}
    />
  );
}

export default function Profile() {
  return <Avatar />;
}
Пропсы, которые вы можете передать тегу <img>, предопределены (ReactDOM соответствует стандартам HTML). Но вы можете передать любые пропсы своим собственным компонентам, таким как <Avatar>, чтобы настроить их.

Передача пропсов в компонент

В этом коде компонент Profile не передает никаких пропсов своему дочернему компоненту Avatar:
export default function Profile() {
  return <Avatar />;
}
Вы можете передать в Avatar некоторые пропсы в два этапа.

Шаг 1: Передайте пропсы дочернему компоненту

Во-первых, передайте некоторые пропсы в Avatar. Например, давайте передадим два пропса: person (объект) и size (число):
export default function Profile() {
  return (
    <Avatar person={{ name: 'User Name 1', imageId: '12345' }} size={100} />
  );
}
Если двойные фигурные скобки после person= вас смущают, помните, что они являются просто объектом внутри фигурных скобок JSX. Теперь вы можете прочитать эти пропсы внутри компонента Avatar.

Шаг 2: Прочтите пропсы внутри дочернего компонента

Вы можете прочитать эти пропсы, указав их имена - person, size - разделенные запятыми внутри ({ и }) непосредственно после function Avatar. Это позволяет использовать их внутри кода Avatar, как если бы вы использовали переменную.
function Avatar({ person, size }) {
  // person и size можно здесь использовть
}
Добавьте немного логики в Avatar, которая использует пропсы person, size в отображении, и все готово. Теперь вы можете настроить Avatar для отображения разными способами с разными пропсы.
// utils.js

export function getImageUrl(person, size = 's') {
  return 'https://example.com/' + person.imageId + size + '.jpg';
}
// App.js

import { getImageUrl } from './utils.js';

function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function Profile() {
  return (
    <div>
      <Avatar
        size={100}
        person={{
          name: 'User Name 1',
          imageId: '12345',
        }}
      />
      <Avatar
        size={80}
        person={{
          name: 'User Name 2',
          imageId: '12346',
        }}
      />
      <Avatar
        size={50}
        person={{
          name: 'User Name 3',
          imageId: '12347',
        }}
      />
    </div>
  );
}
Пропсы позволяют вам думать о родительских и дочерних компонентах независимо друг от друга. Например, вы можете изменить пропсы person или size внутри Profile, не задумываясь о том, как Avatar их использует. Точно так же вы можете изменить то, как Avatar использует эти пропсы, не заглядывая в Profile. Вы можете думать о пропсах как о «ручках», которые вы можете регулировать. Они выполняют ту же роль, что и аргументы для функций — на самом деле пропсы являются единственным аргументом для вашего компонента. Функции компонента React принимают один аргумент, объект props:
function Avatar(props) {
  let person = props.person;
  let size = props.size;
  // ...
}
Обычно вам не нужен весь объект пропса, поэтому можно разбить его на отдельные пропсы. Не пропустите пару фигурных скобок { и } внутри ( и ) при объявлении пропсов:
function Avatar({ person, size }) {
  // ...
}
Этот синтаксис называется «деструктурированием» и эквивалентен чтению свойств из параметра функции:
function Avatar(props) {
  let person = props.person;
  let size = props.size;
  // ...
}

Как указать значения по умолчанию для пропса

Если вы хотите присвоить пропсу значение по умолчанию, чтобы использовать его, когда значение не указано, вы можете сделать это с помощью деструктуризации, поставив = и значение по умолчанию сразу после параметра:
function Avatar({ person, size = 100 }) {
  // ...
}
Теперь, если в <Avatar person={...} /> передан пропс size, размер будет установлен на 100. Значение по умолчанию используется только в том случае, если параметр size отсутствует или если вы передаете size={undefined}. Но если вы передадите size={null} или size={0}, значение по умолчанию не будет использоваться.

Перенаправление пропсов с синтаксисом распыления JSX

Иногда отправка пропсов повторяется:
function Profile({ person, size, isSepia, thickBorder }) {
  return (
    <div className="card">
      <Avatar
        person={person}
        size={size}
        isSepia={isSepia}
        thickBorder={thickBorder}
      />
    </div>
  );
}
В повторяющемся коде нет ничего плохого. Но иногда хочется сделать код короче. Некоторые компоненты передают все свои пропсы своим дочерним компонентам, например, как Profile делает с Avatar. Поскольку они не используют никакие свои пропсы напрямую, может иметь смысл использовать более краткий синтаксис распыления (spread):
function Profile(props) {
  return (
    <div className="card">
      <Avatar {...props} />
    </div>
  );
}
Это пример перенаправления всех пропсов Profile в Avatar без перечисления каждого из их имен. Используйте расширенный синтаксис с ограничениями. Если вы используете его в каждом компоненте - значит что-то не так. Часто это указывает на то, что следует разделить компоненты и передать дочерние компоненты как JSX. Подробнее об этом далее.

Передача JSX в качестве дочернего компонента

Обычно встроенные теги браузера вкладывают друг в друга:
<div>
  <img />
</div>
Иногда вы захотите вложить свои собственные компоненты таким же образом:
<Card>
  <Avatar />
</Card>
Когда вы вкладываете контент в тег JSX, родительский компонент получит этот контент в просе, называемом children. Например, компонент Card ниже получит проп children, который является <Avatar />, и отобразит его в обертке div:
// App.js

import Avatar from './Avatar.js';

function Card({ children }) {
  return <div className="card">{children}</div>;
}

export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{
          name: 'User Name',
          imageId: '12345',
        }}
      />
    </Card>
  );
}
// Avatar.jsx

import { getImageUrl } from './utils.js';

export default function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}
// utils.js

export function getImageUrl(person, size = 's') {
  return 'https://example.com/' + person.imageId + size + '.jpg';
}
Вы можете думать о компоненте с пропсом children как о специальной лозейке, которую можно «заполнить» родительскими компонентами с произвольным JSX. Вы часто будете использовать проп children для визуальных оболочек: панелей, сеток и т.д.

Как пропсы меняются со временем

Компонент Clock ниже получает два пропса от своего родительского компонента: color и time. (Код родительского компонента опущен, поскольку он использует состояние, в которое мы пока не будем углубляться.)
export default function Clock({ color, time }) {
  return <h1 style={{ color: color }}>{time}</h1>;
}
Этот пример иллюстрирует, что компонент может получать пропсы с течением времени. Проп не всегда статичен. Здесь проп time меняется каждую секунду, а проп color меняется, когда вы выбираете другой цвет. Пропсы отражают данные компонента в любой момент времени, а не только в начале. Пропсы иммутабельны — термин из информатики, означающий «неизменяемый». Когда компоненту необходимо изменить свои пропсы (например, в ответ на взаимодействие с пользователем или новые данные), ему придется «попросить» родительский компонент передать ему другие пропсы — новый объект. Затем его старые пропсы будут отброшены, и в конечном итоге движок JavaScript очистит занятую ими память. Не пытайтесь "изменить пропсы" напрямую. Когда вам нужно отреагировать на пользовательский ввод (например, изменить выбранный цвет), вам нужно будет "установить состояние", о котором вы можете узнать в разделе "Состояние - память компонента".

Резюме

  • Чтобы передать пропсы, добавьте их в JSX, как и в случае с атрибутами HTML.
  • Чтобы прочитать пропсы, используйте синтаксис деструктурирования function Avatar({ person, size }).
  • Вы можете указать значение по умолчанию, например size = 100, которое используется для отсутствующих и неопределенных пропсов.
  • Вы можете перенаправить все пропсы с помощью синтаксиса распыления JSX <Avatar {...props} />, но не злоупотребляйте им.
  • Вложенный JSX, такой как <Card><Avatar /></Card>, будет отображаться как проп children компонента Card.
  • Пропсы - доступны только для чтения. Это такой снимок компонента во времени: каждый рендер получает новую версию пропса.
  • Нельзя менять пропсы внутри компонента. Когда вам нужна интерактивность, вам нужно установить состояние.

5 кастомных React хуков, которые улучшат ваш код

год назад·3 мин. на чтение

В этой статье рассмотрим несколько очень полезных React хуков.

1. Хук useWindowSize - хук для получения размера экрана

Я уверен, что в некоторых проектах, над которыми вы работали, вам нужно было получить ширину и высоту окна пользователя. Так вот, теперь у вас есть хук для этого, так что вы можете сделать это еще проще, чем раньше.
import { useState, useEffect } from "react";

interface WindowSize {
  width: number;
  height: number;
}

const useWindowSize = (): WindowSize => {
  const [windowSize, setWindowSize] = useState<WindowSize>({
    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;
};
Этот хук может быть особенно полезен при реализации отзывчивого дизайна, когда по какой-то причине вам нужно запустить определенный код при определенных размерах. Более продвинутый вариант этого хука можно найти в статье “Хук useResize для отслеживания ширины экрана в ReactJS”.

2️. Хук useKeyPress - хук для определения нажатия клавиши

Следующий хук позволяет определить, когда нажата определенная клавиша. Это может вызвать события или действия, основанные на нажатой клавише. Например, для закрытия модального окна, отправки формы и т.д.
import { useState, useEffect } from "react";

const useKeyPress = (targetKey: string): boolean => {
  const [keyPressed, setKeyPressed] = useState(false);

  const downHandler = ({ key }: KeyboardEvent) => {
    if (key === targetKey) {
      setKeyPressed(true);
    }
  };

  const upHandler = ({ key }: KeyboardEvent) => {
    if (key === targetKey) {
      setKeyPressed(false);
    }
  };

  useEffect(() => {
    window.addEventListener("keydown", downHandler);
    window.addEventListener("keyup", upHandler);

    return () => {
      window.removeEventListener("keydown", downHandler);
      window.removeEventListener("keyup", upHandler);
    };
  }, []);

  return keyPressed;
};
Пример использования:
const closeModalKeyPress = useKeyPress("Escape");

3. Хук useInterval - хук для вызова функции setInterval

Этот хук позволяет вам использовать функцию setInterval в качестве хука. Как и функция setInterval, этот хук имеет множество применений, например, анимация, обновление данных через регулярные промежутки времени или даже установка таймера.
import { useState, useEffect, useRef } from "react";

const useInterval = (callback: () => void, delay: number | null) => {
  const savedCallback = useRef<() => void>();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    function tick() {
      savedCallback.current && savedCallback.current();
    }
    if (delay !== null && delay > 0) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    } else {
      tick();
    }
  }, [delay]);
};
Можно использовать этот хук следующим образом:
const [count, setCount] = useState(0);

useInterval(() => {
  setCount(count + 1);
}, 1000);

4. Хук useDebounce

Теперь мы поговорим о хуке, который выполняет функцию только после того, как пройдет определенное количество времени без ее вызова. Это полезно, например, для ограничения скорости вызовов API или обновления состояния при изменении ввода, например, когда вы набираете текст в поисковой строке.
import { useEffect, useRef } from 'react';

export function useDebounce(callback:Function, timeout: number = 200, deps: Array<any> = []) {
  const data = useRef({ firstTime: true });
  useEffect(() => {
    const { firstTime, clearFunc } = data.current;

    const handler = setTimeout(() => {
      if (clearFunc && typeof clearFunc === 'function') {
        clearFunc();
      }
      data.current.clearFunc = callback();
    }, timeout);

    return () => {
      clearTimeout(handler);
    };
  }, [timeout, ...deps]);
}

export default useDebounce;
Пример использования:
const [inputValue, setInputValue] = useState("");

useDebounce(() => {
  // вызов APIl
}, 500);

5️. Хук useThrottle

Throttle означает, что функция будет выполняться один раз за каждый заданный промежуток времени. Это может быть полезно для предотвращения быстрого запуска слишком большого количества вызовов API или событий.
import { useEffect, useRef } from 'react';

export const useThrottle = (callback:Function, delay: number = 200, deps: Array<any> = []) => {
  const lastRan = useRef(Date.now());

  useEffect(
    () => {
      const handler = setTimeout(function() {
        if (Date.now() - lastRan.current >= delay) {
          callback();
          lastRan.current = Date.now();
        }
      }, delay - (Date.now() - lastRan.current));

      return () => {
        clearTimeout(handler);
      };
    },
    [delay, ...deps],
  );
};

export default useThrottle;
Пример:
const [inputValue, setInputValue] = useState("");

useThrottle(() => {
  // вызов API
}, 500);
Еще больше крутых хуков вы можете найти в каталоге хуков.