Translate

Поиск по этому блогу

Показаны сообщения с ярлыком React.js. Показать все сообщения
Показаны сообщения с ярлыком React.js. Показать все сообщения

среда, 17 июня 2020 г.

Ошибки при работе c React state.

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



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

Поскольку мы все учимся, это также означает, что мы все также склонны к ошибкам. Хорошо. Цель - стать лучше и стать лучше. Если ты ошибаешься и учишься на ней, у тебя все отлично! Но если вы не научились чему-то новому и продолжаете повторять одни и те же ошибки, ну ... тогда, похоже, вы застаётесь в своей карьере.

В этом духе, вот три распространенных ошибки, которые я часто вижу во время обзоров кода, которые младшие разработчики делают при работе с состоянием компонента React. Мы рассмотрим каждую ошибку и затем обсудим, как ее исправить.

  1. Изменение состояния напрямую.

  2. При изменении состояния компонента важно, чтобы вы возвращали новую копию состояния с изменениями, а не изменяли текущее состояние напрямую. Если вы неправильно измените состояние компонента, алгоритм сравнения React не поймает изменение, и ваш компонент не будет обновляться должным образом. Давайте посмотрим на пример.

    Скажем, у вас есть состояние, которое выглядит так:

    this.state = {
      colors: ['red', 'green', 'blue']
    }
    


    И теперь вы хотите добавить цвет «yellow» в этот массив. Может быть заманчиво сделать это:

    this.state.colors.push('yellow')
    


    Или даже это:

    this.state.colors = [...this.state.colors, 'yellow']
    


    Но оба эти подхода неверны! При обновлении состояния в компоненте класса вам всегда нужно использовать метод setState, и вы всегда должны быть осторожны, чтобы не изменять объекты. Вот правильный способ добавить элемент в массив:

    this.setState(prevState => ({ colors: [...prevState.colors, 'yellow'] }))
    


    И это приводит нас прямо к ошибке номер два.

  3. Установка состояния, которое опирается на предыдущее состояние без использования функции

  4. Есть два способа использовать метод setState.

    Первый способ - предоставить объект в качестве аргумента.

    Второй способ - предоставить функцию в качестве аргумента.

    Итак, когда вы хотите использовать один поверх другого?

    Если бы у вас была, например, кнопка, которую можно включить или отключить, у вас может быть элемент состояния под названием isDisabled, который содержит логическое значение. Если вы хотите переключить кнопку с включенного на отключенное, может возникнуть соблазн написать что-то вроде этого, используя объект в качестве аргумента:

    this.setState({ isDisabled: !this.state.isDisabled })
    


    Итак, что с этим не так? Проблема заключается в том, что обновления состояния React могут быть пакетными, а это означает, что в одном цикле обновления может происходить несколько обновлений состояния. Если ваши обновления должны были быть пакетными, и у вас было несколько обновлений до включенного / отключенного состояния, конечный результат может быть не тем, что вы ожидаете.

    Более правильным способом обновления состояния здесь было бы предоставление функции предыдущего состояния в качестве аргумента:

    this.setState(prevState => ({ isDisabled: !prevState.isDisabled }))
    


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

    То же самое верно для чего-то вроде увеличения счетчика.

    Не делайте этого!!!:

    this.setState({ counterValue: this.state.counterValue + 1 })
    


    Делайте правильно!!!:

    this.setState(prevState => ({ counterValue: prevState.counterValue + 1 }))
    


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


  5. Не забывайте, что setState является асинхронным

  6. Наконец, важно помнить, что setState является асинхронным методом. В качестве примера, давайте представим, что у нас есть компонент с состоянием, которое выглядит так:

    this.state = { name: 'John' }
    


    И затем у нас есть метод, который обновляет состояние и затем выводит состояние на консоль:

    this.setState({ name: 'Matt' })
    console.log(this.state.name)
    


    Вы можете подумать, что вывод будет 'Matt' на консоль, но это не так! Это будет - «Джон»!

    Причина этого в том, что опять же setState является асинхронным. Это означает, что он собирается запустить обновление состояния, когда доберется до строки, которая вызывает setState, но код ниже будет продолжать выполняться, поскольку асинхронный код не является блокирующим.

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

    Правильный способ записать текущее состояние после обновления:

    this.setState({ name: 'Matt' }, () => console.log(this.state.name))
    


    Намного лучше! Теперь вывод в консоль будет 'Matt', как и ожидалось.
Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.


Удачного кодирования!

Оригинал можно посмотреть здесь: Tyler Hawkins                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer
Помочь проекту (любая валюта). DONATE

четверг, 28 мая 2020 г.

React. Как использовать «useContext» в React Hooks

Следующая статья посвящена пониманию работы хуков «useContext» в компонентах React.



Хук «useContext» используется для создания общих данных, к которым можно обращаться по всей иерархии компонентов, не пропуская реквизиты - props вручную до каждого уровня. Определенный контекст будет доступен для всех дочерних компонентов без использования props.

Ниже приводится цитата о контексте с официальной веб-страницы React:

Контекст позволяет передавать данные через дерево компонентов без необходимости передавать пропсы на промежуточных уровнях.

В типичном React-приложении данные передаются сверху вниз (от родителя к дочернему компоненту) с помощью пропсов. Однако, этот способ может быть чересчур громоздким для некоторых типов пропсов (например, выбранный язык, UI-тема), которые необходимо передавать во многие компоненты в приложении. Контекст предоставляет способ делиться такими данными между компонентами без необходимости явно передавать пропсы через каждый уровень дерева.


В статье ниже мы сосредоточимся на том, как «useContext» работает программно. Но прежде чем начать с «useContext», давайте сначала разберемся, как сделать данные доступными от родительских к дочерним компонентам, используя атрибуты «props». Затем мы введем хук «useContext» для компонента, чтобы понять его влияние.

Передача данных в дочерние компоненты без «useContext»

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

import React, { useState } from "react";

function UserDetailsComponent() {
  var [userDetails, setUserDetails] = useState({
    name: "Yaroslav",
    age: 50
  });
                                               
  return (
    <div>
      <h1>This is the Parent Component</h1>
    </div>                                         
  )
}


Теперь давайте введем еще два компонента в иерархию, мы добавим к иерархии «ChildComponent», который по-прежнему не будет получать доступ к данным, доступным из родительского компонента. ChildComponent должен далее передать эти данные своему дочернему компоненту «SubChildComponent», где данные, наконец, отображаются.

Расширяя код далее, мы отправим переменную состояния далее в дочерний компонент «ChildComponent» с помощью «props», который отправит эти данные дальше в его дочерний компонент «SubChildComponent». Давайте посмотрим на код ниже, чтобы объяснить дальнейшие сценарии.

import React, { useState } from "react";

function UserDetailsComponent() {
  var [userDetails, setUserDetails] = useState({
    name: "Yaroslav",
    age: 50,
  });

  return (
    <div>
      <h1>This is the Parent Component</h1>
      <hr />
      <ChildComponent userDetails={userDetails}></ChildComponent>
    </div>
  );
}

function ChildComponent(props) {
  return (
    <div>
      <h2>This is Child Component</h2>
      <hr />
      <SubChildComponent userDetails={props.userDetails}></SubChildComponent>
    </div>
  );
}

function SubChildComponent(props) {
  return (
    <div>
      <h3>This is Sub Child Component</h3>
      <h4>User Name: {props.userDetails.name}</h4>
      <h4>User Age: {props.userDetails.age}</h4>
    </div>
  );
}



В приведенном выше коде родительский компонент определяет некоторые переменные состояния, которые не используются внутри компонента, вместо этого он отправляется далее дочернему компоненту «ChildComponent» в качестве данных «props». «ChildComponent» не использует «userDetails» и далее отправляет данные своему дочернему компоненту «SubChildComponent» в качестве «propsх» данных. Этот «SubChildComponent» использует данные, переданные ему как «props», и отображает имя пользователя и возраст в компоненте.

Вы можете поиграть с вышеуказанным кодом в следующем онлайн-редакторе: Code Sandbox link

