Translate

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

понедельник, 11 февраля 2019 г.

React - Интерактивная таблица. (I)

Сегодня мы буде создавать интерактивную таблицу с помощью библиотеки React.js



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

Готовое приложение здесь

Приложение будем создавать с помощью готового решения от разработчиков Facebook - create-react-app.

Я не буду на этом останавливаться, потому что много раз уже рассказывал об этом в постах о React. О самом приложении, вы сможете подробно почитать на странице github - /facebook/create-react-app

Здесь все просто. Создаем папку с проектом переходим в нее и ... Начнем с удаления ненужных файлов и удаления их импортов.

В папке src я оставил только три файла: App.js, index.js и serviceWorker.js

В файле App.js я удалил все ненужные подключения (импорты) и внутренность компонента App, а так как мы будем использовать Bootstrap для отображения наших компонентов, то я сразу добавил компоненту класс Bootstrap - className="container"


import React, { Component } from 'react';


class App extends Component {

  render() {
    return (
      <div className="container">
          MyApp
      </div>
    );
  }
}

export default App;



Устанавливаем и подключаем Bootstrap

Для простоты реализации наших задач с таблицей, мы будем использовать библиотеку Bootstrap. Для ее установки нам необходимо, находиться в папке нашего проекта (Если вы используете Visual Studio Code, то просто - создать терминал, а если КС - командную строку Windows или терминал, то перейти в папку проекта и набрать:

npm install bootstrap


или, если вы используете yarn

yarn add bootstrap


Я буду использовать npm, поэтому здесь и далее команды для npm.

После того, как закончится установка, в файле package.json, в разделе зависимостей - dependencies вы увидите установленные пакеты "bootstrap": "^4.2.1"



Теперь нам нужно его подключить в файле index.js


import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'bootstrap/dist/css/bootstrap.min.css'

ReactDOM.render(, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();



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

В консоли набираем:

npm start


Сервер запустится и наше приложение запустится на порту 3000.

Для остановки сервера, вы можете использовать сочетание клавиш Ctrl + C



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

На этом этапе код по ссылке: react-table-abc
ci-m (здесь и далее - коммит) "deleted unnecessary files and import bootstrap"

Получаем данные.

Данные для таблицы мы будем получать из сети по этой ссылке. http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}

Если перейти по ней, то можно увидеть все данные, которые будут переданы нам в формате json



Для читабельности данных и их красивого отображения. я использую расширение для браузера - JSONView

Очень важно здесь понять, что нам нужно получить данные из сети тогда, когда у нас будет уже сформировано DOM дерево.Именно поэтому мы будем использовать жизненный цикл React компонента, а именно - componentDidMount

Написание кода в новом формате, позволяет нам использовать новые возможности, а именно асинхронные запросы- asinc/await. Если вы хотите вспомнить детали, то посмотрите в моем блоге

Для получения данных, мы используем метод fetch() в который передадим эту ссылку внутри обратных кавычек.

Вся конструкция в компоненте App будет выглядеть так:


import React, { Component } from 'react';
import Loader from './Loader/Loader';
import Table from './Table/Table';

class App extends Component {

  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
     console.log(data)
  }
  render() {
    return (
      <div className="container">
         MyApp
      </div>
    );
  }
}

export default App;




Таким образом мы вернем в объект response промис. А в объект data положим результат работы response и его метода - JSON.

Для отображения объекта data мы его выведем в консоль.

Если посмотреть сейчас в консоль приложения, то мы увидим:



Если посмотреть на отдельный элемент, то мы увидим все данные, которые нам нужно будет отобразить на странице в виде таблицы:



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

На этом этапе код по ссылке: react-table-abc
ci -m "Asinc request to the date for table"

Индикатор загрузки.

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

Для решения этой задачи, мы не будем выдумывать свой велосипед и возьмем готовое решение с сайта Pure CSS Loaders.

В папке src мы создадим отдельную, одноименную папку для компонента Loader и поместим туда два файла: Loader.js и Loader.css.

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

Как вы догадались, css-код мы отправляем в файл Loader.css практически без изменений. Я только изменил цвет индикатора на синий и добавил ему позиционирование по центру страницы:


.lds-dual-ring {
    width: 64px;
    height: 64px;
    position: absolute;
    top: 50%;
    left: 50%;
  }
  .lds-dual-ring:after {
    content: " ";
    display: block;
    width: 46px;
    height: 46px;
    margin: 1px;
    border-radius: 50%;
    border: 5px solid rgb(40, 3, 253);
    border-color: rgb(40, 3, 253) transparent rgb(40, 3, 253) transparent;
    animation: lds-dual-ring 1.2s linear infinite;
  }
  @keyframes lds-dual-ring {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }



В файле Loader.js мы импортируем сам React для создания компонента и стили.

Далее, создадим "глупый" простой компонент, как безымянную функцию и сразу же ее экспортируем. Функция будет возвращать пустой div элемент, которому мы дадим класс, который взяли из html - кода нашего индикатора загрузки:


import React from 'react';
import './Loader.css';

export default () => <div className="lds-dual-ring" />



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


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

Сейчас нам нужно импортировать компонент Loader в компонент App и вывести его на страницу, вместо текста "MyApp".

Это очень простая задача и я не буду на ней останавливаться. Если не получится сделать самостоятельно, то я приведу код App компонента полностью ниже.



На этом этапе код по ссылке: react-table-abc
ci -m "Loader"

Формируем таблицу.

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

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

В него мы будем выводить данные в виде таблицы. Для этого мы будем использовать готовую таблицу, которую мы возьмем с сайта Bootstrap Bootstrap Tables- отсюда полностью скопируем thead таблицы. В коде ниже - отметил синим.

В компоненте мы будем возвращать саму таблицу table className="table", внутри поместим thead и ниже него создадим tbody, куда мы и будем выводить нами полученные данные из переменной data, которые мы передадим в виде props нашему компоненту Table в компоненте App.

Если я, все-таки вас запутал, то не огорчайтесь, начнем все по порядку.

Представим, что наш компонент Table уже получил эти данные в виде props с именем data. Наша задача вывести их по отдельности в каждую ячейку таблицы. Для этого мы используем метод map() В коде ниже отметил красным.


import React from 'react';

export default props => (
    <table className="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>First Name</th>
                <th>Last Name</th>
                <th>E-mail</th>
                <th>Phone</th>
            </tr>
        </thead>
        <tbody>
            { props.data.map(item =>(
                <tr key={item.id}>
                    <td>{item.id}</td>
                    <td>{item.firstName}</td>
                    <td>{item.lastName}</td>
                    <td>{item.email}</td>
                    <td>{item.phone}</td>
                </tr>
            ))}
        </tbody>
    </table>
)



