Привет, Хабр!

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

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

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

image

1. Триггер watcher'ов внутри хуков жизненного цикла


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

Вопрос:

Есть компонент TestComponent, у которого есть переменная amount. Внутри основных хуков жизненного цикла мы задаем ей значение в числовом порядке от 1 до 6. На эту переменную стоит watcher, который выводит ее значение в консоль.

Мы создаем инстанс TestComponent и через несколько секунд удаляем. Необходимо сказать, что мы увидим в выводе консоли.

Код:

/* TestComponent.vue */

<template>
  <span>
    I'm Test component
  </span>
</template>

<script>
export default {
  data() {
    return {
      amount: 0,
    };
  },

  watch: {
    amount(newVal) {
      console.log(newVal);
    },
  },

  beforeCreate() {  this.amount = 1; },
  created() {       this.amount = 2; },
  beforeMount() {   this.amount = 3; },
  mounted() {       this.amount = 4; },
  beforeDestroy() { this.amount = 5; },
  destroyed() {     this.amount = 6; },
};
</script>

Дам подсказку: «2345» — неправильный ответ.

Ответ
В консоли мы увидим только цифру 4.

Объяснение
В хуке beforeCreate еще не создан сам инстанс, watcher здесь работать не будет.

Watcher срабатывает на изменения в хуке created, beforeMount и mounted. Так как все эти хуки вызываются во время одного тика, Vue вызовет watcher один раз в самом конце, со значением 4.

Vue отпишется от наблюдения за изменением переменной перед вызовом хуков beforeDestroy и destroyed, поэтому 5 и 6 не попадут в консоль.

Песочница с примером, чтобы убедиться в ответе

2. Неявное поведение props


Этот вопрос основан на редком поведении props во Vue. Все программисты, конечно, просто выставляют нужные валидации для prop'ов и никогда не сталкиваются с таким поведением. Но этого говорить кандидату не нужно. Лучше будет задать этот вопрос, бросить на него осуждающий взгляд после неправильного ответа и перейти к следующему.

Вопрос:

Чем поведение prop'а с типом Boolean отличается от остальных?

/* SomeComponent.vue */

<template>
  <!-- ... -->
</template>

<script>
export default {
  /* ... */
  props: {
    testProperty: {
      type: Boolean,
    },
  },
};
</script>

Ответ
Prop с типом Boolean отличается от всех остальных тем, что во Vue для него есть специальное приведение типов.

Если в качестве параметра будет передана пустая строка или название самого prop'а в kebab-case, то Vue преобразует это в true.

Пример:

У нас есть файл с Boolean prop'ом:

/* TestComponent.vue */

<template>
  <div v-if="canShow">
    I'm TestComponent
  </div>
</template>

<script>
export default {
  props: {
    canShow: {
      type: Boolean,
      required: true,
    },
  },
};
</script>

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

/* TestWrapper.vue */

<template>
  <div>
    <!-- В этом случае canShow будет равен true внутри TestComponent -->
    <TestComponent canShow="" />

    <!-- Этот пример аналогичен предыдущему, vue-template-compiler выставит пустую строку для нашего prop'а -->
    <TestComponent canShow />

    <!-- Тут canShow тоже равен true -->
    <TestComponent canShow="can-show" />
  </div>
</template>

<script>
import TestComponent from 'path/to/TestComponent';

export default {
  components: {
    TestComponent,
  },
};
</script>


Песочница с примером, чтобы убедиться в ответе

3. Использование массива в $refs


Если ваш кандидат знает как работает фреймворк изнутри на уровне Эвана Ю, у вас все еще есть несколько козырей в рукаве: вы можете задать вопрос о незадокументированном и неочевидном поведении фреймворка.

Вопрос:

Во Vuex лежит массив объектов files, у каждого из объектов в массиве есть уникальные свойства name и id. Этот массив раз в несколько секунд обновляется, в нем удаляются и добавляются элементы.