Проблема с вышеуказанным кодом

Я бы попытался объяснить проблему с приведенным выше кодом, указав приведенные ниже утверждения. Надеюсь, это будет иметь смысл для вас.
  1. Данные состояния определяются на верхнем уровне, определенные данные передаются дочернему компоненту, где данные больше не используются. Данные от родителя передаются как «props» данные. Эти «props» данные не влияют на дочерний компонент, но, тем не менее, дочерний компонент должен поддерживать данные «props».
  2. «ChildComponent» далее передает эти «props» данные «SubChildComponent». «ChildComponent» здесь просто идут издержки на управление «props» из родительского компонента, только для того, чтобы сделать его доступным для других дочерних компонентов.
  3. Нам необходимо явно передавать «props» даже тем компонентам, которые даже не используют его только для того, чтобы сделать данные доступными для иерархии ниже. Мы поддерживаем постоянную передачу данных «props» по всей иерархии приложения.
Я надеюсь, что вы сможете решить проблему с передачей «props» по всей иерархии приложения. 😉

«UseContext» для спасения…

Чтобы решить вышеупомянутую проблему передачи «props» данных даже тем компонентам, которые не требуют этого только потому, что данные требуются в дальнейшей иерархии, мы можем использовать «Context API».

«Context API» позволяет нам определять контекстный объект, который хранит некоторые данные и сделает его доступным по всей иерархии, не передавая данные как «props». Для упрощения, контекст предоставляет контейнер, содержащий некоторые данные и делающий его доступным для всей иерархии компонентов ниже.

Давайте воссоздадим приведенный выше пример с «Context API» и увидим разницу.

Чтобы достичь этого, мы сначала создадим объект контекста. После того, как объект создан с использованием React.createContext. Затем мы будем использовать этот объект контекста в компоненте верхнего уровня и добавим данные, необходимые для всей иерархии. UserDetailContext.Provider используется для предоставления значения созданному объекту контекста. Объект, который необходимо добавить, предоставляется атрибуту value.

import React, { useState } from "react";

var userDetailContext = React.createContext(null);

export default function UserDetailsComponent() {
  var [userDetails] = useState({
    name: "Mayank",
    age: 30
  });

  return (
    <userDetailContext.Provider value={userDetails}>
      <h1>This is the Parent Component</h1>
      <hr />
      <ChildComponent userDetails={userDetails} />
    </userDetailContext.Provider>
  );
}


В приведенном выше коде мы создали объект контекста userDetailsContext и добавляем данные о состоянии в контекст в UserDetailsComponent. Поскольку данные добавляются в контекст, предоставленный в этом компоненте, любой компонент в иерархии будет иметь доступ к данным контекста.

Далее, мы хотим, чтобы данные получали доступ через SubChildComponent, не передавая их как «props» данные. Чтобы получить доступ к данным в SubChildComponent, нам нужно получить доступ к контексту, созданному ранее на верхнем уровне, и получить доступ к данным, доступным в этом контексте.

К контекстным данным, добавленным сверху, можно получить доступ через SubChildComponent, используя ключевое слово useContext

React.useContext(userDetailContext);

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

import React, { useState } from "react";

var userDetailContext = React.createContext(null);

export default function UserDetailsComponent() {
  var [userDetails] = useState({
    name: "Yaroslav",
    age: 50,
  });

  return (
    <userDetailContext.Provider value={userDetails}>
      <h1>This is the Parent Component</h1>
      <hr />
      <ChildComponent userDetails={userDetails} />
    </userDetailContext.Provider>
  );
}

function ChildComponent(props) {
  return (
    <div>
      <h2>This is Child Component</h2>
      <hr />
      <SubChildComponent />
    </div>
  );
}

function SubChildComponent(props) {
  var contextData = React.useContext(userDetailContext);
  return (
    <div>
      <h3>This is Sub Child Component</h3>
      <h4>User Name: {contextData.name}</h4>
      <h4>User Age: {contextData.age}</h4>
    </div>
  );
}



В приведенном выше примере мы видим, что ключевое слово useContext сделает данные родительского компонента доступными для дочерних компонентов на любом уровне в иерархии, не раскрывая их как «props» данные. Вы можете играть с кодом в следующем онлайн-редакторе ... Code Sandbox link

Использование

Считается, что связка двух хуков - useContext + useReducer(о нем я расскажу в следующий раз) может полностью заменить Redux! Мнения "за":
  • Вы можете использовать хуки (useContext + useReducer) вместо Redux в не больших приложениях (где нет необходимости в больших комбинированных Reducers). В данном случае Redux действительно может оказаться избыточным.
Мнения "против":
  • Большое количество кода уже написано на связке React + Redux и переписывать его на хуки (useContext + useReducer) кажется мне не целесообразным, по крайней мере сейчас.
  • Redux — проверенная библиотека, хуки — нововведение, их интерфейсы и поведение может измениться в дальнейшем.
  • Для того чтобы сделать использование useContext + useReducer действительно удобным, придется написать некоторые велосипеды.
Выводы делайте сами, а мой ниже...😉

Вывод

«UseContext» - это удивительный способ избавиться от издержек, связанных с передачей данных через props на разные уровни иерархии, даже если это не требуется.

Попробуйте поработать с React Hooks некоторое время и эти интересные функции, которые заставят вас задуматься о функциональном программировании.

Хотите оставаться в курсе новинок в области JS-программирования? Подписывайтесь на мой канал, вступайте в группу на Facebook. Если понравившаяся статья оказалась еще и полезной, то буду благодарен вашим пожертвованиям на развитие сайта - кнопка DONATE ниже (любая валюта).

Удачного кодирования!                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer
Помочь проекту (любая валюта). DONATE

среда, 27 мая 2020 г.

React . Детальный обзор Context API

React Context API предоставляют механизм для передачи данных через дерево компонентов без передачи реквизитов на каждый уровень вручную.



Нам не нужно передавать данные на каждый уровень, вместо этого мы создаем контекст для компонента более высокого уровня и делаем данные контекста доступными для дочерних или дочерних компонентов. Статья посвящена подробному пониманию концепций React Context.

Друзья, я ранее уже много раз рассказывал вам об использовании реакт-хуков для быстрого, удобного и простого построения ваших приложений. Если вы что-то подзабыли, то welcome к моим постам по - React. Там вы найдете много простых и интересных решений, в частности - полноценное приложение построенное только на функциональных компонентах и реакт-хуках. Стоит посмотреть построение React-Сайта и некоторые мои статьи и переводы на gitHub Там я много пишу о самых распространенных и не очень решениях для построений реакт-приложений.


Когда нужно использовать React Context

В этом разделе мы увидим, когда нам нужно использовать React Context API. API React Context просты в использовании и очень эффективны. Приложение React может состоять из иерархии Компонентов, имеющих отношение Parent-Child, иерархия может иметь несколько уровней.

Предположим, что наше приложение содержит компонент на верхнем уровне, содержащий некоторые данные, которые должны использоваться компонентом на нижнем уровне. Компоненты содержатся в древовидной структуре, где один из компонентов является родительским компонентом, а другие компоненты представляют дочерние компоненты, эти дочерние компоненты могут иметь дополнительные дочерние компоненты, представляющие древовидную структуру.



Пример кода:

import React from "react";

export default class GrandParents extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      surName: "Gupta",
    };
  }
  render() {
    return (
      <>
        <h1>This is the Grand Parent Component</h1>
        <Parents surName={this.state.surName} />
      </>
    );
  }
}
function Parents(props) {
  return (
    <div>
      <h2>This is a Parent Component</h2>
      <Children surName={props.surName} />
    </div>
  );
}
function Children(props) {
  return (
    <div>
      <b>Inherited Child Properties from Grand Parents: </b>
      <label>{props.surName}</label>
    </div>
  );
}



Code Sandbox link

