Translate

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

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

вторник, 26 февраля 2019 г.

React + Webpack 4 + Babel 7 Руководство по установке.

Это руководство для тех, кто ищет свои способы настройки среды React, Webpack и Babel.





Это руководство относится к последним версиям React 16, Webpack 4 и Babel 7.

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

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

Создание структуры.

Убедитесь, что у вас установлены Node.js и NPM.

node -v npm -v


Результат:



Если не установлены, то установите с официального сайта.

Я работаю под Windows -8 и в редакторе VS-Code.

Перейдите в нужную вам директорию и создайте основную папку для вашего проекта. У меня это будет папка react-min-setup

mkdir react-min-setup
cd react-min-setup


Выше, я создал папку из КС ( командной строки) и перешел в нее. Теперь у вас есть папка проекта. Далее вы можете инициализировать его как проект npm . Задав ему -y сокращенный флаг, вы сообщаете npm, что он должен принимать все значения по умолчанию. Если вы не оставите флажок, вы должны указать информацию о вашем проекте вручную.

npm init -y


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

npm config list

npm set init.author.name ""
npm set init.author.email "you@example.com"
npm set init.author.url "example.com"
npm set init.license "MIT"


После настройки вашего проекта npm, вы можете установить пакеты узлов (библиотеки) в свой проект с помощью самого npm. После установки нового пакета узла, он должен появиться в вашем файле package.json .

Я это сделаю из терминала редактора.



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

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>React + Webpack 4 + Babel 7 </title>
</head>
<body>
    <div id="app"></div>
    <script src="./bundle.js"></script>
</body>
</html>



В файле ничего особенного. Есть корневой див, в котором будет размещаться весь React - проект и подключен файл bundle.js из этой же папки dist, в который и будут собираться все файлы нашего проекта, с помощью webpack.

Два важных факта о содержании:

файл bundle.js будет сгенерирован Webpack
Атрибут id = "app" поможет нашему корневому компоненту React найти точку входа

Поэтому наши следующие возможные шаги:
  1. настроить Webpack для объединения наших исходных файлов в один файл как bundle.js
  2. построить наш первый корневой компонент React, который использует точку входа id = "app"

На этом этапе все файлы проекта доступны в гит-репо react-min-setup-abc ci -m "Initiall commit layout project"

Настройка Webpack

Вы будете использовать Webpack в качестве модуля сборки и инструмента построения приложения. Более того, вы будете использовать webpack-dev-server для обслуживания вашего приложения в локальной среде. В противном случае вы не сможете увидеть его в браузере для его разработки. И последнее, но не менее важное: вам понадобится пакет узла webpack-cli, чтобы позже настроить конфигурацию Webpack в файле конфигурации. Давайте установим все три пакета узлов, используя npm.

Из корневой папки:

npm install --save-dev webpack webpack-dev-server webpack-cli


Теперь у вас должна быть папка node_modules, где вы можете найти сторонние зависимости. Зависимости также будут перечислены в файле package.json , так как вы использовали флаг –save-dev . Ваша структура папок должна выглядеть следующим образом:

Структура папки:

 - dist
 - - index.html
 - node_modules
 - package.json


В файле package.json вы можете добавить стартовый скрипт (выделил красным) дополнительно к заданным по умолчанию скриптам для запуска webpack-dev-server.

package.json

 "scripts": {
    "start": "webpack-dev-server --config ./webpack.config.js --mode development",
    "test": "echo \"Error: no test specified\" && exit 1"
  },



В файле появились установленные зависимости:



Сценарий определяет, что вы хотите использовать webpack-dev-server с файлом конфигурации с именем webpack.config.js . --mode development это Флаг, который просто добавляет WebPack по умолчанию конфигурации , которые пришли с Webpack 4. Вам не нужен флаг для Webpack 3.

Давайте создадим необходимый файл webpack.config.js .

module.exports = {
    entry: './src/index.js',
    output: {
      path: __dirname + '/dist',
      publicPath: '/',
      filename: 'bundle.js'
    },
    devServer: {
      contentBase: './dist'
    }
  };



Грубо говоря, в конфигурационном файле сказано, что:
  1. мы хотим использовать файл src / index.js в качестве точки входа для объединения всех импортированных файлов.
  2. Связанные файлы приведут к файлу bundle.js, который
  3. будет сгенерирован в нашей уже настроенной папке / dist . Эта папка будет использоваться для обслуживания нашего приложения.


