• Как добавить элементы в массив?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Необходимо понимать, что возвращает метод Sever.GetUsers();. Допустим, он возвращает массив (или другую итерируемую сущность) id-шников.
    Тогда есть много способов, начиная от Array.from(); или решения, предложенного twobomb, и заканчивая чем-то вроде
    const users = Sever.GetUsers();
    const desc = []; // пустой массив, куда будем складывать имена
    
    for (i = 0; i < users.length; i++) {
      const name = Server.GetName(users[i]);
      desc.push(name); // добавляем имена в массив
    }
    
    console.log(desc); // ["Вася","Петя","Саша","Рома"]
    Ответ написан
    Комментировать
  • Как реализовать анимацию в актуальной странице при навигации?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Явно тут не стандартные NavLink, ибо при ее использовании тебя сразу перекидывает на другую страницу.

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


    Реализовать это можно двумя способами:
    1. Что-то сделать с NavLink
    2. Что-то сделать с роутером


    Предварительный импорт - когда нам заранее не известен компонент, который требуется подгрузить, реализуется через <React.Suspense fallback={<>Loading...</>}>, React.lazy() и "import" как функцию: import('./ImportedPage'). Концептуальный пример (и ссылка на песочницу):

    // index.jsx
    // "const Page = ..." - это концепция, в реальности в роутере
    // можно получить название страницы (или id), которые и будут названием файла.
    
    import React from 'react';
    
    function App() {
      const Page = React.lazy(() => import('./ImportedPage'))
      return (
        <div>
          <React.Suspense fallback={<>Loading...</>}>
            <Page />
          </React.Suspense>
        </div>
      );
    }
    export default App;


    // ImportedPage.jsx, то что импортирует главный компонент.
    
    import React from 'react';
    
    function Page() {
      return (
        <div>
          <h1>Some Page, preloading required</h1>
        </div>
      );
    }
    
    export default Page;

    Вместо "<>Loading...>" можете реализовать "полосу загрузки".

    Второй вариант - предварительная загрузка данных. Допустим, компоненту (странице, на которую собираемся перейти), требуются данные с сервера (скажем, список вакансий с картинками). Так вот, вначале запрашиваем данные от сервера, показываем прелоадер (в Вашем случае - полосу загрузки), и только когда данные получены - показываем готовую страницу. Если используете редакс, то при отправке асинхронного действия можно в редуктор запостить что-то типа "LOADING_BAR_SHOW", а после окончания загрузки данных (при получении ответа сервера) - два действия, вроде "PAGE_SHOW" (с payload, по которому можно определить необходимую страницу) и "LOADING_BAR_CLOSE" (который спрячет полосу загрузки).

    Также давайте рассмотрим реализацию изменения поведения NavLink. При клике делаем preventDefault, после чего выполняем необходимые операции (динамические import, запросы к серверу, что угодно), а при завершении этого дела - пушим URL в history. У меня получилось нечто подобное (ссылка на песочницу):
    import React from 'react';
    import { Switch, Route, Link, useHistory } from 'react-router-dom';
    import BarLoader from 'react-bar-loader';
    
    
    export default () => {
    
      const [data, setData] = React.useState();
      const [isLoading, toggleLoading] = React.useState(false);
      const history = useHistory();
    
      const getData = () => (
        new Promise((res, rej) => {
          // Здесь можем выполнять любую нужную операцию - например, запрос к серверу
          setTimeout(() => res('Some data, may be from server...'), 2000);
        }).then(data => setData(data))
      );
    
      const handleLinkClick = (e) => {
        toggleLoading(true)
        e.preventDefault();
        const pathname = e.target.pathname;
        getData()
          .then(data => {
            toggleLoading(false)
            history.push(pathname)
          })
      };
    
      // Просто обертка над Link, чтобы не писать всегда onClick={handleLinkClick}
      const WaitingLink = (props) => <Link onClick={handleLinkClick} {...props}>{props.children}</Link>
    
      return (
        <div>
          <header>
            {
              isLoading && <BarLoader color='#1D8BF1' height='3' />
            }
            <ul>
              <li><WaitingLink to={'/'}>Home Page</WaitingLink></li>
              <li><WaitingLink to={'/somepage'}>Some Other Page</WaitingLink></li>
              <li><WaitingLink to={'/somepage2'}>Some Other Page 2</WaitingLink></li>
            </ul>
          </header>
          <Switch>
            <Route exact path={'/'}>
              <div style={{background: '#ff8888'}}>
                <h1>Home Page</h1>
              </div>
            </Route>
            <Route exact path={'/somepage'}>
              <div style={{background: '#88ff88'}}>
                <h1>Some Other Page</h1>
              </div>
            </Route>
            <Route exact path={'/somepage2'}>
              <div style={{background: '#8888ff'}}>
                <h1>Some Other Page 2</h1>
                {/*Попробуем данные из состояния взять, которые получены при клике*/}
                {/*В реальности это может быть redux-srore*/}
                <p>{data ? data : (getData(), null)}</p>
              </div>
            </Route>
          </Switch>
        </div>
      );
    };
    Ответ написан
  • Каковы best practice по использованию React вместе с PHP-шаблонизаторами (Blade, Twig)?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Доброго дня! Давайте разберемся.
    Достаточно ли просто создать div#catalog и рендерить в него компоненты реакта?

    Ответ: достаточно просто (ссылка на песочницу). Допустим, имеется шаблон (я использовал HTML, но, думаю, с Blade проблем не возникнет):
    <html>
      <head>
        <title>PHP React Test</title>
    
        <script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
        <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
        <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
      </head>
      <body>
        <?php
          $INIT_PARAMS = ['user' => 'Vasilij']; // данные из PHP
          echo '<p>Hello React App</p>';
        ?>
    
        <div id='react-app'></div>
    
        // React-component. Конечно, лучше вынести в отдельный файл, если кода много.
        //  Еще лучше скомпилировать и подключить минифицированный бандл. Здесь - концептуальный пример.
        <script type="text/babel">
          'use strict';
          const {useState} = React;
    
          function App({user}) {
            const [liked, setLiked] = useState(false);
            return (
              liked ? (`You (${user}) liked this.`) : (
                <button onClick={() => setLiked(true)}>
                  Like
                </button>
              )
            )
          }
    
          const domContainer = document.querySelector('#react-app');
          ReactDOM.render(<App user={'<?=$INIT_PARAMS['user']?>'}/>, domContainer);
        </script>
      </body>
    </html>


    И как быть с SEO в таком случае?

    Правильный вопрос. То, что мы сделали выше - называется "клиентский рендеринг". Для оптимизации SEO он не очень подходит. Такой способ прост, но используется там, где SEO не нужно (например, приложение, в которое в принципе нельзя зайти, не пройдя процедуру авторизации). Для оптимизации SEO используется серверный рендеринг (SSR), а точнее изоморфная структура приложения (совмещаем SSR и CSR). Но поскольку PHP и JS - разные языки, нам потребуется "компилятор JS" прямо в PHP (мы же хотим скомпилировать JS на сервере и отправить клиенту готовый HTML). Для этого можно использовать V8Js. Почитать подробнее можно, погуглив, например.

    Но, наверное, многие согласятся, что такое использование не типично и крайне усложняет и без того сложный процесс. Если Вы хотите попробовать из академического интереса - я кое-что объяснил. Если же мы говорим про best practice, то давайте разберемся, что Вы хотите:
    • Использовать PHP?
    • Или использовать React SSR?


    1. Если Вы хотите использовать PHP и добавить компонентам реактивности, нечто подобное уже реализовано в Laravel: присмотритесь к laravel-livewire или inertiajs. Я бы рекомендовал laravel-livewire. Инструкция по установке того и другого: jetstream Получите архитектуру, почти как в React, только на сервере. Все живое, стэйт обновляется (как? автоматически через скрытые ajax-запросы). Вот, нашел наспех что-то из своего проекта (public $adJob играет роль состояния):
      <?php
      
      namespace App\Http\Livewire;
      
      use Livewire\Component;
      use Illuminate\Support\Facades\Auth;
      
      class AdJob extends Component {
          
          public $adJob;
          
          public function mount($adJobId) {
              $this->adJob = \App\Models\AdJob::find( $adJobId );
          }
          
          public function setReleaserForAdJob($releaserId) {
              $this->adJob['releaser_id'] = $releaserId;
              $this->adJob['status'] = 'in_progress';
              $this->adJob->save();
          }
          
          public function setAdJobDone() {
              $this->adJob['status'] = 'done';
              $this->adJob->save();
          }
      	
          public function render() {
              return view('livewire.ad-job-with-reviews');
          }
      }

      И последнее. Если не знакомы с Laravel, но нравится PHP - самое время начать изучать.

    2. Ежели желаете использовать React - то много проще (чем с PHP) использовать React SSR на Node.js. Самые популярные способы:
      1. Nextjs, как заметил Андрей Хохлов. Здесь все просто: изучайте документацию по этой технологии.

      2. React SSR + Express. Здесь, по сути, все то же самое, просто настроить все нужно самому. Хотя, это и более интересно (мне так кажется). Ситуация такова:

        1. Создаем React-приложение на клиенте. Делаем бандл. В пункте 3 будет небольшое дополнение сюда.


        2. На нужных маршрутах на сервере используем
          res.send(ReactDOMServer.renderToString(myElement)) - получаем нечто вроде создания "шаблона" (res.send - API express, остальное - react)


        3. На клиенте вместо ReactDOM.render() вызываем ReactDOM.hydrate(element, container), это позволяет не создавать разметку заново, а "гитратировать" HTML, отрендеренный React-ом на сервере и полученный браузером.


        Есть также пакеты, позволяющие настроить использование React как шаблонизатора, например: @react-ssr/express. Под капотом он использует тот же renderToString, только с правильной архитектурой и дополнительными, уже настроенными возможностями. В 12-ой строке файла default.tsx из пакета можно увидеть:
        const html = ReactDOMServer.renderToString(app);





    Таким образом, если интерес для Вас представляет именно PHP - рекомендую Laravel + livewire-компоненты. Если хотите именно React SSR - я бы предпочел Node-стэк (Next или Express+React, вначале сам попробовал бы, потом можно попробовать использовать готовый пакет). Вообще best practice - Next.
    Ответ написан
    2 комментария
  • Как задать if-else в стилях?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Как заметил Валерий, ошибка, скорее всего, потому, что передаете число. Попробуйте передавать строку (можно использовать "обратные кавычки"):
    <p
        style={
            SIZES.MAX > 0 ? {
                bottom:  `${(669 * size) / SIZES.MAX}px`,
                color: `red`
            } : {
                bottom:  `${(669 * size)}px`,
                color: `blue`
            }
        }
    >


    Но вообще, если стилей много, я бы вынес это из "инлайна". Например, через функцию:
    class MyParagraph extends React.Component {
      render() {
        //init values
        const SIZES = {MAX: 45};
        const size = 1;
        
        //get styles
        function getParagraphStyles(condition) {
          if (condition > 0) {
            return {
              bottom: `${(669 * size) / SIZES.MAX}px`,
              color: `red`,
            }
          } else {
            return {
              bottom: `${(669 * size)}px`,
              color: `blue`,
            }
          }
        }
        
        return (
          <p style={getParagraphStyles(SIZES.MAX)}>
            Paragraph
          </p>
        )
      }
    };


    Еще лучше getParagraphStyles() вынести в метод (если используете классовый подход) или в хук (если используете функциональный подход). Вместо if .. else можно и тернарник заюзать. Можно и стрелку написать - еще короче будет. Но сделал подробнее для наглядности. И да, как заметил Валерий, не забывайте указывать единицы измерения (px, скорее всего). React вроде CSS проверяет и ошибочный не пропускает.
    Ответ написан
    Комментировать
  • Авторизация. Как лучше всего ее организовать?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Давайте для определенности предположим, что имеется стек Express.js (серверная архитектура) + Passport.js (библиотека для авторизации) + Mongoose.js (ODM для бд Mongo) + React.js (клиент). Стоит заметить, что, если нужна именно автономная авторизация и в будущем точно не потребуется OAuth, то Passport можно и не использовать. В данном случае все достаточно просто можно реализовать самостоятельно. Passport необходим (на мой взгляд) в основном для унификации всех способов авторизации.

    Теперь определимся со степенью и способом защиты. Наиболее популярное решение среди токенов - JWT-токены. Смысл их использования в том, что
    • JWT самодостаточный. Имея валидный токен, не нужно дергать базу (как в случае логин/пароль), чтобы проверить права пользователя.
    • JWT можно расшифровать, но нельзя подделать. Если у Васи есть выданный ему токен, он сможет его подделать и прикинуться Ваней, но сервер это узнает и выдаст ошибку.


    Но JWT можно угнать. Как? Например, физически получив доступ к компьютеру пользователя во время активной сессии (открыть консоль браузера и написать короткий код, или посмотрев Redux store, ежели он используется - см. скриншот - это стор моего профиля в одном из онлайн-магазинов).
    600151069e986774161150.jpeg

    Как быть в таком случае? Во-первых, токен имеет время жизни. Когда токен "протухнет", злоумышленнику потребуется ввести логин/пароль, а их он не знает. На самом деле любую систему можно взломать, вопрос лишь в сложности.

    Существует более продвинутая схема. В данной схеме используется два токена: access и refresh. Токен доступа (access) имеет короткое время жизни (обычно 20 минут, но может варьироваться и зависит в основном от интенсивности использования проектируемого веб-приложения: если запросы к серверу выполняются редко, скажем, раз в час - время его жизни можно увеличить), refresh-токен имеет длинное время жизни (несколько дней, а то и недель - опять же зависит от степени защиты/интенсивности использования). При этом, если нам нужно реализовать функционал "запомнить меня" (автологин), refresh будем хранить, скажем, в LocalStorage браузера. При этом рекомендуют не "молча" запоминать пользователя, а сделать флажок на форме авторизации "запомнить меня" - чтобы пользователь понимал, что есть риск угона токена. Кстати, где что хранить на клиенте.
    • Логин/пароль никогда нигде не храним.
    • Access токен хранят в оперативной памяти (в Redux-store или, скажем, в axios.defaults.headers.common).
    • Refresh - если нужна функция автологина - храним в LocalStorage, если не нужна - тоже в оперативной памяти.


    Сделаем авторизацию с автологином. Для этого сессии будем хранить в базе данных, а также дадим пользователю возможность "выйти со всех устройств" и удаления конкретной сессии (кстати, в авторизации Laravel теперь это реализовано по умолчанию). Что хранить в качестве сессии в БД? Это refresh-string + некая информация об устройстве-клиенте (может быть ip, версия браузера, версия ОС, местоположение - все это легко получается сервером из заголовков запроса, местоположение вычисляется по ip). В access-токене можно хранить любую неконфеденциальную информацию (нельзя: пароль, данные кредитных карт и т.д.), я буду хранить просто имя пользователя. Рефреш-токен может представлять собой просто рандомную уникальную строку, но я зашифрую ее дополнительно в JWT, чтобы проверять срок жизни и валидность, не лезя в БД. Итак, приступим (сразу к сути, не пишу про установку пакетов и создание схем для Mongoose и т.п., чтобы пост не получился бесконечным).

    Для начала нам нужно инициализировать Passport.js. Нам нужны две стратегии: локальная для первоначального входа через логин/пароль и JWT. Токен серверу будем передавать через заголовок "authorization".
    passport.use('byUsernameAndPassword', // я так назвал свою стратегию
        new LocalStrategy({
            usernameField: 'username',
            passwordField: 'password',
            // passReqToCallback: true,
            session: false,
        }, (username, password, done) => {
            User
                .verifyByUsernameAndPassword(username, password) // примечание: это, конечно, не базовый метод, он написан "вручную"
                .then(user => done(null, user))
                .catch(error => done(error, false, error.message));
        })
    );
    
    passport.use(
        new JwtStrategy({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),// passport сам дешифрует токен и проверит его валидность, хотя это и не сложно сделать вручную, если решите passport не использовать
            secretOrKey: process.env.ACCESS_SECRET,
            session: false,
        }, ({ username }, done) => {
            // на самом деле можно и не доставать пользователя из базы, просто расшифровать jwt, как я уже писал.
            // Доставать из базы можно только тогда, когда его нужно поменять (скажем, пользователю нужно добавить заказ в корзину)
            // меньше запросов к БД - быстрее работает
            User
                .findOne({ phoneNumber: username })
                .then(user => {
                    if (!user) return done(null, false, 'This user has been deleted or changed. Please, login again.');
                    return done(null, user);
                })
                .catch(err => done(err));
        })
    );
    
    module.exports = passport;


    Далее реализуем следующие маршруты (и контроллеры для них):
    const auth = require('../controller/auth');
    const router = express.Router();
    router.post('/login', passport.authenticate('byUsernameAndPassword'), auth.logIn);
    
    const sessionRouter = express.Router();
    
    // из Refresh-токена извлекаем собственно строку
    function decodeRefreshStringFromToken(refreshToken, errorCallback) {
        const decoded = require('jsonwebtoken').verify(refreshToken, process.env.REFRESH_SECRET, (error, decoded) => {
            return error ? error : decoded;
        });
        if (decoded instanceof Error) // обработка ошибок
        return decoded.refreshToken;
    }
    
    // для любой работы с сессией в теле запроса будем ожидать Refresh-токена
    sessionRouter.use((req, res, next) => {
        if (!req.body.refreshToken) return next(new RefreshTokenUnauthorizedError());
        res.locals.refreshString = decodeRefreshStringFromToken(req.body.refreshToken, next);
        return next();
    });
    
    // выход - удаление сессии из БД
    sessionRouter.post('/logout', auth.logOut); // todo: jwtAuthMiddleware here&&
    // сессия становится неактивной (пользователь закрыл приложение) - зачем нужен данный роут? В БД у сессии будем хранить поле isActive. Если пользователь сидит сразу с двух устройств - подозрительно)
    sessionRouter.post('/interrupt', auth.interruptSession);
    // обновляем сессию, когда время жизни Refresh-токена вышло
    sessionRouter.post('/refresh', auth.refreshSession);
    
    router.use('/session', sessionRouter);
    module.exports = router;


    Контроллеры проектируем "тонкими" (если коротко, контроллер вызывает метод модели, а не 100 ее методов), например:
    module.exports.logIn = async (req, res, next) => {
        req.user.installSession(req.useragent)
            .then(newSessionData => {
                res.json(newSessionData);
                next();
            });
    };


    При этом installSession может выглядеть как-то так (разделен на методы по смыслу, эти же методы используются и в других случаях, например, при рефреше сессии задействуем makeSessionData):
    userSchema.methods.installSession = async function(useragent) {
        const newSession = this.createSession(useragent); // метод реализован ниже
    
        if (this.sessions.length > 5) this.sessions = []; // допустим ограничение максимум в 5 сессий
        if (this.sessions.find(session => session.isActive)) {// выходить из всех активных сессий, может даже удалять их}
        this.sessions.push(newSession);
        await this.save();
        return this.makeSessionData(newSession.refreshToken);
    };
    
    userSchema.methods.createSession = function({ browser, os, platform }) {
        return {
            refreshString: this.generateRefreshString(),
            browser,
            os,
            platform,
        };
    };
    
    userSchema.methods.makeSessionData = function(refreshString) {
        const {
            id,
            name,
            phoneNumber,
            role,
        } = this;
        const refreshToken = this.makeRefreshJwtToken(refreshString);
    
        return ({ id, name, phoneNumber, role, refreshToken, accessToken: this.makeAccessJwtToken({ username: this.username }), tokenType: 'Bearer', expiresIn: process.env.ACCESS_EXPIRES_IN, });
    };
    
    userSchema.methods.makeRefreshJwtToken = function(refreshString) {
        return jwt.sign(
            { refreshString },
            REFRESH_SECRET,
            { expiresIn: REFRESH_EXPIRES_IN });
    };

    Заключение в комментарии к данному ответу (чуть-чуть не уложился в 10 000 символов)
    Ответ написан
    3 комментария
  • Как реализовать такой функционал на MERN?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Я бы реализовал следующим образом.
    Допустим, мы используем Mongoose. При этом у нас есть схемы Category и Product. Одно из полей Product, как Вы заметили, ссылается на Category. В Category же я сделал бы виртуал "Products", представляющий собой массив (виртуал - то, что не хранится в БД, но модель его заполняет, следуя некоторой логике: здесь, например, будет заносить в массив все продукты, которые ссылаются на нее). Не забудем создать категорию "uncategorized"

    Так. Это был предварительный этап. Теперь основное. При удалении любой категории проходимся по ее виртуальному массиву "products" и для всех продуктов меняем ссылку с данной категорией на ссылку на категорию "uncategorized". Профит!
    Ответ написан
  • Когда стоит вкладывать input в label, а когда нет?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    В целом, с точки зрения правильности/валидности кода, как заметил Danny Arty, разницы нет. В примитивных случаях верстки визуальной разницы тоже особо не наблюдается.

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

    1. В таком варианте у нас имеется два рядом расположенных тега. Чтобы выбрать второй, идущий после первого, можно использовать "соседский" селектор "+" (Label идет сразу после Input). Данный вариант более "независимый", так как мы можем менять местами input и label, и даже разносить их на удаленное расстояние.
    <style>
      input + label {
        color: #ff0000;
      }
    </style>
    
    <input type="checkbox" id="checkbox" />
    <label for="checkbox">чекбокс</label>


    2. И второй вариант. Здесь мы не можем только средствами html/css обратиться "от input к label", так как не существует способа обратиться от ребенка к родителю (это можно, конечно, легко сделать через JS). Существуют селекторы потомков и соседей, но не родителей. Можно обратиться только от label к input, что дает мало преимуществ, так как
    1. input плохо стилизуется
    2. label обычно ничем не примечателен в отличие от input, который информативен (содержит, например, атрибут type).

    <style>
      label > input  {
        outline-color: #ff0000;
        outline-style: double;
      }
    </style>
    
    <label>
      <input type="checkbox" />
      чекбокс
    </label>


    А теперь представим, что нам нужно сверстать нечто подобное на основе (см. картинку):
    5ffea6239c1da641320430.png

    Как это реализовать? Для label соорудим ":before" и ":after", которым дадим форму скругленного прямоугольника и круга (который, к тому же будет перемещаться).

    Здесь второй вариант нам явно не подойдет, так как мы бы хотели в зависимости от состояния checkbox (.checkbox:checked) изменять родственный ему label:before (изменять цвет, перемещать).
    Если хотите взглянуть подробнее на код, пожалуйста:

    Таким образом, принципиальной разницы нет, но первый вариант (с id) более универсален, так как
    1. input и label могут быть разнесены физически (находится в разных местах документа)
    2. label может идти после input, что дает нам возможность обратиться в CSS от второго к первому

    Надеюсь, ответил на Ваш вопрос.
    Ответ написан
    Комментировать
  • Какие методы отправки http запросов сейчас наиболее современные, актуальные?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Если Вы находитесь на стадии обучения основам JS/front-end, вначале изучите XMLHttpRequest и технологию HTTP (основы). На основе него создана промис-обертка (гораздо удобнее в реальном использовании) - Fetch. Но и это еще не конец.

    В реальной разработке часто используют готовые решения, упрощающие решение. Например, для разработки клиента часто используют React, Vue, Angular и др.. Для управления состоянием приложения - Redux, MobX и др.. Так вот, хорошая библиотека для выполнения запросов - это Axios. Чем она лучше Fetch? Во-первых, самостоятельно парсит тело ответа (например, в/из JSON), отправляет необходимые заголовки. Сравните:
    fetch('https://jsonplaceholder.typicode.com/users')
        .then(res => res.json())
        .then(users => dispatch(addUsers(users)));
    
    axios('https://jsonplaceholder.typicode.com/users')
        .then(users => dispatch(addUsers(users)));

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

    Стоит понимать разницу между AJAX, XMLHttpRequest, Fetch, Axios.
    • AJAX - технология опроса сервера без перезагрузки (противовес ей - отправка данных кнопкой submit или ссылкой "a href="""). Чаще всего сегодня данными сервер и клиент обменивается через JSON
    • jQuery.ajax() - метод библиотеки jQuery, обертка над XMLHttpRequest. Т.е. библиотека.
    • XMLHttpRequest - объект браузера (BOM), позволяющий отправлять запросы к серверу, т.е. реализация упомянутой в первом пункте технологии AJAX
    • Fetch - более современный метод, использующий промисы. Более удобен, но под капотом используется XMLHttpRequest, естественно.
    • Axios - одна из библиотек (т.е. ее нужно подключать, как, например, тот же jQuery), наверное, самая распространенная, еще более упрощающая реальную разработку.


    Изучайте архитектуру на React (или др. фрэймворке), и увидите насколько проще использовать axios.
    Ответ написан
    2 комментария
  • Как передать через fetch() на бекенд картинку?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Есть смысл сгенерировать картинку в формате строки base64 и потом JSON-ом передавать на бекенд?

    Думаю, смысл есть. Нужно знать контекст. Смотря что разрабатываете. Смотря что ожидает сервер. Смотря, как хранятся данные. Скажем, если у Вас RestFull API, и общение с сервером происходит на основе JSON, то я бы и картинку передавал в JSON, как Вы и написали. Если же это "доделка" к, скажем, WordPress, или другой CMS, и она ожидает именно картинку, то см. ответ Выше (Tim).
    Ответ написан
    Комментировать
  • Первая верстка. Как вам?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    По поводу продвижения вперед посоветовал бы изучать программирование. Изучите JS (я бы посоветовал этот учебник). Присмотритесь к React, раз уж Вам нравится front-end. Когда поймете суть реакта, хорошенько пописав на нем, изучайте в дополнение к нему redux, redux-thunk, и другие необходимые вещи (остальные проще:) ) - react-router, formic, immutable, etc. Из оформления к нему - styled-components, CSS-modules.

    Если все же интересует (или пока интересует) именно верстка - bootstrap (обычно нужно иметь понятие, там не сложно), SASS/SCSS (это препроцессоры, "помогающие" писать код CSS). Можно изучить шаблонизатор, pug, например, но это уже все-таки немножко программирование:)

    Если хочется побольше шаблонов, регистрируйтесь на fl.ru/ - там часто скидывают шаблоны прямо в тексте задания.
    Ответ написан
  • Какими технологиями стоит пользоваться при создании CMS на js?

    KulakovAngel
    @KulakovAngel
    Full Stack Developer (Node.JS)
    Конечно использовал бы пакеты.

    Хотя все зависит от цели, которую Вы перед собой ставите. Когда у меня была обучающая цель написать собственный фрэймворк для разработки серверной части на Node, естественно, я писал все сам, на этом я хорошо усвоил паттерны проектирования, архитектуру, да и сам Node (EventEmitters, etc).

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

    Что касается совета по технологиям, я бы использовал express как каркас приложения (это небольшой и несложный фрэймворк, основной задачей которого является реализация паттерна Chain of Responsibility + маршрутизация). Для авторизации я заюзал бы passport. Для фронтэнда взял бы шаблонизатор pug. Стоит понимать, что первые два - это стандарт де-факто для многих, а вот третья рекомендация (pug) вообще особо ни на что не влияет, просто вопрос привычки/удобства. Если знаете другой шаблонизатор - используйте. Также я бы установил админ-панель, например adminbro. Естественно, будет еще множество более мелких пакетов, например "jsonwebtoken" для токенов, если будете их использовать и т.д.. Можете что-то написать полностью вручную, например, авторизацию. Или Админ-панель. Но для этого нужно четко понимать, как это делается.

    Последний совет - как новичку)) Не пытайтесь установить кучу пакетов, не понимая принципа их работы. Если Вы только начинаете изучать программирование - изучайте паттерны, ООП. Узнайте про client-server архитектуру. Напишите пару простых приложений на чистом JS (раз Вы взялись за него), например, todo-app. Изучите основной функционал Node. Параллельно изучайте Express. Если знаете/хотите узнать TypeScript - присмотритесь к loopback (только для CMS он не очень подходит, мне кажется).
    Ответ написан
    Комментировать