В приведенном выше примере у нас есть компонент верхнего уровня «GrandParent», который содержит некоторую строку данных «surName». Этот компонент содержит другой компонент «Parent», которому мы отправляем эту информацию о «surName» в данных «props». Этот «родительский - Parent» компонент не использует эти данные, ему просто нужно передать эти данные следующему дочернему компоненту «Children», а затем этот компонент «Children» использует эту информацию «surName».

Проблема с вышеуказанным кодом

В приведенном выше сценарии данные требуются только на дочернем уровне. Но мы распространяем данные по всей Иерархии, чтобы сделать их доступными на более низком уровне («Children»). На первый взгляд ничего особенного, но представьте, что на месте одного родительского компонента, который просто передает пропсы ниже, не один а десять, а то и двадцать уровней компонентов? Используя контекст, мы можем сократить издержки на передачу данных по всей иерархии, чтобы сделать их доступными для компонентов самого низкого уровня. Далее давайте рассмотрим детали того, как уменьшить издержки на передачу props с помощью React Context.

Работа с контекстом React - React Context

Чтобы решить вышеуказанную проблему, мы можем использовать API React Context.React Context API состоит из:
  1. Context Object

    Чтобы использовать Context в приложении, нам сначала нужно создать объект контекста - Context Object, который способен хранить данные для приложения. Этот объект можно сделать доступным для иерархии для подачи или получения данных. Приведенный ниже код позволит нам создать объект контекста для всей иерархии.

    const FamilyContext = React.createContext({});


  2. Context Provider

    const FamilyProvider = FamilyContext.Provider;

    Context Provider используется для предоставления значения этому объекту контекста - Context Object. Мы можем добавить к контексту, используя этот объект Context Provider.


  3. Context Consumer

    const FamilyConsumer = FamilyContext.Consumer;

    Этот объект потребителя - Context Consumer используется для получения значения контекста и предоставления значения для компонента. Значение, сохраненное в контексте с использованием «Context Provider», может быть получено с помощью объекта Consumer Context.

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


import React from "react";
const FamilyContext = React.createContext({});
export const FamilyProvider = FamilyContext.Provider;
export const FamilyConsumer = FamilyContext.Consumer;

export default class GrandParents extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      surName: "Kolesnikov",
      random: "Text",
    };
  }
  render() {
    return (
      <FamilyContext.Provider value={this.state}>
        <div>
          <h1>Grand Parents Initial Surname: </h1>
          {this.state.surName}
        </div>
        <Parents></Parents>
      </FamilyContext.Provider>
    );
  }
}

function Parents(props) {
  return (
    <div>
      <h2>Inherited Parent Property With "props":</h2>
      <label></label>
      <br />
      <br />
      <br />
      <Children />
    </div>
  );
}

class Children extends React.Component {
  render() {
    return (
      <FamilyConsumer>
        {(context) => {
          return (
            <div>
              <h3>Inherited Child Properties without "props": </h3>
              <label>{context.surName}</label>
            </div>
          );
        }}
      </FamilyConsumer>
    );
  }
}




Code Sandbox link

В приведенном выше коде следует обратить внимание на следующее:

  1. Сверху мы создали Объект контекста (FamilyContext), Объект провайдера (FamilyProvider) и Объект потребителя (FamilyConsumer).
  2. Значение добавляется в контекст в компоненте «GrandParent» с использованием объекта «Провайдер» - Provider Object. Мы используем свойство value для добавления необходимых данных в объект провайдера -Provider Object. Данные не нужно использовать в компоненте «Родитель» - Parent.
  3. Чтобы сделать данные напрямую доступными для компонента Children, мы используем Consumer Object - объект-потребитель (FamilyConsumer). Это сделает данные доступными непосредственно дочерним компонентам.
А если в двух словах, то мы просто взяли от реакта нужные нам части контекста и определили их в самом верху файла:

const FamilyContext = React.createContext({});
export const FamilyProvider = FamilyContext.Provider;
export const FamilyConsumer = FamilyContext.Consumer;


Затем, просто обернули все, что рендерим в корневом компоненте в провайдер, в который и передали пропсы, а в нужном месте (Дочерний компонент) достали их из контекста через функцию.

Все это в коде выделил красным!

PS

В приведенном выше примере данные не нужно передавать по всей иерархии, данные были доступны на верхнем уровне и были непосредственно доступны дочернему элементу с помощью объекта контекста. Отсюда устраняется необходимость передачи данных с использованием «реквизита - props» по всей иерархии.

Завтра, я более подробно расскажу вам об использовании этого удивительно полезного хука и познакомлю вас с некоторыми новыми аспектами его применения.

React. Как использовать «useContext» в React Hooks

Хотите оставаться в курсе новинок в области JS-программирования? Подписывайтесь на мой канал, вступайте в группу на Facebook. Если понравившаяся статья оказалась еще и полезной, то буду благодарен вашим пожертвованиям на развитие сайта - кнопка DONATE ниже (любая валюта).

Удачного кодирования!                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer
Помочь проекту (любая валюта). DONATE

понедельник, 19 августа 2019 г.

React - шпаргалка.

Шпаргалка по React.js в картинках.


См. этот пост на gitHub                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer

четверг, 15 августа 2019 г.

Методы жизненного цикла React 16 на русском.


//  React 16 Lifecycle Methods

class ComponentWithLifecycle extends React.Component {
  //  Часто используемые методы ЖЦ (Жизненного цикла)
  constructor() {
    //  - ЖЦ: Mounting  - Монтаж ( до рендера )
    //  - Цель: Инициализация состояния
  }

  componentDidMount() {
    //  - ЖЦ: Mounting - Монтаж ( немедленно после рендера )
    //  -Цель: Инициализация состояний требующее существование ДОМ узлов
    //    Сетевые запросы и побочный эффекты.
  }
   componentDidUpDate() {
    //  - ЖЦ:  Updating - Обновление 
( yнемедленно после построения - обновления)
    //  -Цель: Управление обновлением ДОМ или поддержка сетевых запросов
   }
   componentWillUnmount() {
      //  - ЖЦ: Unmounting - Размонтирование
      // -Цуль: Убрать такие вещи как обработчики событий,
 отмена сетевых запросов и т.д.
   }

  //  Редко используемые методы жизненного цикла
  static static getDerivedStateFromProps(props, state) {
    //  -ЖЦ: Mounting  Монтирование и размонтирование -  Unmounting
( немедленно до рендера )
    //  -Цель: Когда state - состояние зависит от свойств - props 
( следует избегать )
  }

  shouldComponentUpdate(nextProps, nextState) {
    //  -ЖЦ: Updating -Обновление (немедленно до рендера )
    //  -Цель: Позволяет разработчику предотвращать рендеринг
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    //  -ЖЦ: Updating - Обновление 
( непосредственно перед подтверждением вывода рендера )
    //  -Цель Захват информации ДОМ, такой как данные скролла - 
scroll position которые могут меняться.
  }

  // Особые случаи использования
  componentDidCatch() {
    //  - Создает границы ошибок для захвата их от дочерних компонентов
  }

  // Необходимый - обязательный
  render() {
    // -Выводит код компонента на экран
  }

  // Объяснение жизненного цикла:
  // Mounting -  монтаж: Вызывается до того как ваш компонент 
отображается в пользовательском интерфейсе - UI
  // Updating - обновление: Вызывается изменением  
state-состояния или props и повторный рендеринг UI
  // Unmounting - Размонтирование: Вызывается когда ваш 
компонент больше не будет отображаться в UI
}
React Lifecycle Methods - on GitHub                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer

четверг, 16 мая 2019 г.

React-Сайт ( II ). Инфо-основная секция.

Не отступая от выбранной нами концепции написания сайта легкими - функциональными компонентами без состояния, мы сегодня создадим секции сайта несущие основную информацию, сделаем отдельным компонентом многоразового использования - кнопку и много других интересных вещей.




Все статьи этого проекта:




Мы зададим анимацию -плавное, последовательное проявление верхних информационных блоков и "прокрутка" скидки на билет. Если посмотреть на готовое решение, то наши секции должны выглядеть так.



Секция Инфо

В папке Components создадим папку Info, а в ней файл - index.js, в котором создадим react stateless components (rsc) компонент без состояния.

