sadr0b0t / snippets

Разные куски кода
1 stars 4 forks source link

Добавить примеры проектов для ReactJS и ReactJS+Node.js #3

Closed sadr0b0t closed 7 years ago

sadr0b0t commented 7 years ago

Добавить примеры проектов для чистного клиентского ReactJS и веб-приложения ReactJS+Node.js с ReactJS на клиенте и Node.js на сервере.

Как обычно, использовать минимальное количество зависимостей.

sadr0b0t commented 7 years ago

Для начала, апгрейд Nodejs до версии 6 в убунте https://github.com/nodesource/distributions#debmanual

кодовое имя Убунты 16.04 - xenial:

lsb_release -s -c
xenial

добавить ключ через гуй синаптика https://deb.nodesource.com/gpgkey/nodesource.gpg.key или командой curl --silent https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add -

добавить репозиторий через гуй синаптика

deb https://deb.nodesource.com/node_6.x xenial main

найти пакет nodejs, обновить до последней версии

sadr0b0t commented 7 years ago

ReactJS - простой вариант - простое подключение библиотек без левых скриптов

https://reactjs.org/docs/installation.html http://codepen.io/gaearon/pen/rrpgNB?editors=0010

If you prefer to use your own text editor, you can also download this HTML file, edit it, and open it from the local filesystem in your browser. It does a slow runtime code transformation, so don’t use it in production.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">

      ReactDOM.render(
        <h1>Hello, world!</h1>,
        document.getElementById('root')
      );

    </script>
  </body>
</html>

демо-проект без зависимостей и без ноды здесь https://github.com/sadr0b0t/snippets/tree/master/js-web/react-basic

sadr0b0t commented 7 years ago

ReactJS - длинный вариант для продакшена с использованием NodeJS (здесь он пока используется только на этапе разработки - в зависимости финальной клиентской части приложения он не попадает) https://reactjs.org/docs/installation.html

Установить скрипты для управления проектом

глобально (рутом) sudo npm install -g create-react-app или локально (без рута) npm install create-react-app

создать проект с глобальным create-react-app create-react-app react-basic2

с локальным ./node_modules/.bin/create-react-app react-basic2

cd react-basic2
npm start

веб-приложение откроется в браузере по адресу http://localhost:3000/

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

Дальше само демо-приложение пишет, что если изменить сохранить App.js, то изменения каким-то образом подгрузятся (вероятно, в браузере):

To get started, edit src/App.js and save to reload

но нифига, после правки js нужно останавливать сервер и запускать заново

решается так: https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-start-doesnt-detect-changes https://webpack.github.io/docs/troubleshooting.html#not-enough-watchers

On Linux and macOS, you might need to tweak system settings to allow more watchers.

cat /proc/sys/fs/inotify/max_user_watches
8192

sudo nano /proc/sys/fs/inotify/max_user_watches сделал 81920 - заработало (сохраняю файл - браузер обновляется)

Для продакшена типа вариант с компонентами, заранее скомпилированными в чистый JS:

npm run build

Файлик index.html и сопутствующие скомпилированные скрипты появляются в папочке buid, но он как есть локально не открывается, они предлагают его деплоить сразу в корень сайта (в скомпилированных файлах не работают ссылки на ресурсы).

Чтобы открывался локально, в package.json нужно добавить строчку

"homepage" : "./",

https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-build

npm run build

The project was built assuming it is hosted at the server root. To override this, specify the homepage in your package.json. For example, add this to build it for GitHub Pages:

"homepage" : "http://myname.github.io/myapp",

The build folder is ready to be deployed. You may serve it with a static server:

npm install -g serve serve -s build

Это приём описан в документации https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#building-for-relative-paths

достаточно одной точки без слеша:

If you are not using the HTML5 pushState history API or not using client-side routing at all, it is unnecessary to specify the URL from which your app will be served. Instead, you can put this in your package.json:

"homepage": ".",

