Translate

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

суббота, 12 мая 2018 г.

React.js (6) Жизненный цикл компонентов.

Ранее нас интересовали только то, как выглядит наш компонент для тех или иных props или state. Однако, в реальной жизни бывают более сложные задачи, когда нам хочется реагировать на разные события из жизни нашего компонента. Напрмер - его появление, изменения, исчезновение.



Все статьи по React.js



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

Сегодня мы разберемся с тем как его использовать и где он может быть полезен.

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

I. Первая группа - Инициализация.

Инициализация - когда наш компонент будет впервые помещаться вначале в виртуальный, а затем и в реальный DOM.

1) constructor(props)

React для этого вызовет ряд методов жизненного цикла и первый из них - конструктор. - constructor(props).

Мы его уже неявно использовали в Article.js с помощью экспериментального синтаксиса.


  import React, {Component} from 'react'

  class Article extends Component {
state = { isOpen: true }
render() { const {article} = this.props const style = {width:'50%'} const body = this.state.isOpen && <section className="card-text">{ article.text }</section> return( <div className="card mx-auto" style={style}> <div className="card-header"> <h2> { article.title } <button onClick={this.handleClick} className="btn btn-primary btn-lg float-right"> {this.state.isOpen ? 'close' : 'open'} </button> </h2> </div> <div className="card-body"> <h6 className="card-subtitle text-muted"> "creation date : "{ (new Date(article.date)).toDateString()} </h6> { body } </div> </div> ); } handleClick = () =>{ console.log('---', 'clicked') this.setState({ isOpen: !this.state.isOpen }) } } export default Article


На самом деле Babel это перепишет вот таким образом:


  constructor(props) {
    super(props)

    this.state = {
      isOpen: true
    }
  }



Здесь super(props) - это вызов конструктора родительского класса. Это обязательно. А ниже this.state - инициализация состояния. Здесь, теоретически, мы можем сделать состояние компонента (isOpen: true) зависимым от props приходящих в конструктор.

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


  constructor(props) {
    super(props)

    this.state = {
      isOpen: props.defaultOpen
    }
  }


Тогда мы в props можем передать параметр props.defaultOpen. И теперь мы этот параметр будем передавать из Article.js в нашу статью (ArticleList/index.js)


  import React from 'react'
  import Article from '../Article'
  import './style.css'
  export default function ArticleList({articles}) {
        const articleElements = articles.map((article, index) =>
            <li key = {article.id} className="article-list__li">
              <Article article = {article} defaultOpen = {index === 0}/>
            </li>
          )
        return(
            <ul>
              {articleElements}
            </ul>
          );
  }


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



2) Затем вызовется компонент componentWillMount()

C ним мы можем взаимодействовать, реализовав это метод на классе.

Любой из методов жизненного цикла мы должны реализовать - например таким образом:

componentWillMount() {} - чтобы реагировать на соответствующее событие.

Стоит отметить, что методы жизненного цикла доступны исключительно для классовых компонентов ( обьявленые через class - например: class Article extends Component). Они недоступны для упрощенного синтаксиса - (например: function App() ). При реализации компонентов упрощенными методами у них нет ни состояния, ни жизненного цикла!



  import React, {Component} from 'react'

  class Article extends Component {
    constructor(props) {
      super(props)

      this.state = {
        isOpen: props.defaultOpen
      }
    }

componentWillMount() { console.log('---','mounting') }
render() { const {article} = this.props const style = {width:'50%'} const body = this.state.isOpen && <section className="card-text">{ article.text }</section> return( <div className="card mx-auto" style={style}> <div className="card-header"> <h2> { article.title } <button onClick={this.handleClick} className="btn btn-primary btn-lg float-right"> {this.state.isOpen ? 'close' : 'open'} </button> </h2> </div> <div className="card-body"> <h6 className="card-subtitle text-muted"> "creation date : "{ (new Date(article.date)).toDateString()} </h6> { body } </div> </div> ); } handleClick = () =>{ console.log('---', 'clicked') this.setState({ isOpen: !this.state.isOpen }) } } export default Article


Если мы захотим использовать жизненный цикл для таких компонентов, то нам их придется переписать в вид class Article extends Component.

componentWillMount() - часто используется для получения необходимых данных. например - оправка запроса статьи на сервер.

3) Затем, вызывается метод - render()

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

После построения виртуального DOMа, он будет помещен в реальный DOM и после этого вызовется метод componentDidMount().

4) componentDidMount()

Этим методом мы можем реагировать на появление нашего компонента в реальном DOM, на окончание его инициализации. Например: мы можем получить размеры или позиционирование узла (DOM Node) где он отображается, мы можем подписаться на изменение его данных или повесить собственные listener (слушатели событий) на соответствующие DOM элементы.

II. Обновление

Обновление может происходить по двум причинам. Либо у нас произошел setState() внутри компонента (например изменилось isOpen с true на false) , либо setState() произошел у кого-то из "родителей" компонента. Если setState() произошел у кого-то из "родителей", тогда компонент (например ArticleList) перестраивает собственное дерево, в том числе перестраивает и все дочерние компоненты.

1) componentWillReceiveProps(nextProps)

Когда Reac будет перестраивать виртуальное дерево для статьи Reac вызовет метод для статьи componentWillReceiveProps(nextProps), в который передаст новые проперти с которыми строится виртуальный DOM.

Обратите внимание, что это не означает. что проперти на самом деле поменялись. React не делает никаких предположений о наших данных. Они могли остаться неизменными. Для этого в аргументы передается объект - nextProps, который мы можем сравнить с текущими проперти (this.props).

У этого метода существует два распространенных примеры использования.

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

Второе - если мы завязали наше состояние компонента на проперти. Теперь нам придется следить за их изменениями и возможно приводить состояние State к нужному виду.

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

Для этого перепишем App.js Нам нужно поменять function App на class App extends Component чтобы добавить ему состояние. И добавим кнопку по которой будем менять это состояние.
App.js


  import React, {Component} from 'react'
  import ArticleList from './ArticleList'
  import articles from '../fixtures.js'
  import 'bootstrap/dist/css/bootstrap.css'

  class App extends Component {
    state = {
      reverted: false
    }
    render(){
      return (
        <div className="container">
          <div className="jumbotron">
            <h1 className="display-3">
                App name
                <button className="btn" onClick = {this.revert}>Revert</button>
            </h1>
          </div>  
            <ArticleList articles={this.state.reverted ?articles.reverse() : articles}/>
        </div>
      );
    }
    revert = () => this.setState({
      reverted: !this.state.reverted
    })
  }
  export default App



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



Так происходит потому что статьи не инициализируются заново. Они просто отображаются в другом порядке. Теперь, не смотря на то, что в "первую статью" (при реверсе) пришло свойство defaultOpen: true, мы не привели состояние к нужному виду.

Для этого мы реализуем метод componentWillReceiveProps(nextProps) и приведем состояние к нужному виду:
Article.js

  import React, {Component} from 'react'

  class Article extends Component {
    constructor(props) {
      super(props)

      this.state = {
        isOpen: props.defaultOpen
      }
    }

    componentWillMount() {
      console.log('---','mounting')
    }

    componentWillReceiveProps(nextProps) {
      if(nextProps.defaultOpen !== this.props.defaultOpen) this.setState({
        isOpen: nextProps.defaultOpen
      })
    }
    render() {
        const {article} = this.props
        const style = {width:'50%'}
        const body = this.state.isOpen && <section className="card-text">{ article.text }</section> 
      return(
        <div className="card mx-auto" style={style}>
          <div className="card-header">
            <h2>
                { article.title }
                <button onClick={this.handleClick} className="btn btn-primary btn-lg float-right">
                    {this.state.isOpen ? 'close' : 'open'}
                </button>
            </h2>
          </div>
          <div className="card-body"> 
                <h6 className="card-subtitle text-muted">
                   "creation date : "{ (new Date(article.date)).toDateString()}
                </h6>
                { body }
          </div>
        </div>
      );
    }
    handleClick = () =>{
      console.log('---', 'clicked')
      this.setState({
        isOpen: !this.state.isOpen
      })
    }
  }

  export default Article



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