Так, как вверху у нас будут иконки, то мы импортируем их в наш компонент.Иконки я подготовил заранее и поместил их в отдельную папку в папке resources/img/icons

import icon_calendar from '../../resources/img/icons/calendar.png';
import icon_location from '../../resources/img/icons/location.png';

Так как стилизация компонентов, да и приложения в целом, не является целью данного поста, то я для стилизации этих секций воспользуюсь готовыми стилями, которые приведу ниже. Вы же можете написать их самостоятельно

src/resources/style.css
body {
    padding:0;
    margin:0;
    font-family: 'Roboto', sans-serif;
}

.font_righteous {
    font-family: 'Righteous', cursive;
}

.bck_black {
    background: #3c3c3c;
}
.bck_white {
    background: #ffffff;
}
.bck_red {
    background: #ff4800;
}
.bck_yellow {
    background: #ffa800;
}

.iconImage {
    width: 20px;
    margin-right: 10px;
}

.center_wrapper {
    width: 900px;
    margin:0 auto;
}
/* ===============>>> HEADER <<<================ */
header {
    padding:10px 0px;
    transition: all 300ms ease-in;
}

header .header_logo {
    flex-grow: 1;
}
header .header_logo_title {
    text-transform: uppercase;
    font-size: 40px;
}

header .header_logo_caption {
    text-transform: uppercase;
}

/* =================>>> SLIDER <<<================== */
.event_name {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 400px;
    height: 160px;
    -webkit-transform: translate(-50%, -50%);  
    transform: translate(-50%, -50%);   
    border: 2px solid white;
}

.event_name .wrapper {
    border: 2px solid white;
    height: 121px;
    margin: 6px;
    text-align: center;
    color: #fff;
    font-size: 40px;
    padding: 21px 70px 0px 70px;
    text-transform: uppercase;
}
.carrousel_wrapper {
    height:700px;
    overflow: hidden;
}

.carrousel_image {
    
    background-size: cover !important;
}

/*===================>>>  INFO <<======================*/

.vn_wrapper {
    display: flex;
    padding: 170px 0px;
}

.vn_item {
    width:50%;
    color:#ffffff;
    text-align: center;
}
                      
.vn_item .vn_outer {
    width: 300px;
    border: 2px solid #828282;
    margin: auto;
    padding: 5px;
}

.vn_item .vn_inner { 
    border: 2px solid #828282;
    position: relative;
    padding: 0px 20px;
    transition: all 500ms ease;
}

.vn_item .vn_inner:hover {
    background: #3e3e3e;
}

.vn_item .vn_icon_square {
    width: 100px;
    height: 100px;
    position: absolute;
    -ms-transform: rotate(45deg);
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
    top: -54px;
    left: 100px;
}

.vn_item .vn_icon {
    width: 50px;
    height: 50px;
    background-size: contain !important;
    background-repeat: no-repeat !important;
    position: absolute;
    top: -30px;
    left: 124px;
}

.vn_item .vn_title {
    font-size: 28px;
    margin: 90px 0px 20px 0px;
    border-bottom: 1px solid #5d5d5d;
    padding-bottom: 10px;
}

.vn_item .vn_desc {
    font-size: 30px;
    font-weight: 300;
    margin-bottom: 90px;
}

/*==============>>> HIGHLIGHT <<<======================*/


.highlight_wrapper h2 {
    text-align: center;
    text-transform: uppercase;
    color:#2c2c2c;
    font-size: 52px;
}

.highlight_wrapper .highlight_description {
    line-height: 30px;
    font-size: 18px;
    font-weight: 300;
    border-top: 1px solid #dddddd;
    border-bottom: 1px solid #dddddd;
    padding: 30px 0px;
}

.discount_wrapper {
    display: flex;
    padding: 70px 0px;
}

.discount_wrapper .discount_porcentage {
    padding-right: 100px;
}

.discount_wrapper .discount_porcentage span:nth-child(1) {
    color: #ff4800;
    font-size: 125px;
    font-weight: 400;
    display: block;
    line-height: 120px;
}

.discount_wrapper .discount_porcentage span:nth-child(2) {
    color: #2c2c2c;
    font-size: 50px;
    font-weight: 400;
    display: block;
}


.discount_wrapper .discount_description h3 {
    font-size: 34px;
    font-weight: 100;
    margin: 0px;
    color: #2c2c2c;
}

.discount_wrapper .discount_description p { 
    line-height: 30px;
    font-size: 18px;
    font-weight: 300;
}


В компоненте Info просто добавим класс диву и выведем его название. Подключим его в App копоненте и проверим на экране.

src/Components/Info/index.js
import React from "react";
import icon_calendar from "../../resources/images/icons/calendar.png";
import icon_location from "../../resources/images/icons/location.png";
const Info = () => {
  return <div className="bck_black">Info</div>;
};

export default Info;



Теперь уберем инлайновый стиль для компонента App, чтоб не мешал и добавим в Info/index.js разметку. Здесь все как обычно. Остановлюсь только на том, что мы используем уже установленный пакет react-reveal его возможность - Zoom для плавного вывода отдельного блока. Второму блоку придали задержку, для того, чтобы показывать их последовательно.

src/Components/Info/index.js
import React from "react";
import Zoom from "react-reveal/Zoom";

import icon_calendar from "../../resources/img/icons/calendar.png";
import icon_location from "../../resources/img/icons/location.png";
const Info = () => {
  return (
    <div className="bck_black">
      <div className="center_wrapper">
        <div className="vn_wrapper">
          <Zoom duration={500}>
            <div className="vn_item">
              <div className="vn_outer">
                <div className="vn_inner">
                  <div className="vn_icon_square bck_red" />
                  <div
                    className="vn_icon"
                    style={{
                      background: `url(${icon_calendar})`
                    }}
                  />
                  <div className="vn_title">Event Date & Time</div>
                  <div className="vn_desc">7 August 2017 @10.00 pm</div>
                </div>
              </div>
            </div>
          </Zoom>

          <Zoom duration={500} delay={500}>
            <div className="vn_item">
              <div className="vn_outer">
                <div className="vn_inner">
                  <div className="vn_icon_square bck_yellow" />
                  <div
                    className="vn_icon"
                    style={{
                      background: `url(${icon_location})`
                    }}
                  />
                  <div className="vn_title">Event Location</div>
                  <div className="vn_desc">
                    345 Speer Street Oakland, CA 9835
                  </div>
                </div>
              </div>
            </div>
          </Zoom>
        </div>
      </div>
    </div>
  );
};

export default Info;



В итоге мы получаем полностью готовую первую секцию с плавным и последовательным выводом на экран двух ее блоков с иконками.



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Info section"


Основная секция

Сейчас перейдем к созданию основной секции. Если посмотреть на нее, то мы сможем легко и просто разделить её на несколько компонентов.

Для работы, и чтобы в дальнейшем задать отдельную анимацию, мы разобьем этот блок на два больших компонента - Description (на фото желтым) и Discount (зеленым)

Кнопку (красным на фото)создадим отдельным компонентом, который будет многократно использован в этом приложении, поэтому мы можем вынести её в отдельную папку, но подключим ее в файле компонента Discount



Все эти компоненты мы соберем в отдельный файл index.js и уже его подключим и выведем в основной компонент App

Для компонентов Description и Discount и этого файла index.js создадим отдельную папку и назовем ее Highlights

Сразу в ней создадим файл index.js

src/Components/Highlight/index.js
import React from "react";
import Description from "./Description";


const Highlights = () => {
  return (
    <div className="highlight_wrapper">
      <Description />
    </div>
  );
};

export default Highlights;



Пока что здесь нет ничего интересного, просто мы выводим компонент Description, который сейчас и создадим.

src/Components/Highlight/Description.js
import React from "react";
import Fade from "react-reveal/Fade";

