10 фишек из JavaScript, которые помогут писать более качественный код на React

0
505
views
Photo by Annie Spratt on Unsplash

React построен на основе чистого JavaScript, если не считать некоторых особенностей. В статье «10 JavaScript Concepts You Need to Master React (+ Cheatsheet)», перевод которой опубликовал tproger.ru, собраны 10 основных концепций JavaScript, с которыми вы столкнётесь практически в любом серьёзном приложении на React. Их понимание позволит увереннее работать с библиотекой пользовательских интерфейсов.

Переменные let и const

В JavaScript

Ключевые слова let и const дают более предсказуемые переменные, чем те, что объявлены через var.

У них блочная область видимости: такие переменные существуют только в пределах участка кода, ограниченного фигурными скобками. Такой подход позволяет избежать конфликта переменных, делая код более предсказуемым.

let используется для переменных, которые нужно переназначить после создания. Переменные, объявленные с помощью const, невозможно переназначить или изменить.

let greeting;
    const newUser = true;

    if (newUser) {
      // let-переменные можно переназначить...
      greeting = "Nice to meet you!";
    } else {
      greeting = "Welcome back!";
    }

    // ...а const — нет
    newUser = false; // Uncaught TypeError: Assignment to constant variable.

В React

let и const — предпочтительные ключевые слова для объявления переменных в React, поскольку их поведение предсказуемо. Как и в JS, переменные, которые планируется переназначить, объявляют с помощью let, а те, которые не нужно переназначать, — const.

const также используют при объявлении компонентов в комбинации со стрелочными функциями, поскольку компоненты остаются неизменными.

// Компонент функции, объявленный с помощью const (стрелочная функция)
    const Header = () => {
      // Локальная переменная, объявленная с помощью const
        const username = "Bob";
      return <Header>Welcome back, {username}!;
    }

Шаблонные строки

В JavaScript

Шаблонные строки намного динамичнее базового типа String в JS, получаемого с помощью одинарных или двойных кавычек. С их помощью гораздо проще интерполировать и вставлять значения в строки.

Достаточно использовать синтаксис ${}, чтобы вставить допустимое в JS выражение. Для конкатенации и комбинирования строк не нужен оператор «+», проще писать многострочные объекты типа String, поскольку не нужно создавать новые строки с помощью символа новой строки (\n) или возврата каретки (\r).

Кроме того, в шаблонных строках можно использовать вложенные кавычки (одинарные и двойные), не опасаясь ошибок.

const username = "Fred";
    // Код, в котором строки объединены с помощью «+», трудно читать
    const greeting = "Hi " + username + ", how are you?";

    // Шаблонные строки (``) намного проще писать и читать
    const username = "Anna";
    // Динамические значения вставляются с помощью выражения ${}
    const greeting = `Hi ${username}, how are you?`;

В React

Шаблонные строки используют, чтобы создавать сложные объекты класса String, а также динамически вычислять стили элементов в зависимости от условий. Можно вставлять значения переменных, операторы, вызовы функций и т. д.

function UserCard({ id, name }) {
      // Проверка чётности id пользователя...
        const isOddUser = id % 2 !== 0;

        // Нечётные получают тёмный фон
      return <div className={idOddUser ? 'dark-bg' : ''}>{name}</div>     }
     <UserCard id={1} name="Bob" /> // выводит UserCard с тёмным фоном

Стрелочные функции

В JavaScript

Со стрелочными функциями можно использовать сокращённый синтаксис. Это делает весь код короче. Можно заменить ключевое слово return (возврат происходит по умолчанию, если нет фигурных скобок) и тело функции (фигурные скобки) большой стрелкой (=>).

Проще работать с объектами и классами через ключевое слово this. Кроме того, можно удалить скобки вокруг одиночного параметра.

    // Стандартная функция
    function capitalize(word) {
      return word.toUpperCase();
    }

    // Стрелочная функция
    const capitalize = (word) => {
      return word.toUpperCase();
    }

    // Используем все особенности стрелочной функции
    const capitalize = word => word.toUpperCase();