Здесь все еще есть небольшой баг (множественного нажатия на кнопку) но мы с ним разберемся позднее.

2) Следующий элемент жизненного цикла - shouldComponentUpdate()

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

Подробнее о нем мы поговорим в следующих постах.

После него вызывается...

3) componentWillUpdate(nextProps, nextState)

Этот метод предупреждает нас о том. что сейчас мы будем перестраивать виртуальный DOM для данного копонента. У нас уже есть готовые Props и State , и здесь мы можем аналогичным образом отреагировать на какие-то изменения. Например: если статья у нас была закрыта, а стала открыта т.е. у нас поменялся State isOpen, и мы хотим загрузить текст для данной статьи.

Еще одни важный нюанс.
componentWillReceiveProps(nextProps) будет вызываться исключительно тогда. если у нас перестраивается кто-то из родителей и у нас могли поменяться Props.

shouldComponentUpdate() и componentWillUpdate(nextProps, nextState) будет вызываться независимо от того перестраивается кто-то из родителей, или у нас произошел setState() в нашем компоненте
Поcмотрим на примере Article.js

 componentWillReceiveProps(nextProps) {
     console.log("---",'will receive props')
    if(nextProps.defaultOpen !== this.props.defaultOpen) this.setState({
      isOpen: nextProps.defaultOpen
    })
  }



Добавим ниже:


  componentWillUpdate() {
    console.log('---','component will update')
    
  }



Теперь откроем консоль в нашем приложении. Вы увидите. что когда у нас перестраивается виртуальный дом всего нашего приложения (нажимаем кнопку - Revert) у нас будет вызываться component will receive props и component will update для всех наших статей.



А если мы будем просто открывать и закрывать статья (кнопка статьи Open/Close), то будет вызываться только component will update.



После метода componentWillUpdate(nextProps, nextState) будет вызываться метод render().

4) Метод render()

Перестраивается виртуальное дерево. Сравнивается с прошлым виртуальным деревом. Все необходимые изменения вносятся в реальный DOM и после этого вызывается componentDidUpdate()

5) Метод - componentDidUpdate(prevProps, prevState)

в этом методе есть также доступ к прошлым prevProps и prevState и к современным props и state. Единственное что, так это то, что прошлые теперь будут жить в аргументах функции, а современные - this.props и this.state чаще всего используются если вас интересуют какие-либо составляющие реального DOM. Например: размер вашего компонента в каждый момент времени или его позиционирование на экране.

III. Удаление - смерть компонента

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

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

Все статьи по React.js



Файлы, которые мы изменяли в этот раз:



App.js

  import React, {Component} from 'react'
  import ArticleList from './ArticleList'
  import articles from '../fixtures.js'
  import 'bootstrap/dist/css/bootstrap.css'

  class App extends Component {
    state = {
      reverted: false
    }
    render(){
      return (
        <div className="container">
          <div className="jumbotron">
            <h1 className="display-3">
                App name
                <button className="btn" onClick = {this.revert}>Revert</button>
            </h1>
          </div>  
            <ArticleList articles={this.state.reverted ?articles.reverse() : articles}/>
        </div>
      );
    }
    revert = () => this.setState({
      reverted: !this.state.reverted
    })
  }
  export default App




Article.js

  import React, {Component} from 'react'

  class Article extends Component {
    constructor(props) {
      super(props)

      this.state = {
        isOpen: props.defaultOpen
      }
    }

    componentWillMount() {
      console.log('---','mounting')
    }

    componentWillReceiveProps(nextProps) {
       console.log('---','will receive props')
      if(nextProps.defaultOpen !== this.props.defaultOpen) this.setState({
        isOpen: nextProps.defaultOpen
      })
    }
    componentWillUpdate() {
      console.log('---','component will update')
      
    }

    render() {
        const {article} = this.props
        const style = {width:'50%'}
        const body = this.state.isOpen && <section className="card-text">{ article.text }</section> 
      return(
        <div className="card mx-auto" style={style}>
          <div className="card-header">
            <h2>
                { article.title }
                <button onClick={this.handleClick} className="btn btn-primary btn-lg float-right">
                    {this.state.isOpen ? 'close' : 'open'}
                </button>
            </h2>
          </div>
          <div className="card-body"> 
                <h6 className="card-subtitle text-muted">
                   "creation date : "{ (new Date(article.date)).toDateString()}
                </h6>
                { body }
          </div>
        </div>
      );
    }
    handleClick = () =>{
      console.log('---', 'clicked')
      this.setState({
        isOpen: !this.state.isOpen
      })
    }
  }

  export default Article




index.js

  import React from 'react'
  import Article from '../Article'
  import './style.css'
  export default function ArticleList({articles}) {
        const articleElements = articles.map((article, index) =>
            <li key = {article.id} className="article-list__li">
              <Article article = {article} defaultOpen = {index === 0}/>
            </li>
          )
        return(
            <ul>
              {articleElements}
            </ul>
          );
  }



                                                                                                                                                             

пятница, 11 мая 2018 г.

React.js (5) Оформление. React + CSS

Пришло время сделать наше приложение более привлекательным. Для этого давайте научимся работать со стилями в React.js



Все статьи по React.js



Для того, чтобы стилизовать React.js приложение существует огромное количество подходов, библиотек, разнообразных стилизованных компонентов. Мы возьмем самый популярный фреймворк - Bootstrap. Даже когда вы принимаете решение использовать Bootstrap, вы можете легко найти в Гугл множество библиотек, которые интегрировали в себя Bootstrap и дают вам возможность использовать уже готовые стилизованные компоненты. Вы можете выбрать для себя любую из них. Этих библиотек огромное количество.

Мы пойдем классическим путем и будем использовать чистый (классический) css от Bootstrap.

Подключение Bootstrap.


Для этого идем в консоль и устанавливаем с помощью npm сам bootstrap


   npm install bootstrap



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

Это нам позволит сделать webpack, у которого для этого есть специальные возможности (модули), которые позволят подключать стили css в наш проект.

Для этого достаточно в файле App.js написать import и какой именно css модуль мы хотим подключить.

В нашем случае это 'bootstrap'. Обратите внимание, что подключаем без всяких слешей, потому что webpack и так поймет, что этот модуль стоит искать в папке -> node_modules. Указываем путь к файлу bootstrap.css.



Подключать файл bootstrap.min.css не имеет смысла, поскольку перед продакшн вы будете все равно минимизировать файлы с помощью webpack.

App.js

  import React from 'react'
  import ArticleList from './ArticleList'
  import articles from '../fixtures.js'
  import 'bootstrap/dist/css/bootstrap.css'

  function App() {
    return (
      <div>
          <h1>App Name</h1>
          <ArticleList articles={articles}/>
      </div>
    );
  }

  export default App



Теперь мы можем использовать bootstrap в нашем проекте.

Для того, чтобы дать класс какому-либо элементу нам достаточно написать className = "имя класса"

Например, пометить весь код в контейнер - в файле App.js первому тегу div добавили -