const Description = () => {
  return (
    <Fade>
      <div className="center_wrapper">
        <h2>Highlights</h2>
        <div className="highlight_description">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
          minim veniam, quis nostrud exercitation ullamco laboris nisi ut
          aliquip ex ea commodo consequat. Duis aute irure dolor in
          reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
          pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
          culpa qui officia deserunt mollit anim id est laborum.
        </div>
      </div>
    </Fade>
  );
};

export default Description;



Этот компонент просто выводит текст и разметку. Из интересного есть то, что мы добавили плавное появление его на странице, с помощью все того же пакета react-reveal.

На странице это будет выглядеть так.



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Description component"


Счетчик скидки - Discount- компонент.

В этой части у нас будет один компонент, который будет содержать текст и счетчик скидки, который при загрузке (первой прокрутке) будет динамически меняться и останавливаться на определенном значении. Тесктовый блок не представляет интереса - это простая разметка. Единственное что, так это то , что мы добавим плавное появление.

Поэтому, хотя и можно условно (да и не только условно :-)) разделить этот компонент на два, я не хочу создавать отдельный компонент для разметки и просто помещу его в вывод одного компонента. Для себя, вы можете сделать иначе. Таким образом у вас получится счетчик для многоразового использования.

Создадим компонент Discount

src/Components/Highlight/Discount.js
import React, { useState, useEffect } from "react";
import Fade from "react-reveal/Fade";
import Slide from "react-reveal/Slide";

const Discount = () => {
  const [discountStart, setDiscountStart] = useState(0);
  const discountEnd = 30;

  const porcentage = () => {
    if (discountStart < discountEnd) {
      setDiscountStart(discountStart + 1);
    }
  };

  useEffect(() => {
    setTimeout(() => {
      porcentage();
    }, 30);
  }, [discountStart]);

  return (
    <div className="center_wrapper">
      <div className="discount_wrapper">
        <Fade onReveal={() => porcentage()}>
          <div className="discount_porcentage">
            <span>{discountStart}%</span>
            <span>OFF</span>
          </div>
        </Fade>

        <Slide right>
          <div className="discount_description">
            <h3>Purchase tickets before 20th JUNE</h3>
            <p>
              Sed ut perspiciatis unde omnis iste natus error sit voluptatem
              accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
              quae ab illo inventore veritatis et quasi architecto beatae vitae
              dicta sunt explicabo.
            </p>
          </div>
        </Slide>
      </div>
    </div>
  );
};

export default Discount;



В этом файле самая интересная часть это счетчик, который выводит последовательность цифр. Это достигается тем, что мы меняем значение переменной "состояния" discountStart в сторону увеличения на единицу.

Каждый раз при изменении переменной "состояния" discountStart у нас обновится наш компонент, потому что мы передали эту переменную, как массив в хук useEffect вторым аргументом. Подробнее я писал об этом в первой части.

Функцию, которая запустить это процесс мы поместили в Fade для того, чтобы отсчет начался с момента показа пользователю этого компонента.



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Discount component"


Кнопка

Её мы создадим, как и договорились ранее, в отдельной папке в компонентах. Назовем ее произвольно, например Utils MyButton.js. Так как она будет использоваться многократно, то мы будем в нее передавать стили и линк, по которому будет осуществляться переход, в виде props. Сама же кнопка будет иметь иконку, которую также будет получать от родителя.

В данном случае, дольше рассказывать, чем показать.

src/Components/Utils/MyButton.js

import React from "react";
import Button from "@material-ui/core/Button";

import TicketIcon from "../../resources/img/icons/ticket.png";

const MyButton = props => {
  return (
    <Button
      href={props.link}
      variant="contained"
      size="smal"
      style={{
        background: props.bck,
        color: props.color
      }}
    >
      <img src={TicketIcon} className="iconImage" alt="icon_button" />
      {props.text}
    </Button>
  );
};

export default MyButton;



Эту кнопку мы поместим в компонент Discount и передадим ей нужные свойства -props

src/Components/Highlight/Discount.js
import React, { useState, useEffect } from "react";
import MyButton from "../Utils/myButton";
import Fade from "react-reveal/Fade";
import Slide from "react-reveal/Slide";

const Discount = () => {
  const [discountStart, setDiscountStart] = useState(0);
  const discountEnd = 30;

  const porcentage = () => {
    if (discountStart < discountEnd) {
      setDiscountStart(discountStart + 1);
    }
  };

  useEffect(() => {
    setTimeout(() => {
      porcentage();
    }, 30);
  }, [discountStart]);

  return (
    <div className="center_wrapper">
      <div className="discount_wrapper">
        <Fade onReveal={() => porcentage()}>
          <div className="discount_porcentage">
            <span>{discountStart}%</span>
            <span>OFF</span>
          </div>
        </Fade>

        <Slide right>
          <div className="discount_description">
            <h3>Purchase tickets before 20th JUNE</h3>
            <p>
              Sed ut perspiciatis unde omnis iste natus error sit voluptatem
              accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
              quae ab illo inventore veritatis et quasi architecto beatae vitae
              dicta sunt explicabo.
            </p>
            <MyButton
              text="Purchase tickets"
              bck="#ffa800"
              color="#ffffff"
              link="http://google.com"
            />
          </div>
        </Slide>
      </div>
    </div>
  );
};

export default Discount;



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



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Button Component"


                                                                                                                                                             

Телеграм канал - Full Stack JavaScript Developer

понедельник, 13 мая 2019 г.

React-Сайт ( I ). Меню и слайдер.

Создадим полноценный сайт с меню, слайдером, обратным отчетом и прочими обычными и не очень компонентами



Что мы будем строить?

Лучше один раз увидеть , чем 100 раз услышать.

Готовый сайт на surge.sh


Все статьи этого проекта:




Начнем, как обычно с создания проекта на основе create-react-app. Если вы еще не знакомы с новой версией, то вам сюда ==> Что нового в приложении Create React App 3, а если не получилось запустить обновленную, третью версию, то вам сюда ==> cra3-boilerplate. Возмите готовое, настроенное мной решение и пользуйтесь в удовольствие.

Запуск и настройку не рассказываю. Об этом я много писал ранее. Считаю, что вы уже развернули приложение в отдельной папке, запустили его и готовы приступить к работе.

Удаляем лишнее.

В папке src удаляем все кроме App.js index.js В файле index.js оставляем только то, что будем использовать:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

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


В файле App.js оставляем только то, что будем использовать:

import React from "react";

function App() {
  return <div className="App">
              My App
         </div>;
}

export default App;


Добавляем нужное.

Папки-файлы

В папке src мы создадим папку resources, а в не папку слайд-фото для нашего слайдера - img. Вы можете взять любые три изображения размером 1920px x 1200px и сразу поместить их в эту папку.

В resources положим и файл для стилизации всего приложения - style.css Все стили для нашего приложения мы поместим в папку

Зависимости

Мы будем использовать Material-UI. Ранее я уже рассказывал о том. как использовать библиотеку Materialize. Позднее вы увидите. что они очень похожи и их использование не будет вызывать у вас беспокойства.

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

Итак. Мы будем использовать следующие модули:
  1. @material-ui/core
  2. @material-ui/icons
  3. react-reveal
  4. react-scroll
  5. react-slick
Устанавливаем material-ui

Ядро:

// with npm
npm install @material-ui/core

// with yarn
yarn add @material-ui/core



И иконки:

npm install @material-ui/icons


Одной командой:

npm install @material-ui/core @material-ui/icons


В файл App.js импортируем стили, которые создадим создадим в ранее созданном файле стилей и будем их добавлять к классам, которые мы будем присваивать нашим элементам разметки во всем приложении. Так можно поступить в данном случае, потому что у нас компонент App будет являться родителем для всего приложения. Само приложение будет небольшое, поэтому нет смысла использовать стороннюю библиотеку.

import './resources/styles.css';

Все компоненты мы будем помещать в отдельной папке. Создадим её - Components в папке src. В ней разу создадим папку для компонента Header с одноименным названием и в ней файл Header.js .

import React from "react";

const Header = () => {
  return <div>
          Header
         </div>;
};

export default Header;


И сразу импортируем и выведем его в App.js