Таким образом, мы выводим нужные нам ячейки (столбцы) в оду строку таблицы для каждого отдельного объекта данных.

Теперь, переходим в компонент App и там импортируем нашу таблицу - компонент Table

import Table from './Table/Table';

Сейчас нам нужно сделать так, чтобы пока идет загрузка, показывать компонент Loader, как только загрузка закончилась, то Table

Для этого мы в компоненте App создадим состояние - state, в котором создадим поле isLoading: со значением true и поле data, которое равно пустому массиву.


import React, { Component } from 'react';
import Loader from './Loader/Loader';
import Table from './Table/Table';

class App extends Component {
  state ={
    isLoading: true,
    data: [],
  }
  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
   console.log(data)
  }
  render() {
    return (
      <div className="container">
      {
        this.state.isLoading 
        ? <Loader />
        : <Table 
        data={this.state.data}
        />
      }
      </div>
    );
  }
}

export default App;



В методе render мы проверим. Если this.state.isLoading , то мы показываем компонент Loader, а если данные уже загрузили, то показываем таблицу.

В сам компонент Table мы передадим данные, о которых говорили выше - data={this.state.data}

Теперь нам нужно изменить state, как только загрузятся данные.

Для этого мы в методе жизненного цикла компонента, после получения данных, вместо вывода их в консоль, будем менять состояние компонента:


  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
    // console.log(data)
    this.setState({
      isLoading: false,
      data
    })

  }



В функции изменения состояния мы имеем право просто написать data, потому что у нас совпадают ключ и значение (data: data).

Все вместе:


import React, { Component } from 'react';
import Loader from './Loader/Loader';
import Table from './Table/Table';

class App extends Component {
  state ={
    isLoading: true,
    data: [],
  }
  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
    // console.log(data)
    this.setState({
      isLoading: false,
      data
    })

  }
  render() {
    return (
      <div className="container">
      {
        this.state.isLoading 
        ? <Loader />
        : <Table 
        data={this.state.data}
        />
      }
      </div>
    );
  }
}

export default App;



Посмотрим на приложение:



Мы увидим предупреждение. Это значит, что нам передали два элемента с одинаковым id, поэтому нам нужно внести некоторые изменения в наш ключ - key в файле Table.js и сделать его равным не просто id, а прибавить к нему, например телефонный номер для большей уникальности:

<tr key={item.id + item.phone}>

Теперь у нас есть таблица с данными.

На этом этапе код по ссылке: react-table-abc
ci -m "create table"

Сортировка элементов в таблице.

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

Для этого нам нужно написать функцию сортировки (мы это сделаем с помощью библиотеки Lodash), передать ее в виде props компоненту Table, повесить событие onClickс этой функцией на заголовок таблицы и передать в эту функцию название столбца ( id, firstName, lastName или phone ).

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

onClick={props.onSort.bind(null, 'id')}

Table.js

<tr>
  <th onClick={props.onSort.bind(null, 'id')}>ID</th>
  <th onClick={props.onSort.bind(null, 'firstName')}>First Name</th>
  <th onClick={props.onSort.bind(null, 'lastName')}>Last Name</th>
  <th onClick={props.onSort.bind(null, 'email')}>E-mail</th>
  <th onClick={props.onSort.bind(null, 'phone')}>Phone</th>
</tr>



Теперь, нам нужно передать эту функцию в виде свойства компоненту <Table /> : onSort={this.onSort}


{
  this.state.isLoading 
  ? <Loader />
  : <Table 
  data={this.state.data}
  onSort={this.onSort}
  />
}



Сразу над методом render(), компонента App мы напишем саму эту функцию.

Для сохранения контекста мы напишем ее в виде стрелочной функции.

Название параметру - sortField дали произвольное.


onSort = sortField => (
    console.log(sortField)
  )


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



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

Собственно сортировка.

Для упорядочивания элементов по алфавиту и в цифровом порядке, мы обратимся к orderBy Lodash.

Добавим ее в наш проект.

npm i --save lodash


После того, как завершится установка, в зависимостях (package.json) появится нужная нам библиотека:



Теперь нам нужно ее импортировать. Для этого идем в файл App.js и добавляем следующую строку:

import _ from 'lodash';

В методе onSort создадим копию нашего массива:

const cloneData = this.state.data.concat();

Это делаем для того, чтобы у нас никак не менялся state.data в состоянии компонента.

Теперь нам нужно отсортировать эту копию массива по определенному полю, которое мы передаем в функцию в виде аргумента - sortField и в определенном направлении

Направление мы сохраним в стейтах как sort:'asc'. Стоит помнить, что оно может меняться на 'desc'


  state ={
    isLoading: true,
    data: [],
    sort: 'asc',  // 'desc'
    sortField: 'id', // поле по умолчанию
  }


Теперь нам нужно определить направление. Назовем переменную - sortType и проверим, что у нас лежит в свойстве sort наших стейт. И если значение переменной будет 'asc', то мы меняем его на 'desc' и наоборот.

const sortType = this.state.sort === 'asc' ? 'desc' : 'asc';

Создадим переменную для отсортированного массив - orderedData. Метод сортировки напишем по примеру документации. передадим туда копию нашего массива - cloneData, вторым параметром нам нужно указать поле по которому мы сортируем - sortField и направление - sortType

const orderedData = _.orderBy(cloneData, sortField, sortType);

В итоге мы получаем отсортированные данные и нам остается изменить состояние нашего компонента - this.setState({}).

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

Поле data мы меняем на orderedData, sort на sortType и поле sortField на sortField.

sortField нужно для того чтобы мы знали какое поле мы сортировали. Поэтому добавили его в наши стейт с значением 'id' по умолчанию.

Метод onSort компонента App.js полностью:

  onSort = sortField => {
    
    const cloneData = this.state.data.concat();
    const sortType = this.state.sort === 'asc' ? 'desc' : 'asc';
    const orderedData = _.orderBy(cloneData, sortField, sortType);

    this.setState({
      data: orderedData,
      sort: sortType,
      sortField
    })
  }



Теперь можно посмотреть на результат в браузере. Нажимаем на LastName и происходит сортировка в этом столбце по алфавиту.



Еще раз нажимаем и сортировка начинается в обратном порядке:



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

Визуальное отображение сортировки.

Во первых мы будем передавать в компонент Table два параметра из стейта компонента App. За направление отвечает -sort и за поле - sortField. При изменении сортировки они будут меняться в методе this.setState({}), который мы написали ранее.


      {
        this.state.isLoading 
        ? <Loader />
        : <Table 
        data={this.state.data}
        onSort={this.onSort}
        sort={this.state.sort}
        sortField={this.state.sortField}
        />
      }