<div className="container" >



Убедиться, что все работает, вы можете открыв браузер и "исследовав элемент"



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

Теперь давайте оформим каждый элемент.

Завернем наш header в div классом jumbotron, а самому заголовку дадим класс display-3


  import React from 'react'
  import ArticleList from './ArticleList'
  import articles from '../fixtures.js'
  import 'bootstrap/dist/css/bootstrap.css'

  function App() {
    return (
      <div className="container">
        <div className="jumbotron">
          <h1 className="display-3">App Name</h1>
        </div>  
          <ArticleList articles={articles}/>
      </div>
    );
  }

  export default App


Результат:



Теперь, перейдет к оформлению статей.

В файле Article.js мы добавим стили сразу на всю статью в className="card".

Заголовок поместим в header - div className="card-header", а сам текст статьи и дату в отдельный div className="card-header". Тело статьи в div className="card-body",

Article.js

import React, {Component} from 'react'

  class Article extends Component {
        state = {
          isOpen: true
      }

    render() {
        const {article} = this.props
        const body = this.state.isOpen && <section>{ article.text }</section> 
      return(
        <div className="card">
          <div className="card-header">
            <h2>
                { article.title }
                <button onClick={this.handleClick}>
                    {this.state.isOpen ? 'close' : 'open'}
                </button>
            </h2>
          </div>
          <div className="card-body"> 
               { body }
                <h3>
                   "creation date : "{ (new Date(article.date)).toDateString()}
                </h3>
          </div>
        </div>
      );
    }
    handleClick = () =>{
      console.log('---', 'clicked')
      this.setState({
        isOpen: !this.state.isOpen
      })
    }
  }

  export default Article



Посмотрим:



Немного изменим тело самой статьи и кнопку.

В файле Article.js
  import React, {Component} from 'react'

class Article extends Component {
      state = {
        isOpen: true
    }

  render() {
      const {article} = this.props
      const body = this.state.isOpen && <section className="card-text">{ article.text }</section> 
    return(
      <div className="card">
        <div className="card-header">
          <h2>
              { article.title }
              <button onClick={this.handleClick} className="btn btn-primary btn-lg float-right">
                  {this.state.isOpen ? 'close' : 'open'}
              </button>
          </h2>
        </div>
<div className="card-body"> <h6 className="card-subtitle text-muted"> "creation date : "{ (new Date(article.date)).toDateString()} </h6> { body } </div>
</div> ); } handleClick = () =>{ console.log('---', 'clicked') this.setState({ isOpen: !this.state.isOpen }) } } export default Article


Результат:



Делаем статью на пол экрана и работаем с инлайновыми стилями.


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

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

Например так: <div className="card" style={{width:'50%'}}>

В принципе, мы могли бы вынести это таким вот образом -

const style = {width: '50%'}

а уже в самом теге оставить только <div className="card" style={style}>

Можно писать и так и так.



import React, {Component} from 'react'

  class Article extends Component {
        state = {
          isOpen: true
      }

    render() {
        const {article} = this.props
        const style = {width:'50%'}
        const body = this.state.isOpen && <section className="card-text">{ article.text }</section> 
      return(
        <div className="card" style={style}>
          <div className="card-header">
            <h2>
                { article.title }
                <button onClick={this.handleClick} className="btn btn-primary btn-lg float-right">
                    {this.state.isOpen ? 'close' : 'open'}
                </button>
            </h2>
          </div>
          <div className="card-body"> 
                <h6 className="card-subtitle text-muted">
                   "creation date : "{ (new Date(article.date)).toDateString()}
                </h6>
                { body }
          </div>
        </div>
      );
    }
    handleClick = () =>{
      console.log('---', 'clicked')
      this.setState({
        isOpen: !this.state.isOpen
      })
    }
  }

  export default Article




То, что получилось:



Для того, чтобы позиционировать нашу статью по центру добавим: mx-auto элементу

.card <div className="card mx-auto" style={style}>



Теперь нам остается убрать маркер списка.

Для этого мы напишем в файле ArticleList.js специальный стиль для тега li, а за одно научимся создавать собственные css-модули.

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

Подключением файла стилей React - компоненту.

В папке components мы создадим отдельную папку -> ArticleList внутри которой мы будем держать реактовский код index.js, так и стили ArticleList.js

В файл index.js мы вставим весь код из нашего ArticleList.js, только поправим пути:

index.js

  import React from 'react'
  import Article from '../Article'
  import './style.css'
  export default function ArticleList({articles}) {
        const articleElements = articles.map(article =>
            <li key = {article.id} className="article-list__li">
              <Article article = {article}/>
            </li>
          )
        return(
            <ul>
              {articleElements}
            </ul>
          );
  }



и здесь же создадим файл style.css -здесь будут стили данного компонента.

Файл ArticleList.js удаляем.

При этом webpack поймет, что в App.js ->ArticleList - это папка, и коль в ней есть файл index.js, то он возьмет именно этот файл. Поэтому файл index.js не нужно отдельно указывать в этом пути (т.е делать так - > import ArticleList from './ArticleList/index.js')
App.js

  import React from 'react'
  import ArticleList from './ArticleList'
  import articles from '../fixtures.js'
  import 'bootstrap/dist/css/bootstrap.css'

  function App() {
    return (
      <div className="container">
        <div className="jumbotron">
          <h1 className="display-3">App Name</h1>
        </div>  
          <ArticleList articles={articles}/>
      </div>
    );
  }

  export default App



Теперь добавим стили компоненту в файле style.css

Лучше всего себя показала методология БЭМ (сайт в России с vpn - отдельное спасибо РосКомНадзору) Блок Элемент Модификатор от Яндекса. Вы можете с ней ознакомиться. Мы пока что запишем:

style.css

   .article-list__li {
     list-style: none;
    }



Теперь, для того, чтобы воспользоваться этими стилями. нам необходимо сделать импорт. Мы сделаем это на уровне нашего компонента в файле index.js import './style.css'

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

Нам остается только добавить класс className="article-list__li" к тегу li

Сохраняем, и... Не пугайесь. Ваш компилятор может сломаться и получите такое предупреждение, вместо приложения.



Это происходит, потому что webpack ожидал файл ArticleList.js, а теперь он живет в папке.

Чтобы это исправить, достаточно перезапустить сервер -> Ctrl + C


    npm start



Для того. чтобы он собрал уже все с index.js Таким образом, мы с вами подключили Bootstrap к нашему React-приложению, научились добавлять стили инлайн двумя способами и научились подключать стили непосредственно к React - компонентам. сделав их по настоящему независимыми "строительными блоками" React - приложения.

Все статьи по React.js



Файлы, которые мы изменяли в этот раз:



components/App.js

  import React from 'react'
  import ArticleList from './ArticleList'
  import articles from '../fixtures.js'
  import 'bootstrap/dist/css/bootstrap.css'

  function App() {
    return (
      <div className="container">
        <div className="jumbotron">
          <h1 className="display-3">App Name</h1>
        </div>  
          <ArticleList articles={articles}/>
      </div>
    );
  }

  export default App





components/Article.js

  import React, {Component} from 'react'

  class Article extends Component {
        state = {
          isOpen: true
      }

    render() {
        const {article} = this.props
        const style = {width:'50%'}
        const body = this.state.isOpen && <section className="card-text">{ article.text }</section> 
      return(
        <div className="card mx-auto" style={style}>
          <div className="card-header">
            <h2>
                { article.title }
                <button onClick={this.handleClick} className="btn btn-primary btn-lg float-right">
                    {this.state.isOpen ? 'close' : 'open'}
                </button>
            </h2>
          </div>
          <div className="card-body"> 
                <h6 className="card-subtitle text-muted">
                   "creation date : "{ (new Date(article.date)).toDateString()}
                </h6>
                { body }
          </div>
        </div>
      );
    }
    handleClick = () =>{
      console.log('---', 'clicked')
      this.setState({
        isOpen: !this.state.isOpen
      })
    }
  }

  export default Article