import React from "react";
import './resources/style.css';
import Header from "./Compomemts/Header_Footer/Header";

function App() {
  return (
    <div className="App">
      <Header />
    </div>
  );
}

export default App;


Сейчас мы просто увидим слово Header на странице приложения.

Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Creating the main structure and connecting the header"
ci -m — здесь и далее - коммит.

Создание меню

Вывод логотипа и заголовка.

Импортируем в файл Header.js все необходимые для этого компоненты и создадим простую структуру на основе Material-UI.

Header.js
import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import MenuIcon from "@material-ui/icons/Menu";
import IconButton from "@material-ui/core/IconButton";

const Header = () => {
  return (
    <AppBar
      position="fixed"
      style={{
        background: "#090050",
        padding: " 20px 0px"
      }}
    >
      <Toolbar>
        <div className="header_logo">
          <div className="font_left header_logo_title">wmg</div>
          <div className="font_left header_logo_caption">React mitting</div>
        </div>
      </Toolbar>
    </AppBar>
  );
};

export default Header;



Помимо инлайновых стилей, добавил немного и в resources/style.css

body {
    padding:0;
    margin:0;
    font-family: 'Roboto', sans-serif;
}

.font_left {
    font-family: 'Righteous', cursive;
}

/* ===============>>> HEADER <<<================ */
header {
    padding:10px 0px;
    transition: all 300ms ease-in;
}

header .header_logo {
    flex-grow: 1;
}
header .header_logo_title {
    text-transform: uppercase;
    font-size: 40px;
}

header .header_logo_caption {
    text-transform: uppercase;
}


Стили для шрифтов, я взял в Google Fonts и сразу же добавил их в header файла public/index.html В браузере пока что так:



Вывод значка сандвич меню.

Добавим вывод значка сандвич-меню в файл Header.js

import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import MenuIcon from "@material-ui/icons/Menu";
import IconButton from "@material-ui/core/IconButton";

const Header = () => {
  return (
    <AppBar
      position="fixed"
      style={{
        background: "#090050",
        padding: " 20px 0px"
      }}
    >
      <Toolbar>
        <div className="header_logo">
          <div className="font_left header_logo_title">wmg</div>
          <div className="font_left header_logo_caption">React mitting</div>
        </div>
        <IconButton aria-label="Menu" color="inherit">
          <MenuIcon />
        </IconButton>
      </Toolbar>
    </AppBar>
  );
};

export default Header;



В браузере так:



Пока что на клик ничего не происходит. Но мы сделаем так, что по клику будет открываться сайдбар с меню справа. Вы можете это легко сделать и сверху, и снизу и слева, самостоятельно.

Пока что мы просто добавим элементу

<IconButton aria-label="Menu" color="inherit" onClick={() => console.log("OPEN")} >

Если теперь посмотретьв браузере. то в консоли мы увидим "OPEN" при клике.

Идем в "Material-UI"==> "Component Demos"==> "Drawer". Здесь вы можете выбрать различные варианты для расположения сайтбара.

Оно определяется значение атрибута anchor="top" компонента Drawer. Атрибут open={this.state.top} получает значение из состояния компонента. Функция изменяет его и от того, true / false определяется показывать или нет меню-сайдбар.

Для сохранения порядка в нашем приложении, мы создадим в папке Header_Footer отдельный файл - SideDrawer.js

Внутри него определим обычный компонент без состояния (stateless component) и импортируем в него Driwer из Material-UI. Сразу же импортируем компонент List и ListItem оттуда же. Они нам потребуются для создания самого сайдбара с элементами.

Кстати, если вы используете VS-Code, то там есть отличный плагин - Reactjs code snippets, котоый позволяет писать компоненты одной короткой командой:
  • rcc - React Class Component
  • rsc - React Stateless Component
Компонент будет принимать props


import React from 'react';
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
const SideDrawer = () => {
  return (
    <Drawer
    anhor="right"
    open={}
    onClose={()=>{}}
    >
      Drawer
    </Drawer>
  );
};

export default SideDrawer;



В open мы в пропсах передадим состояние,и функцию закрытия. Пока что подключим наш файл в Header.js и добавим функцию, которую будем передавать вниз. Добавим состояние, используя hook - useState. Проще показать:

Header.js

import React, { useState } from "react";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import MenuIcon from "@material-ui/icons/Menu";
import IconButton from "@material-ui/core/IconButton";
import SideDrawer from "./SideDrawer";
const Header = () => {
  const [drawerOpen, setDrawerOpen] = useState(false);

  const toggleDrawer = value => {
    setDrawerOpen(value);
  };
  return (
    <AppBar
      position="fixed"
      style={{
        background: "#090050",
        padding: " 20px 0px"
      }}
    >
      <Toolbar>
        <div className="header_logo">
          <div className="font_left header_logo_title">wmg</div>
          <div className="font_left header_logo_caption">React mitting</div>
        </div>
        <IconButton
          aria-label="Menu"
          color="inherit"
          onClick={() => toggleDrawer(true)}
        >
          <MenuIcon />
        </IconButton>
        <SideDrawer open={drawerOpen} onClose={value => toggleDrawer(value)} />
      </Toolbar>
    </AppBar>
  );
};

export default Header;



И доделаем SideDrawer.js

import React from "react";
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
const SideDrawer = props => {
  return (
    <Drawer
      anchor="right"
      open={props.open}
      onClose={() => props.onClose(false)}
    >
      Drawer
    </Drawer>
  );
};

export default SideDrawer;



Теперь по клику на сандвич показывается сайдбар.



Добавим разделы меню:

SideDrawer.js
import React from "react";
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
const SideDrawer = props => {
  return (
    <Drawer
      anchor="right"
      open={props.open}
      onClose={() => props.onClose(false)}
    >
      <List component="nav">
        <ListItem button onClick={() => console.log("Start Events")}>
          Event starts in
        </ListItem>

        <ListItem button onClick={() => console.log("React info")}>
          React info
        </ListItem>

        <ListItem button onClick={() => console.log(" Our Team")}>
          Our Team
        </ListItem>

        <ListItem button onClick={() => console.log("Pricing")}>
          Pricing
        </ListItem>

        <ListItem button onClick={() => console.log("Location")}>
          Location
        </ListItem>
      </List>
    </Drawer>
  );
};

export default SideDrawer;



Стилизацию придает сама библиотека Material UI, в зависимости от заданных параметров листа и его итемов. Более подробно см в документации.

На клик в консоль, пока что просто вывел название элемента.

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



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Toggle Menu"

Добавление интерактивности меню.

Можно сделать так. что при открытии страницы меню будет иметь прозрачность, а при скроле, оно будет проявляться и находиться в фиксированном состоянии, прижатое к верхнему краю. Вначале нам нужно задать большую высоту странице, чтобы получить скрол. Для этого в компоненте App добавим инлайн стиль для всего компонента:

App
import React from "react";
import "./resources/style.css";
import Header from "./Compomemts/Header_Footer/Header";

function App() {
  return (
    <div className="App" style={{ height: "2000px", background: "#BEE0FF"  }}>
      <Header />
    </div>
  );
}

export default App;



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

Добавим меню прозрачности при позиции без скрола - то есть в самом верхнем положении.

Для этого идем в файл Header.js и нам нужно добавить "состояние" нашему компоненту, которое будет отвечать за прозрачность меню (показать или нет меню полностью).

Сделаем это с помощью хука useState и зададим начальное состояние - false

const [headerShow, setHeaderShow] = useState(false);

Там, гдде мы задавали с вами цвет компоненту AppBar, внутри метода render(), мы добавим изменение цвета компонента по условию:

background: headerShow ? "#090050" : "transparent"

Сейчас все будет выглядеть так:



Уберем тень от меню - boxShadow: 'none'

Теперь наша задача изменить значение нашей переменной headerShow на true при скроле. Для этого мы будем использовать useEffect хук, который позволяет нам заменять жизненые циклы в компонентах без состояния.

Для этого нам нужно будет получить величину скрола. Это все просто, как в ванильном JS.

window.addEventLestener('scroll', handleScroll)