В нашем проекте отсутствует папка и файл src / index.js . Создадим их. В файл index.js добавим, какой - нибудь вывод в консоль. Например:


   console.log('My Minimal React Webpack Babel Setup');



Структура проекта:

 - dist
  - - index.html
 - node_modules
 - src
  - - index.js
 - package.json
 - webpack.config.js


Теперь вы сможете запустить свой webpack-dev-server.

npm start


По умолчанию, проект запустится на порту 8080. И если посмотрим в консоль, то увидим наше сообщение.



Сейчас, вы подаете свое приложение через Webpack прямо в браузер. Вы связываете файл точки входа src / index.js как bundle.js , используете его в dist / index.html и можете увидеть его console.log()в консоли разработчика.

Пока это только файл src / index.js . Но позже вы импортируете больше файлов JS в этот файл, который автоматически будет упакован Webpack в файл bundle.js, который и будет создан.

На этом этапе все файлы проекта доступны в гит-репо react-min-setup-abc
ci -m "Webpack installation and setup"

Настройка Babel

Babel позволяет вам писать свой код с помощью JavaScript, который еще не поддерживается в большинстве браузеров. Например: JavaScript ES6 (ES2015) и более поздних версиях . С помощью Babel код возвращается обратно в стандартный JavaScript, так что каждый браузер, не имеющий всех реализованных функций JavaScript ES6 и не только, может его интерпретировать. Чтобы заставить Babel работать, вам нужно установить две его основные зависимости.

Из корневой папки:

npm install --save-dev @babel/core @babel/preset-env


Кроме того, чтобы подключить его к Webpack, вам необходимо установить так называемый загрузчик ("лодырь" - 😉 ):

npm install --save-dev babel-loader


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

Из корневой папки:

npm install --save-dev @babel/preset-react


Теперь, когда все пакеты узлов установлены, вам нужно настроить файлы package.json и webpack.config.js так, чтобы они соответствовали изменениям Babel.

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

package.json


{
  "name": "react-min-setup",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server --config ./webpack.config.js --mode development",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "Yaroslav",
  "license": "ISC",
  "babel": {
    "presets": [
      "@babel/preset-env",
      "@babel/preset-react"
    ]
  },
  "devDependencies": {
    "@babel/core": "^7.3.3",
    "@babel/preset-env": "^7.3.1",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.5",
    "webpack": "^4.29.5",
    "webpack-cli": "^3.2.3",
    "webpack-dev-server": "^3.2.0"
  }
}



webpack.config.js


module.exports = {
    entry: './src/index.js',
    module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          exclude: /node_modules/,
          use: ['babel-loader']
        }
      ]
    },
    resolve: {
      extensions: ['*', '.js', '.jsx']
    },
    output: {
      path: __dirname + '/dist',
      publicPath: '/',
      filename: 'bundle.js'
    },
    devServer: {
      contentBase: './dist'
    }
  };



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

Я просто допишу в файл src/index.js, что-то такое, например:


   console.log('My Minimal React Webpack Babel Setup');

   const obj={name:'Yaroslav', age: 48, id:1};
   let {name} = obj;
   console.log(name);


Запускаем проект:

npm start


Идем на 8080 порт и в консоли видим:



Необязательный шаг - извлечь конфигурацию Babel в отдельный файл конфигурации .babelrc


Создаем файл .babelrc в корне проекта.

Теперь вы можете добавить конфигурацию для Babel, которую вы ранее добавили в свой package.json (в коде файла выше это красным выделил) , в файл .babelrc . Не забудьте удалить конфигурацию из package.json позже.
Это должно быть настроено только в одном месте!
.babelrc


{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}



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

На этом этапе все файлы проекта доступны в гит-репо react-min-setup-abc
ci -m "Babel installation and setup"

Настраиваем React

Чтобы использовать React, вам нужно еще два пакета узлов. Пакеты установим через npm.

Из корневой папки:

npm install --save react react-dom


В вашем src / index.js вы можете реализовать свою первую запись в мире React.

src / index.js

import React from 'react';
import ReactDOM from 'react-dom';

const title = 'My Minimal React Webpack Babel Setup';

ReactDOM.render(
  <div>{title}</div>,
  document.getElementById('app')
);



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

ReactDOM.render нужны два параметра. Первый параметр - это ваш JSX. У него должен быть всегда один корневой узел.