У нас есть компонент, который выводит name каждого объекта массива с кнопкой, по клику на которую в консоль должен выводиться dom-элемент, связанный с текущим файлом:

/* FileList.vue */

<template>
  <div>
    <div
      v-for="(file, idx) in files"
      :key="file.id"
      ref="files"
    >
      {{ file.name }}

      <button @click="logDOMElement(idx)">
        Log DOM element
      </button>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState('files'),
  },

  methods: {
    logDOMElement(idx) {
      console.log(this.$refs.files[idx]);
    },
  },
};
</script>

Необходимо сказать, где здесь потенциальная ошибка и как ее исправить.

Ответ
Проблема в том, что массив внутри $refs может идти не в том порядке, как и оригинальный массив (ссылка на issue). То есть, может произойти такая ситуация: кликаем на кнопку третьего элемента списка, а на консоль выводится dom-элемент второго.

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

Методы решения написаны в issue на GitHub'е:

1. Создавать уникальный ref для каждого элемента

<template>
  <div>
    <div
      v-for="(file, idx) in files"
      :key="file.id"
      :ref="`file_${idx}`"
    >
      {{ file.name }}

      <button @click="logDOMElement(idx)">
        Log DOM element
      </button>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState('files'),
  },

  methods: {
    logDOMElement(idx) {
      console.log(this.$refs[`file_{idx}`]);
    },
  },
};
</script>

2. Дополнительный аттрибут

<template>
  <div>
    <div
      v-for="(file, idx) in files"
      :key="file.id"
      :data-file-idx="idx"
    >
      {{ file.name }}

      <button @click="logDOMElement(idx)">
        Log DOM element
      </button>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState('files'),
  },

  methods: {
    logDOMElement(idx) {
      const fileEl = this.$el.querySelector(`*[data-file-idx=${idx}]`);

      console.log(fileEl);
    },
  },
};
</script>


4. Странное пересоздание компонента


Вопрос:

У нас есть специальный компонент, который пишет в консоль каждый раз, когда вызывается хук mounted:

/* TestMount.vue */

<template>
  <div>
    I'm TestMount
  </div>
</template>

<script>
export default {
  mounted() {
    console.log('TestMount mounted');
  },
};
</script>


Этот компонент используется в компоненте TestComponent. Он имеет кнопку, по нажатию на которую на 1 секунду покажется надпись Top message.
/* TestComponent.vue */

<template>
  <div>
    <div v-if="canShowTopMessage">
      Top message
    </div>

    <div>
      <TestMount />
    </div>

    <button
      @click="showTopMessage()"
      v-if="!canShowTopMessage"
    >
      Show top message
    </button>
  </div>
</template>

<script>
import TestMount from './TestMount';

export default {
  components: {
    TestMount,
  },

  data() {
    return {
      canShowTopMessage: false,
    };
  },

  methods: {
    showTopMessage() {
      this.canShowTopMessage = true;

      setTimeout(() => {
        this.canShowTopMessage = false;
      }, 1000);
    },
  },
};
</script>

Кликнем на кнопку и посмотрим, что будет в консоли:



Первый маунт был ожидаемый, но откуда еще два? Как это исправить?

Песочница с примером, чтобы понять ошибку и исправить ее

Ответ
Проблема здесь возникает из-за особенностей поиска различий Virtual DOM'ов во Vue.

В самом начале наш Virtual DOM выглядит так:


После клика на кнопку он выглядит так:



Vue пытается сопоставить старый Virtual DOM с новым, чтобы понять, что нужно удалить и добавить:


Удаленные элементы перечеркнуты красным, созданные — выделены зеленым

Vue не смог найти компонент TestMount, поэтому пересоздал его.

Аналогичная ситуация повторится через секунду после нажатия кнопки. В этот момент компонент TestMounted третий раз выведет на консоль информацию о своем создании.

Чтобы пофиксить проблему, достаточно поставить атрибут key к div'у с компонентом TestMounted:

/* TestComponent.vue */