Мы их передали в наш компонент и теперь мы их проверим.

{props.sortField === 'id' ? <small>{props.sort}</small> : null}

Проще говоря, если в sortField нам будет передан 'id', то мы покажем некий компонент - <small>{props.sort}</small> на экране (в данном случае в поле заголовка - ID), а если нет, то ничего не покажем - null.

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


<thead>
  <tr>
      <th onClick={props.onSort.bind(null, 'id')}>
          ID {props.sortField === 'id' ? <small>{props.sort}</small> : null}
      </th>
      <th onClick={props.onSort.bind(null, 'firstName')}>
          First Name {props.sortField === 'firstName' ? <small>{props.sort}</small> : null}
      </th>
      <th onClick={props.onSort.bind(null, 'lastName')}>
          Last Name {props.sortField === 'lastName' ? <small>{props.sort}</small> : null}
      </th>
      <th onClick={props.onSort.bind(null, 'email')}>
          E-mail {props.sortField === 'email' ? <small>{props.sort}</small> : null}
      </th>
      <th onClick={props.onSort.bind(null, 'phone')}>
          Phone {props.sortField === 'phone' ? <small>{props.sort}</small> : null}
      </th>
  </tr>
</thead>



Посмотрим в браузере. Если нажать на заголовок - ID, то появится метод сортировки - (asc), а при повторном нажатии - (desc)



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

Поэтому, чтобы не путать пользователя, мы в компоненте App, в момент получения данных в сет-стейт метода componentDidMount() добавим сортировку:


  async componentDidMount() {
    const response = await fetch(` http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}`)
    const data = await response.json()
   
    this.setState({
      isLoading: false,
      data: _.orderBy(data, this.state.sortField, this.state.sort)
    })

  }



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

Сейчас мы можем проверить сортировку по всем полям.



На этом этапе код по ссылке: react-table-abc
ci -m "Sort items in columns and display sorting options in the header."

Как задание для тренировки: вы можете заменить строку <small>{props.sort}</small> на отдельный компонент в котором вывести отдельно индикаторы сортировки в виде картинок или иконок.

Вывод информации по клику.

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

Для этого мы в компоненте Table каждому элементу tr добавим обработчик события:

onClick={props.onRowSelect.bind(null, item)}

передадим туда item

Table.js часть:


<tbody>
  { props.data.map(item =>(
      <tr key={item.id + item.phone} onClick={props.onRowSelect.bind(null, item)}>
          <td>{item.id}</td>
          <td>{item.firstName}</td>
          <td>{item.lastName}</td>
          <td>{item.email}</td>
          <td>{item.phone}</td>
      </tr>
  ))}
</tbody>



Сейчас нам нужно написать сам метод в компоненте App и передать его в Table Передаем его в компонент:


{
  this.state.isLoading 
  ? <Loader />
  : <Table 
  data={this.state.data}
  onSort={this.onSort}
  sort={this.state.sort}
  sortField={this.state.sortField}
  onRowSelect={this.onRowSelect}
  />
}


Выше метода render в компоненте App пишем сам метод, который будет принимать в качестве параметра row и выведем его в консоль:


  onRowSelect = row => (
    console.log(row)
  )



Проверим, что же у нас будет в консоли?



При клике по строке, мы получаем все данные пользователя в консоль. Для того, чтобы вывести данные, нам нужно в стейт добавить новое свойство - row


  state ={
    isLoading: true,
    data: [],
    sort: 'asc',  // 'desc'
    sortField: 'id',
    row: null,
  }


И вместо вывода в консоль данных объекта в методе onRowSelectсделать изменение стейта:


  onRowSelect = row => (
    this.setState({row})
  )


Теперь в этом же компоненте мы будем выводить детальное отображение этой строчки. Для этого нам потребуется новый компонент DetailRowView и некоторая логика:


      {
        this.state.row ? <DetailRowView person={this.state.row} /> : null
      }


Если в this.state.row что-то есть, то покажем новый компонент, а если нет, то ничего не покажем.

В компонент мы передадим состояние.

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

В папке src создадим папку- DetailRowView с одноименным js-файлом. Создадим простой компонент и выведем какой-либо текст на экран:


import React from 'react';

export default () => (
   <div>
     <h1>DetailRowView</h1>
   </div>
)


Не забываем импортировать этот компонент в App.js

import DetailRowView from './DetailRowView/DetailRowView';

Посмотрим в браузере.



При клике по любому компоненту внутри таблицы, мы будем видеть текст внизу.

Данные из props получим сразу путем деструктуризации. ({props})

Теперь нам стоит посмотреть на то, что получает данный компонент в консоли на вкладке Networks



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

DetailRowView.js

import React from 'react';

export default ({person}) => (
    <div>
    <p>Выбран пользователь <b>{person.firstName + ' ' + person.lastName}</b></p>
    <p>
    Описание: <br />
    <textarea defaultValue={person.description} />
    </p>

    <p>Адрес проживания: <b>{person.address.streetAddress}</b></p>
    <p>Город: <b>{person.address.city}</b></p>
    <p>Провинция/штат: <b>{person.address.state}</b></p>
    <p>Индекс: <b>{person.address.zip}</b></p>

  </div>
)


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



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

На этом этапе код по ссылке: react-table-abc
ci -m "data output from the table row to the page"

Продолжение ->

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


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

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

среда, 6 февраля 2019 г.

JS ES6 Strings.

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

Любой текст внутри двойных или одинарных кавычек считается строкой в JavaScript.



Больше материалов по JavaScript ES6 в этом блоге.

Определение строк.

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

let firstName = "John"



Вы также можете создать строковый объект с помощью конструктора String ():

let firstName = new String("John")



Строковые Методы

Ниже приведен список методов, поддерживаемых объектом String в JavaScript.

length

Метод length возвращает количество символов в строке, включая пробелы:

let myString = "hello"

myString.length

//returns 5



charAt()

Функция charAt () возвращает символ по указанному индексу:

let myString = "hello"

myString.charAt(2)

//returns 'l'



charCodeAt()

То же, что charAt (), но возвращает представление символа в юникоде с указанным индексом:

let myString = "hello"

myString.charCodeAt(2)

//returns 108



concat()

Метод concat () объединяет строки:

let firstName = "John"
let lastName = " Doe"

firstName.concat(lastName)

//returns 'John Doe'



indexOf()

Метод indexOf () возвращает индекс первого вхождения указанного значения. Если совпадений не найдено, возвращается -1:

let firstName = "John"

firstName.indexOf('o')
//returns 1

firstName.indexOf('z')
//returns -1



lastIndexOf()

То же, что indexOf (), но возвращает индекс последнего вхождения вместо первого вхождения:

let fruit = "banana"

fruit.lastIndexOf('a')
//returns 5

fruit.lastIndexOf('z')
//returns -1



localeCompare()







Вы можете скачать готовую шпаргалку на моей странице на GitHub -> /arrays-js-es6-abc, или добавить страницу в закладки.                                                                                                                                                              

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

четверг, 31 января 2019 г.

JS ES6 Arrays.

Массивы (Arrays) — лаконичным способом хранения списка элементов под одним именем.


Больше материалов по JavaScript ES6 в этом блоге.

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


let names = ["Steve", "Bob", "Sam", "Erick"]

names[0] //returns "Steve"
names[2] //returns "Sam"



Массивы начинают считать с 0.
В приведенном выше примере "Steve" является первым элементом в массиве имен. Когда мы вызываем names[0], мы просим вернуть элемент в позиции 0 массива. Вот почему names[0] возвращает "Steve", а names[2] возвращает "Sam".

Определение массивов - Defining Arrays.

Есть несколько способов «создать» или инициализировать массивы в JavaScript.
Самый простой способ - инициализировать массив в определении переменной.
Например:


let scores = [87,43,88,99]



Массивы также могут быть созданы с помощью объекта Array:


let scores = new Array(87, 43, 88, 99)



Доступ к элементам массива - Accessing Array Elements

Доступ к элементам в массиве прост.
Вы просто предоставляете имя массива с индексом, который хотите вернуть:


let scores = [87,43,88,99]

scores[0] // returns 87
scores[1] // returns 43
scores[2] // returns 88



Перебор массива циклом.

Чтобы перебрать каждый элемент массива, вы можете использовать встроенный метод forEach ().


let scores = [87,43,88,99]

scores.forEach((x) => {
  console.log(x)
})

//logs 87, 43, 88, 99



Другие методы массива.

Метод forEach () является одним из многих поддерживаемых методов для массивов JavaScript. Ниже приведен список других методов с краткими примерами для каждого:

concat()

Метод concat () объединяет массивы.


let scores = [87, 43, 88, 99]
let moreScores = [100,33,78,44]

scores.concat(moreScores)

//returns [87, 43, 88, 99, 100, 33, 78, 44]



every()

Метод every () возвращает true, если каждый элемент проходит заданное условие:


let scores = [87, 43, 88, 99]

scores.every((x) => { return x > 10 })

//returns true since every element is greater than 10



filter()

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


let scores = [87, 43, 88, 99]

scores.filter((x) => { return x > 80 })

//returns [87, 88, 99]



indexOf()

Этот метод возвращает первый элемент массива, который соответствует заданному значению. Если нет элементов соответствующих значению, возвращается -1:


let scores = [87, 43, 88, 99]

scores.indexOf(88) //returns 2
scores.indexOf(103) // returns -1



join()

Метод join () объединяет все элементы массива в строку.


let scores = [87, 43, 88, 99]

scores.join() //returns "87,43,88,99"
scores.join("|") //returns "87|43|88|99"



lastIndexOf()

Аналогичен indexOf, но возвращает индекс последнего элемента, который соответствует предоставленному значению. Возвращает -1, если совпадений нет:


let scores = [87, 43, 88, 99, 88]

scores.lastIndexOf(88) //returns 4
scores.lastIndexOf(11) //returns -1



map()

Функция map () применяет блок кода или функцию к каждому элементу в массиве и возвращает новый массив с результатами каждой операции.


let scores = [87, 43, 88, 99]

scores.map((x) => {return x + 10})

//returns [97, 53, 98, 109]



pop()

Метод pop () удаляет последний элемент в массиве и возвращает этот элемент.


let scores = [87, 43, 88, 99]

scores.pop() //returns 99

//scores now equals [87, 43, 88]



push()

Метод push () добавляет новый элемент в массив и возвращает новую длину массива.


let scores = [87, 43, 88, 99]

scores.push(44) //returns 5

//scores now equals [87, 43, 88, 99, 44]



reduce()

Метод reduce () возвращает агрегированный результат функции, примененной к массиву


let count = [1,2,3,4]

count.reduce((a,b) => {return a + b})

//returns 10



reduceRight()

То же, что и reduce (), но работает справа налево:


let count = [1,2,3,4]

count.reduceRight((a,b) => {return a - b})

//returns -2



reverse()

Повернет в другую сторону элементы в массиве:


let scores = [87, 43, 88, 99]

scores.reverse()

//returns [99, 88, 43, 87]



shift()

Метод shift () похож на pop (), но действует на первый элемент массива, а не на последний.


let scores = [87, 43, 88, 99]

scores.shift() //returns 87

//scores now equals [43, 88, 99]



slice()

Возвращает подмассив на основе начальной и конечной позиции:


let scores = [87, 43, 88 99]

scores.slice(1,3)

//returns [43,88]



some()

Метод some () возвращает true, если хотя бы один элемент удовлетворяет данному условию


let scores = [87, 43, 88, 99]

scores.some((x) => {return x < 50}) //returns true
scores.some((x) => {return x < 40}) //returns false



sort()

Сортирует элементы массива:


let scores = [87, 43, 88, 99]

scores.sort() //returns [43, 87, 88, 99]
scores.sort((a,b) => {return b - a}) // returns [99, 88, 87, 43]



splice()

Метод splice () изменяет содержимое массива. Этот метод принимает три аргумента: (начальный индекс, количество удаляемых элементов, замещающие элементы)


let scores = [87, 43, 88, 99]

scores.splice(2, 0, 95) //returns []

//scores now equals [87, 43, 95, 88, 99]

let counts = [1, 2, 3, 4, 5]

counts.splice(1, 3) //returns [2,3,4]

//scores now equals [1,5]



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

toString()

Просто преобразует массив в строку.


let scores = [87, 43, 95, 88, 99]

scores.toString()

//returns '87,43,95,88,99'



unshift()

Добавляет элементы в начало массива и возвращает новую длину массива.


let scores = [87, 43, 95, 88, 99]

scores.unshift(50,70) //returns 7

//scores now equals [50, 70, 87, 43, 95, 88, 99]



find()

Метод find () возвращает первый элемент, который соответствует указанному условию.


let scores = [87, 43, 95, 88, 99]

scores.find((x) => {return x > 90})

//returns 95



findIndex()

То же, что find (), но возвращает первый индекс вместо первого значения, которое удовлетворяет условию.