В React

Если в JavaScript можно создать стандартную функцию, можно создать и стрелочную. Как правило, второй вариант предпочтительнее, потому что синтаксис проще.

Чаще всего стрелочные функции используют для создания компонентов, а также для методов массивов высшего порядка, таких как .map() или .filter().

    const UserList = ({ users }) => {
      return (
{users.map((user, index) => ( ))}
      );
    }

Методы массивов .map(), .filter(), .reduce(), и т. д.

В JavaScript

Так же, как и цикл for, методы массивов map, filter и reduce позволяют перебирать элементы массива:

  • .map() — позволяет преобразовать каждый элемент массива;
  • .filter() — создаёт новый массив со всеми элементами, которые прошли проверку, заданную в функции;
  • .reduce() — позволяет преобразовать весь массив в соответствии с нашими нуждами (даже в другой тип данных).

Указанные методы короче и читаются легче, чем цикл for. Комбинируя эти методы и стрелочные функции, можно ещё больше сократить код.

// Цель: преобразовать массив users в массив usernames
    const users = [
        { name: "Bob", id: 1 },
        { name: "Jane", id: 2 },
        { name: "Fred", id: 3 }
    ];
    const usernames = [];

    // Цикл for
    for (let i = 0; i < users.length; i++) {
      usernames[i] = users[i]
    }

    usernames; // ["Bob", "Jane", "Fred"]

    // .map() - concise + readable
    const usernames = users.map(user => user.username);

    usernames; // ["Bob", "Jane", "Fred"]

В React

Методы .map(), .filter(), .reduce() можно использовать для преобразования данных. Обычно их применяют для динамического отображения элементов и компонентов с помощью .JSX, поскольку эти методы можно связывать в цепочки для последовательных трансформаций.

    function UserList() {
      const users = [
        { name: "Bob", id: 1 },
        { name: "Jane", id: 2 },
        { name: "Fred", id: 3 }
    ];

        // Отбрасываем пользователя с id = 2 и затем распределяем записи и выводим имена пользователей
      return (
        <ul>
          {users
             .filter(user => user.id !== 2)<
             .map(user => <li key={id}>{user.name}</li>)
         }
        </ul>
      );
    };

Деструктурирующее присваивание

В JavaScript

Деструктурирующее присваивание позволяет распаковать массивы или объекты в переменные. Это удобная концепция, поскольку не нужно ссылаться на весь объект, когда мы хотим его использовать.

Деструктуризация позволяет извлечь нужное нам значение из объекта, создав для этого значения независимую переменную. Мы можем сделать код чище, не ссылаясь на объект, когда нам нужно только одно его значение.

    const user = {
      name: "Reed",
      username: "ReedBarger",
      email: "reed@gmail.com",
      details: {
        title: "Programmer"
      }
    };

    // Доступ к объекту без деструктуризации
    console.log(`${user.name}, ${user.email}`); // logs: Reed, reed@gmail.com

    // Деструктуризация объекта для уменьшения повторений
    const { name, email } = user;

    console.log(`${name}, ${email}`); // logs: Reed, reed@gmail.com

    // Деструктуризация объекта со вложенным объектом "details"
    const { username, details: { title } } = user;

    console.log(`${username}, ${title}`); // logs: ReedBarger, Programmer

В React

Чаще всего деструктурирующее присваивание используют, чтобы получить значение одиночного свойства объекта. Если мы передаём в заданный компонент только одно свойство объекта, все остальные нам не нужны. Вместо того чтобы давать ссылку на свойства, мы можем деструктурировать их и передать в компонент одну переменную.

function App() {
  return (
    <div>
      <h1>All Users</h1>
      <UserList users={["Bob", "Jane", "Fred"]} />
    </div>
  );
}

function UserList({ users }) {
  return (
    <ul>
      {users.map((user, index) => (
        <li key={index}>{user}</li>
      ))}
    </ul>
  );
}