components/ArticleList/index.js

  import React from 'react'
  import Article from '../Article'
  import './style.css'
  export default function ArticleList({articles}) {
        const articleElements = articles.map(article =>
            <li key = {article.id} className="article-list__li">
              <Article article = {article}/>
            </li>
          )
        return(
            <ul>
              {articleElements}
            </ul>
          );
  }





components/ArticleList/style.css

  .article-list__li {
    list-style: none;
  }



                                                                                                                                                             

четверг, 10 мая 2018 г.

React.js (4) Отображение массивов. Атрибут key.

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


Все статьи по React.js



Идем в файл App.js и видим, что у нас там подключен файл fixtures.js, где хранятся наши статьи в виде массива, но пока что мы его использовали только для того, чтобы взять первый элемент массива (одну статью) - <Article article={articles[0]}/>. Давайте научимся показывать список полностью.

Для этого мы заведем отдельный компонент.

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

В папке components мы создадим новый файл - ArticleList.js.

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

В начале файла сделали импорт компонента React и сразу же сделали экспорт нашего компонента в виде функции. В props нашей функции будет приходить массив статей - {articles}

Возвращать функция будет статьи в виде списка return(<ul>
<li></li>
</ul>);

ArticleList.js.

  import React from 'react'
  import Article from './Article'
  export default function ArticleList({articles}) {
        return(
            <ul>
               <li><Article article={articles[0]}/></li>
               <li><Article article={articles[1]}/></li>
               <li><Article article={articles[2]}/></li>
            </ul>
          );
  }



И изменим импорт в файле App.js - Вместо import Article from './Article' (одной статьи) -> import ArticleList from './ArticleList.

И конечно же строку вывода - <Article articles={articles[0]}/>

изменить на <ArticleList articles={articles}/>
App.js

  import React from 'react'
  import ArticleList from './ArticleList'
  import articles from '../fixtures.js'

  function App() {
    return (
      <div>
          <h1>App Name</h1>
          <ArticleList articles={articles}/>
      </div>
    );
  }

  export default App



Запускаем сервер -

   npm start


Должно получиться как на картинке внизу



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

Но мы хотим сделать вывод всех статей из массива.

Для этого идем в файл ArticleList.js и вспомним про то, что когда мы пишем JSX, то на самом деле мы пишем не что иное, как обычный JavaScript. Это значит что у нас есть все возможности. как в обычном JavaScript.

Так как на входе мы получаем массив из объектов - {articles}, мы можем првратить массив на выходе в массив из реактовских элементов и отобразить уже его.

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

Наша задача превратить каждый элемент <li><Article article={articles[0]}/></li> в реактовский элемент.

Для этого :

  const articleElements = articles.map(article =>
      <li><Article article = {article}/></li>
     )


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


   <ul>
     {articleElements}
   </ul>



Файл ArticleList.js.

   import React from 'react'
   import Article from './Article'
   export default function ArticleList({articles}) {
         const articleElements = articles.map(article =>
             <li><Article article = {article}/></li>
           )
         return(
             <ul>
               {articleElements}
             </ul>
           );
   }



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

Но в консоли вы увидите предупреждение, которое говорит нам о том, что каждый элемент массива должен иметь уникальное свойство - key



Ключ надо дать в "верхней обертке", чтобы он был уникальным в пределах этого массива, предсказуемым. Идеальный кандидат это article.id


   const articleElements = articles.map(article =>
       <li key = {article.id}><Article article = {article}/></li>
   )



После этого исчезнет это грозное предупреждение.

Вот так просто мы смогли отобразить весь список статей.

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

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

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

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

Идеальные это - идентификаторы, или например title статьи.

В этот раз мы изменяли файлы ArticleList.js и App.js. Ниже я привел их полностью.

App.js

  import React from 'react'
  import ArticleList from './ArticleList'

  import articles from '../fixtures.js'

  function App() {
    return (
      <div>
          <h1>App Name</h1>
          <ArticleList articles={articles}/>
      </div>
    );
  }

  export default App  




ArticleList.js

  import React from 'react'
  import Article from './Article'
  export default function ArticleList({articles}) {
        const articleElements = articles.map(article =>
            <li key = {article.id}><Article article = {article}/></li>
          )
        return(
            <ul>
              {articleElements}
            </ul>
          );
  }




Все статьи по React.js

                                                                                                                                                             

Интервью JavaScript: Что такое Promise?

«Мастер интервью с JavaScript» представляет собой серию сообщений, предназначенных для подготовки кандидатов на общие вопросы, с которыми они могут столкнуться при подаче заявки на позицию JavaScript на среднем и старшем уровнях (a mid to senior-level JavaScript position). Это вопросы, которые я часто использую в реальных интервью.




Что такое Promise?

Обещание (Promise)- это объект, который может произвести одно значение в будущем: либо разрешенное значение, либо причина, по которой он не разрешен (например, произошла сетевая ошибка).

Обещание может быть в одном из трех возможных состояний: выполнено- fulfilled, отклонено - rejected или ожидается- pending. Пользователи Promise могут присоединять обратные вызовы для обработки выполненного значения или причины отклонения.

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

Неполная история Promise - обещаний

Раннее, реализации обещаний и фьючерсов (аналогичная / смежная идея) начали появляться в таких языках, как MultiLisp и Concurrent Prolog еще в 1980-х годах. Использование слова Promise - «обещание» было придумано Барбарой Лисковой и Любой Шририа в 1988 году.

В первый раз, когда я услышал о обещаниях в JavaScript, Node был совершенно новым, и сообщество обсуждало лучший способ обработки асинхронного поведения. Сообщество какое-то время экспериментировало с обещаниями, но в конечном итоге решилось на Node-standard error-first callbacks (стандарт Ноды с ошибкой первого обратного вызова).

Примерно в то же время Dojo добавили обещания через Deferred API. Растущий интерес и активность в конечном итоге привели к недавно сформированной спецификации Promises / A, призванной сделать различные обещания более интероперабельными.

Асинхронное поведение jQuery было реорганизовано вокруг обещаний (Promise). Поддержка обещаний (Promise) jQuery имела большое сходство с Dojo's Deferred, и она быстро стала наиболее часто используемой реализацией (Promise) обещаний в JavaScript из-за огромной популярности jQuery - на то время. Однако, там не было поддержки двухканального (выполненное / отклоненное) поведения цепочки и управления исключениями, на которое люди рассчитывали при создании инструментов поверх обещаний (Promise).

Несмотря на эти недостатки, jQuery официально сделал JavaScript обещания (Promise) мейнстримом, и лучше автономные библиотеки обещаний, таких как Q, When, и Bluebird стали очень популярными. Несовместимость реализации jQuery привела к некоторым важным разъяснениям в спецификации обещаний, которая была переписана и переименована как спецификация Promises / A +.

ES6 принесла Promise / A + совместимый глобальный Promise, и некоторые очень важные API были построены поверх новой стандартной поддержки Promise: особенно спецификация WHATWG Fetch (ссылка заблокирована в России-> use VPN - пр. переводчика) и стандарт Async Functions (проект на 3 стадии на момент написания этой статьи).

