• Как в nuxt.js указать отдельный домен для статики?

    @karambafe
    Если не ошибаюсь, то за это отвечает свойство publicPath.
    Ссылка на офф сайт
    Ответ написан
  • Как правильно проверять на валидность значение input и правильно выстроить ее архитектуру?

    @karambafe
    Стандартная проблема с кастомными инпутами и валидацией в форме.

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

    Пример кастомного инпута c выводом текста ошибки под ним. Если есть хоть одна ошибка, то навешивается "ошибочный" класс со своими стилями.
    <template>
      <div class="input">
        <input
          type="text"
          class="input__field"
          :class="{ 'input__field_error': errors.length > 0 }"
          :placeholder="placeholder"
          :value="value"
          :disabled="disabled"
          @input="handleInput"
        >
    
        <div class="input__errors">
          <p
            v-for="(error, index) in errors"
            :key="index"
            class="input__error"
          >
            {{ error }}
          </p>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'custom-input',
      props: {
        disabled: {
          type: Boolean,
          default: false,
        },
        errors: {
          type: Array,
          default: () => ([]),
        },
        value: {
          type: String,
          default: '',
        },
        placeholder: {
          type: String,
          default: '',
        },
      },
      methods: {
        handleInput({ target: { value } }) {
          this.$emit('onInput', value);
        },
      },
    };
    </script>


    А в форме уже мы распределяем все ошибки по нужным инпутам:
    <template>
    <form @onSubmit.prevent="handleSubmit">
      <custom-input
         :value="phone"
         :disabled="loading"
         :errors="phoneErrors" // передаем только относящиеся к конкретному инпуту ошибки
         @onInput="handleInput"
      />
    
      <button type="submit">Отправить</button>
    </form>
    </template>
    
    <script>
    export default {
      data: () => ({
         phone: '',
         errors: [],
         loading: false,
      }),
      computed: {
         phoneErrors() {
           // этот код лучше вынести в хелпер, чтобы потом переиспользовать с другими поинтерами
            if (this.errors.length === 0) return [];
            
            return this.errors
             .filter(error => error.pointer === 'phone') // проверка на нужный поинтер
             .map(item => item.detail); // делаем массив только из текста ошибок
         },
      },
      methods: {
        handleInput(newPhone) {
         // при вводе нового значения обнуляем все ошибки. 
         // Можно сделать обнуление ошибок по конкретному поинтеру
          this.errors = []; 
          this.phone = newPhone;
        },
        handleSubmit() {
            this.errors = [];
    
           // Если инпут пустой, то сами генерируем ошибку
           // и не делаем отправку данных на сервер
           if (this.phone.length === 0) {
            this.errors = [...this.errors, {
              detail: 'Телефон не может быть пустым',
              pointer: 'phone',
            }];
            return;
          }
    
          // Если же все ок, то отправляем данные на сервер
          this.loading = true;
          this.sendPhone();
        },
      },
    };
    </script>


    Возможно это и многословный код, но он хорошо читается, понятен и крайне легко покрывается unit тестами
    Ответ написан
    2 комментария
  • Почему LocalStorage is not defined?

    @karambafe
    process.client - это всего лишь проверка на тип рендеринга, а не гарантия клиентского :)

    В вашем коде не понятно где именно вызывается метод getItem.
    Исходя из этого могу сказать лишь одно - дергайте его в хуке mounted, в котором точно доступен объект window (localStorage является его свойством)
    Ответ написан
    3 комментария
  • Как можно скрыть ошибку в консоли?

    @karambafe
    401 - нормальный ответ сервера на неправильные данные при авторизации.
    MDN:
    Код ответа на статус ошибки HTTP 401 Unauthorized клиента указывает, что запрос не был применен, поскольку ему не хватает действительных учетных данных для целевого ресурса.


    Скрывать не нужно, так как консоль предназначена для разработчика (не зря в хроме раздел называется Chrome DEV tools), а не для пользователя. По таким ошибкам самому же будет намного проще дебажить.
    Пользователю же важно вывести понятную ошибку в интерфейсе.
    Ответ написан
    Комментировать
  • Как исправить ошибки vue-masonry?

    @karambafe
    Такая ошибка обычно возникает, когда в шаблоне используется переменная, которая не объявлена в компоненте в data/props/computed.

    В вакууме тяжело сказать ошибка это плагина или кода внутри компоненты.
    Сделал маленький пример, где просто подключил библиотеку и заюзал директиву v-mansory - никаких ошибок в консоли нет (codesanbox), так что скорее всего надо искать косяки в шаблоне компоненты
    Ответ написан
  • Как поймать ошибку ведущую на error layout до перехода на нее и перенаправить на кастомную страницу?

    @karambafe
    Если делается аснхронный запрос за данными на странице, то можно это разрулить в хуке asyncData методом redirect
    async asyncData({ redirect }) {
      try {
         // тут делаем асинхронный запрос за данными
      } catch (error) {
         // в случае ошибки выполняем редирект на роут custom-error-page
         redirect('/custom-error-page')
      }
    }
    Ответ написан
    Комментировать
  • Можно/целесообразно ли делать анимированные переходы в вебе?

    @karambafe
    Классный пример, спасибо, сохранил себе в закладки.

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

    Тут 2 подводных камня:
    1. Красивые анимации - это всегда сложно и долго для тех, кто с ними плотно не работает.
    2. Почти вся информация забирается с сервера, а значит надо при переходе на новые страницы постоянно делать запросы, выполняющиеся определенное время. В этот момент в интерфейсе желательно вставлять какие-то заглушки, которые потом аккуратно будут заменяться на новый контент.
    Ответ написан
    2 комментария
  • Как в компоненте получить данные из хранилища?

    @karambafe
    Если вы хотите подписаться на данные из store, то используйте computed properties:

    export default {
      computed: {
        productName() {
           return this.$store.state.products.map(product => product.name);
        },
      },
    };


    В store же вообще не надо заводить отдельный стейт productName, потому что это вычисляемое значение относительно стейта products - можно объявить как отдельный getter productName в Vuex, а можно получить через computed в компоненте как описано выше

    Геттеры Vuex
    Ответ написан
  • Почему возникает ошибка?

    @karambafe
    А вы уверены, что вот тут стейт корректно обновляется?
    this.setState({
      [e.target.id]: { inputValue: e.target.value },
    });


    Попробуйте использовать prevState и spread для явной перезаписи:
    updateInputValue({ target: { id, value } }) {
      this.setState(prevState => ({ 
         [id]: {
            ...prevState.id,
            inputValue: value,
         },
      }));
    }
    Ответ написан
    2 комментария
  • Вопрос про класс и его прототип?

    @karambafe
    и в прототипах не видно переменной , в которой лежит 'this'.


    Так она в this и не записана, надо вот так делать:
    this.popup = this // хотя это абсолютно бессмысленно


    И всё бы хорошо, но если методы записывать как прототипы, то 'close' перестаёт работать и в прототипах не видно переменной

    this.overlay.on('click', this.close);
    Тут идет потеря контекста, если сделать в методе close console.log(this), то увидите в this обычную DOM ноду, а не jq объект. Соответсвенно там и не будет метода removeClass

    //  Это решение проблемы, но не решение очень странного, на мой взгляд, кода
    Popup.prototype.close = function(){
      $(this).removeClass('open');
    }
    Ответ написан
    2 комментария
  • Как реализовать фигуру в углу сайта?

    @karambafe
    Если фигура является обычной картинкой, которая вставляется через css, то можно использовать свойство
    background-position. Оно может принимать и отрицательные значения.

    Подробнее можно на почитать на webref.ru
    Ответ написан
    Комментировать
  • По клику на кнопку получить следующий/предыдущий день?

    @karambafe
    На learn.javascript очень хорошо описана работа с датой и временем в JS:


    Например, нужно увеличить на 2 дня дату «28 февраля 2011». Может быть так, что это будет 2 марта, а может быть и 1 марта, если год високосный. Но нам обо всем этом думать не нужно. Просто прибавляем два дня. Остальное сделает Date:

    var d = new Date(2011, 1, 28);
    d.setDate(d.getDate() + 2);
    
    alert( d ); // 2 марта, 2011



    Ссылка
    Ответ написан
    1 комментарий
  • Nuxt.js Как разделить приложение?

    @karambafe
    Явным образом нельзя, по крайней мере в документации об этом ни слова, да и при создании нового приложения через стартер есть выбор universal (SSR+CSR) или client-side only mode.

    Стандартным решением данного вопроса является разделение на два разных приложения:
    1. Морда (особенного если это простенький лендос) делается на любой технологии (тот же nuxtjs в режиме генерации статики).
    2. Админка делается на любом client-side фреймворке (Vue, React, etc), потому что ей обычно не нужно SEO и нет повышенных требований к скорости загрузки, время tti и тд. "Поселить" ее можно на поддомене.

    Таким образом получится 2 статических приложения, а значит не нужно беспокоиться о node.js :)

    Плюс по опыту работы с nuxt.js могу сказать, что там есть свои особенности с роутером, хранилищем, подключением сторонних библиотек. Плюс своя система модулей - отдельный модуль axios, proxy и тд.
    Еще надо четко понимать, когда идет клиентский, а когда серверный рендеринг.
    В общем на первоначальном этапе время разработки может прилично увеличиться.
    Ответ написан
    Комментировать
  • Почему Vue.js возвращает __ob__: Observer?

    @karambafe
    Павел Корнилов все правильно написал, метод get у axios асинхронный, возвращает промис.

    Поэтому метод getPosts тоже нужно сделать асинхронным:
    async getPosts(author) {
       try {
         const { data } = await axios.get(`${options.serverUrl}/posts/${author}`);
         return data; 
      } catch(error) {
         return error; // важный момент, что будет возвращать функция при ошибках выполнения запроса на сервер
      }
     },


    Сделал небольшой пример на codeSanbox, файл App.vue.
    В консоли видна разница вывода для текущего метода getPosts и нового getPostsAsync

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

    Я бы внес запрос к серверу прямо в метод loadPosts:
    async loadPosts () {
      this.loading = true; // вешаем лоадер на момент асинхронного запроса
    
      try {
         const { data } = await axios.get(`${options.serverUrl}/posts/${this.$route.params.id}`);
         this.posts = data;
         this.loading = false;
      } catch(error) {
         // делаем что-то в случае ошибки
         this.loading = false;
      }
    },
    Ответ написан
    Комментировать