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

2 года назад·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.
  • Пропсы - доступны только для чтения. Это такой снимок компонента во времени: каждый рендер получает новую версию пропса.
  • Нельзя менять пропсы внутри компонента. Когда вам нужна интерактивность, вам нужно установить состояние.

Как использовать переменные среды в React

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

О различных способах доступа к переменным среды в React приложении

Если у вас нет опыта server side программирования, переменные среды могут показаться чем-то магическим. Этот недостаток знаний может поставить вас в тупик, когда вы закончите создавать приложения todo на localhost и попытаетесь создать продакшн сборку в первый раз. Если вы хотите узнать, как использовать переменные среды в ваших собственных инструментах, или глубоко погрузиться в то, как переменные среды работают в React, вы можете продолжить чтение этой статьи. Но если вы ищете быстрое решение и используете Create React App, ознакомьтесь с документацией здесь. Пользователи NextJS, ознакомьтесь с документацией здесь.

Проблема, которую мы решаем

Как объявить различные URL-адресов API для локальной разработки и для продакшн сборки.

Как решить эту проблему

Использовать переменные среды. При работе с React переменные среды — это переменные, доступные через глобальный объект process.env. Этот глобальный объект предоставляется вашей средой через NodeJS. И поскольку у нас нет NodeJS в браузере, нам понадобится webpack. В этой статье рассмотрим два способа установки и использования переменных среды для ваших React проектов с помощью webpack: с помощью скриптов npm и с помощью файла .env.

Способ 1: Использование скриптов npm для установки переменных среды

Во-первых, установите webpack и webpack-cli из npm:
npm install --save-dev webpack webpack-cli
Перейдите в файл package.json, проверьте поле scripts и найдите команды, которые запускают webpack. Вероятно, это будет выглядеть примерно так:
{
  // ...
  scripts: {
    "dev": "webpack --config webpack.config.dev.js",
    "build": "webpack --config webpack.config.build.js"
  }
}
Давайте добавим некоторые переменные окружения с флагом --env в scripts:
{
  // ...
  scripts: {
    "dev": "webpack --env.API_URL=http://localhost:8000 --config webpack.config.dev.js",
    "build": "webpack --env.API_URL=https://www.myapi.com --config webpack.config.build.js"
  }
}
Мы добавили --env.API_URL= часть в обоих скриптах. Теперь запустите команду npm run dev, перейдите к React компоненту и используйте process.env.API_URL:
const App = () => <h1>{process.env.API_URL}</h1>;
И тут проект должен сломаться.
Сломается он потому, что когда мы используем переменные окружения в клиентском коде, они на самом деле просто служат заполнителями, которые будут заменены при компиляции нашего кода. Проблема в том, что мы не сказали webpack скомпилировать эти переменные в реальные значения. Давайте сделаем это в нашем конфигурационном файле webpack с плагином DefinePlugin:
const webpack = require('webpack'); // DefinePlugin это часть webpack, поэтому это require обязателен

// возвращаем функцию из config файла
// переменная `env` будет просто объектом { API_URL: 'http://localhost:8000' }
// в ней будут содержаться все переменные среды, которые мы указали в package.json

module.exports = (env) => {
  // этот объект это сама конфигурация webpack
  return {
    plugins: [
      // добавим плагин в список плагинов
      new webpack.DefinePlugin({ `process.env.API_URL`: JSON.stringify(${env.API_URL}) })
    ]
  };
};
DefinePlugin требует, чтобы вы буквально определили свои «переменные среды». Вы также можете применить .reduce к переменным среды, чтобы получить объект:
module.exports = (env) => {
  // создаем объект из переменных среды
  const envKeys = Object.keys(env).reduce((prev, next) => {
    prev[`process.env.${next}`] = JSON.stringify(env[next]);
    return prev;
  }, {});

  return {
    plugins: [
      new webpack.DefinePlugin(envKeys)
    ]
  };
};
Если вы запустите команду сейчас, все скомпилируется, и ваш process.env.API_URL будет скомпилирован в правильный URL-адрес на основе переменной среды.

Способ 2: Использование файла .env для установки переменных среды

Вся идея здесь состоит в том, чтобы создать файл (называемый просто .env), заполненный переменными среды. Чтобы защитить пароли и другие значения переменных среды, добавьте файл .env в .gitignore. Фронтенд код будет ссылаться на одну и ту же переменную среды (process.env.API_URL) в обеих средах (при локальной разработке и на продакшене), но поскольку вы определили разные значения в своих .env, скомпилированные значения будут отличаться.