Эту конструкцию мы поместим внутри функции, которая будет находиться в хуке useEffect, и для того, чтобы этот хук отслеживал только изменение переменной headerShow, мы передадим ее хуку вторым аргументом в виде массива. Это не будет создавать нагрузку на приложение, потому что хук будет вызываться только при изменении этой переменной "состояния".

Теперь создадим стрелочную функцию handleScroll в которой будем изменять значение этого "состояния" в зависимости от данных скрола.

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
  }, [headerShow]);

  const handleScroll = () =>{
    if (window.scrollY > 0) {
      setHeaderShow(true)
    } else {
      setHeaderShow(false)
    }
  }


Эту функцию можно записать еще короче, тернарным оператором, но я оставлю ее здесь так, для наглядности.В коде можно записать и так:


  const handleScroll = () =>{
    (window.scrollY > 0) ? setHeaderShow(true) : setHeaderShow(false);
  }



И даже скобки не нужны, но... Мне вообще нравится тенденция использования коротких записей, только там, где это действительно необходимо, а не там, где хочется разработчику. Красота чтения такого кода, как книги, превосходит затраты на пару килобайт, компилированног кода. Всегда есть "золотая середина" и ее стоит придерживаться!

Теперь в браузере мы получим такую картину:



Вся красота создания таких компонентов в том, что они не только легкие, но еще и могут быть использованы как модули в других проектах. Они имеют свои независимые "состояния" и могут использоваться многократно со стилизацией под каждый отдельный проект.

Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Transparent Menu on Scroll events"

Слайдер

Переходим к слайдеру. В папке ресурсов, как мы и договорились ранее, есть папка с изображениями - img. все изображения имеют размер 1920 X 1200 px. Это позволит нам создавать слайдер на всю ширину окна браузера, без потери качества, в большинстве случаев.

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

В нижней части слайдера мы выведем калькулятор, который будет показывать оставшееся время (дни, часы, минуты, секунды) до намеченного события. Например - конференции React-разработчиков в Амстердаме.

Идем в проект и в папке Componentsи создадим там папку для нашего слайдера, заголовка и таймера - Sliderв ней файл index.js в который и будет собираться весь наш слайдер из трех вышеперечисленных компонентов.

Набираем - rsc и жмем Enter и у нас уже есть готовый функциональный компонент. переименовываем его в Slider. Не забываем изменить имя и в экспорте!

Всему возвращаемому элементу - div добавим стиль div style={{position:'relative'}}

Внутри него создадим два вложенных элемента в которых и будет находиться заголовок:

import React from "react";

const Slider = () => {
  return (
    <div style={{ position: "relative" }}>
      <div className="event_name">
        <div className="wrapper">React Meetting</div>
      </div>
    </div>
  );
};

export default Slider;



Импортируем все это в App.js и выводим на экран.

import React from "react";
import "./resources/style.css";
import Header from "./Compomemts/Header_Footer/Header";
import Slider from "./Compomemts/Slider";
function App() {
  return (
    <div className="App" style={{ height: "2000px", background: "#BEE0FF" }}>
      <Header />
      <Slider />
    </div>
  );
}

export default App;



На экране покажется заголовок, который мы вывели, справа, просто черным текстом. Добавим немного стилей в файле

src/resources/style.css
/* =================>>> SLIDER <<<================== */
.event_name {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 400px;
    height: 160px;
    -webkit-transform: translate(-50%, -50%);  
    transform: translate(-50%, -50%);   
    border: 2px solid white;
}

.event_name .wrapper {
    border: 2px solid white;
    height: 121px;
    margin: 6px;
    text-align: center;
    color: #fff;
    font-size: 40px;
    padding: 21px 70px 0px 70px;
    text-transform: uppercase;
}


Вы можете стилизовать все сами, как вам больше нравится.



Добавляем React-Slick

Прям там, на странице, мы возьмем cdn - ссылки для подключения css и добавим их в header нашего html - файла public/index.html

Там же на странице есть хорошие примеры подключения и использования слайдера. Добавляем его в проект:
npm install react-slick --save
Внутри папки Slider создадим файл Carousel.js, там так же создаем компонент без состояния. Импортируем сам слайдер import Slider from "react-slick"; В индексовый файл слайдера импортируем карусель: import Carousel from './Carousel'; И выводим ее на страницу перед заголовком. Возвращаемся в компонент Carousel и внутри компонента создадим константу с настройками слайдера.

    const settings = {
        dots: false,
        infinite:true,
        autoplay: true,
        speed: 500
    }


Думаю, что тут все и так понятно: dots - не показывать "точки" внизу слайдера, с бесконечной прокруткой, автоматический старт и задаем произвольно скорость.

Нашему оборачивающему диву добавим класс для стилизации и некоторые произвольные стили, которые я написал ниже.

src/resources/style.css
.carrousel_wrapper {
    height:700px;
    overflow: hidden;
}

.carrousel_image {
    
    background-size: cover !important;
}


Добавим ему и инлайновые стили

style={{
     height:`${window.innerHeight}px`,
     overflow:'hidden'
}}


Здесь самый интересный, это высота, которая рассчитывается динамически от высоты окна браузера. Именно благодаря этому наш слайдер будет всегда занимать полностью всю высоту любого экрана и второй компонент будет появляться только при скроле.

Если сейчас прописать инлайн этому элементу какой либо другой цвет (например: background:'red'), то мы наглядно в этом убедимся.

Идем далее и по образцу на странице библиотеки слайдера, создадим компонент Slider внутри которого получим с помощью оператора rest - ... все настройки ...settings, как если бы мы их передали по отдельности так ( Slider dots: false infinite: true и т.д). Внутри него разместим наши слайды, которые импортируем в этот файл по отдельности из папки img. зададим им динамически высоту. А сами фото пойдут как фон дива. все очень очень просто!

Приведу этот файл полностью для наглядности.

Carousel.js
import React from "react";
import Slider from "react-slick";

import slide_one from "../../resources/img/slide_one.jpg";
import slide_two from "../../resources/img/slide_two.jpg";
import slide_three from "../../resources/img/slide_three.jpg";

const Carousel = () => {
  const settings = {
    dots: false,
    infinite: true,
    autoplay: true,
    speed: 500
  };
  return (
    <div
      className="carrousel_wrapper"
      style={{
        height: `${window.innerHeight}px`,
        overflow: "hidden"
      }}
    >
      <Slider {...settings}>
        <div>
          <div
            className="carrousel_image"
            style={{
              background: `url(${slide_one})`,
              height: `${window.innerHeight}px`
            }}
          />
        </div>
        <div>
          <div
            className="carrousel_image"
            style={{
              background: `url(${slide_two})`,
              height: `${window.innerHeight}px`
            }}
          />
        </div>
        <div>
          <div
            className="carrousel_image"
            style={{
              background: `url(${slide_three})`,
              height: `${window.innerHeight}px`
            }}
          />
        </div>
      </Slider>
    </div>
  );
};

export default Carousel;



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



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "Slider and Menu"

Обратный отсчет

Создаем сам таймер

Начнем с того, что в папке Slider создадим для этого компонента отдельный файл Timer.js c rsc (react stateless component). Сразу же импортируем его в Slider/index.js и выведем его сразу перед закрывающим последним дивом.

В компоненте Timer.js зададим общему диву класс для дальнейшей стилизации className="countdown_wrapper" Внутри него будут секции с днями, часами, минутами и секундами.

Внутри него создадим еще один див с заголовком события. Сразу после него еще один див с классом countdown_bottom и уже внутри него у нас будут 4 разных значения для вывода цифр.

Внутри него будет див с классом countdown_item, в который мы поместим еще два дива, один из которых будет для вывода значений - countdown_time, второй для вывода названия значения - countdown_tag.

Timer.js
import React from "react";

const Timer = () => {
  return (
    <div className="countdown_wrapper">
      <div className="countdown_top">Event starts in</div>
      <div className="countdown_bottom">
        <div className="countdown_item">
          <div className="countdown_time">15</div>
          <div className="countdown_tag">Days</div>
        </div>
      </div>
    </div>
  );
};