<template>
  <div>
    <!-- ... -->
    <div key="container">
      <TestMount />
    </div>
    <!-- ... -->
  </div>
</template>

 /* ... */

Теперь Vue сможет однозначно сопоставить нужные элементы Virtual DOM'ов.

5. Создание компонента-таблицы


Задача:

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

Информация о колонках и виде ячейки должна передаваться через специальный компонент (так же, как и у element-ui):

/* SomeComponent.vue */

<template>
  <CustomTable :items="items">

    <CustomColumn label="Name">
      <template slot-scope="item">
        {{ item.name }}
      </template>
    </CustomColumn>

    <CustomColumn label="Element Id">
      <template slot-scope="item">
        {{ item.id }}
      </template>
    </CustomColumn>

  </CustomTable>
</template>

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

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

Решение
Основная идея состоит в том, чтобы в компоненте CustomColumn передать все данные компоненту CustomTable, а дальше он уже сам все срендерит.

Ниже дан пример реализации. Он не учитывает некоторых моментов (как, например, изменение label), но основной принцип должен быть понятен.

/* CustomColumn.js */

export default {
  render() {
    return null;
  },

  props: {
    label: {
      type: String,
      required: true,
    },
  },

  mounted() {
    // Передаем в компонент CustomTable необходимые данные
    this.$parent.setColumnData({
      label: this.label,
      createCell: this.$scopedSlots.default,
    });
  },
};

/* CustomTable.js */
/* Использется JSX, так как в template не получится использовать метод createCell, переданный из CustomColumn.js */

export default {
  render() {
    const { columnsData, items } = this;
    const { default: defaultSlot } = this.$slots;

    return (
      <div>
        // Создаем элементы CustomColumn
        {defaultSlot}

        <table>
          // Создаем хедер
          <tr>
            {columnsData.map(columnData => (
              <td key={columnData.label}>
                {columnData.label}
              </td>
            ))}
          </tr>

          // Создаем строки таблицы
          {items.map(item => (
            <tr>
              {columnsData.map(columnData => (
                <td key={columnData.label}>
                  {columnData.createCell(item)}
                </td>
              ))}
            </tr>
          ))}
        </table>
      </div>
    );
  },

  props: {
    items: {
      type: Array,
      required: true,
    },
  },

  data() {
    return {
      columnsData: [],
    };
  },

  methods: {
    setColumnData(columnData) {
      this.columnsData.push(columnData);
    },
  },
};


6. Создание портала


Если ваш кандидат не справился с предыдущим заданием, ничего страшного: можете дать ему еще одно, не менее сложное!

Задача:

Создать компонент Portal и PortalTarget, как у библиотеки portal-vue:

/* FirstComponent.vue */

<template>
  <div>
    <Portal to="title">
      Super header
    </Portal>
  </div>
</template>

/* SecondComponent.vue */

<template>
  <div>
    <PortalTarget name="title" />
  </div>
</template>

Решение
Для создания портала нужно реализовать три объекта:

  • Хранилище данных о порталах
  • Компонент Portal, который добавляет данные в хранилище
  • Компонент PortalTarget, который извлекает данные из хранилища и отображает их

/* dataBus.js */
/* Файл содержит реактивное хранилище данных */

import Vue from 'vue';

const bus = new Vue({
  data() {
    return {
      portalDatas: [],
    };
  },

  methods: {
    setPortalData(portalData) {
      const { portalDatas } = this;

      const portalDataIdx = portalDatas.findIndex(
        pd => pd.id === portalData.id,
      );

      if (portalDataIdx === -1) {
        portalDatas.push(portalData);

        return;
      }

      portalDatas.splice(portalDataIdx, 1, portalData);
    },

    removePortalData(portalDataId) {
      const { portalDatas } = this;

      const portalDataIdx = portalDatas.findIndex(
        pd => pd.id === portalDataId,
      );

      if (portalDataIdx === -1) {
        return;
      }

      portalDatas.splice(portalDataIdx, 1);
    },

    getPortalData(portalName) {
      const { portalDatas } = this;

      const portalData = portalDatas.find(pd => pd.to === portalName);

      return portalData || null;
    },
  },
});