Второй параметр - это узел, к которому должен быть добавлен ваш вывод. Помните , когда мы использовали <div id="app"></div> в / index.html файл? Этот же идентификатор - ваша точка входа в React.



На этом этапе все файлы проекта доступны в гит-репо react-min-setup-abc
ci -m "React installation and setup"

Hot Module Replacement

Очень полезный пакет, который позволит вам значительно упростит разработку и сократит время. Когда вы что-то меняете в исходном коде, эти изменения будут применяться в вашем приложении, запущенном в браузере, без перезагрузки всей страницы . Более подробно, на странице приложения - react-hot-loader

Из корневой папки:

npm install --save-dev react-hot-loader


Вы должны добавить еще несколько настроек в ваш файл конфигурации Webpack.

webpack.config.js

const webpack = require('webpack');
module.exports = {
    entry: './src/index.js',
    module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          exclude: /node_modules/,
          use: ['babel-loader']
        }
      ]
    },
    resolve: {
      extensions: ['*', '.js', '.jsx']
    },
    output: {
      path: __dirname + '/dist',
      publicPath: '/',
      filename: 'bundle.js'
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
      contentBase: './dist',
      hot: true
    }
  };



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

SRC / index.js

import React from 'react';
import ReactDOM from 'react-dom';

const title = 'My Minimal React-16 Webpack- 4  Babel Setup';

ReactDOM.render(
  
{title}
, document.getElementById('app') ); module.hot.accept();


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

Из корневой папки:

npm start


Когда вы меняете свой title в компоненте React в файле src / index.js , вы должны увидеть обновленный вывод в браузере без перезагрузки браузера. Если вы удалите module.hot.accept();строку в консоли, то браузер выполнил перезагрузку. То есть, если что-то изменилось в коде, изменения применились!

На этом этапе все файлы проекта доступны в гит-репо react-min-setup-abc ci -m "Installation and setup Hot Module Replacement"

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

Успехов и хорошего кодинга!



                                                                                                                                                             

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

понедельник, 14 мая 2018 г.

React.js (8) Отладка




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



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

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

В каждом случае, когда у вас идет то-то не так, вам нужно просто вспомнить как работает React и повторить за ним эти шаги.

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



Давайте проверим все ли работает у нас так, как задумано?

В первую очередь нас интересует вызывается ли этот collback(2)?

Нам для этого ненужны какие-то специальные средства отладки. просто добавим console.log('---',1); в эту функцию.

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

В него тоже добавим console.log('---',2);

Третье, что может пойти не так - это, теоретически у нас может как-то не так поменяться состояние. В функции render() добавим в console.log('---',2, this.state);

Идем в консоль - Ctrl + Shift + I и видим:



Collback - вызывается, все вызывается и состояние, на самом деле, меняется. Вроде бы все хорошо. Почему же тогда не работает? Давайте проанализируем наш код:

<ArticleList articles={this.state.reverted ?articles.reverse() : articles}/>

В каждый момент времени он получает массив статей. Или перевернутый - ? articles.reverse() или обычный -

: articles

Проблема в том, что метод articles.reverse() не просто возвращает перевернутый массив, но и меняет этот объект по ссылке!. Таким образом, когда мы в следующий момент времени захотим просто передать в ArticleListмассив статей, он уже будет перевернутый.

Чтобы это продемонстрировать мы добавим в вывод консоли (2) articles.map(article => article.id

console.log("---",2, this.state, articles.map(article => article.id))

И для наглядности вынесем действие смены массива ArticleList наверх.


  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(){
      const articlesList = this.state.reverted ?articles.reverse() : articles
      console.log("---",2, this.state, articles.map(article => article.id))
      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={articlesList}/>
        </div>
      );
    }
      revert = () => {
        console.log("---",1)
       this.setState({
        reverted: !this.state.reverted
      })
    }
  }
  export default App



Давайте посмотрим



Массивы действительно меняются местами, но когда мы нажимаем еще раз, и у нас уже {reverted: false} (2) мы все еще ссылаемся на предыдущий массив с тем же порядком. Соответственно, пока мы его не перевернем в следующий раз. ничего не произойдет.

Попробуем "починить" это "наивным способом".

Добавили articles.reverse()



Таким образом у нас каждый раз будет по клику меняться направление. Даже наш флажок мы оставили без изменений - reverted: !this.state.reverted, потому как он уже ни на что влиять не будет.

В ArticleList мы просто передадим articles. Мы будем их менять по ссылке - <ArticleList articles={articles}/>

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