Параметры по умолчанию

В JavaScript

Параметру по умолчанию пригодятся, чтобы обрабатывать событие, переданное функцией без аргументов. Также они помогут избежать ошибок и сделать код более предсказуемым.

 // Без параметров по умолчанию
function sayHi(name) {
  return "Hi" + name;
}
sayHi(); // "Hi undefined"

// С параметрами по умолчанию
function sayHi(name = 'Bob') {
  return "Hi" + name;
}
sayHi(); // "Hi Bob"

// С параметрами по умолчанию и стрелочной функцией
const sayHi = (name = 'Jane') => "Hi" + name; 
sayHi(); // "Hi Jane"

В React

Параметры по умолчанию часто применяют при определении свойств. В примере ниже мы используем деструктурирующее присваивание для получения параметра ‘username’ из свойств объекта. И хотя свойство не передано, дефолтное значение установлено на ‘guest’ и компонент всё равно работает.

    const Header = ({ username = "guest" }) => {
      return <header>Welcome, {username}!</header>;
    }

    <Header /> // выводит: Welcome, guest!

Синтаксис spread (три точки — …)

В JavaScript

Синтаксис spread позволяет расширять объекты (их пары ключ-значение) и массивы, получая новые объекты. Этот синтаксис работает только при создании нового объекта или массива.

Синтаксис spread хорош для объединения свойств старого объекта в новом. Когда объект или массив расширяется, создавая новый объект или массив, появляется временная копия.

    // Объединение пустых дефолтных данных с пользовательскими данными из формы подписки 
    // с помощью оператора operator
    const user = {
      name: "",
      email: "",
      phoneNumber: "",
    };

    const newUser = {
      name: "ReedBarger",
      email: "reed@gmail.com",
    };

    /* 
      Последний объект при расширении заменяет значение одноимённых свойств предыдущего объекта на свои    
*/
    const mergedUser = { ...user, ...newUser }; 
    mergedUser; // { name: "ReedBarger", email: "reed@gmail.com", phoneNumber: "" };

В React

Синтаксис spread отлично подходит для динамического создания новых объектов и массивов, зачастую его применяют в React-библиотеках (например Redux) для более предсказуемого изменения данных.

Кроме того, в React такой приём можно использовать, для того чтобы передать данные объекта в виде набора свойств, не обращаясь к ним по очереди: для этого мы можем расширить объект в компонент, поскольку при этом мы получим объект набора свойств.

function App() {
  const name = {
    first: "Reed",
    last: "Barger"
  };

  return (
    <div>
      {/*    
     <UserGreeting 
       first={name.first}
       last={name.last}
      />
      */}
      <UserGreeting {...name} />
    </div>
  );
}

function User({ first, last }) {
  return (
    <p>
      Hi, {first} {last}
    </p>
  );
}

Короткие условные операторы

В JavaScript

В JavaScript есть сокращённая форма записи условных операторов if-else — тернарная операция (ternary). В отличие от if-else тернарные операции являются выражениями. Это даёт большую гибкость, позволяя использовать их так же, как и любые другие выражения (такие как ${} в случае с шаблонными строками).

Тернарные операции не всегда лучше оператора if-else. Например при обработке множественных условий первые окажутся неудобочитаемыми.

let age = 26;
let greeting;

// Без оператора if-else в подобных случаях можно обойтись. Тут мы просто
//присваиваем переменной значение в зависимости от условия
if (age > 18) {
  greeting = "Hello, fellow adult";
} else {
  greeting = "Hey kiddo";
}

//Тернарные операции делают то же самое, но намного короче
const greeting = age > 18 ? "Hello, fellow adult" : "Hey kiddo";
greeting; // 'Hello, fellow adult';

В React

Можно вывести через JSX один результат, если условие истинно, и другой, если оно ложно, при этом записав код намного короче, чем через if-else.