export default bus;

/* Portal.vue */
/* Этот компонент передает данные в dataBus */

import dataBus from './dataBus';

let currentId = 0;

export default {
  props: {
    to: {
      type: String,
      required: true,
    },
  },

  computed: {
    // Уникальный id компонента.
    // Нужен для идентификации данных в dataBus
    id() {
      return currentId++;
    },
  },

  render() {
    return null;
  },

  created() {
    this.setPortalData();
  },
 
  // Подхватываем изменение слотов
  updated() {
    this.setPortalData();
  },

  methods: {
    setPortalData() {
      const { to, id } = this;
      const { default: portalEl } = this.$slots;

      dataBus.setPortalData({
        to,
        id,
        portalEl,
      });
    },
  },

  beforeDestroy() {
    dataBus.removePortalData(this.id);
  },
};

/* PortalTarget.vue */
/* Компонент извлекает и отображает данные */

import dataBus from './dataBus';

export default {
  props: {
    name: {
      type: String,
      required: true,
    },
  },

  render() {
    const { portalData } = this;

    if (!portalData) {
      return null;
    }

    return (
      <div class="portal-target">
        {portalData.portalEl}
      </div>
    );
  },

  computed: {
    portalData() {
      return dataBus.getPortalData(this.name);
    },
  },
};

Данное решение не поддерживает изменение атрибута to, не поддерживает анимации через transition и не имеет поддержки дефолтных значений, как portal-vue. Но общая идея должна быть понятна.

7. Предотвращение создания реактивности


Вопрос:

Вы получили от апи большой объект и отобразили его пользователю. Примерно так:

/* ItemView.vue */

<template>
  <div v-if="item">
    <div> {{ item.name }} </div>
    <div> {{ item.price }} </div>
    <div> {{ item.quality }} </div>
    <!-- И еще много полей -->
  </div>
</template>

<script>
import getItemFromApi from 'path/to/getItemFromApi';

export default {
  data() {
    return {
      item: null,
    };
  },

  async mounted() {
    this.item = await getItemFromApi();
  },
};
</script>

В этом коде есть проблема. У объекта item мы не меняем name, price, quality и остальные свойства. Но Vue об этом не знает и добавляет реактивность в каждое поле.

Как можно этого избежать?

Ответ
Чтобы избежать изменения свойств на реактивные, надо заморозить объект перед добавлением внутрь Vue с помощью метода Object.freeze.

Vue проверит, заморожен ли объект с помощью метода Object.isFrozen. И если это так, то Vue не станет добавлять реактивные геттеры и сеттеры к свойствам объекта, так как их в любом случае невозможно изменить. При очень больших объектах эта оптимизация помогает сохранить до нескольких десятков миллисекунд.

Оптимизированный компонент будет выглядеть так:

/* ItemView.vue */

<template>
  <!-- ... -->
</template>

<script>
import getItemFromApi from 'path/to/getItemFromApi';

export default {
  /* .... */

  async mounted() {
    const item = await getItemFromApi();
    Object.freeze(item);

    this.item = item;
  },
};
</script>

Object.freeze замораживает только свойства самого объекта. Так что, если объект содержит в себе вложенные объекты, их тоже необходимо заморозить.

Обновление от 19.01.2019: По совету Дмитрия Злыгина глянул библиотеку vue-nonreactive и нашел еще один способ. Он отлично подойдет для ситуации, когда у вас много вложенных объектов.

Vue не станет добавлять реактивность в объект, если увидит, что он уже реактивен. Мы можем обмануть Vue, создав пустой Observer для объекта:

/* ItemView.vue */

<template>
  <!-- ... -->
</template>

<script>
import Vue from 'vue';
import getItemFromApi from 'path/to/getItemFromApi';

