Советы по использованию Vue

внешний интерфейс Vue.js
Советы по использованию Vue

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

Заинтересованные студенты могут добавить группу WeChat в конце статьи для совместного обсуждения~

1. Децентрализация событий изменения размера нескольких графиков

1.1 Общая ситуация

Иногда мы сталкиваемся с таким сценарием, в компоненте есть несколько диаграмм, и мы хотим, чтобы диаграмма изменялась при изменении размера браузера, поэтому мы напишем в родительском компоненте-контейнере:

mounted() {
  setTimeout(() => window.onresize = () => {
    this.$refs.chart1.chartWrapperDom.resize()
    this.$refs.chart2.chartWrapperDom.resize()
    // ... 
  }, 200)
destroyed() { window.onresize = null }

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

1.2 Оптимизация

используется здесьlodashФункцию дроссельной заслонки также можно реализовать самостоятельно, этостатьяСуществуют также реализации дросселирования, на которые вы можете ссылаться. Возьмем, к примеру, Echarts в каждом компоненте диаграммы:

computed: {
  /**
   * 图表DOM
   */
  chartWrapperDom() {
    const dom = document.getElementById('consume-analy-chart-wrapper')
    return dom && Echarts.init(dom)
  },
  /**
   * 图表resize节流,这里使用了lodash,也可以自己使用setTimout实现节流
   */
  chartResize() {
    return _.throttle(() => this.chartWrapperDom && this.chartWrapperDom.resize(), 400)
  }
},
mounted() {
  window.addEventListener('resize', this.chartResize)
},
destroyed() {
  window.removeEventListener('resize', this.chartResize)
}

1.3 Повторная оптимизация

Спасибо @JserWang за напоминание, поскольку несколько экземпляров диаграммы используют один и тот же набор логики инициализации, вы можете использовать расширения для повторного использования, поэтому я подумал о том, что предоставляется Vue.Mixins, поэтому я сделал здесь небольшую оптимизацию, чтобы сделать каждый компонент диаграммы одного типа немного более элегантным: Создайте новый файл mixin.js:

import Echarts from 'echarts'
import _ from 'lodash'

export default {
  computed: {
    /* 图表DOM */
    $_chartMixin_chartWrapperDom() {
      const dom = document.getElementById(this.thisDomId)
      return dom && Echarts.init(dom)
    },
    
    /** 图表resize节流,这里使用了lodash,也可以自己使用setTimout实现节流 */
    $_chartMixin_chartResize() {
      return _.throttle(() => this.$_chartMixin_chartWrapperDom.resize(), 400)
    }
  },
  
  methods: {
    /* 图表初始化 */
    $_chartMixin_initChart() {
      this.$_chartMixin_chartWrapperDom.setOption({ /* options */ })
  },
  
  mounted() {
    this.$_chartMixin_initChart()
    window.addEventListener('resize', this.$_chartMixin_chartResize)
  },
  
  destroyed() {
    window.removeEventListener('resize', this.$_chartMixin_chartResize)
  }
}

Затем в каждом компоненте диаграммы:

<script type='text/javascript'>
import ChartMixin from './mixin'
export default {
  mixins: [ChartMixin],
  data() {
    return {
      thisDomId: 'consume-analy-chart-wrapper'
    }
  }
}
</script>

Это позволяет смешивать каждый компонент диаграммы передmixin.jsЛогика события изменения размера определена в , и она автоматически инициализируется, а событие автоматически уничтожается при уничтожении~

Конечно, его можно дополнительно оптимизировать. Например, если на странице несколько диаграмм, приведенной выше реализации недостаточно. Здесь необходимо провести рефакторинг. Конкретный код см.chartInitMixin - Githubреализация~

2. Регистрация глобального фильтра

2.1 Общая ситуация

Официальный способ регистрации фильтра:

export default {
  data () { return {} },
  filters:{
    orderBy (){
      // doSomething
    },
    uppercase () {
      // doSomething
    }
  }
}

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

// 注册
Vue.filter('my-filter', function (value) {
  // 返回处理后的值
})
// getter,返回已注册的过滤器
var myFilter = Vue.filter('my-filter')

Однако, если он написан отдельно, это не красиво, поэтому его можно извлечь в отдельный файл.

2.2 Оптимизация

Мы можем извлечь его в отдельный файл, а затем использовать Object.keys, чтобы зарегистрировать его в записи main.js.

/src/common/filters.js

let dateServer = value => value.replace(/(\d{4})(\d{2})(\d{2})/g, '$1-$2-$3') 

export { dateServer }

/src/main.js

import * as custom from './common/filters/custom'
Object.keys(custom).forEach(key => Vue.filter(key, custom[key]))

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

<template>
  <section class="content">
    <p>{{ time | dateServer }}</p> <!-- 2016-01-01 -->
  </section>
</template>
<script>
  export default {
    data () {
      return {
        time: 20160101
      }
    }
  }
</script>

3. Глобальная регистрация компонентов

3.1 Общая ситуация

Сценарии, требующие компонентов:

<template>
    <BaseInput  v-model="searchText"  @keydown.enter="search"/>
    <BaseButton @click="search">
        <BaseIcon name="search"/>
    </BaseButton>
</template>
<script>
    import BaseButton from './baseButton'
    import BaseIcon from './baseIcon'
    import BaseInput from './baseInput'
    export default {
      components: { BaseButton, BaseIcon, BaseInput }
    }
</script>

Мы написали кучу базовых компонентов UI, а потом каждый раз, когда нам нужно использовать эти компоненты, нам приходится сначала импортировать, а потом объявлять компоненты, что очень громоздко, здесь мы можем использовать форму единой регистрации

3.2 Оптимизация

Нам нужно использовать веб-пакет артефакта для использованияrequire.context()метод создания собственногомодульконтекст, чтобы добиться автоматического динамического требования компонентов. Этот метод принимает 3 параметра: каталог папки для поиска, следует ли также искать его подкаталоги, и регулярное выражение для сопоставления файлов. Мы добавляем файл с именем componentRegister.js в папку компонентов, и в этом файле мы используем веб-пакет для динамической упаковки всех необходимых базовых компонентов.

/src/components/componentRegister.js

import Vue from 'vue'

/**
 * 首字母大写
 * @param str 字符串
 * @example heheHaha
 * @return {string} HeheHaha
 */
function capitalizeFirstLetter(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

/**
 * 对符合'xx/xx.vue'组件格式的组件取组件名
 * @param str fileName
 * @example abc/bcd/def/basicTable.vue
 * @return {string} BasicTable
 */
function validateFileName(str) {
  return /^\S+\.vue$/.test(str) &&
    str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => capitalizeFirstLetter($1))
}

const requireComponent = require.context('./', true, /\.vue$/)

// 找到组件文件夹下以.vue命名的文件,如果文件名为index,那么取组件中的name作为注册的组件名
requireComponent.keys().forEach(filePath => {
  const componentConfig = requireComponent(filePath)
  const fileName = validateFileName(filePath)
  const componentName = fileName.toLowerCase() === 'index'
    ? capitalizeFirstLetter(componentConfig.default.name)
    : fileName
  Vue.component(componentName, componentConfig.default || componentConfig)
})

Эта структура папок:

components
│ componentRegister.js
├─BasicTable
│ BasicTable.vue
├─MultiCondition
│ index.vue

Здесь оценивается имя компонента.Если это индекс, атрибут имени в компоненте обрабатывается как зарегистрированное имя компонента, поэтому последний зарегистрированный компонент:multi-condition,basic-tableНаконец, мы импортируем 'components/componentRegister.js' в main.js, и тогда мы можем использовать эти базовые компоненты в любое время и в любом месте без ручного введения~

4. Компонентное повторное использование разных маршрутов

4.1 Восстановление сцены

Когда vue-router переходит с /post-page/a на /post-page/b в сцене. Потом мы с удивлением обнаружили, что данные не обновились после того, как страница перескочила? ! Причина в том, что vue-router «интеллектуально» находит, что это один и тот же компонент, а потом решает повторно использовать этот компонент, поэтому метод, который вы написали в созданной функции, вообще не выполняется. Обычное решение состоит в том, чтобы прослушивать изменения в $route для инициализации данных следующим образом:

data() {
  return {
    loading: false,
    error: null,
    post: null
  }
},
watch: {
  '$route': {        // 使用watch来监控是否是同一个路由
    handler: 'resetData',
    immediate: true
  }
},
methods: {
  resetData() {
    this.loading = false
    this.error = null
    this.post = null
    this.getPost(this.$route.params.id)
  },
  getPost(id){ }
}

4.2 Оптимизация

Для достижения этого эффекта необходимоrouter-viewДобавьте другой ключ, чтобы, даже если это общедоступный компонент, при изменении URL-адреса компонент создавался заново.

<router-view :key="$route.fullpath"></router-view>

также можно добавить после+ +new Date()Временная метка, гарантированно уникальная

Спасибо пользователю сети @rolitter за напоминание, если компонент находится в<keep-alive>В случае , вы можете поместить метод получения новых данных в активированный хук вместо исходной задачи получения данных в созданных и смонтированных хуках.

5. Проникновение атрибута события компонента

5.1 Общая ситуация

// 父组件
<BaseInput :value="value"
           label="密码"
           placeholder="请填写密码"
           @input="handleInput"
           @focus="handleFocus">
</BaseInput>

// 子组件
<template>
  <label>
    {{ label }}
    <input :value=" value"
           :placeholder="placeholder"
           @focus="$emit('focus', $event)"
           @input="$emit('input', $event.target.value)">
  </label>
</template>

5.2 Оптимизация

В экземпляре компонента vue$props,$attrsЭто обеспечивает нам большое удобство, особенно при передаче значений между родительским и дочерним компонентами. 1. Каждый реквизит, передаваемый из родительского компонента в дочерний, должен быть явно объявлен в реквизитах дочернего компонента, прежде чем его можно будет использовать. Таким образом, нашим дочерним компонентам нужно каждый раз объявлять множество реквизитов, здесь мы знаемv-bind — это объект, который можно передатьДа, ты можешьvm.$propsПолучить значения всех реквизитов родительского компонента вv-bind="$props"

<input  v-bind="$props" 
       @input="$emit('input', $event.target.value)">

2. Можно использовать нативные свойства DOM, такие как заполнитель$attrsПередается напрямую от родителя к дочернему, объявление не требуется. Методы, как показано ниже:

<input :value="value"
       v-bind="$attrs"
       @input="$emit('input', $event.target.value)">

$attrsСодержит привязки свойств (кроме класса и стиля), которые не распознаются (и не получаются) как свойства в родительской области. Когда компонент не объявляет никаких реквизитов, сюда включаются все привязки родительской области, и к ним можно получить доступ черезv-bind="$attrs"Передайте внутренний компонент.

3. Обратите внимание на подкомпоненты@focus="$emit('focus', $event)"По сути ничего не делается, просто передача события обратно родительскому компоненту, что собственно аналогично вышеописанному, и явно объявлять не нужно:

<input :value="value"
       v-bind="$attrs"
       v-on="listeners"/>

computed: {
  listeners() {
    return {
      ...this.$listeners,
      input: event =>
        this.$emit('input', event.target.value)
    }
  }
}

$listenersВключено в родительскую область (без декоратора .native)v-onпрослушиватель событий. это может пройтиv-on="$listeners"Передача во внутренних компонентах - полезно при создании компонентов более высокого уровня.

Следует отметить, что, поскольку наш ввод не является корневым узлом компонента BaseInput, родительская область не распознается по умолчанию.propsПривязки атрибутов будут «отступать» и применяться как обычные HTML-атрибуты к корневому элементу дочернего компонента. Итак, нам нужно установитьinheritAttrs: false, это поведение по умолчанию будет удалено, а приведенные выше оптимизации будут успешными.

6. Маршруты лениво загружаются в соответствии со статусом разработки

6.1 Общие

Как правило, когда мы загружаем компоненты в маршруты:

import Login from '@/views/login.vue'

export default new Router({
  routes: [{ path: '/login', name: '登陆', component: Login}]
})

Когда вам нужна отложенная загрузка, вам нужно изменить компоненты маршрутов на() => import('@/views/login.vue'), очень хлопотно.

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

6.2 Оптимизация

Согласно ВьюАсинхронные компонентыи веб-пакетразделение кодаЛенивая загрузка компонентов может быть легко реализована, например:

const Foo = () => import('./Foo.vue')

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

_import_production.js

module.exports = file => () => import('@/views/' + file + '.vue')

_import_development.js(Такой способ написанияvue-loaderВерсия не ниже v13.0.0 или выше)

module.exports = file => require('@/views/' + file + '.vue').default

при настройке маршрутаrouter/index.jsВ файле:

const _import = require('./_import_' + process.env.NODE_ENV)

export default new Router({
  routes: [{ path: '/login', name: '登陆', component: _import('login/index') }]
})

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

7 советов по vue-loader

vue-loaderэто загрузчик веб-пакетов, который обрабатывает файлы *.vue. Он сам по себе предоставляет множество API-интерфейсов, некоторые из которых полезны, но малоизвестны. Например, следующийpreserveWhitespaceиtransformToRequire

7.1 ИспользованиеpreserveWhitespaceУменьшить размер файла

Иногда мы не хотим иметь пробелы между элементами при написании шаблонов, мы можем написать так:

<ul>
  <li>1111</li><li>2222</li><li>333</li>
</ul>

Конечно, есть и другие способы, например, установка шрифтаfont-size: 0, а затем задайте размер шрифта отдельно для необходимого контента, чтобы убрать пробелы между элементами. Фактически, мы можем полностью выполнить это требование, настроив vue-loader.

{
  vue: {
    preserveWhitespace: false
  }
}

Его роль заключается в предотвращении создания пустого содержимого между элементами и используется после компиляции шаблона Vue._v(" ")Выражать. Если в проекте много шаблонов, они все равно будут занимать некоторый размер файла. Например, после настройки этого свойства для Element размер файла уменьшается почти на 30 КБ без сжатия.

7.2 ИспользованиеtransformToRequireБольше не нужно записывать изображения в качестве переменных

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

<template>
  <div>
    <avatar :default-src="DEFAULT_AVATAR"></avatar>
  </div>
</template>
<script>
  export default {
    created () {
      this.DEFAULT_AVATAR = require('./assets/default-avatar.png')
    }
  }
</script>

На самом деле, настроивtransformToRequireПосле этого вы можете настроить его напрямую, чтобы vue-loader автоматически запрашивал соответствующие свойства и передал их компоненту.

{
  vue: {
    transformToRequire: {
      avatar: ['default-src']
    }
  }
}

Так что наш код можно сильно упростить

<template>
  <div>
    <avatar default-src="./assets/default-avatar.png"></avatar>
  </div>
</template>

В шаблоне веб-пакета vue-cli конфигурация по умолчанию:

transformToRequire: {
  video: ['src', 'poster'],
  source: 'src',
  img: 'src',
  image: 'xlink:href'
}

Вы можете сделать аналогичную конфигурацию

vue-loader также имеет много полезных API, таких как недавно добавленныйпользовательский блок, те, кто заинтересован, могут перейти к документации, чтобы найти его.

8. функция визуализации

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

8.1 Динамические теги

1. Генеральный

Например, сценарий, в котором теги генерируются на основе реквизита.

<template>
  <div>
    <div v-if="level === 1"> <slot></slot> </div>
    <p v-else-if="level === 2"> <slot></slot> </p>
    <h1 v-else-if="level === 3"> <slot></slot> </h1>
    <h2 v-else-if="level === 4"> <slot></slot> </h2>
    <strong v-else-if="level === 5"> <slot></slot> </stong>
    <textarea v-else-if="level === 6"> <slot></slot> </textarea>
  </div>
</template>

Уровень это переменная в данных.Вы видите,что здесь много повторяющегося кода.Если логика сложная,то будет сложнее с какими-то привязками и суждениями.Здесь можно использовать функцию рендера для оценки тегов быть сгенерированным.

2. Оптимизация

Использование метода рендеринга для генерации соответствующих тегов в соответствии с параметрами позволяет избежать описанной выше ситуации.

<template>
  <div>
    <child :level="level">Hello world!</child>
  </div>
</template>

<script type='text/javascript'>
  import Vue from 'vue'
  Vue.component('child', {
    render(h) {
      const tag = ['div', 'p', 'strong', 'h1', 'h2', 'textarea'][this.level]
      return h(tag, this.$slots.default)
    },
    props: {
      level: {  type: Number,  required: true  } 
    }
  })   
  export default {
    name: 'hehe',
    data() { return { level: 3 } }
  }
</script>

Примеры можно посмотретьCodePen

8.2 Динамические компоненты

Конечно, функция рендеринга имеет множество применений, например, использование динамических компонентов в дополнение к использованию:isВы также можете использовать функцию рендеринга в дополнение к

<template>
  <div>
    <button @click='level = 0'>嘻嘻</button>
    <button @click='level = 1'>哈哈</button>
    <hr>
    <child :level="level"></child>
  </div>
</template>

<script type='text/javascript'>
  import Vue from 'vue'
  import Xixi from './Xixi'
  import Haha from './Haha'
  Vue.component('child', {
    render(h) {
      const tag = ['xixi', 'haha'][this.level]
      return h(tag, this.$slots.default)
    },
    props: { level: { type: Number, required: true } },
    components: { Xixi, Haha }
  })
  export default {
    name: 'hehe',
    data() { return { level: 0 } }
  }
</script>

Примеры можно посмотретьCodePen


@ 20180702 Добавить метод децентрализованной оптимизации для событий с несколькими значками, напомнивший @JserWang


Большинство сообщений в Интернете имеют разную глубину и даже некоторые несоответствия.Следующие статьи являются кратким изложением процесса обучения.Если вы найдете какие-либо ошибки, пожалуйста, оставьте сообщение, чтобы указать~

Ссылаться на:

  1. Глобальный фильтр Vue2 (vue-cli)
  2. Лучшие практики Vue.js
  3. документация по веб-пакету — require.context
  4. Используйте веб-пакет require.context для обеспечения «децентрализованного» управления маршрутизацией.
  5. документация vue-element-admin
  6. Практические советы по Vue.js
  7. Оптимизируйте скорость открытия страницы, хотите знать~

PS: Всех приглашаю обратить внимание на мой публичный аккаунт [Front End Afternoon Tea], давайте работать вместе~

Кроме того, вы можете присоединиться к группе WeChat «Front-end Afternoon Tea Exchange Group», нажмите и удерживайте, чтобы определить QR-код ниже, чтобы добавить меня в друзья, обратите вниманиеДобавить группу, я заберу тебя в группу~