let scores = [87, 43, 95, 88, 99]

scores.findIndex((x) => {return x > 90})

//returns 2



entries()

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


let scores = [87, 43, 95, 88, 99]

var entries = scores.entries()

console.log(entries.next().value) //returns [0, 87]
console.log(entries.next().value) //returns [1, 43]



from()

Метод from () создает новый массив из объекта, похожего на массив.


let nameArray = Array.from('Sam')

console.log(nameArray)

//logs ['S', 'a', 'm']



keys()

Метод keys () возвращает новый объект итератора, который содержит ключи для каждого индекса в массиве.


let scores = [87, 43, 95, 88, 99]
let iterator = scores.keys();

console.log(iterator.next())

//logs {value: 0, done: false}



Заключение:

Массивы - это мощные типы данных в JavaScript. Используйте массивы, если вы хотите отслеживать коллекцию значений и порядок их расположения.

Вы можете скачать готовую шпаргалку на моей странице на GitHub -> /arrays-js-es6-abc, или добавить страницу в закладки.                                                                                                                                                              

среда, 26 декабря 2018 г.

Redux - простое руководство.

Пришло время разобраться с Redux и написать простое приложение с его использованием.




Посмотреть готовое приложение можно на хосте.

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

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

Для простоты доступа к данным и работы с ними, мы будем использовать Redux.



Стоит напомнить, что Redux использует Flux архитектуру (не путайте с Flux-библиотекой, которая тоже использует такую архитектуру). Flux предполагает однонаправленный поток данных - One-way binding

Для простоты понимания этого, стоит посмотреть на картинку <<Схема>>:



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

Store - это хранилище и именно так его нужно понимать в данном контексте. По своей сути Store - это обыкновенный объект, который и определяет, как у нас будет выглядеть наши компоненты.

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

Далее, у нас идёт Dispatcher. Dispatcher это центральный узел, который обрабатывает все наши потоки данных. То есть - всё проходит через него! А конкретно, через него проходит те данные, которые определяют как раз-таки action.

Значит есть один action (слева на картинке). Этот Action у нас стоит самого начала и считаете это теми данными которые идут по умолчанию. То есть, по умолчанию поступает какие-то данные в Action говорит диспетчеру - "Покажи мне Username такой-то и Lastname такой-то!". И диспетчер заносит изменения в Store, откуда они подтягиваются View (React -components) и мы видим картинку на экране.

В тоже время, если мы хотим поменять нашу визуальную картинку со стороны пользователя, мы так же создаем новый Action. Новый Action, который опять обращается к диспетчеру и делает тоже самое. Говорит диспетчеру "Обнови username Коля на username Вася" и он его обновляет. Это определяет уже наш View.

Вот в этом собственное заключается вся суть архитектуры Flux и самого принципа One Way binding, то есть однонаправленного потока данных. Если вы заметили, то у нас поток данных идёт всегда в одну сторону!

View не может непосредственно повлиять на что-то, что есть в нашим хранилище! Это главное отличие его от так называемого Two-way binding, например, который использует ангуляр.

Действия загрузки данных.

Передача данных.

Для простоты, мы будем использовать готовое решение create-react-app. Как его установить я рассказывал в своих постах о React Переходим в папку нашего приложения и устанавливаем два дополнительных модуля:
  1. redux
  2. react-redux
Эти модули позволят нам использовать весь функционал Redux и просто совместить его с React.

npm install redux react-redux


--save-dev команду можно не писать.

После установки, мы можем открыть наш проект в редакторе.

Выбираем файл index.js в папке src - это точка входа в наше приложение.

Импортируем компонент Provider из модуля react-redux

import { Provider } from "react-redux";

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


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


Следующее, что нам нужно сделать, это импортировать функцию, которая и создает Store.

import { createStore } from 'redux';

Для создания хранилища - Store напишем:

const store = createStore(()=>{});

В качестве параметра эта функция должна принимать reducer. Сам по себе редусер - это просто функция, поэтому мы для начала, можем записать вместо него , пустую функцию, например стрелочную - ()=>{}.

Для соединения провайдера с хранилищем Store, мы добавим Provider атрибут store, со значением store.


ReactDOM.render(<Provider store={store}>
                    <App />
                </Provider>, document.getElementById('root'));


Таким образом, мы создали Store, обернули его в провайдер и сделали данные доступные для наших компонентов.

Создание Reducers.

В папке src создадим папку reducers и в ней два файла index.js и info.js.

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

Сам термин пришел к нам из JS Array.prototype.reduce().

Вся суть редусера - это взять предыдущее, значение, плюс текущее значение и в конце - вернуть новое значение.

То есть, мы берем предыдущее состояние State, то что нам нужно поменять, соединяем это и получаем что-то новое, что в последствии у нас отрендерится на экране.

Создадим первый редусер в файле info.js


// имитация базы данных
const initialState = {
    user: "unknown user"
}
//  сам редусер 
export default function userInfo (state = initialState) {
    return state
}


Функция принимает в качестве стандартного значения - initialState и возвращает новый state на основании старого.

Проще говоря редусер принимает один state, а возвращает новый state.

Файл index.js в папке reducers, нам потребуется для того, чтобы все редусеры держать в одном месте, но в разных файлах. В качестве "родительского" редусера мы будем использовать index.js. В этом нам поможет, то что уже реализовано в библиотеке Redux - combineReducers


import { combineReducers } from "redux"
import userInfo from "./info"

const rootReducer = combineReducers({
  userInfo
})

export default rootReducer;



Теперь, самое время вернуться в файл index.js в корне проекта, и заменить анонимную, пустую функцию на rootReducer. И добавим импорт редусеров в наш файл.


import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

import { Provider } from "react-redux";
import { createStore } from "redux";
import rootReducer from "./reducers/index";

const store = createStore(rootReducer);

ReactDOM.render(<Provider store={store}>
                    <App />
                </Provider>, document.getElementById('root'));
serviceWorker.unregister();



Таким образом наш Store, будет знать, что когда мы к нему обращаемся, то он будет вызывать все редусеры, которые у нас есть.

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

Фактически, мы сейчас с помощью нашей функции в редусере ( function userInfo ), занесли в наш State данных переменной user со значением "unknown user". Теперь осталась нам подключить наш компонент к этому Store и взять с него то что принадлежит нам по праву, а именно наши свойства.

App.js


import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

import { connect } from "react-redux"

class App extends Component {
  render() {
    return (
          <h2>Welcome to React</h2>
    );
  }
}


export default connect()(App);



Чтобы подключить Redux к компоненту есть специальная функция - connect

Подключаем ее:

import { connect } from "react-redux";

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

export default connect()(App);

Если сейчас запустит и посмотреть приложение:



Здесь можно видеть, что функция connect создает дополнительный компонент над компонентом App.

Передача свойств приложению.

Для передачи свойств приложению:

App.js

import React, { Component } from 'react';

import './App.css';

import { connect } from "react-redux"

class App extends Component {
  render() {
    return (
          <h2>Welcome to React {this.props.user}</h2>
    );
  }
}

function mapStateToProps(state) {
  return {
    user: state.userInfo.user
  }
}

export default connect(mapStateToProps)(App);



Создаем дополнительную функцию mapStateToProps. Название произвольное, но оно рекомендуется, чтобы другие разработчики понимали то, что делает эта функция. Эта функция будет передавать наши state в свойства. Функция принимает наш state, а возвращать будет некую переменную ( для логичности - user), а в качестве значения возьмем те данные, которые указали на данный момент в редюсере.

Чтобы обратится к этим данным, нам нужно обратиться к нашему state, с которого мы подтягиваем данные. Дaлее к userinfo из rootReducer и уже используя userinfo мы попадаем в наш изначальный редусер и получаем доступ к значению переменной user в файле info.js.

user: state.userInfo.user

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



Таким образом мы с помощью функции передали в компонент App свойство user со значением unknown user

Мы смогли передать те значения, которые мы изначально несли в нашу "Базу данных" - const initialState.

Вывести свойства на экран нам легко - {this.props.user}

Практически, мы получили тоже самое, как если бы передали свойство user={"unknown user"} непосредственно в компонент реакт.

Псомотреть весь код приложения, можно в репо - redux-simple-abc ci -m"Simple data transfer with Redux"

Действия пользователя.

Действия - Actions

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

Действия пользователя будут представлены в Action, который на картинке <<Схема>> вверху.

Важно запомнить, что Action определяют что именно нужно поменять, а Reducer - как именно.


Для лучшего понимания работы редусеров, следует вспомнить, как различаются компоненты в React:



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

В папке src создадим папки: containers и components.

В папке components создадим: User.js и Year.js

В файле User.js создадим простой, глупый компонент - Dumb Component:


import React from "react"
export default class User extends React.Component {
  render() {
    return (
      <h2>Welcome to React {this.props.user}</h2>
    );
  }
}



Его render() мы взяли из файла App.js.

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


import React from "react"

export default class Year extends React.Component {
  constructor(props) {
    super(props)
    this.onBtnClick = this.onBtnClick.bind(this)
  }

  onBtnClick(event) {
    return this.props.setYear(event.target.textContent)
  }

  render() {
    return <div>
      <button onClick={this.onBtnClick}>1975</button>
      <button onClick={this.onBtnClick}>1991</button>
      <button onClick={this.onBtnClick}>2015</button>
      <p>This year has been chosen - {this.props.year}</p>
    </div>
  }
}



Если коротко, то создали компонент, который будет выводит кнопки. На кнопки навесили событие onClick, которое вызывает функцию onBtnClick. В конструкторе прибиндили эту функцию. Эта функция возвращает функцию setYear(), которая в качестве аргумента принимает значение кнопки (текст кнопки - в данном случае год) - event.target.textContent.

Таким образом мы написали два dumb-компонента.

Теперь нам нужно поменять файл App.js

Поскольку он у нас будет выполнять функцию smart - компонента, мы переместим его в папку src/containers

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

import App from './containers/App';

На этом этапе нам нужно запустить приложение, чтобы убедиться, что оно по прежнему работает.



Посмотреть весь код приложения на этом этапе, можно в репо - redux-simple-abc ci -m"divided the application into components"

Теперь нам необходимо в наш smart - компонент App.js добавить наши dump-компоненты - User.js и Year.js


    import React, { Component } from 'react';
    import './App.css';
    import User from '../components/User';
    import Year from '../components/Year';
    import { connect } from "react-redux"

    class App extends Component {
      render() {
        return (
           <div>
            <User />
            <Year />
          </div>
              
        );
      }
    }

    function mapStateToProps(state) {
      return {
        user: state.userInfo.user
      }
    }

    export default connect(mapStateToProps)(App);


Если сейчас посмотреть в браузер, то мы увидим:



Вывели на экран все статические данные, кроме необходимых переменных.

Переменная user не задействована в экшенах и мы ее можем легко отобразить прямо сейчас:

<User user={this.props.user} />



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

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

В папке src создаем папку actions а в ней файл actionYear.js


export default function setYearAction(year) {
    return {
      type: "SET_YEAR",
      payload: year
    }
  }



Экшен по своей сути это обычный объект. Для удобства их создают через специальные функции, которые называются Action Creators. Это упрощает чтение кода и очень удобно для разработки!

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

Функция обязательно возвращает объект и в нем должно быть обязательно поле - type.

Поле - type, чем то похоже на идентификатора экшена, и значение этого поля всегда строка с заглавными буквами - UPPERCASE.

Вторая переменная payload, будет принимать значение year то, которое мы передаем в эту функцию изначально.

payload - тоже вне гласное соглашение. Обозначает что-то типа "полезной нагрузки".

Более подробно о формате написания экшенов можно почитать в стандартах - Flux Standard Action

Как мы говорили ранее - экшен - ЧТО МЕНЯЕМ... А мы меняем -year, который в аргументе и меняем его экшеном - "SET_YEAR"

Теперь нам нужно понять КАК нам его изменить.

За это у нас отвечает редусер.

Переходим в редусеры src/reducers/info.js и будем его усовершенствовать.

Добавим некий изначальный стейт - например: year: 2015, а в самом редусере добавим еще один аргумент - action

Таким образом редусер будет принимать начальное состояние: state = initialState и то, что должно измениться - action, все это соединяет и возвращает нам новый стейт на основании проделанной работы.

Обычно их указывают в формате switch/case.

В нем обратимся к типу экшена и обработаем различные варианты.

Если у нас тип = "SET_YEAR", то мы возвращаем весь предыдущий стейт ( ...state) и ключ year с новым значением action.payload.

Другими совами: Мы берем наш изначальный стейт :

reducers/info.js

const initialState = {
    user: "unknown user",
    year: 2015
}



Вторым аргументом берем стейт из экшена: actions/actionYear.js
export default function setYearAction(year) {
    return {
      type: "SET_YEAR",
      payload: year
    }
  }



Соединяем их вместе и получаем что-то новое! А конкретно - изменяем поле year на то, которое указано в экшене.

Теперь нашей задачей является - доделать наш "умный компонент", который должен отправлять данные в Store.

Переходим в src/containers/App.js