Обратите внимание на то, что мы делаем! Это ужасно!!! Никогда так не делайте!!!

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

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


Иначе, может получиться так, что вы поменяете переменную в одном месте, а потом не сможете понять, почему соседний компонент работает не так, как вы это задумали! Поэтому->

Никогда не меняйте по ссылке внешние переменные и тем более то, что приходит вам в props


Теперь давайте попробуем исправить ситуацию.

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

articles = articles.slice()

И теперь у нас для каждого компонента App будет своя копия articles. И теперь с копией мы можем работать. Нам нужно только добавить this к изменению порядка в функции reverse()->

this.articles.reverse()

и

<ArticleList articles={this.articles}/>

Теперь мы не будем менять глобальную переменную articles, а меняем только то, что "живет" в this.

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

Давайте предположим, что мы решили сделать любой наш компонент - PureComponent, соответсвенно, чтобы у нас не происходило лишних перестроений виртуального дерева, когда это не нужно. И заменим Component на PureComponent

import React, {PureComponent} from 'react'

и соответственно -

class App extends PureComponent

И аналогично в файле index.js перепишем ArticleList перепишем с функционального компонента в классовый компонент. И тоже сделаем его PureCompomemt

index.js

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



Посмотрим, как это работает. Идем в приложение. Нажимаем на кнопку Revert и ...ничего не происходит!

Почему так?

Давайте пройдемся по нашему алгоритмы.

В первую очередь - в какой момент времени у нас должно было что-то произойти?

В функции revert (App.js) произошло изменение состояния, значит в render, начнется перестроение виртуального дерева.

Ставим проверочный вывод в консоль в самом верху рендера - console.log("---",1)

Затем, в рамках перестроения общего виртуального дерева будет перестраиваться и ArticlaList (index.js) поставим там - console.log("---",2), так же в самом начале рендера. И теперь посомотрим, что происходит у нас в консоле.



В первый раз (1 на фото) - сработали обе функции, потому что DOM дерево строилось в первый раз.

Потом, мы нажали кнопку Revert и у нас сработала только 1 функция (2 на фото)

Если заменить PureComponent на обычный Component, то будут срабатывать обе функции.

Так происходит потому что с точки зрения ArticleList, когда мы будем проверять props в shouldComponentUpdate(), props у нас не поменяются.

Единственный props , который к нам приходит - this.props.articles

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

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

Иммутабельные - это данные, которые вы не меняете по ссылке

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

Соответственно правильным будет вот такое решение!

App.js

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

  class App extends PureComponent {
    state = {
      reverted: false
    }
    render(){
      console.log('---',1)
      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.slice().reverse() : articles}/>
        </div>
      );
    }
      revert = () => {
       this.setState({
        reverted: !this.state.reverted
      })
    }
  }
  export default App



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

Вот теперь у нас все будет хорошо!

Никогда не меняйте ничего из внешних переменных!

Никогда не меняйте ничего, что приходит в props!

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


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



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


App.js ( со всеми изменениями последний на странице)

components/ArticleList/index.js

  import React, {PureComponent} from 'react'
  import Article from '../Article'
  import './style.css'
  export default class ArticleList extends PureComponent {
    render(){
      console.log("---",2)
        const articleElements = this.props.articles.map((article, index) =>
            <li key = {article.id} className="article-list__li">
              <Article article = {article} defaultOpen = {index === 0}/>
            </li>
          )
        return(
            <ul>
              {articleElements}
            </ul>
          );
    }
  }



                                                                                                                                                             

воскресенье, 13 мая 2018 г.

React.js (7) shouldComponentUpdate(). Оптимизация.

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


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



Давайте посмотрим как ведет себя React без него. В файле Article.js. У нас в этом файле слишком много различных выводов данных в консоль (console.log()).

Часть из них, например - в componentWillReceiveProps(nextProps) и в функции handleClick , мы уберем и посмотрим, что происходит тогда, когда мы хотим поменять порядок статей.

Мы нажимаем на кнопку и у нас семь раз (!!!) перестраивается порядок наших статей component will update.

См.фото ниже.



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

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

Изначально наше виртуальное дерево выглядело так (для первых трех статей):



У нас открыта первая статья и закрыты все остальные.

Мы нажимаем на кнопку Revert и будем перестраивать виртуальный DOM.



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

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

См. фото ниже.



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

Так мы дойдем до последней статьи, где нужно поменять текст на кнопке и убрать секцию



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

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