const Observer = new Vue()
  .$data
  .__ob__
  .constructor;

export default {
  /* .... */

  async mounted() {
    const item = await getItemFromApi();

    // Добавляем пустой Observer для объекта
    item.__ob__ = new Observer({});

    this.item = item;
  },
};
</script>


8. Ошибки медленных девайсов


Вопрос:

Есть компонент с методом, который выводит одно из свойств объекта item в консоль, а затем удаляет объект item:

/* SomeComponent.vue */

<template>
  <div v-if="item">
    <button @click="logAndClean()">
      Log and clean
    </button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      item: {
        value: 124,
      },
    };
  },

  methods: {
    logAndClean() {
      console.log(this.item.value);

      this.item = null;
    },
  },
};
</script>

Что здесь может пойти не так?

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

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

Чтобы избежать ее, просто добавьте проверку на существование item в начале функции:

<template>
  <!-- ... -->
</template>

<script>
export default {
  /* ... */

  methods: {
    logAndClean() {
      const { item } = this;

      if (!item) {
        return;
      }

      console.log(item.value);

      this.item = null;
    },
  },
};
</script>

Чтобы воспроизвести баг, можете перейти в песочницу с примером, выставить максимальный троттлинг CPU и быстро-быстро покликать на кнопку. У меня, например, получилось.



Ссылка на песочницу, чтобы убедиться в ответе

Спасибо, что дочитали статью до конца! Думаю, теперь вы точно сможете казаться умнее на собеседованиях и у ваших кандидатов сильно упадут зарплатные ожидания!