This will make sure that all the asset paths are relative to index.html. You will then be able to move your app from http://mywebsite.com to http://mywebsite.com/relativepath or even http://mywebsite.com/relative/path without having to rebuild it.

Короче, в папке build получаем чистого работающего клиента, которого можно открывать хоть локально из файла, хоть водружать на веб-сервер со статикой хоть на сервер приложений с динамикой. Проблема только в том, что отлаживать скомпилированный в классический JS и оптимизированный код в браузере уже не получится (там все ошибки указывают на одну первую строку, в которую реально вытянуто вообще всё приложение). Для отладки или вариант1 (npm start) или еще повнимательнее почитать документацию.

sadr0b0t commented 7 years ago

Теперь веб-приложение клиент ReactJS + сервер NodeJS с динамическим взаимодействием.

Будет два отдельных проекта webapp-server - сервер Node.js webapp-client - клиент React

Клиент - обычный проект, сгенерированный с create-react-app (далее немного почищенный). Билдим npm run build, отдаем содержимое на раздачу серверу.

На сервере два запроса API: "/call1" и "/call2" + отдать статикой содержимое клиента.

В проекте сервера делаем симлинк на каталог build из клиента ln -s ../webapp-client/build client-build

Обновление клиента - выполнить npm run build в каталоге клиента, дальше перезагрузить страничку в браузере, перезапускать сервер не нужно. Главный минус - нет нормальной отладки клиентского кода, т.к. в build попадает оптимизированный JS без синтаксиса React JSX.

Клиентский контент раздаём статикой из папки client-build с serve-static https://github.com/expressjs/serve-static

Сначала вызовы API /call1 и /call2, потом страницы из файлов.

( этот вариант относительно простой, альтернатив до фига, есть еще например https://www.npmjs.com/package/connect)

Последним обработчиком для отдачи 404 для несуществующих ресурсов лучше использовать finalhandler, но здесь минимизируем зависимости - сгенерим вручную https://www.npmjs.com/package/finalhandler

var http = require('http');
var serveStatic = require('serve-static');

var serve = serveStatic('client-build', {'index': ['index.html', 'index.htm']})

http.createServer(function (req, res) {
    console.log("request: " + req.url);
    switch(req.url) {
        case "/call1":
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.end("reply for call1");
            break;
        case "/call2":
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.end("reply for call2");
            break;
        default:
            serve(req, res, function(err) {
                // better use https://www.npmjs.com/package/finalhandler
                // in production instead
                if(!err) {
                    res.writeHead(404, { 'Content-Type': 'text/html' });
                    res.write('<!DOCTYPE html>\n' +
                        '<html>\n' +
                        '  <head>\n' +
                        '    <meta charset=\'utf-8\'>\n' +
                        '  </head>\n' +
                        '  <body>\n'
                        );
                    res.write("404, NOT FOUND: " + req.url);
                    res.end(
                        '  </body>\n' + 
                        '</html>\n');
                } else {
                    res.writeHead(200, { 'Content-Type': 'text/html' });
                    res.end(err);
                    console.log(err);
                }
            });
    }
}).listen(3000);

console.log('Server running at http://localhost:3000/');

Запускаем сервер nodejs webapp-server.js

обновляем билд клиента npm run build

Открываем http://localhost:3000

Видим страницу index.html как надо и получаем первый трап: 1) Во-первых, вызовы API http://localhost:3000/call1 и http://localhost:3000/call2 нифига не работают (вместо них - содержимое index.html) 2) Во-вторых, обновление кода клиентской страницы тоже нифига не работает - приложение показывает все время версию index.html, которая загрузилась в первый раз 3) Если (на чистом браузере) загрузить сначала http://localhost:3000/call1, то можно получить ответ API, но если потом загрузить http://localhost:3000 с index.html и после этого снова вернуться на call1, будет уже только index.html 4) Самое крутое - даже если вырубить сервер, страница все равно загружается и обновляется, как ни в чем ни бывало. 5) Очистка кеша в настройках Фаерфокса не помогает.