Единственное что его интересует это как ваше приложение выглядело до обновления и как оно должно выглядеть после. То есть - какие изменения нужно сделать. Однако, мы зная особенности наших компонентов. особенности бизнес-логики можем помочь React и подсказать, что в некоторых случаях перестраивать компоненты необязательно. Для этого нам необходимо реализовать на компоненте метод shouldComponentUpdate()

Предположим. что мы знаем, что на наш компонент сейчас может повлиять только то открыт он или закрыт. То есть состояние isOpen: true / false. Соответственно, мы можем сравнить старое состояние с новым, понять поменялось ли что-либо и принять решение - нужно ли нам перестраивать данный компонент или нет.

Давайте посмотрим как это работает.

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

Если он вернет true, то он зайдет, сравнит и внесет изменения.

Затем пойдет в следующую статью и вызовет у нее shouldComponentUpdate() и если вернет false, то он просто пропустит ее. Не будет заходить и перестраивать виртуальный DOM и тем более ничего не станет менять в реальном DOM. Он просто пойдет дальше.

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

см фото ниже



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

Давайте это реализуем.

Идем в файл Article.js


  shouldComponentUpdate(nextProps, nextState) {
    return this.state.isOpen !== nextState.isOpen
  }



Здесь мы будем следить за состоянием (открыто \ закрыто). Функция вернет true или false в зависимости от того, поменялось ли состояние.

Проверяем.



Теперь will update вызывается всего два раза!все равно сколько раз бы мы не меняли порядок. Почему так происходит?. Потому что для "внутренних" статей у нас ничего не меняется.

Но у такой реализации shouldComponentUpdate() есть опасность!

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

Для этого мы повесили бы на заголовок hendler

Article.js

  import React, {Component} from 'react'

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

      this.state = {
        isOpen: props.defaultOpen,
        count: 0
      }
    }
    shouldComponentUpdate(nextProps, nextState) {
      return this.state.isOpen !== nextState.isOpen

    }

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

    componentWillReceiveProps(nextProps) {
      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 onClick={this.incrementCounter}>
                { article.title }
                clicked {this.state.count}
                <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>
      );
    }
    incrementCounter = () => {
      this.setState({
        count: this.state.count + 1
      })
    }

    handleClick = () =>{
     
      this.setState({
        isOpen: !this.state.isOpen
      })
    }
  }

  export default Article



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



Почему так происходит? Потому что мы делали предположения только про isOpen

Теперь нам нужно принимать во внимание еще и count. Следить за изменением такой функциональности достаточно неудобно. Поэтому чаще всего shouldComponentUpdate() реализуют другим способом.

Сравнивая все props старый с новыми и все элементы state. и если у нас поменяется хоть одно, то компонент будет перестраиваться. Если нет, то тогда все без изменений.

Этот подход настолько распространенный, что для него есть даже отдельный компонент PureComponent в React (мы его и добавим) в импорт и унаследуем Article от него, то нам не придется реализовывать shouldComponentUpdate(), как ранее.

то есть вот эту часть можно будет убрать.
Article.js

    shouldComponentUpdate(nextProps, nextState) {
      return this.state.isOpen !== nextState.isOpen

    }




Article.js

  import React, {Component, PureComponent} from 'react'

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

      this.state = {
        isOpen: props.defaultOpen,
        count: 0
      }
    }

    // shouldComponentUpdate(nextProps, nextState) {
    //   return this.state.isOpen !== nextState.isOpen

    // }

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

    componentWillReceiveProps(nextProps) {
      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 onClick={this.incrementCounter}>
                { article.title }
                clicked {this.state.count}
                <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>
      );
    }
    incrementCounter = () => {
      this.setState({
        count: this.state.count + 1
      })
    }

    handleClick = () =>{
     
      this.setState({
        isOpen: !this.state.isOpen
      })
    }
  }

  export default Article




PureComponent отличается от обычного Component тем, что у него уже изначально реализовано - shouldComponentUpdate(nextProps, nextState), который изначально сравнивает все элементы props и все элементы state.

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

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

Тем не менее этим не следует злоупотреблять!

Как и любая другая оптимизация она должна применяться с умом!

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

Вывод - используйте PureComponent только по назначению не злоупотребляя им!

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



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


Article.js (я его привел полностью со всеми изменениями этого поста выше)
                                                                                                                                                             

суббота, 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;
  }



                                                                                                                                                             


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