Комментарии (30)


  1. greabock
    18.01.2019 10:33
    +2

    Опять эти люки… Как и написано в заголовке, эти вопросы действительно ужасны, и совершенно не подходят для собеседования )

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

    А задачки интересные, конечно )


    1. AndreasCag Автор
      18.01.2019 10:36
      +1

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

      На текущий момент мы сразу применяем физическое насилие к кандидатам. Обычно используем наручники и батарею.


      1. lightmaann
        18.01.2019 10:46
        +2

        А если кандидат чемпион мира по рукопашному коду?


        1. AndreasCag Автор
          18.01.2019 10:50
          +2

          Для таких случаев у нас в офисе есть перцовый балончик.

          Хотя до такого еще не доходило, обычно справляемся своими силами.


        1. hatari90
          18.01.2019 14:09

          Для этого собеседующих несколько.


      1. greabock
        18.01.2019 10:49

        На текущий момент мы сразу применяем физическое насилие к кандидатам. Обычно используем наручники и батарею.

        Можно подробнее? Очень интересует практика в данном вопросе.


        1. AndreasCag Автор
          18.01.2019 10:52
          +2

          Конечно, приходите к нам на собеседование!


          1. greabock
            18.01.2019 10:55

            Нет, уж лучше вы к нам! ©


      1. berezuev
        18.01.2019 11:01

        Страшно представить, что у вас делают с бэкендерами… Недельные спринты в темных подвалах?


        1. AndreasCag Автор
          18.01.2019 11:06
          +2

          Трехдневные спринты с митингами два раза в день, утром и ночью.

          За баги в проде лишаем еды и будим палкой по ночам.


          1. berezuev
            18.01.2019 11:32
            +1

            Трехдневные спринты

            Лучше сразу пристрелите.


      1. Agel_Nash
        18.01.2019 19:17
        +1

        Это такой новый тренд moikrug.ru/vacancies/1000032070?


      1. kolobokspb
        19.01.2019 01:27
        +4

        на первой своей работе коллеги мне рассказали историю (10 лет назад), что за год до моего прихода они наняли человека для кражи кода c прошивки с микроконтроллера и переделки его из ASM в код С++ (тайваньский алгоритм распознавания отпечатков пальцев). Код был скачан, но с конвертацией были проблемы. Чел потихоньку слился. Его нашли, прицепили к батарее пока он не сделал (1 месяц это заняло). Не только вы одни такое практикуете. Сейчас эти деятели пилят оборонные заказы.


  1. CyberAP
    18.01.2019 11:17
    +1

    Object.freeze замораживает только свойства самого объекта. Так что, если объект содержит в себе вложенные объекты, их тоже необходимо заморозить.

    Или чтобы этого не делать достать данные из замыкания. Думаю понятно что флаг dataLoaded нужен только из-за отсутствия реактивности у item.


    import getItemFromApi from 'path/to/getItemFromApi';
    
    let item;
    
    export default {
      data() {
        return { dataLoaded: false }
      }
      async mounted() {
        item = await getItemFromApi();
        this.dataLoaded = true;
      },
      computed() {
        return this.dataLoaded && item;
      }
    };

    Статья крутая, спасибо!


    1. Kuorell
      18.01.2019 18:44
      +1

      Так у вас тогда будет 1 item на все компоненты.
      Если мне не изменяет память, (сменил vue на реакт примерно 8 месяцев назад) можно просто не объявлять его в data и таким же способом вернуть через computed. (только как this.item)


      1. CyberAP
        18.01.2019 19:03

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


  1. ameli_anna_kate
    18.01.2019 19:37
    +2

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


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


    1. mapron
      18.01.2019 21:18
      +3

      кандидат сразу почувствовал ваше превосходство

      ну вредные советы же, ирония, не?


    1. Psychosynthesis
      18.01.2019 22:03

      А чего стрессовать на собеседовании?


  1. DreamCream
    18.01.2019 23:46
    +1

    Ох, почувствовал я себя днищем. Ни на один вопрос…
    Как раз недавно читал тут статью про заниженную самооценку русскоязычных IT-шников.
    И где вы только умудряетесь такие вопросы находить, изверги! =)
    Спасибо, интересная и полезная статья.


  1. Duke565
    19.01.2019 00:38
    +1

    К слову говоря — эта песочница тоже написана на Vue.js


    1. AndreasCag Автор
      19.01.2019 01:57
      +1

      О, так это ваша песочница!

      Спасибо большое за нее, песочница с лучшей консолью!


      1. Duke565
        19.01.2019 02:32
        +1

        Благодарю!
        Не могли бы вы рассказать, чем консоль столь примечательна?


        Возможно у вас есть пожелания?


        1. AndreasCag Автор
          19.01.2019 03:16
          +1

          Консоль примечательна тем, что:
          1. Она есть
          2. Она удобно расположена под preview

          Единственный аналог с консолью на сайте, который я нашел — это jsbin.com. Вначале я пытался использовать его, но что-то у них не завелась подсветка синтаксиса


          Из пожеланий — мне показалось неудобным, что нельзя сразу увидеть html, css и js.

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


          1. Duke565
            19.01.2019 03:47
            +1

            Спасибо Андрей!
            Планируется вторая версия, там будет возможность открыть несколько файлов в разных «местах».


  1. Duke565
    19.01.2019 01:52
    +1

    Отличное изложение, спасибо


    А я то думал: «зачем это подавление справочными вопросами, которые решаются с помощью гугла и исходников того же element-ui?».


    «… упадут зарплатные ожидания» — теперь понятно. Умно!


  1. ganqqwerty
    19.01.2019 04:09

    Вообще зачем задавать вопросы по фреймворку?


  1. AndreasCag Автор
    19.01.2019 22:15
    +1

    Обновление: Добавил еще одну фишку в ответе к 7 вопросу.

    CyberAP Kuorell посмотрите, думаю вам будет интересно.


    1. b360124
      20.01.2019 14:09
      +1

      это уже похоже на хак, с фризом объекта вариант получше. А за статью спасибо, реально полезная статья по Vue за последний год. Пишите еще ;)


  1. Sergofan_Git
    20.01.2019 16:14
    +1

    Спасибо за пост. Вы здорово повеселили меня и скрасили вечер. :)
    Отдельное спасибо Agel_Nash за комментарий.