Promises - обещаний, описанные здесь, это промисы которые совместимы с спецификациями Promises / A +, с акцентом на стандартную реализацию Promise ECMAScript.

Как работают promis?

Обещание (promis)- это объект, который можно синхронно возвращать из асинхронной функции. Он будет в одном из трех возможных состояний:

  1. Fulfilled(выполнено): onFulfilled() будет вызвано (например: reject() был вызван)
  2. Rejected (отклонено): onRejected() будет вызвано (например: reject() был вызван)
  3. Pending (ожидает): еще не выполнено или отклонено.


Обещание (promis) разрешается - settled, если оно не ожидается (оно было разрешено или отклонено - resolved or rejected). Иногда люди используют resolved и settled как то же самое: not pending..

После settled обещание не может быть resettled. Вызов resolve () или reject () снова не будет иметь эффекта. Важнейшей особенностью является неизменность обещанного обещания (promis).

Собственные обещания JavaScript не раскрывают состояния обещаний. Вместо этого вы должны рассматривать обещание как черный ящик. Только функция, ответственная за создание обещания, будет обладать знанием статуса обещания (promise status) или доступа к разрешению или отказу (resolve or reject).

Вот функция, которая возвращает обещание, которое будет разрешено после заданной задержки:

const wait = time => new Promise((resolve) => setTimeout(resolve, time));

wait(3000).then(() => console.log('Hello!')); // 'Hello!'


Пример кода на CodePen

Наш вызов wait (3000) будет ждать 3000 мс (3 секунды), а затем выведет в консоль «Hello!». Все спецификации, совместимые с обещаниями, определяют метод .then (), который вы используете для передачи обработчиков, которые могут принимать разрешенные или отклоненные значения.

Конструктор обещаний ES6 выполняет функцию. Эта функция принимает два параметра: resolve () и reject (). В приведенном выше примере мы используем только метод resolve (), поэтому я оставил reject () в списке параметров. Затем мы вызываем setTimeout () для создания задержки и вызываем resolve (), когда время ожидания закончено.

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

Когда я отклоняю reject () со значением, я всегда передаю объект Error. Обычно я хочу два возможных состояния разрешения: нормальный счастливый путь или исключение - все, что останавливает нормальный - удачный путь. Передача объекта Error делает это явным.

Важные правила обещания (promise)

Стандарт для обещаний был определен сообществом Promises / A + Существует множество реализаций, которые соответствуют стандарту, включая общие требования стандарта ECMAScript для JavaScript.

Обещания следующей спецификации должны соответствовать определенному набору правил:
  1. Обещание или «thenable» - это объект, который поставляет стандартный метод .then ().
  2. Ожидающее обещание (pending promise) может перейти в отработанное (fulfilled) или отвергнутое (rejected) состояние.
  3. уже установленное состояние fulfilled - исполненное или rejected - отвергнутое. не должно и не может меняться. переходить в другие состояния.
  4. Однажды установленное состоянеи должно иметь значение ( оно может быть и undefined). Это значение не должно меняться.
Изменение в этом контексте относится к сравнению идентичности (===). Объект может использоваться как выполненное значение, а свойства объекта могут мутировать.

Каждое обещание должно предоставить метод .then () со следующей подписью:

promise.then(
  onFulfilled?: Function,
  onRejected?: Function
) => Promise



Метод .then () должен соответствовать следующим правилам:
  1. И onFulfilled (), и onRejected () являются необязательными.
  2. Если предоставленные аргументы не являются функциями, они должны быть проигнорированы.
  3. onFulfilled () будет вызван после того, как обещание будет исполнено, причем значение обещания станет первым аргументом.
  4. onRejected () будет вызываться после того, как обещание будет отклонено, и причина отказа в качестве первого аргумента. Причиной может быть любое допустимое значение JavaScript, но поскольку отклонения по существу являются синонимом исключений, я рекомендую использовать объекты Error.
  5. Ни onFulfilled (), ни onRejected () не могут быть вызваны более одного раза.
  6. .then () можно вызвать много раз по тому же обещанию. Другими словами, обещание может быть использовано для агрегирования обратных вы
  7. .then () должен вернуть новое обещание, promise2.
  8. Если onFulfilled () или onRejected () возвращают значение x, а x является обещанием, promise2 будет блокироваться (предполагать то же состояние и значение, что и x). В противном случае promise2 будет выполнено со значением x.
  9. Если либо onFulfilled, либо onRejected выбрасывает исключение e, promise2 должен быть rejected (отклонен) вместе с e в качестве причины.
  10. Если onFulfilled не является функцией и promise1 выполняется, promise2 должно выполняться с тем же значением, что и promise1.
  11. Если onRejected не является функцией и promise1 отвергается- rejected, promise2 должен быть отклонен по той же причине, что и promise1.


Цепочка обещаний

Именно потому, что .then () всегда возвращает новое обещание, можно связать обещания с точным контролем над тем, как и где обрабатываются ошибки. Обещания позволяют вам имитировать поведение обычного / синхронного кода try / catch.

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

fetch(url)
  .then(process)
  .then(save)
  .catch(handleErrors)
;



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

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

const wait = time => new Promise(
  res => setTimeout(() => res(), time)
);

wait(200)
  // onFulfilled() can return a new promise, `x`
  .then(() => new Promise(res => res('foo')))
  // the next promise will assume the state of `x`
  .then(a => a)
  // Above we returned the unwrapped value of `x`
  // so `.then()` above returns a fulfilled promise
  // with that value:
  .then(b => console.log(b)) // 'foo'
  // Note that `null` is a valid promise value:
  .then(() => null)
  .then(c => console.log(c)) // null
  // The following error is not reported yet:
  .then(() => {throw new Error('foo');})
  // Instead, the returned promise is rejected
  // with the error as the reason:
  .then(
    // Nothing is logged here due to the error above:
    d => console.log(`d: ${ d }`),
    // Now we handle the error (rejection reason)
    e => console.log(e)) // [Error: foo]
  // With the previous exception handled, we can continue:
  .then(f => console.log(`f: ${ f }`)) // f: undefined
  // The following doesn't log. e was already handled,
  // so this handler doesn't get called:
  .catch(e => console.log(e))
  .then(() => { throw new Error('bar'); })
  // When a promise is rejected, success handlers get skipped.
  // Nothing logs here because of the 'bar' exception:
  .then(g => console.log(`g: ${ g }`))
  .catch(h => console.log(h)) // [Error: bar]
;


Посмотреть на Codepen

Обработка ошибок

Обратите внимание, что обещания имеют как успешное завершение, так и обработчик ошибок, и очень часто можно увидеть код, который делает это:

save().then(
  handleSuccess,
  handleError
);



Но что произойдет, если handleSuccess () выдает ошибку? Обещание, возвращенное из .then (), будет отклонено, но нет ничего, чтобы поймать отказ - это означает, что ошибка в вашем приложении проглатывается. К сожалению!

По этой причине некоторые люди считают, что приведенный выше код является анти-шаблоном, и вместо этого рекомендуют следующее:


save()
  .then(handleSuccess)
  .catch(handleError)
;



Разница тонкая, но важная. В первом примере ошибка, возникающая в операции save (), будет обнаружена, но ошибка, возникающая в функции handleSuccess (), будет проглатываться.


Без .catch () ошибка в обработчике успеха не реализована.
Во втором примере .catch () будет обрабатывать отклонения от save () или handleSuccess ().


С .catch () обрабатываются оба источника ошибок. (источник диаграммы)