Первое - импортировать функцию из actionYear.js

import setYearAction from '../actions/actionYear';

Выведем год статически:


    import React, { Component } from 'react';
import './App.css';
import User from '../components/User';
import Year from '../components/Year';
import setYearAction from '../actions/actionYear';
import { connect } from "react-redux";

class App extends Component {
  render() {
    return (
      <div>
        <User user={this.props.user} />
        <Year year={this.props.year}/>
      </div>
          
    );
  }
}

function mapStateToProps(state) {
  return {
    user: state.userInfo.user,
    year: state.userInfo.year
  }
}

export default connect(mapStateToProps)(App);


Посмотрим в браузере:



Таким образом мы вывели "стартовые значения" стейта.

Теперь нам нужно оживить наши кнопки!

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

Для этого есть специальная функция - mapDispatchToProps(dispatch) .Она принимает аргумент - dispatch и возвращает некий объект.


function mapDispatchToProps(dispatch) {
  return {
    setYearFunction: year => {
      dispatch(setYearAction(year))
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App);



setYearFunction - произвольное название. Мы выбрали его, чтобы просто отличать функцию от других. В качестве ключа в нее мы передаем функцию, которую мы записали в стрелочном формате. Эта функция принимает аргумент year, который передаст наш экшен actionYear и с ним мы в функции dispatch() вызываем экшен креатор. - setYearAction(year) и в качестве аргумента ей передадим тот же year

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

Если посмотреть в браузер, то в панели разработчика мы увидим:



Поскольку мы подключили в наш компоненту новую функцию - mapDispatchToProps она передала в наш компоненте новое свойство - setYearFunction (как мы его и назвали в этой функции)

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

В файле App.js передадим компоненту Year напишем свойство, которому передадим функцию setYearFunction из ретурна mapDispatchToProps

<Year year={this.props.year} setYear={this.props.setYearFunction} />

this.props - пишем, потому что функция передается в качестве свойства.

Теперь становится ясным, что в функции onBtnClick мы как раз и возвращаем это свойство со значением текста кнопки:

Файл: components/Year.js

 onBtnClick(event) {
    return this.props.setYear(event.target.textContent)
  }



Теперь самое время посмтреть наше приложение и проверить, что все работает правильно:



Коротко:

Все начианется в нашем компоненте - Year.Здесь есть кнопки, при нажатии на которые срабатывает функция onBtnClic

Эта функция возвращает нам event.target.textContent - то есть в данном случае это текст кнопки.

Далее эти данные попадают в функцию mapDispatchToProps(dispatch) и в ней уже year будет равен тому, что мы указали в компоненте и функция dispatch(setYearAction(year)) отправляет наш экшен в хранилище -Store с помощью метода connect(mapStateToProps, mapDispatchToProps)(App)

В Store запускается редусер.Он принимает начальный стейт и экшен, который ему прислал mapDispatchToProps(dispatch), срабатывает условие - "SET_YEAR" и year меняет значение на новое - action.payload.

Далее возвращается в приложение функцией mapStateToProps(state) и мы получаем доступ к этим данным.

Посмотреть весь код приложения, можно в репо - redux-simple-abc ci -m"Finish"





                                                                                                                                                             

воскресенье, 23 декабря 2018 г.

React-Router простое руководство.

Всем привет И сегодня я вам хочу рассказать что такое реакт-роутер.




Работу простого реакт-роутера вы можете увидеть здесь

Итак, давайте начнём сначала и определим, что вообще такое роутер?

Это такой небольшой программный модуль который помогает нам распоряжаться нашими Url - адресами в браузере. Если всё что мы делали ранее было размещено на одной странице и поэтому в роутере смысла никакого не было, то теперь мы можем с помощью роутера разбивать информацию на нашей странице на некоторые такие логические куски. То есть на заглавной странице у нас будет одна информация, на странице контактов - другая. На странице "О нас" - третья и т.д.

Если говорить конкретно о реализации роутера в react приложении, то есть, как минимум три способа как это можно сделать:
  1. - это написать свой собственный роутер.
  2. - использовать стороннюю библиотеку в которой уже реализован роутер, например Ember.js или другие какие-нибудь.
  3. - использовать специальный npm package который называется реакт-роутер.
Поскольку первый вариант нужно очень долго зарабатывать чтобы он был похож на хороший роутер 2-ой вариант немного выбивается из общей картины нашего react приложения, мы будем использовать 3-ий вариант, а именно - npm module react-router.

Само реакт-приложение мы развернем с помощью create-react-app.

В общем поехали!
И так первое что нужно сделать это установить create-react-app, то есть - нашу заготовку для react приложения.

Установка самого React-приложения.

Перейдем на страницу этого модуля facebook/create-react-app и здесь вы можете подчеркнуть всю необходимую для себя информацию.

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

Вы можете делать так, как вам более удобно. Я использую новый вариант установки. Он будет зависеть от версии вашего npm. Чтобы узнать ее, вам нужно открыть терминал или КС(командная строка, окно команд) в Windows. ⊞ Win + R, написать - cmd и нажать Enter.

npm -v


Если вы предпочитаете развернуть проект из глобально установленного, то вы можете просто выполнить команду:

create-react-app name_your_project_folder


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

cd name_your_project_folder


И теперь, чтобы запустить приложение нужно набрать:

npm start




А перейдя по адресу: http://localhost:3000/ вы увидите работающее приложение.

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

Теперь на очереди реакт-роутер.


Устанавливаем react-router

Посмотреть документацию можно по ссылке: ReactTraining/react-router

Поскольку реакт-роутер не входит в стандартный набор, то мы должны установить его вручную.

Для того чтобы это сделать, нужно остановить сервер и перейти в командную строку Ctrl + C -> y и нажать Enter.

Здесь пишем следующее:

npm install react-router --save


и ставим флажок --save чтобы сохранить его package.json зависимости.

Также нам понадобится ещё один дополнительный модуль это react-router-dom, который позволяет нам более легко оперировать с процессом роутинга.

npm install react-router-dom --save


Можно сделать и одной командой:

npm install react-router react-router-dom --save


Теперь можем переходить непосредственно к созданию самого роутера.

Пишем роутер.

Итак, я захожу в папку со своей приложением и чтобы редактировать приложение созданное с помощью create-react-app, нам необходимо редактировать файл App.js и index.js в папке src.

Если мы вернемся на нас нашу страничку, где можно скачать модуль react-router, то здесь вы увидите что они нам демонстрируют два варианта использования данного плагина 1-ый с использованием ES6 и второй это CommonJS. То есть, по сути разница между ними никакой нет.

Если рассмотреть более подробно файл App.js, то вверху мы импортируем сначала react и компонент чтобы не писать React.Component каждый раз при создание нового компонента.

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

Поскольку нам это приложение уже не нужно, то мы удаляем все из файла App.js.

Итак начнем. Чистый файл App.js.
Я сначала импортирую все необходимые нам модули.

import React, { Component } from 'react';

Далее импортируем несколько новых компонентов из библиотеки react-router. Если вы хотите импортировать именно компоненты вам нужно их указывать в фигурных скобках, как Component, выше. Если же нет, то можно писать просто как импорт React.

Импортируем необходимые компоненты из react-router-dom

import { BrowserRouter, Route, Link } from 'react-router-dom';

И так что делают компоненты?

BrowserRouter - тот самый роутер. Он использует HTML5 History API и следит за тем чтобы ваш UI был всегда синхронизирован с тем что написано в адресной строке.

Следующий Route - он отвечает за отображение непосредственного UI И следит за тем чтобы пути совпадали между компонентом и адресной строкой.

и Link - это такая немножко видоизмененная ссылка под react для редиректа на нужную нам страницу.

Также я рекомендую сделать еще одну небольшую вещь:

import createBrowserHistory from 'history/createBrowserHistory';


UPDATE:В последней версии реакт-роутера, рекомендуют импортировать и подключать так:
import { createBrowserHistory } from "history";
const history = createBrowserHistory();




Это нужно для того, чтобы импортировать createBrowserHistory. Дополнительный модуль history устанавливается вместе с установкой react-router. Он нужен он для того чтобы наше джаваскрипт приложение вело свою историю одинаково, независимо от той среды в которой оно находится.

И давайте сразу его заключим некоторую константу.

const history = createBrowserHistory();

Скобки - поскольку эта функция.

Далее, нам необходимо создать примеры наших страниц, которые мы будем использовать.

Я создам самую самую элементарную базовую структуру нашего сайта. При желании Вы можете усложнить и это будет не так сложно сделать.

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

Первая страница которой она у нас будет это страница Home

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

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

И это будет абсолютно нормально.

Продублирую этот компонент несколько раз и переименуем и вот что получилось:


const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);

const About = () => (
  <div>
    h<2>About</h2>
  </div>
);

const Contacts = () => (
  <div>
    <h2>Contacts</h2>
  </div>
);



Теперь начинается самое интересное!

Нам необходимо создать наш роутер. Роутер - Это такой же компонент как и все остальные. Приведу код файла App.js полностью, а ниже - пояснения. Создаем его:


import React, { Component } from 'react';

import { BrowserRouter, Route, Link } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';

const history = createBrowserHistory();

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);