При этом, если сервер загружен, на запрос любых ресурсов из строки браузера (те же вызовы api /call1 и /call2 и любые другие) лог сервера печатает

request: /service-worker.js

По этому подлому service-worker.js вышел на объяснение глючной истории.

Оказывается, вместе с компилятором проекта React в обычный JS фейсбуковцы решили упаковать в генератор продакшен-билда заодно технику, которая так и назыается - ServiceWorker (в терминах ректовцев - Progressive Web App). https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app

Типа там всё работает в оффлайне, пиздато добавляется на рабочий стол мобильника с Андроидом, только обновляется по своему усмотрению раз в сутки, а что делать с API вообще не понятно (точнее, можно почитать, но пока лень, начать здесь https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#serving-apps-with-client-side-routing).

чтобы нафиг вырубить https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#opting-out-of-caching

убрать в index.js 2 строки про ServiceWorker (в демо-приложении вообще нах вычищу вместе с файлом)

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
//import registerServiceWorker from './registerServiceWorker';

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

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

about:serviceworkers

Дальше ок, клиент обновляется перезагрузкой страницы и дёргает API сервера, когда хочет

import React from 'react';

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {reply: ''};
    }

    readServerString(url) {
    console.log("read server string: " + url);
        var req = new XMLHttpRequest();
        req.onreadystatechange = function() {
            if (req.readyState === 4) { // only if req is "loaded"
                if (req.status === 200) { // only if "OK"
                    this.setState({reply: req.responseText});
                } else {
                    // error
                }
            }
        }.bind(this);
        // can't use GET method here as it would quickly 
        // exceede max length limitation
        req.open("POST", url, true);

        //Send the proper header information along with the request
        req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        req.send();
    }

    call1() {
        console.log("call1");
        this.readServerString('/call1');
    }

    call2() {
        console.log("call1");
        this.readServerString('/call2');
    }

    render() {
        return (
            <div>
                <p>
                    <a onClick={()=>{this.call1()}}>Прочитать с сервера значение 1</a>,
                    <a onClick={()=>{this.call2()}}>прочитать с сервера значение 2</a>
                </p>
                <p>
                    Результат: <span style={{fontStyle: 'italic'}}>{this.state.reply}</span> 
                </p>
            </div>
        );
    }
}

export default App;

Стоит так же отметить, что в новом реакте окончательно форсят новый синтаксис с классами (React.createClass deprecated, для старого синтаксиса предлагают ставить дополнительным модулем).

С таким синтаксисом важно использовать в onClick функцию, как стрелку - она делает автоматический bind к текущему компоненту (без нее не будет работать this внутри call1 и call2)

см

ES6/Babel version of binding to function of an object https://stackoverflow.com/questions/31617697/es6-babel-version-of-binding-to-function-of-an-object#31628960

бонус: еще пример приложения на React+Node+Express, можно посмотреть чего полезного https://www.nodejsdesignpatterns.com/

sadr0b0t commented 7 years ago

всё запулил, полезные проекты:

простейший Ajax https://github.com/sadr0b0t/snippets/tree/master/js-web/node-ajax-demo

React без инфраструктуры reac-scripts https://github.com/sadr0b0t/snippets/tree/master/js-web/react-basic

React и React+Material-UI с инфраструктурой reac-scripts https://github.com/sadr0b0t/snippets/tree/master/js-web/react-basic2

веб-приложение NodeJS+React https://github.com/sadr0b0t/snippets/tree/master/js-web/react-node-webapp

sadr0b0t commented 7 years ago

С таким синтаксисом важно использовать в onClick функцию, как стрелку - она делает автоматический bind к текущему компоненту (без нее не будет работать this внутри call1 и call2)

здесь подробнее и больше вариантов https://reactjs.org/docs/handling-events.html

Например, можно определить поле с функцией сразу как стрелку

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

и тогда в onClick можно передать ссылку на функцию по имени поля, как и раньше