Конечно, ошибка save () может быть сетевой ошибкой, тогда как ошибка handleSuccess () может быть вызвана тем, что разработчик забыл обработать определенный код состояния. Что делать, если вы хотите обращаться с ними по-другому? Вы можете выбрать для них обоих:

save()
  .then(
    handleSuccess,
    handleNetworkError
  )
  .catch(handleProgrammerError)
;



Независимо от того, что вы предпочитаете, я рекомендую прекратить все цепочки обещаний с помощью .catch (). Это стоит повторить:
I recommend ending all promise chains with a .catch().



Как отменить обещание?

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

Вот некоторые распространенные ошибки, которые люди совершают, когда откатываются от обещания:

Добавление .cancel () к обещанию

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

Забыли почистить.

367/5000 Некоторые умные люди поняли, что есть способ использовать Promise.race () как механизм отмены. Проблема заключается в том, что контроль отмены берется из функции, которая создает обещание, которое является единственным местом, где вы можете проводить правильные действия по очистке, такие как очистка тайм-аутов или освобождение памяти путем очистки ссылок на данные и т. д.

забыли обработать reject отклоненного обещания

Знаете ли вы, что Chrome бросает предупреждающие сообщения по всей консоли, когда вы забываете обработать отказ от обещаний? К сожалению!

Слишком сложно

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

Вы хотите переключиться в зависимости от того, есть ли исключение или отмена? Да, конечно. Это работа обещания? По-моему, нет, это не так.

Переосмысление отмены обещаний

Как правило, я передаю всю информацию, которую обещание необходимо определить, как разрешить / отклонить / отменить на время создания обещания. Таким образом, нет необходимости в методе .cancel () по обещанию. Возможно, вам будет интересно узнать, как вы могли бы узнать, собираетесь ли вы отменить время обещания.

«Если я еще не знаю, отменить или нет, как я узнаю, что передать, когда я создам обещание?»


Если бы существовал какой-то объект, который мог бы выдерживать потенциальную ценность в будущем ... о, подождите.

Значение, которое мы передаем, чтобы представлять, отменить или нет, может быть обещанием. Вот как это может выглядеть:


const wait = (
  time,
  cancel = Promise.reject()
) => new Promise((resolve, reject) => {
  const timer = setTimeout(resolve, time);
  const noop = () => {};

  cancel.then(() => {
    clearTimeout(timer);
    reject(new Error('Cancelled'));
  }, noop);
});

const shouldCancel = Promise.resolve(); // Yes, cancel
// const shouldCancel = Promise.reject(); // No cancel

wait(2000, shouldCancel).then(
  () => console.log('Hello!'),
  (e) => console.log(e) // [Error: Cancelled]
); 


Посмотреть на CodePen

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

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

Примечание. Возможно, вам интересно, для чего предназначена функция noop (). Слово noop означает no-op, что означает функцию, которая ничего не делает. Без него V8 будет вызывать предупреждения: UnhandledPromiseRejectionWarning: Отказ от необработанного обещания. Это хорошая идея, чтобы всегда обрабатывать отклонения обещаний, даже если ваш обработчик является noop ().


Абстрагированное Отклонение обещаний

Это нормально для таймера wait (), но мы можем абстрагировать эту идею дальше, чтобы инкапсулировать все, что вам нужно запомнить:
  1. Отклонить обещание отмены по умолчанию - мы не хотим отменять или бросать ошибки, если не будет отменено обещание отмены.
  2. Не забудьте выполнить очистку, когда вы отклоняете для отмены.
  3. Помните, что очистка onCancel сама может выдать ошибку, и эта ошибка также потребует обработки. (Обратите внимание, что обработка ошибок в примере ожидания отсутствует - легко забыть!)


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


speculation(fn: SpecFunction, shouldCancel: Promise) => Promise



Функция SpecFunction аналогична функции, которую вы передали бы в конструктор Promise, за одним исключением: для него используется обработчик onCancel ():


SpecFunction(resolve: Function, reject: Function, onCancel: Function) => Void






// HOF Wraps the native Promise API
// to add take a shouldCancel promise and add
// an onCancel() callback.
const speculation = (
  fn,
  cancel = Promise.reject() // Don't cancel by default
) => new Promise((resolve, reject) => {
  const noop = () => {};

  const onCancel = (
    handleCancel
  ) => cancel.then(
      handleCancel,
      // Ignore expected cancel rejections:
      noop
    )
    // handle onCancel errors
    .catch(e => reject(e))
  ;

  fn(resolve, reject, onCancel);
});


GitHub

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

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

Давайте используем улучшенную абстракцию библиотеки для перезаписи утилиты waitable () для отмены. Сначала установите спекуляцию:


   npm install --save speculation



Теперь вы можете импортировать и использовать это:


import speculation from 'speculation';

const wait = (
  time,
  cancel = Promise.reject() // By default, don't cancel
) => speculation((resolve, reject, onCancel) => {
  const timer = setTimeout(resolve, time);

  // Use onCancel to clean up any lingering resources
  // and then call reject(). You can pass a custom reason.
  onCancel(() => {
    clearTimeout(timer);
    reject(new Error('Cancelled'));
  });
}, cancel); // remember to pass in cancel!

wait(200, wait(500)).then(
  () => console.log('Hello!'),
  (e) => console.log(e)
); // 'Hello!'

wait(200, wait(50)).then(
  () => console.log('Hello!'),
  (e) => console.log(e)
); // [Error: Cancelled]


GitHub wait-speculation.js

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

Дополнительные возможности Native JS Promise

У родного объекта Promise есть некоторые дополнительные вещи, которые могут вас заинтересовать:
  • Promise.reject() - возвращает отклоненное обещание.
  • Promise.resolve() - возвращает разрешенное обещание.
  • Promise.race() - берет массив (или любой итерабельный) и возвращает обещание, которое разрешает со значением первого разрешенного обещания в итерируемом, или отклоняет по причине первого обещания, которое отвергается.
  • Promise.all() - берет массив (или любой итерабельный) и возвращает обещание, которое разрешает, когда все обещания в итерируемом аргументе разрешаются, или отклоняется по причине первого обещанного обещания, которое отклоняет.


Заключение

Обещания стали неотъемлемой частью нескольких идиом в JavaScript, включая стандарт WHATWG Fetch, используемый для большинства современных запросов ajax, и стандарт Async Functions, используемый для создания синхронного асинхронного кода.

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

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

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

Исследуйте серию



Барбара Лисков; Люба Шрира (1988). «Обещания: лингвистическая поддержка эффективных асинхронных вызовов процедур в распределенных системах». Материалы конференции SIGPLAN '88 по программированию и внедрению языка программирования; Атланта, Джорджия, США, стр. 260-267. ISBN 0-89791-269-1, опубликованный ACM. Также опубликовано в ACM SIGPLAN Notices, том 23, выпуск 7, июль 1988 г.







Автор: Eric Elliott .
Перевод: Kolesnikov Yaroslav



                                                                                                                                                             

среда, 9 мая 2018 г.

Конспект по Bash.

Если вы работаете в ИТ, то как никто знаете о цене времени. Оптимизация рабочего процесса - один из важнейших аспектов работы в ИТ.
Так или иначе, наша работа (будь то верстка сайта, написание модулей, или тестирования приложений) требует повторения одних и тех же действий: быстрые скриншоты с загрузкой на сервер, обработка выделенного текста, конвертация файлов, парсинг данных и многое другое. Чтобы не делать лишних действий, а сконцентрироваться на идее и самой сути ее реализации, еще в 1978 году Стивен Борн разработал командную оболочку [sh] [wiki-sh], которая впоследствии, в 1987 году была усовершенствована Брайаном Фоксом и переросла в то, что мы знаем сегодня как [bash] [wiki-bash] (Bourne again shell).