Если результат нужно выводить только при истинности условия, можно использовать оператор &&.

    const Navbar = () => {
      const isAuth = true;

      return (

           <div>
           // Для авторизованных пользователей показывает список ссылок, для остальных — экран авторизации
            {isAuth ? <AuthLinks /> : <Login />}
           // Показывает профиль только авторизованным пользователям
            {isAuth &amp;&amp; <UserProfile/>}
          </div>
      );
    }

Модули ES

В JavaScript

С помощью модулей ES удобно распределять код по файлам приложения. Мы экспортируем то, что хотим передать в другие файлы нашего приложения (в основном переменные и функции), а затем импортируем их там, где нужно.

Можно осуществлять множественный экспорт/импорт с помощью фигурных скобок (и ключевых слов export/import) или одиночный без скобок (с ключевыми словами export default и import).

Такой подход позволяет сделать код модульным. Мы можем писать код там, где он нужен, не собирая всё в один большой файл. В примере ниже показано, как вызвать функцию getLocalTime из отдельного файла в app.js.

// utils/getLocalTime.js
    const getLocalTime = () => new Date().toLocaleTimeString();

    export default getLocalTime;

    // app.js
    import getLocalTime from './utils/getLocalTime.js'

    const App = () => {
      return (
        <div>
          <header>The time is {getLocalTime()}</header>
          ...
        </div>
      );
    }

В React

Экспортировать и импортировать можно практически всё что угодно, не только JS, но и CSS, файлы изображений и т. д.

В React часто не нужно добавлять расширение файла при импорте JavaScript. Расширения нужны только при импорте в JS других типов файлов.

 // App.js
    const App = () =>
hello world!
    // styles.css
    html, body {
      margin: 0;
        padding: 0;
    }
    h1 {
      color: cornflowerblue;
    }

    // index.js
    import React from 'react';
    import './styles.css'

    import ReactDOM from "react-dom";

    import App from "./App";

    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);

Промисы + async/await

В JavaScript

Можно отложить на неопределённое время выполнение некоторых участков кода в JavaScript (например setTimeout(), событие listener или сетевой запрос с fetch API).

Промисы — способ сделать предсказуемым асинхронный код на JS. Они помогают разрешать код, созданный с помощью async. Успешно выполненный код обрабатывается с помощью функций обратного вызова .then(), ошибки — с помощью функции .catch();

async/await — улучшенный синтаксис для работы с промисами, который заставляет асинхронный код выглядеть синхронным.

    // Асинхронный код; 'done' журналирован после данных о положении, несмотря на то что 'done' предполагается
    // исполнить в коде позже
    navigator.geolocation.getCurrentPosition(position => {
      console.log(position);
    }, error => {
      console.error(error);
    });
    console.log("done");

    // Асинхронный код обработан после промиса; мы получаем желаемый результат — данные о положении
    // журналированы, затем журналировано 'done'
    const promise = new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(resolve, reject);
    });

    promise
      .then(position => console.log(position))
      .catch(error => console.error(error))
      .finally(() => console.log('done'));

    // Асинхронный код с async/await выглядит как синхронный, наиболее удобочитаемый способ 
    // работы с промисами
    async function getPosition() {
      // async/await работает только в функциях (пока)
        const result = await new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(resolve, reject);
      });
        const position = await result;
        console.log(position);
        console.log('done');
    }

    getPosition();

В React

Промисы и async/await используют для создания сетевых запросов, таких как обращение к API.

Библиотеки вроде fetch API или axios применяют промисы для запросов, выполнение которых может занять неопределённое время. Ещё промисы и async/await в подобных библиотеках используют для формирования сетевых запросов:

    // Получаем данные через API с помощью базового синтаксиса промиса (обратите внимание на стрелочные функции)
    window.fetch('http://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => console.log(data));

    // Получаем те же данные через API с помощью async/await
    async function getPostData() {
      const response = await window.fetch('http://jsonplaceholder.typicode.com/posts')
        // Нам нужно разрешить два промиса, используя await, чтобы получить итоговые данные
      const data = await response.json();
      console.log(data);
    }
    getPostData();