Создадим файл .env

Этот файл должен находиться в корневом каталоге проекта и называться .env. Добавим переменную:
API_URL=http://localhost:8000

Обработка файла .env

Теперь нам нужен какой-то способ обработки файлов и их содержимого. Для этого мы собираемся использовать популярный npm пакет под названием dotenv. Dotenv широко используется (create-react-app использует его). Он будет получать переменные из нашего файла .env и добавлять их в глобальный process.env.
$ npm install --save-dev dotenv

Добавление переменных в проект React

Есть одна проблема. Dotenv работает только на стороне сервера. А мы хотим использовать переменные среды на стороне клиента, на фронтенде. В данном случае мы разрабатываем клиентскую часть. И dotenv нужна какая-то среда для фактического хранения переменных. Здесь поможет Webpack. Воспользуемся плагином DefinePlugin в нашей webpack конфигурации:
const webpack = require('webpack');
const dotenv = require('dotenv');

module.exports = () => {
  // dotenv вернет объект с полем parsed 
  const env = dotenv.config().parsed;
  
  // сделаем reduce, чтобы сделать объект
  const envKeys = Object.keys(env).reduce((prev, next) => {
    prev[`process.env.${next}`] = JSON.stringify(env[next]);
    return prev;
  }, {});

  return {
    plugins: [
      new webpack.DefinePlugin(envKeys)
    ]
  };
};
При необходимости проверьте параметры конфигурации dotenv в документации на github. Вызов .config() в dotenv вернет объект со всеми переменными среды, установленными в вашем файле .env через поле parsed. Теперь давайте проверим наш React код:
const App = () => <h1>{process.env.API_URL}</h1>;
И это работает! Он показывает значение переменной среды API_URL, определенной в .env. Осталась только одна проблема: нам все еще нужно определить различные API_URL для локальной разработки и продакшена.

Различные переменные среды для разных сред

Вся идея состоит в том, чтобы создать разные файлы .env для разных сред и позволить webpack выбрать правильный файл .env в зависимости от активной среды. Поэтому создайте два файла в корневом каталоге проекта:
  • .env (содержит все переменные среды для продакшн)
  • .env.development (содержит все переменные среды для локальной разработки)
Чтобы было ясно: мы добавляем к имени файла .env сопоставление имени среды. Общепринятой практикой является использование исходного файла .env для продакшн сборки, поэтому мы не будем добавлять постфикс для продакшн .env .

Настройка активной среды с помощью scripts в package.json

Мы собираемся использовать scripts (как мы это делали в методе 1), чтобы установить текущую среду в нашем package.json:
{
  "scripts": {
    "dev": "webpack --env.ENVIRONMENT=development --config webpack.config.dev.js",
    "build": "webpack --env.ENVIRONMENT=production --config webpack.config.build.js"
  }
}
Так как мы определили нашу среду в нашем package.json, теперь она доступна в нашей конфигурации webpack. Следующим шагом будет переход к webpack конфигурации и дать ему использовать файл .env, принадлежащий активной среде. Как и раньше, мы используем dotenv, но теперь мы указываем пользовательский path в параметрах.
const webpack = require('webpack');
const dotenv = require('dotenv');
const fs = require('fs'); // для проверки существования файла
const path = require('path'); // для получения текущего пути

module.exports = (env) => {
  // получаем корневой путь (предполагаем, что webpack config лежит в корне проекта)
  const currentPath = path.join(__dirname);
  
  // путь по умолчанию (будет использован для продакшена - `.env`)
  const basePath = currentPath + '/.env';

  // склеиваем имя среды с именем файла для получения имени env файла
  const envPath = basePath + '.' + env.ENVIRONMENT;

  // проверяем существует ли env файл, если нет используем имя по умолчанию
  const finalPath = fs.existsSync(envPath) ? envPath : basePath;

  // устанавливаем параметр path в dotenv
  const fileEnv = dotenv.config({ path: finalPath }).parsed;
  
  // сделаем reduce, чтобы получить объект
  const envKeys = Object.keys(fileEnv).reduce((prev, next) => {
    prev[`process.env.${next}`] = JSON.stringify(fileEnv[next]);
    return prev;
  }, {});

  return {
    plugins: [
      new webpack.DefinePlugin(envKeys)
    ]
  };
Эта вся необходимая настройка, но вы можете создать больше .env файлов для большего количества сред (например, .env.staging) по аналогии.