const About = () => (
  <div>
    <h2>About</h2>
  </div>
);

const Contacts = () => (
  <div>
    <h2>Contacts</h2>
  </div>
);

class Menu extends Component {
  render() {
    return (
      <BrowserRouter history={history}>
        <div>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/about">About</Link></li>
            <li><Link to="/contacts">Contacts</Link></li>
          </ul>
          <hr />
          <Route exact path="/" component={Home} />
          <Route exact path="/about" component={About} />
          <Route path="/contacts" component={Contacts} />
        </div>
      </BrowserRouter>
    );
  }
}

export default Menu;



BrowserRouter это есть непосредственно роутер. К нему мы напишем сразу же атрибут history, то есть мы используем константу history для того, чтобы использовать history/createBrowserHistory библиотеку.

Дальше у нас идёт обычный div и в нём уже будут заключительные наши ссылки и пути к ним.

Их поместим внутри списка ul.

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

Каждый Link имеет специальный атрибут to и он и отвечает за то, на какую страницу у нас будет идти редирект.

1 -ый у нас будет идти на страницу Home. Обычно её отображает как слэш - "/".

2 -ой нас будет вести на страницу About, и так далее.

Дальше, для визуального разделения навигации и контента страницы, напишем стандартный тег <hr />

Далее, нам необходимо использовать компонент Route, который мы обозначили сверху.

Данный компонент принимает параметр path. Тут мы указываем такой же путь, как мы указали в линках и указан параметр component, который отвечает за рендер именно того компонента который нам нужен.

В данном случае, нам нужен компонент Home По такому же принципу заполняем и другие роуты.

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

Чтобы это сделать напишем внизу файла:

export default Menu;

Теперь нужно внести некоторые изменения в файл index.js

import Menu from './App';

ReactDOM.render(<Menu />, document.getElementById('root'));

Мы импортировали Menu, вместо App, и рендерить на страницу - отправляем Menu, а не App.

Можно смотреть браузер.



Вот мы видим наши простенькое структура. У нас есть наши "кнопки-стрелялки" которые мы указали, и рендер нашей странице, за которую отвечает каждый Link.

То есть, если мы находимся по адресу localhost: 3000 - это является адресом страницы Home. Если же мы нажмем на кнопку Аbout, то рендерится компонент Аbout, но как вы уже заметили почему-то наша компонент Home не пропал!



Это происходит потому, что в нашем коде необходимо указать дополнительные параметр - exact.

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

Если мы сейчас придём на нашу страницу, то компонент Home рендерится только тогда, когда мы находимся на нашей главной странице.

Чтобы это наглядно продемонстрировать, я могу дописать exact, например к ссылке About.

Теперь, если я захожу на вкладку About, в адресной строке написано - about, и если я через слэш допишу ещё какую-нибудь абракадабру конечно же такой ссылки нету, то наш компоненты About уже не высвечивается.

Можете сами в этом убедиться.

Но если же я уберу здесь exact то чтобы мы здесь не написали компонент About будет рендерится всё равно.

Об этом следует помнить!

А вообще, обо всех react-router методах и компонентах, вы можете почитать в официальной документации - reacttraining.com.

Всё достаточно адекватно написано и особенно радует что здесь есть примеры, которые можно использовать в реальных проектах. Это очень удобно. Просто копируете их в ваш проект и всё.

Файлы проекта, на этом этапе, вы можете увидеть по адресу: router-react-abc ci -m"React-Router simple" (ci -m - коммит).

Для простоты дальнейшего использования, масштабирования, я решил разделить компоненты. Для этого я создал папку components в папке src и каждый компонент поместил в отдельный файл с соответствующим названием.

Эти компоненты я подключил в файл App.js. Для примера, добавил еще один файл компонента - Blog.js

Файлы проекта, на этом этапе, вы можете увидеть по адресу: router-react-abc ci -m"Split application into components." (ci -m - коммит).

Это всё что я хотел вам сегодня рассказать. До встречи!



                                                                                                                                                             


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