export default Timer;



В браузере пока что это выглядит очень неприглядно:



Добавим стили:

src/resources/style.css
/* =====================>>> TIMER <<<======================== */
.countdown_wrapper {
    position:absolute;
    bottom: 0px;
    color:#ffffff;
}

.countdown_wrapper .countdown_top {
    background: #ff4800;
    font-size: 19px;
    padding: 10px;
    display: inline-block;
    text-transform: uppercase;
}

.countdown_wrapper .countdown_bottom {
    display: flex;
    background: red;
    padding: 10px 20px;
}

.countdown_wrapper .countdown_time {
    font-size: 70px;
    border-right: 1px solid red;
    margin-right: 14px;
    padding-right: 49px;
}
.countdown_wrapper .countdown_item:last-child .countdown_time{
    border:none;
}

.countdown_wrapper .countdown_tag {
    text-transform: uppercase;
    font-size: 20px;
    padding-left: 7px;
}


Теперь так:



Идем в файл Timer.js.Теперь мы продублируем див с классом - countdown_item для того, чтобы на страницу выводились не только дни, но и часы, минуты и секунды.



Пока что так.

Добавляем эффект появления

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

Нам потребуется для этого - react-reveal

Отлично все написано на официальном сайте - react-reveal

Или на странице gitHub - github.com react-reveal

Согласно документации устанавливаем его.
npm install react-reveal --save


Здесь есть github.com react-reveal - примеры, которые вы можете смело использовать в ваших приложениях:

Теперь импортируем его в файл:

import Slide from 'react-reveal/Slide';

И обернем в него все что возвращает наш компонент (внутри return ()). Зададим ему параметры left delay={1000}

Таким образом у нас появился Fade эффект - плавного появления таймера справа.

Файл полностью:

Timer.js
import React from "react";
import Slide from "react-reveal/Slide";

const Timer = () => {
  return (
    <Slide left delay={1000}>
      <div className="countdown_wrapper">
        <div className="countdown_top">Event starts in</div>
        <div className="countdown_bottom">
          <div className="countdown_item">
            <div className="countdown_time">15</div>
            <div className="countdown_tag">Days</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">15</div>
            <div className="countdown_tag">Hs</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">15</div>
            <div className="countdown_tag">Min</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">15</div>
            <div className="countdown_tag">Sec</div>
          </div>
        </div>
      </div>
    </Slide>
  );
};

export default Timer;



Добавляем логику (динамическое изменение значений)

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

Timer.js
import React, {useState, useEffect} from "react";
import Slide from "react-reveal/Slide";

const Timer = () => {
  const  deadline = 'Dec, 16, 2019';

  const [days, setDays] = useState('0');
  const [hours , setHours] = useState('0');
  const [minutes, setMinutes] = useState('0');
  const [seconds, setSeconds] = useState('0');

     const getTimeUntil = (deadline) =>{
        const time = Date.parse(deadline) - Date.parse(new Date());
        if(time < 0) {
            console.log('Date passed')
        } else {
            const seconds = Math.floor((time/1000)%60);
            const minutes = Math.floor((time/1000/60)%60);
            const hours = Math.floor((time/(1000*60*60))%24);
            const days = Math.floor(time/(1000*60*60*24));

          setDays(days);
          setHours(hours);
          setMinutes(minutes);
          setSeconds(seconds);
        }    
    }
    useEffect(()=>{
      setInterval(()=> getTimeUntil(deadline),1000)
    },[seconds])
  return (
    <Slide left delay={1000}>
      <div className="countdown_wrapper">
        <div className="countdown_top">Event starts in</div>
        <div className="countdown_bottom">
          <div className="countdown_item">
            <div className="countdown_time">{days}</div>
            <div className="countdown_tag">Days</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{hours}</div>
            <div className="countdown_tag">Hs</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{minutes}</div>
            <div className="countdown_tag">Min</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{seconds}</div>
            <div className="countdown_tag">Sec</div>
          </div>
        </div>
      </div>
    </Slide>
  );
};

export default Timer;



Если посмотреть в браузер, то все "появляется и тикает". В итоге мы получили меню, которое меняется в зависимости от прокрутки, слайдер, который запускается автоматически и работает с определенным интервалом (кстати это не лишает возможности крутить его вручную мышкой), таймер, который плавно появляется при загрузке страницы и динамически отсчитывает время оставшееся до наступления события.



Все файлы проекта на этом этапе смотрите в репо - react-site-slider-abc
ci -m "TimerUntil event"



PS:
Иногда, бывают такие ситуации, что указывать deadline статически (присвоить постоянное значение) это не очень удобно. Я столкнулся с этим разместив этот учебный макет на surge. Время, в конце концов истекло и на сайте появились унылые нули.

Как исправить? Стоит задать deadline динамически, ну например, прибавлять к сегодняшней дате несколько дней. Я решил эту задачу очень просто - получил текущую дату, назначил новую и потом получил все данные новой даты deadline в нужном формате

Timer.js
import React, { useState, useEffect } from "react";
import Slide from "react-reveal/Slide";

const Timer = () => {
  let deadline = getDeadline();
  function getDeadline() {
    var D = new Date();
    D.setDate(D.getDate() + 50);

    var month = D.getMonth();

    function LitMonth(month) {
      switch (month) {
        case 0:
          month = "Jan";
          break;
        case 1:
          month = "Feb";
          break;
        case 2:
          month = "Mar";
          break;
        case 3:
          month = "Apr";
          break;
        case 4:
          month = "May";
          break;
        case 5:
          month = "Jun";
          break;
        case 6:
          month = "Jul";
          break;
        case 7:
          month = "Aug";
          break;
        case 8:
          month = "Sep";
          break;
        case 9:
          month = "Oct";
          break;
        case 10:
          month = "Nov";
          break;
        case 11:
          month = "Dec";
          break;

        default:
      }
      return month;
    }
    month = LitMonth(month);
    var day = D.getDate();
    var year = D.getFullYear();
    D = month + " " + day + " " + year;
    return D;
  }

  const [days, setDays] = useState("0");
  const [hours, setHours] = useState("0");
  const [minutes, setMinutes] = useState("0");
  const [seconds, setSeconds] = useState("0");

  const getTimeUntil = (deadline) => {
    const time = Date.parse(deadline) - Date.parse(new Date());
    if (time < 0) {
      console.log("Date passed");
    } else {
      const seconds = Math.floor((time / 1000) % 60);
      const minutes = Math.floor((time / 1000 / 60) % 60);
      const hours = Math.floor((time / (1000 * 60 * 60)) % 24);
      const days = Math.floor(time / (1000 * 60 * 60 * 24));

      setDays(days);
      setHours(hours);
      setMinutes(minutes);
      setSeconds(seconds);
    }
  };
  useEffect(() => {
    setInterval(() => getTimeUntil(deadline), 1000);
  }, [seconds]);
  return (
    <Slide left delay={1000}>
      <div className="countdown_wrapper">
        <div className="countdown_top">Event starts in</div>
        <div className="countdown_bottom">
          <div className="countdown_item">
            <div className="countdown_time">{days}</div>
            <div className="countdown_tag">Days</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{hours}</div>
            <div className="countdown_tag">Hs</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{minutes}</div>
            <div className="countdown_tag">Min</div>
          </div>
          <div className="countdown_item">
            <div className="countdown_time">{seconds}</div>
            <div className="countdown_tag">Sec</div>
          </div>
        </div>
      </div>
    </Slide>
  );
};

export default Timer;




Все что я добавил, выделено красным. Я думаю, что объяснять это не надо. С помощью библиотек, это можно было сделать проще и нагляднее, но мы решили идти по пути наименьшего использования сторонних ресурсов, поэтому так.

Все последние изменения вы можете посмотреть на gitHub react-site-slider-abc
ci -m "added dinamic deadline date"
                                                                                                                                                             

Телеграм канал - Full Stack JavaScript Developer


Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.Пишите мне - kolesnikovy70 почта gmail.com