Вполне логично, что появляется вопрос: "Для чего мне нужно что-то, что написали почти полвека назад?" Так вот ответ на него прост: это "что-то" до сих пор является самым мощным инструментом автоматизации и, де-факто, стандартом для написания простых, но эффективных сценариев на всех unix-based системах. Именно поэтому знать общий синтаксис bash и уметь писать на нем - критический скилл для разработчика.

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

Оболочки и вызов сценариев

Пользовательская оболочка bash может работать в двух режимах - интерактивном и, соответственно, неинтерактивном. Открыть оболочку в Ubuntu можно комбинацией клавиш Ctrl + Alt + F1, привычный графический интерфейс исчезнет, а перед вами откроется один из семи виртуальных терминалов, доступных в дистрибутиве Ubuntu. Если оболочка выдает приглашение (что-то вроде того, которое можно увидеть ниже), то вы работаете в интерактивном режиме:


    user@host:~$



Здесь можно вводить самые разнообразные unix-команды (как то: ls, grep, cd, mkdir, rm) и видеть результат их выполнения. Интерактивной эта оболочка называется потому, что она взаимодействует с пользователем направления. Окружение рабочего стола (графический интерфейс), в семействе систем Debian (к которым относится и Ubuntu), принято размещать в седьмом виртуальном терминале, для того чтобы вернуться к привычному окружение рабочего стола наберите комбинацию Ctrl + Alt + F7. Конечно работать в виртуальных терминалах не слишком удобно, особенно, если нужно редактировать документ и одновременно выполнять какие-либо команды, поэтому в дальнейшем мы будем пользоваться встроенным в графический интерфейс эмулятором виртуального терминала, встроенным в Ubuntu. Открыть его можно комбинацией клавиш Ctrl + Alt + T, или Unity Dash, найдя его в списке программ.

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


    sh скрипт
    bash скрипт



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


    chmod +x скрипт



Кроме этого, в первой строке скрипта необходимо указать которая оболочка должна выполнять этот сценарий. Это можно сделать, разместив в начале соответствующее указание #! / Bin / sh (для оболочки sh) или #! / Bin / bash (соответственно для bash). После этого файл можно будет вызвать на выполнение обратившись к нему в терминале:


    ./скрипт



Комментарии

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


    #!/bin/bash
    # Сценарий, выведет имя пользователя
    whoami



Переменные

Оболочка позволяет создавать и удалять переменные, а также выполнять над ними операции. Переменные в bash могут находиться в 3-х областях видимости:

Локальные переменные - это обычные переменные в внутри одного сценария. Они не доступны другим программам и сценариям, которые запускаются с этой оболочки. Объявляются переменные с помощью символа = (обратите внимание на то, что перед и после = нет пробелов), а с их значением обращаются с помощью символа $:


    name="Петро Петрович"
    echo $name    # вывод значения
    unset name    # удаление переменной



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


    localлокальная_переменная=значение



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


    exportглобальная_переменная=значение



В bash есть много переменных окружения, которые достаточно часто встречаются в сценариях, например:
  • > HOME - путь к домашнему каталогу пользователя;
  • > PATH - список каталогов, в которых оболочка ищет исполняемые файлы;
  • > PWD - путь к рабочему каталогу;
  • > RANDOM - формирует целое случайное число;
  • > HOSTNAME - имя компьютера, на котором выполняется оболочка;
  • >


Переменные оболочки - это переменные, которые устанавливаются оболочкой и необходимые ей для корректной работы. Эти переменные имеют имена порядкового номера ($ 1, $ 2, $ 3, ...) и содержат аргументы, которые передавались сценария при запуске, как:


    ./some_script.sh VAL1 VAL2  # внутри сценария $1='VAL1', $2='VAL2' 



Переменным можно присваивать значения по умолчанию следующим образом:


    : ${VAR:='значення за замовчуванням'} # Если переменная VAR пуста, присвоить ей "значение по умолчанию"



Массивы и списки

В bash также есть возможность работать с массивами. При работе с массивами часто пользуются переменной окружения IFS - разделителя полей для входных строк (IFS - Input Field Separator). По умолчанию IFS равный Пробельные символа, но может быть изменен для разбиения строки на элементы массива, например, запятыми. Обратите внимание, что для формирования переменных оболочки, которые доступны через $ 1, $ 2 и т.д., используется именно переменная IFS, то есть введен после имени скрипта строку аргументов, будет разделен именно с первым символом, который хранится в этой переменной.

Объявить массив можно следующим образом:


    files[0]=Яблоко
    files[1]=Груша
    echo ${files[*]}    # напечатает элементы массива без учета IFS
    echo ${files[@]}    # напечатает элементы массива с IFS в качестве разделителя.



Доступ к элементу массива можно с помощью срезов: $ {arr: 0: 1}. Удалить первый элемент массива можно с помощью сдвига: shift arr. Добавить в элементы в массив: arr = ("$ {arr [@]}" "Item 1" "Item 2"). Проверить вхождения элемента в массив реализуется с помощью несколько более сложной конструкции:


    if [[ ${arr[(r)some]} == some ]]; then
         # команды, если элемент входит
    else
         # команды, если не входит
    fi



В этом примере arr - некоторый массив, а some - это элемент, который мы проверяем на вхождение.

Подстановки результатов операций.

Присвоить переменной результат работы команды или арифметических операций можно с помощью апострофов, или конструкции $ (выражение):


    now=`data +%T`
    # або
    now=$(data +%T)

    echo now # 19:08:26



Арифметические операции необходимо накладывать в двойные скобки:

   
    foo=$(( ((10 + 5*3) – 7) / 2 ))
    echo $foo    #> 9



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


    echo beg{i,a,u}n #> begin began begun



Стоит вспомнить и о строгости кавычек в bash: одинарные кавычки - строгие, двойные - нестрогие. Это означает, что при подстановке переменных в строку с двойными кавычками, интерпретатор подставит соответствующее значение переменной. Одинарные кавычки выведут строку так, как вы его написали. пример:


    echo "Домашняя директория: $HOME"  #> Домашняя директория: /home/user
    echo 'Домашняя директория: $HOME'  #> Домашняя директория: $HOME



Потоки.

Файл с которого происходит чтение, называют стандартным потоком ввода, а в какой происходит запись, соответственно - стандартным потоком вывода. В bash есть три стандартных потока:

 
    0  stdin   ввод         
    1  stdout  выводд        
    2  stderr  поток ошибок



Для перенаправления потоков используют основные операторы:
  • > - перенаправления потока вывода в файл (файл будет создан, или перезаписан)
  • >> - дописать поток вывода в конец файла;
  • < - перенаправляет данные из файла в поток ввода;
  • <<< - чтение данных из строки, вместо всего содержимого файла (работает для bash 3+)
  • 2> - перенаправляет поток ошибок в файл (файл будет создан, или перезаписан)
  • 2>> - дописать ошибки в конец файла; тадада


Kаналы

Стандартные потоки можно перенаправить не только в файлы, но и на вход других сценариям. Соединение потока вывода одной программы с потоком ввода другой называют каналом или пайпом (pipe). Ниже приведен простой конвейер из трех команд: команда1 перенаправляет свой вывод на вход команды2, которая, в свою очередь, перенаправляет собственный вывод на вход команды3:


  cmd1 | cmd2 | cmd3



Kонвейеры

Конвейеры - это команды, которые соединены операторами ; , && , || для выполнения в определенной последовательности. Операторы организации конвейеров работают следующим образом:
  • > команда1; команда2 - команда2 исполнится после команды1 независимо от результата ее работы команды1;
  • > команда1 && команда2 - команда2 выполнятся только после успешного выполнения команды1 (т.е. с кодом завершения 0);
  • > команда1 || команда2 - команда2 исполнится только после неудачного выполнения команды1 (то есть код завершения команды1 будет отличным от 0)


Условные операторы

В скриптовом языке bash поддерживаются два оператора ветвления: if и case. Оператор if, как и в других языках, выполняет определенный блок указаний, в зависимости от условия. Условие окутывают в двойные квадратные скобки [[...]], которые bash рассматривает как один элемент с кодом выхода. Внутри блока операторов накрытого в [[ ]] разрешается использовать операторы && и ||. примеры:


   # однострочная запись
   if [ ... ]; then echo "true"; else echo "false"; fi;

   ## вложенные условия
   if [ ... ] && [ ... ]; then
        ...
   elif [[ ... && ... ]]; then
        ...
   else
        ...
   fi;



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

В ниже приведена таблица с возможными условиями сравнения:


  # Работа с файлами
  -e    Проверить существует ли файл или директория (-f, -d)
  -f    Файл существует (!-f - не существует)
  -d    Каталог существует (!-f - не существует)
  -s    Файл существует и он не пустой
  -r    Файл существует и доступен для чтения
  -w    ... для записи
  -x    ... для выполнения
  -h    Есть c символической ссылкой

  # Работа со строками
  -z    Пустая строка
  -n    Не пустая строка
  ==    Ровно
  !=    Не ровно

  # Операции с числами
  -eq   Ровно
  -ne   Не ровно
  -lt   Менше
  -le   Менше или ровно
  -gt   Больше
  -ge   Больше или ровно


все основные команды можно посмотреть здесь - Основные команды Bash

примеры:


    if [ `uname` == "Adam"]; then
    echo "Не їж яблуко!"
elif [ `uname` == "Eva"] then
    echo "Не бери яблуко!"
else
    echo "Яблука зараз дуже дорогі!"
fi;



Если необходимо сделать выбор из нескольких альтернатив, пригодится оператор case. Принцип его работы легче понять на примере:


 case "$extension" in
    (jpg|jpeg)
        echo "Це зображення у форматі jpeg."
    ;;
    png)
        echo "Це зображення у форматі png"
    ;;
    gif)
         echo "А це гіфочка))"
    *)
        echo "Оу! Це взагалі не зображення!"
    ;;
 esac


В примере оператор проверяет значение переменной $ extension на совпадение с одним из шаблонов и в случае совпадения выполнит соответствующий блок кода. В случае, если не будет найдено совпадений, выполнятся указания, соответствуют шаблону * .

Циклы

Язык оболочки дает пользователю возможность организовывать циклическое выполнение инструкций при помощи циклов:
  1. > while
  2. > for
  3. > select
Оператор while описывается следующим образом:


    while условие do
         тeло
    done



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


   #!/bin/sh
   # Квадраты чисел от 1 до 10
   x=0
   while [ $x –lt 10 ] do 
   #значение переменной x меньше 10?
       echo $(($x*$x))
       x=`expr $x + 1` # увеличиваем х на 1
   done



Цикл for выполняет тело для каждого элемента из списка. Синтаксис цикла for таков:


   for имя in елемент1 елемент2 ... елементN do
        тeло
   done



В качестве элементов обычно используют различные шаблоны (wildcards). Очень удобно применять for для прохождения по каталогам и выполнения операций над группой файлов. В примере ниже, цикл проходит по всем файлах с расширением * .bash, перемещает их в директорию ~ / scripts и добавляет их права на исполнение.


   #!/bin/sh
   # Перемещение всех скриптов из ~ в директорию ~/scripts
   for FILE in $HOME/*.bash do
        mv $FILE ${HOME}/scripts
        chmod +x ${HOME}/scripts/${FILE}
   done


Цикл select помогает организовать удобное меню выбора и применяется тогда, когда пользователь должен выбрать один элемент из предложенного списка. В общем цикл select имеет такой же синтаксис как и цикл for:


  select ответ in елемент1 елемент2 ... елементN do
       тіло
done



При выполнении этого оператора, все элементы из списка высвечиваются на экране со своими порядковыми номерами в виде списка вариантов ответа, после списка выводится специальное приглашение для ввода. Обычно оно имеет вид # ? . Введенный пользователем номер списка записывается в переменную ответ. Если ответ содержит номер пункта меню, то в переменную заносится значение соответствующего элемента из списка. Если в списке нет введенного пункта, список будет показан снова. После того, как пользователь сделает правильный выбор, выполнятся указания в теле, а цикл перейдет к следующей итерации и все действия повторятся с самого начала - именно поэтому работу цикла select желательно прерывать


   #!/bin/sh

   echo -n "Введите название пакета: " && read PACKAGE
   PS3="Выберите пакетный менеджер : "
   select ITEM in bower, npm, pip do
        case $ITEM in
            bower) bower install $PACKAGE ;;
            npm) npm install $PACKAGE ;;
            pip) pip install $PACKAGE ;;
        esac
        break
   done



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

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

Функции.

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


   имя_функции () {
        команды
   }

   имя_функции    # обращение к функции



Объявление функции обязательно должно предшествовать ее первом вызове. Обращение к функции происходит путем указания ее имени в качестве команды.

Функция может принимать аргументы и возвращать после своего выполнения результат - код выхода. Функция направляется в своих аргументов точно так же, как и в локальных переменных, с помощью позиционных переменных - $ 1, $ 2 и тд. Результат работы можно поворачивать с помощью команды return. Например, функция, которая принимает параметр (имя) и заканчивая свою работу с кодом 0:


   #!/bin/sh
   #функция с параметром
   greeting() {
        if [ -n "$1" ]; then
            echo "Привет, $1!"
        else
             echo "Привет, неизвестный!"
        fi
        return 0
   }

   greeting пользователь    #> Привет, пользователь!
   greeting               #> Привет, неизвестный!



Команда return возвращает код завершения 0 - это код успешного завершения сценария. Каждая программа по завершению работы записывает в переменную окружения #? код завершения - число от 0 до 255. С помощью этой переменной можно определять статус выполнения каждой отдельной команды или скрипта. Если программа завершилась ошибкой, кодом завершения будет целое число отличное от нуля. Обратите внимание, что если сценарий завершается командой exit без параметров, кодом завершения сценария будет код завершения последней выполненной команды.

Отладка сценариев.

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


   #!/bin/sh опция



Можно выбирать среди следующих функций:
  1. > -n - читать все команды, но не выполнять их;
  2. > -v - выводить все строки по мере их обработки интерпретатором;
  3. > -x - выводить все команды и их аргументы по мере их выполнения.
Для налаживания сценария частями, нужный фрагмент замечают вызовом команды set с соответствующей опцией из таблицы. Причем, для включения режима отладки, перед опцией указывают символ - для отключения режима отладки используют + .


   set –x # включаем режим  отладки
   ...
   set +x # выключаем режим  отладки



Общая практика налаживания заключается в том, чтобы прежде чем запустить его на выполнение, необходимо проверить его синтаксис с помощью опции -n . Для большей детальности можно комбинировать ключи -nv . После исправления синтаксических ошибок проводится отладка с помощью опции -x .

Послесловие.

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

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

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






Выражаю благодарность изданию codeguida за любезно предоставленный материал.

Автор: denysdovhan.
Перевод: Kolesnikov Yaroslav.                                                                                                                                                              


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