Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность.
предисловие
Цель статьи очевидна 🐱🐉, сортировка и обобщение важных особенностей и принципов фреймворка Vue.
Откуда взялась "дальновидность"?
Да, я собираюсь снова рассказать короткую историю, но на этот раз это продолжение истории.
Эпизод истории 1:Препроцессор CSS, вы все еще только вкладываете?
Эпизод истории 2:[Адаптивный] px to rem, вы все еще считаете?
Почему сказано, что это продолжение, ведь это все задает один и тот же босс, я хотел бы поблагодарить босса за материал 🤣.
продолжение истории
Большой парень: Вы видели исходный код Vue?
Я: Хм, я видел.
Большой парень: Давайте поговорим о базовой реализации nextTick?
Я: После паузы примерно в 10 секунд я забыл кое-что сказать. (Неразумный и сильный)
Босс: О, все в порядке. (Вероятно, я отказался от добычи знаний в своем сердце)
Поскольку это видео-интервью, с экрана переливается смущение от притворства уверенным в себе, что, вероятно,普通且自信
🤦♂️? Неудачный случай с притворством Х можно воспринимать как предупреждение, а результаты интервью, которые могут написать продолжение, не упоминаются.
Это интервью было настоящим ударом, а содержание расследования было всеобъемлющим и подробным. После интервью я разбирал точки знаний, связанные с Vue, поэтому я не будуnextTick实现
Он написан отдельно и включен только в тестовые вопросы ниже.前车之鉴可以为鉴
, вы можете использовать эту статью в качестве викторины, чтобы узнать, хорошо ли вы владеете этими знаниями.
Текст объемом 10 000 символов постоянно обновляется, если в нем отсутствует какой-либо пункт знаний, он будет дополнен в будущем.
тема
Преимущества и недостатки Vue
преимущество
- Облегченная среда веб-приложений для создания одностраничных приложений.
- Простой в использовании
- двусторонняя привязка данных
- Идея компонентизации
- виртуальный DOM
- представление на основе данных
недостаток
Нет поддержки IE8
Понимание СПА
СПА этоSingle-Page-Application
Аббревиатура , что переводится как одностраничное приложение. Загружайте Html, Javascript, Css вместе при инициализации веб-страницы. После загрузки страницы SPA не будет перезагружать или переходить страницу из-за действий пользователя, а вместо этого использует механизм маршрутизации для реализации преобразования содержимого HTML.
преимущество
- Хороший пользовательский интерфейс, контент меняется без перезагрузки страницы.
- Исходя из вышеизложенного, SPA менее нагружен, чем сервер.
- Обязанности переднего и заднего плана разделены, и структура ясна.
недостаток
- Поскольку одностраничное веб-приложение должно запрашивать файлы JavaScript и Css при загрузке и отображении страницы, это занимает больше времени.
- Из-за внешнего рендеринга поисковые системы не будут анализировать JS и могут сканировать только неотрендеренные шаблоны на главной странице, что не способствует SEO.
- Поскольку одностраничным приложениям необходимо отображать весь контент на одной странице, по умолчанию не поддерживаются браузеры вперед и назад.
Недостаток 3, у кого-то должен быть такой же вопрос, как у меня.
Благодаря обзору данных на самом деле前端路由机制
Исправлена ошибка, из-за которой одностраничные приложения не могли перемещаться вперед или назад. Изменения хэша в режиме хеширования будут записаны браузером (onhashchange
событие), в режиме истории используется новый H5pushState
иreplaceState
способ изменить стек истории браузера.
Что делает новый Vue (опции)
Как показано в следующем конструкторе Vue, он в основном выполняетthis._init(options)
метод, который находится вinitMixin
Функции зарегистрированы.
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// Vue.prototype._init 方法
this._init(options)
}
// _init 方法在 initMixin 注册
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
ПроверятьinitMixin
Реализацию метода, конкретную реализацию других функций вы можете посмотреть сами и здесь выкладываться не будут.
let uid = 0
export function initMixin() {
Vue.prototype._init = function(options) {
const vm = this
vm._uid = uid++
vm._isVue = true
// 处理组件配置项
if (options && options._isComponent) {
/**
* 如果是子组件,走当前 if 分支
* 函数作用是性能优化:将原型链上的方法都放到vm.$options中,减少原型链上的访问
*/
initInternalComponent(vm, options)
} else {
/**
* 如果是根组件,走当前 else 分支
* 合并 Vue 的全局配置到根组件中,如 Vue.component 注册的全局组件合并到根组件的 components 的选项中
* 子组件的选项合并发生在两个地方
* 1. Vue.component 方法注册的全局组件在注册时做了选项合并
* 2. { component: {xx} } 方法注册的局部组件在执行编译器生成的 render 函数时做了选项合并
*/
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
vm._self = vm
/**
* 初始化组件实例关系属性,如:$parent $root $children $refs
*/
initLifecycle(vm)
/**
* 初始化自定义事件
* <component @click="handleClick"></component>
* 组件上注册的事件,监听者不是父组件,而是子组件本身
*/
initEvents(vm)
/**
* 解析组件插槽信息,得到vm.$slot,处理渲染函数,得到 vm.$createElement 方法,即 h 函数。
*/
initRender(vm)
/**
* 执行 beforeCreate 生命周期函数
*/
callHook(vm, 'beforeCreate')
/**
* 解析 inject 配置项,得到 result[key] = val 的配置对象,做响应式处理且代理到 vm 实力上
*/
initInjections(vm)
/**
* 响应式处理核心,处理 props、methods、data、computed、watch
*/
initState(vm)
/**
* 解析 provide 对象,并挂载到 vm 实例上
*/
initProvide(vm)
/**
* 执行 created 生命周期函数
*/
callHook(vm, 'created')
// 如果 el 选项,自动执行$mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
Понимание МВВМ
МВВМ этоModel-View-ViewModel
аббревиатура от. Модель представляет собой уровень данных, который может определять и изменять данные и писать бизнес-логику. Представление представляет слой представления и отвечает за отображение данных на страницах. ViewModel отвечает за мониторинг изменений данных в слое данных и управление поведением взаимодействия слоя представления, короче говоря, он синхронизирует объекты слоя данных и слоя представления. ViewModel связывает слои View и Model через двустороннюю привязку, а работа синхронизации не требует вмешательства человека, так что разработчики сосредотачиваются только на бизнес-логике, им не нужно часто манипулировать DOM и не нужно обращать внимание на синхронизация состояний данных.
Как реализовать v-модель
Директива v-model используется для реализацииinput
,select
Двустороннее связывание, такое как элементы формы, является синтаксическим сахаром.
Если собственный элемент вводаtext/textarea
type, используя свойство value и событие ввода.
Если собственный элемент вводаradio/checkbox
тип, используя проверенное свойство и событие изменения.
Собственный элемент выбора, использующий атрибут значения и событие изменения.
Использование v-модели для входного элемента эквивалентно
<input :value="message" @input="message = $event.target.value" />
Реализовать v-модель для пользовательских компонентов
пользовательские компонентыv-model
Используйте значение свойства какvalue
иinput
событие. еслиradio/checkbox
тип, нужно использоватьmodel
Чтобы обойти собственный DOM, используйте свойство checked и событие изменения, как показано ниже.
// 父组件
<template>
<base-checkbox v-model="baseCheck" />
</template>
// 子组件
<template>
<input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)" />
</template>
<script>
export default {
model: {
prop: 'checked',
event: 'change'
},
prop: {
checked: Boolean
}
}
</script>
Как понять однонаправленный поток данных Vue
В официальной документации Vue в меню Prop есть файл с именем单项数据流
подменю.
Мы часто говорим, что двусторонняя привязка Vue на самом деле добавляет элементы к элементам на основе односторонней привязки.input/change
события для динамического изменения представления. Передача данных между компонентами Vue по-прежнему одноэлементная, то есть родительский компонент передается дочернему компоненту. Дочерний компонент может определять значение в зависимых свойствах, но не имеет права изменять данные, передаваемые родительским компонентом, что предотвращает случайное изменение дочерним компонентом состояния родительского компонента, что затрудняет понимание потока данных приложения. .
Если внутри дочернего компонента直接
Измените опору, вы столкнетесь с обработкой предупреждений.
2 определения зависят от значения в реквизитах
- Определите свойства через данные и используйте prop в качестве начального значения.
<script>
export default {
props: ['initialNumber'],
data() {
return {
number: this.initailNumber
}
}
}
</script>
- Используйте вычисляемые вычисляемые свойства для определения зависимых от реквизита значений. Если страница изменит текущее значение, оцените методы get и set.
<script>
export default {
props: ['size'],
computed: {
normalizedSize() {
return this.size.trim().toLowerCase()
}
}
}
</sciprt>
Принципы отзывчивости Vue
Расположение основного исходного кода: vue/src/core/observer/index.js
Отзывчивые принципы3
Шаги: захват данных, сбор зависимостей, распространение обновлений.
Существует два типа данных: объекты и массивы.
объект
перебрать объект с помощьюObject.defineProperty
Перехват данных путем добавления геттеров и сеттеров для каждого свойства. Функция геттера используется для сбора зависимостей при чтении данных и сохранения всех наблюдателей в соответствующем отделении; сеттер уведомляет всех наблюдателей о необходимости обновления после обновления данных.
основной исходный код
function defineReactive(obj, key, val, shallow) {
// 实例化一个 dep, 一个 key 对应一个 dep
const dep = new Dep()
// 获取属性描述符
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 通过递归的方式处理 val 为对象的情况,即处理嵌套对象
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 拦截obj.key,进行依赖收集
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// Dep.target 是当前组件渲染的 watcher
if (Dep.target) {
// 将 dep 添加到 watcher 中
dep.depend()
if (childOb) {
// 嵌套对象依赖收集
childOb.dep.depend()
// 响应式处理 value 值为数组的情况
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 获取旧值
const value = getter ? getter.call(obj) : val
// 判断新旧值是否一致
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
// 如果是新值,用新值替换旧值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 新值做响应式处理
childOb = !shallow && observe(newVal)
// 当响应式数据更新,依赖通知更新
dep.notify()
}
})
}
множество
Используйте метод расширения массива, чтобы переопределить метод массива по умолчанию для исходного свойства, чтобы гарантировать, что при добавлении или удалении данных все наблюдатели будут уведомлены через dep для обновления.
основной исходный код
const arrayProto = Array.prototype
// 基于数组原型对象创建一个新的对象
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
// 分别在 arrayMethods 对象上定义7个方法
def(arrayMethods, method, function mutator (...args) {
// 先执行原生的方法
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 针对新增元素进行响应式处理
if (inserted) ob.observeArray(inserted)
// 数据无论是新增还是删除都进行派发更新
ob.dep.notify()
return result
})
})
Режим наблюдения за рукописным вводом
Шаблон наблюдателя используется, когда между объектами существует отношение «один ко многим». Например: когда объект изменяется, он автоматически уведомляет объекты, которые от него зависят.
let uid = 0
class Dep {
constructor() {
this.id = uid++
// 存储所有的 watcher
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
if(this.subs.length) {
const index = this.subs.indexOf(sub)
if(index > -1) return this.subs.splice(index, 1)
}
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
class Watcher {
constructor(name) {
this.name = name
}
update() {
console.log('更新')
}
}
Рукописная модель публикации-подписки
Подобно шаблону наблюдателя, отличие состоит в том, что издатель и подписчик отделены друг от друга, а промежуточный диспетчерский центр взаимодействует с издателем и подписчиком.
Принцип отзывчивости Vue лично предпочитает модель публикации-подписки. Где Observer — издатель, Watcher — подписчик, а Dep — диспетчерский центр.
Является ли шаблон проектирования принципа привязки данных vue наблюдателем или публикацией-подпиской?, Зная, что есть сопутствующие споры, можете глянуть, если интересно.
class EventEmitter {
constructor() {
this.events = {}
}
on(type, cb) {
if(!this.events[type]) this.events[type] = []
this.events[type].push(cb)
}
emit(type, ...args) {
if(this.events[type]) {
this.events[type].forEach(cb => {
cb(...args)
})
}
}
off(type, cb) {
if(this.events[type]) {
const index = this.events[type].indexOf(cb)
if(index > -1) this.events[type].splice(index, 1)
}
}
}
Что нужно знать о Vue.observable
Vue.observable делает объекты отзывчивыми. Возвращенный объект можно использовать напрямую渲染函数
и计算属性
и инициировать соответствующее обновление, когда происходит изменение. Может также использоваться в качестве минимального межкомпонентного хранилища состояний.
В Vue 2.x передаваемый объект и возвращаемый объект являются одним и тем же объектом.
Vue 3.x не является объектом, исходный объект не является реактивным.
Применимые сценарии: если в проекте не так много взаимодействий между компонентами, не являющимися родительскими и дочерними, вместо этого вы можете использовать Vue.observable.eventBus
иvuex
план.
Использование заключается в следующем
// store.js
import Vue from 'vue'
export const state = Vue.observable({
count: 1
})
export const mutations = {
setCount(count) {
state.count = count
}
}
// vue 文件
<template>
<div>{{ count }}</div>
</template>
<script>
import { state, mutation } from './store.js'
export default {
computed: {
count() {
return state.count
}
}
}
</script>
Основная часть и ответный принцип обработки данных компонента — это одна и та же функция, которая создает экземпляр наблюдения и перехватывает данные.
Почему данные в компоненте являются функцией
Объекты хранятся в стеке с адресами.Функция функции заключается в приватизации свойств, чтобы гарантировать, что когда компонент изменяет свои собственные свойства, это не повлияет на другие повторно используемые компоненты.
Жизненный цикл Vue
жизненный цикл | описывать |
---|---|
beforeCreate | После инициализации экземпляра vue, перед наблюдателем данных и конфигурацией событий. данные, вычисленные, часы, методы недоступны. |
created | Сразу после создания экземпляра vue он может получить доступ к данным, вычислениям, наблюдениям и методам. DOM не смонтирован, $el, $ref недоступны. |
beforeMount | Вызывается перед началом монтирования DOM. |
mounted | Экземпляр vue монтируется в DOM. |
beforeUpdate | Вызывается перед обновлением данных, перед исправлением виртуального DOM. |
updated | Вызывается после обновления данных. |
beforeDestroy | Вызывается перед уничтожением экземпляра. |
destroyed | Вызывается после уничтожения экземпляра. |
Вызов асинхронного запроса может быть выполнен вcreated
,beforeMount
,mounted
Вызывается в течение жизненного цикла, поскольку соответствующие данные были созданы. Лучший вариант вcreated
позвонил в.
Получить DOM вmounted
войти, стать доступным$ref
метод, не сомневайтесь.
Порядок выполнения родительского и дочернего компонентов Vue в жизненном цикле
Загрузить процесс рендеринга
Родитель создается первым, прежде чем может быть дочерний; когда дочерний создан, родитель завершен.
Порядок: родитель beforeCreate -> создан родитель -> родитель beforeMount -> дочерний beforeCreate -> создан дочерний элемент -> дочерний beforeMount -> смонтирован дочерний элемент -> смонтирован родительский
Процесс обновления подкомпонента
- Обновление дочернего компонента влияет на родительский компонент.
Порядок: родительский до обновления -> дочерний перед обновлением -> дочерний обновленный -> родительский обновленный
- Обновления дочерних компонентов не влияют на ситуацию с родительским компонентом.
Порядок: дочерний перед обновлением -> дочерний обновленный
Процесс обновления родительского компонента
- Обновления родительских компонентов влияют на дочерние компоненты.
Порядок: родительский до обновления -> дочерний перед обновлением -> дочерний обновленный -> родительский обновленный
- Обновления родительских компонентов не влияют на дочерние компоненты.
Порядок: родитель до обновления -> родитель обновлен
процесс разрушения
Порядок: родитель перед уничтожением -> дочерний перед уничтожением -> дочерний уничтожен -> родительский уничтожен
Как родительский компонент прослушивает функцию ловушки жизненного цикла дочернего компонента
Оба метода берут смонтированный в качестве примера.
$выпустить реализацию
// 父组件
<template>
<div class="parent">
<Child @mounted="doSomething"/>
</div>
</template>
<script>
export default {
methods: {
doSomething() {
console.log('父组件监听到子组件 mounted 钩子函数')
}
}
}
</script>
//子组件
<template>
<div class="child">
</div>
</template>
<script>
export default {
mounted() {
console.log('触发mounted事件...')
this.$emit("mounted")
}
}
</script>
реализация @хука
// 父组件
<template>
<div class="parent">
<Child @hook:mounted="doSomething"/>
</div>
</template>
<script>
export default {
methods: {
doSomething() {
console.log('父组件监听到子组件 mounted 钩子函数')
}
}
}
</script>
//子组件
<template>
<div class="child">
</div>
</template>
<script>
export default {
mounted() {
console.log('触发mounted事件...')
}
}
</script>
Связь между компонентами Vue
Взаимодействие компонентов родитель-потомок
- реквизит и $emit
- $родитель и $дети
Компонентная коммуникация между поколениями
- $attrs и $listeners
- обеспечить и ввести
Связь между отцом и сыном, братом и компонентами поколений
- EventBus
- Vuex
v-on слушает несколько методов
<button v-on="{mouseenter: onEnter, mouseleave: onLeave}">鼠标进来1</button>`
часто используемые модификаторы
модификатор формы
- ленивый: синхронизировать информацию после потери фокуса
- обрезка: автоматически фильтровать начальные и конечные пробелы
- число: входное значение преобразуется в числовой тип
модификатор события
- стоп: прекратить пузыриться
- предотвратить: предотвратить поведение по умолчанию
- self: срабатывает только на самом связанном элементе
- один раз: срабатывает только один раз
модификатор кнопки мыши
- слева: левая кнопка мыши
- вправо: правая кнопка мыши
- средний: средняя кнопка мыши
Как класс и стиль динамически связаны
класс и стиль могут быть динамически связаны с помощью синтаксиса объекта и синтаксиса массива
Обозначение объекта
<template>
<div :class="{ active: isActive }"></div>
<div :style="{ fontSize: fontSize }">
</template>
<script>
export default {
data() {
return {
isActive: true,
fontSize: 30
}
}
}
</script>
запись массива
<template>
<div :class="[activeClass]"></div>
<div :style="[styleFontSize]">
</template>
<script>
export default {
data() {
return {
activeClass: 'active',
styleFontSize: {
fontSize: '12px'
}
}
}
}
</script>
Разница между v-show и v-if
Общая основа: отображение и скрытие элемента управления.
разница:
- v-show управляет CSS (отображением) элемента, v-if управляет добавлением или удалением самого элемента.
- Когда v-show изменяется с false на true, жизненный цикл компонента не запускается. v-if из false в true приведет к срабатыванию компонента
beforeCreate
,create
,beforeMount
,mounted
Хук, изменение значения с true на false приведет к срабатыванию компонентаbeforeDestory
,destoryed
метод. - v-if имеет более высокую стоимость производительности, чем v-show.
Почему v-if нельзя использовать с v-for
Производительность тратится впустую, и каждый рендеринг должен быть зациклен перед условным суждением, и вместо этого следует рассмотреть возможность использования вычисляемых свойств.
Vue2.xv-for
Сравниватьv-if
более высокий приоритет.
Vue3.xv-if
Сравнивать v-for
более высокий приоритет.
Разница между вычисленными и наблюдаемыми сценариями и сценариями приложений
И вычисляемый, и наблюдательный, по сути, реализуются путем создания экземпляра Watcher.Самое большое отличие состоит в том, что применимые сценарии различны.
computed
Вычисляемые свойства зависят от значений других свойств, и эти значения кэшируются. Только если значение свойства, от которого оно зависит, изменяется, при следующем извлечении значение будет пересчитано.
Он подходит для численных расчетов и когда зависит от других свойств. Поскольку функцию кэширования можно использовать, чтобы избежать необходимости пересчета каждый раз при получении значения.
принцип
- Каждое вычисляемое свойство инициализируется для создания экземпляра ленивого наблюдателя, ленивый означает ленивое выполнение, что означает, что значение инициализации не определено, и оно будет вычисляться и выполняться только при чтении.
- Вызов вычисляемого свойства запускает функцию get. Поскольку исходное значение dirty равно true, выполняется вычисление, возвращается результат, а для dirty устанавливается значение false.
- Если зависимое значение вычисляемого свойства будет обновлено, будет запущен вычисляемый метод обновления.Поскольку lazy имеет значение true, обновленное грязное значение равно true, и кеш не будет извлечен при следующем вызове, а будет пересчитан.
watch
Наблюдайте за свойствами и отслеживайте изменения значений свойств. Всякий раз, когда значение свойства изменяется, выполняется соответствующий обратный вызов.
Он подходит для выполнения асинхронных или дорогостоящих операций при изменении данных.
принцип
- Каждый атрибут наблюдения создает наблюдателя. Если для параметра «немедленный» установлено значение «истина», обратный вызов будет выполнен немедленно, и результат будет возвращен.
- В процессе создания будет получено значение отслеживаемого свойства, что приведет к запуску метода получения отслеживаемого свойства и добавлению наблюдателя к экземпляру Dep отслеживаемого свойства.
- Когда свойство мониторинга изменяется, наблюдатель уведомляется об обновлении. Если синхронизация наблюдателя имеет значение true, обратный вызов, сохраненный наблюдателем, будет выполнен немедленно, в противном случае соответствующий метод обновления будет помещен в очередь событий и выполнен в следующем цикле событий. .
- Если deep имеет значение true, свойство является рекурсивным, а в остальном процесс аналогичен описанному выше.
слот слот
слот-слот, который можно понимать какslot
Выгружаемая позиция в шаблоне компонента. При повторном использовании компонента, когда используется соответствующий тег слота, содержимое в теге автоматически заменит положение соответствующего тега слота в шаблоне компонента в качестве выхода для переноса распространяемого контента.
Основная функция заключается в повторном использовании и расширении компонентов, а также в некоторой обработке настроенных компонентов.
Есть 3 основных слота
- слот по умолчанию
// 子组件
<template>
<slot>
<div>默认插槽备选内容</div>
</slot>
</template>
// 父组件
<template>
<Child>
<div>替换默认插槽内容</div>
</Child>
</template>
- именованный слот
тег слота нетname
свойство, слот по умолчанию. имеютname
свойство, именованный слот
// 子组件
<template>
<slot>默认插槽的位置</slot>
<slot name="content">插槽content内容</slot>
</template>
// 父组件
<template>
<Child>
<template v-slot:default>
默认...
</template>
<template v-slot:content>
内容...
</template>
</Child>
</template>
- слот с прицелом
Свойства, привязанные к области действия дочернего компонента, используются для передачи информации о компоненте родительскому компоненту, эти свойства будут навешиваться на объект, принятый родительским компонентом.
// 子组件
<template>
<slot name="footer" childProps="子组件">
作用域插槽内容
</slot>
</template>
// 父组件
<template>
<Child v-slot="slotProps">
{{ slotProps.childProps }}
</Child>
</template>
Разница между Vue.$delete и delete
Vue.$delete удаляет элемент напрямую и изменяет длину массива; delete преобразует удаленный элемент во внутреннийundefined
, а ключевые значения других элементов остаются неизменными.
Как Vue.$set решает проблему, из-за которой новые добавленные свойства объекта не могут отвечать
Vue.$set появляется из-заObject.defineProperty
Ограничение: невозможно обнаружить добавление или удаление свойств объекта.
Расположение исходного кода: vue/src/core/observer/index.js
export function set(target, key, val) {
// 数组
if(Array.isArray(target) && isValidArrayIndex(key)) {
// 修改数组长度,避免索引大于数组长度导致splice错误
target.length = Math.max(target.length, key)
// 利用数组splice触发响应
target.splice(key, 1, val)
return val
}
// key 已经存在,直接修改属性值
if(key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = target.__ob__
// target 不是响应式数据,直接赋值
if(!ob) {
target[key] = val
return val
}
// 响应式处理属性
defineReactive(ob.value, key, val)
// 派发更新
ob.dep.notify()
return val
}
Принцип реализации:
- Если это массив, используйте метод соединения массива напрямую, чтобы вызвать реактивный ответ.
- Если это объект, определите, существует ли свойство и является ли объект отзывчивым.
- Ничего из вышеперечисленного не выполняется, и, наконец, свойства обрабатываются в ответ через defineReactive.
Механизм асинхронного обновления Vue
Ядро механизма асинхронного обновления Vue реализовано с использованием очереди асинхронных задач браузера.
Когда ответные данные будут обновлены, будет запущен dep.notify, чтобы уведомить всех наблюдателей о выполнении метода обновления.
уведомить метод класса dep
notify() {
// 获取所有的 watcher
const subs = this.subs.slice()
// 遍历 dep 中存储的 watcher,执行 watcher.update
for(let i = 0; i < subs.length; i++) {
subs[i].update()
}
}
watcher.update ставит себя в глобальную очередь наблюдателя и ожидает выполнения.
метод обновления класса наблюдателя
update() {
if(this.lazy) {
// 懒执行走当前 if 分支,如 computed
// 这里的 标识 主要用于 computed 缓存复用逻辑
this.dirty = true
} else if(this.sync) {
// 同步执行,在 watch 选项参数传 sync 时,走当前分支
// 若为 true ,直接执行 watcher.run(),不塞入异步更新队列
this.run()
} else {
// 正常更新走当前 else 分支
queueWatcher(this)
}
}
queueWatcher, откройте для себя знакомый метод nextTick. Когда вы видите это, вы можете сначала перейти к принципу nextTick, а затем вернуться после того, как поймете его. 😊
function queueWatcher(watcher) {
const id = watcher.id
// 根据 watcher id 判断是否在队列中,若在队列中,不重复入队
if (has[id] == null) {
has[id] = true
// 全局 queue 队列未处于刷新状态,watcher 可入队
if (!flushing) {
queue.push(watcher)
// 全局 queue 队列处于刷新状态
// 在单调递增序列寻找当前 id 的位置并进行插入操作
} else {
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
if (!waiting) {
waiting = true
// 同步执行逻辑
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
// 将回调函数 flushSchedulerQueue 放入 callbacks 数组
nextTick(flushSchedulerQueue)
}
}
}
Функция nextTick фактически выполняет функцию flushCallbacks в конце, а функция flushCallbacks предназначена для запуска функции обратного вызова flushSchedulerQueue и обратного вызова, переданного путем вызова функции nextTick в проекте.
Что вы сделали, когда переместили исходный код flushSchedulerQueue
/**
* 更新 flushing 为 true,表示正在刷新队列,在此期间加入的 watcher 必须有序插入队列,保证单调递增
* 按照队列的 watcher.id 从小到大排序,保证先创建的先执行
* 遍历 watcher 队列,按序执行 watcher.before 和 watcher.run,最后清除缓存的 watcher
*/
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
// 标识正在刷新队列
flushing = true
let watcher, id
queue.sort((a, b) => a.id - b.id)
// 未缓存长度是因为可能在执行 watcher 时加入 watcher
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
// 清除缓存的 watcher
has[id] = null
// 触发更新函数,如 updateComponent 或 执行用户的 watch 回调
watcher.run()
}
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
// 执行 waiting = flushing = false,标识刷新队列结束,可以向浏览器的任务队列加入下一个 flushCallbacks
resetSchedulerState()
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
Посмотрите, что делает watcher.run, сначала вызовите функцию get, давайте посмотрим.
/**
* 执行实例化 watcher 传递的第二个参数,如 updateComponent
* 更新旧值为新值
* 执行实例化 watcher 时传递的第三个参数,用户传递的 watcher 回调
*/
run () {
if (this.active) {
// 调用 get
const value = this.get()
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
// 更新旧值为新值
const oldValue = this.value
this.value = value
// 若是项目传入的 watcher,则执行实例化传递的回调函数。
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* 执行 this.getter,并重新收集依赖。
* 重新收集依赖是因为触发更新 setter 中只做了响应式观测,但没有收集依赖的操作。
* 所以,在更新页面时,会重新执行一次 render 函数,执行期间会触发读取操作,这时进行依赖收集。
*/
get () {
// Dep.target = this
pushTarget(this)
let value
const vm = this.vm
try {
// 执行回调函数,如 updateComponent,进入 patch 阶段
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// watch 参数为 deep 的情况
if (this.deep) {
traverse(value)
}
// 关闭 Dep.target 置空
popTarget()
this.cleanupDeps()
}
return value
}
Принцип Vue.$nextTick
nextTick: выполняет отложенный обратный вызов после завершения следующего цикла обновления DOM. Часто используется для получения обновленного DOM после изменения данных.
Расположение исходного кода: vue/src/core/util/next-tick.js
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// 是否使用微任务标识
export let isUsingMicroTask = false
// 回调函数队列
const callbacks = []
// 异步锁
let pending = false
function flushCallbacks () {
// 表示下一个 flushCallbacks 可以进入浏览器的任务队列了
pending = false
// 防止 nextTick 中包含 nextTick时出现问题,在执行回调函数队列前,提前复制备份,清空回调函数队列
const copies = callbacks.slice(0)
// 清空 callbacks 数组
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
// 浏览器能力检测
// 使用宏任务或微任务的目的是宏任务和微任务必在同步代码结束之后执行,这时能保证是最终渲染好的DOM。
// 宏任务耗费时间是大于微任务,在浏览器支持的情况下,优先使用微任务。
// 宏任务中效率也有差距,最低的就是 setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 将 nextTick 的回调函数用 try catch 包裹一层,用于异常捕获
// 将包裹后的函数放到 callback 中
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// pengding 为 false, 执行 timerFunc
if (!pending) {
// 关上锁
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
Суммировать:
- Используйте концепцию асинхронных блокировок, чтобы гарантировать, что в очереди задач одновременно есть только один flushCallbacks. Когда pengding равно false, это означает, что в очереди задач браузера нет функции flushCallbacks; когда pengding равно true, это означает, что flushCallbacks были помещены в очередь задач браузера; когда должна выполняться функция flushCallback, будет установлено pengding снова в false, указывая, что следующий flushCallbacks может войти в очередь задач.
- Для определения возможностей среды выберите наиболее эффективную (макрозадачу/микрозадачу) для упаковки и выполнения и убедитесь, что такие операции, как изменение модели DOM, выполняются после выполнения кода синхронизации.
- flushCallbacks сначала копируется, а затем очищается, чтобы предотвратить вложенность nextTick и цикл не заканчивается.
Реализовать виртуальный DOM
Появление виртуального DOM решило проблемы с производительностью браузера. Виртуальный DOM — это объект структуры DOM (Vnode), смоделированный JS, который используется для сравнения новых и старых Vnodes, обновления и получения последних Vnodes и, наконец, одновременного сопоставления их с реальным DOM. Причина этого в том, что манипулирование объектами JS в памяти намного быстрее, чем манипулирование DOM.
Возьмите настоящий ДОМ 🌰
<div id="container">
<p>real dom </p>
<ul>
<li class="item">item 1</li>
<li class="item">item 2</li>
<li class="item">item 3</li>
</ul>
</div
Используйте JS для имитации узлов DOM для создания виртуального DOM.
function Element(tagName, props, children) {
this.tageName = tagName
this.props = props || {}
this.children = children || []
this.key = props.key
let count = 0
this.children.forEach(child => {
if(child instanceof Element) count += child.count
count++
})
this.count = count
}
const tree = Element('div', { id: container }, [
Element('p', {}, ['real dom'])
Element('ul', {}, [
Element('li', { class: 'item' }, ['item1']),
Element('li', { class: 'item' }, ['item2']),
Element('li', { class: 'item' }, ['item3'])
])
])
Виртуальный DOM в реальный узел
Element.prototype.render = function() {
let el = document.createElement(this.tagName)
let props = this.props
for(let key in props) {
el.setAttribute(key, props[key])
}
let children = this.children || []
children.forEach(child => {
let child = (child instanceof Element) ? child.render() : document.createTextNode(child)
el.appendChild(child)
})
return el
}
Принцип Diff в Vue
Исходный код ядра: vue/src/core/vdom/patch.js
Переместите и сравните новую и старую запись функции исправления узла
/**
* 新节点不存在,老节点存在,调用 destroy,销毁老节点
* 如果 oldVnode 是真实元素,则表示首次渲染,创建新节点,并插入 body,然后移除来节点
* 如果 oldVnode 不是真实元素,则表示更新阶段,执行patchVnode
*/
function patch(oldVnode, vnode) {
// 新的 Vnode 不存在,老的 Vnode 存在,销毁老节点
if(isUndef(vnode)) {
if(isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
// 新的 Vnode 存在,老的 Vnode 不存在
// <div id="app"><comp></comp></div>
// 这里的 com 组件初次渲染就走当前的 if 逻辑
if(isUndef(oldVnode)) {
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
// 新老节点相同,更精细化对比
if(!isRealElement && sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode)
} else {
// 是真实元素,渲染根组件
if(isRealElement) {
// 挂载到真实元素以及处理服务端渲染情况
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// 基于真实节点创建一个 vnode
oldVnode = emptyNodeAt(oldVnode)
}
// 获取老节点的真实元素
const oldElm = oldVnode.elm
// 获取老节点的父元素,即 body
const parentElm = nodeOps.parentNode(oldElm)
// 基于新的 vnode 创建整颗 DOM 树并插入到 body 元素下
creatElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// 递归更新父占位符节点元素
if(isDef(vnode.parent)) {
...
}
// 移除老节点
if(isDef(parentEle)) {
...
} else if(isDef(oldVnode.tag)) {
...
}
}
}
}
Переместите часть исходного кода patchVnode.
/**
* 更新节点
* 如果新老节点都有孩子,则递归执行 updateChildren
* 如果新节点有孩子,老节点没孩子,则新增新节点的这些孩子节点
* 如果老节点有孩子,新节点没孩子,则删除老节点这些孩子
* 更新文本节点
*/
function patchVnode(oldVnode, vnode) {
// 如果新老节点相同,直接返回
if(oldVnode === vnode) return
// 获取新老节点的孩子节点
const oldCh = oldVnode.children
const ch = vnode.children
// 新节点不是文本节点
if(isUndef(vnode.text)) {
// 新老节点都有孩子,则递归执行 updateChildren
if(isDef(oldCh) && isDef(ch) && oldCh !== ch) { // oldVnode 与 vnode 的 children 不一致,更新children
updateChildren(oldCh,ch)
// 如果新节点有孩子,老节点没孩子,则新增新节点的这些孩子节点
} else if(isDef(ch)) {
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
// 如果老节点有孩子,新节点没孩子,则删除老节点这些孩子
} else if(isDef(oldCh)) {
removeVnodes(oldCh, 0, oldCh.length - 1)
// 老节点文本存在,新的节点不存在文本,清空文本
} else if(isDef(oldVnode.text)){
nodeOps.setTextContent(elm, '')
}
// 新老文本节点都是文本节点,且文本发生改变,则更新文本节点
} else if(oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
}
Переместите исходный код updateChildren.
function updateChildren(oldCh, ch) {
// const oldCh = [n1, n2, n3, n4]
// const ch = [n1, n2, n3, n4, n5]
// 旧节点起始索引
let oldStartIdx = 0
// 新节点起始索引
let newStartIdx = 0
// 旧节点结束索引
let oldEndIdx = oldCh.length - 1
// 新节点结束索引
let newEndIdx = newCh.length - 1
while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
const newStartVnode = ch[newStartIdx]
const oldStartVnode = oldCh[oldStartIdx]
const newEndVnode = ch[newEndIdx]
const oldEndVnode = oldCh[oldEndIdx]
// 如果节点被移动,在当前索引上可能不存在,检测这种情况,如果节点不存在则调整索引
if(isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]
} else if(isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
// 新开始和老开始节点是同一个节点
} else if(sameVnode(oldStartNode, newStartNode)) {
patchVnode(oldStartNode , newStartNode)
oldStartIdx++
newStartIdx++
// 新开始节点和老结束节点是同一节点
} else if(sameVnode(oldEndNode, newEndNode)) {
patchVnode(oldEndNode, newEndNode)
oldEndIdx--
newEndIdx--
// 老开始和新结束是同一节点
} else if(sameVnode(oldStartNode, newEndNode)) {
patchVnode(oldStartNode, newEndNode)
oldStartIdx++
newEndIdx--
// 老结束和新开始是同一节点
} else if(sameVnode(oldEndNode, newStartNode)) {
patchVnode(oldEndNode, newStartNode)
oldEndIdx--
newStartIdx++
} else {
// 上面假设都不成立,则通过遍历找到新开始节点和老节点中的索引位置
// 创建老节点每个节点 key 和 索引的关系 { key: idx }
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
// 寻找新开始节点在老节点的索引位置
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
// 没有找到,则说明是新创建的元素,执行创建
if (isUndef(idxInOld)) {
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
// 在关系映射表中找到新开始节点
vnodeToMove = oldCh[idxInOld]
// 如果是同一个节点,则执行patch
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
// patch 结束后将老节点置为 undefined
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// 最后这种情况是,找到节点,但发现两个节点不是同一个节点,则视为新元素,执行创建
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
// 新节点向后移动一位
newStartVnode = newCh[++newStartIdx]
}
if(newStartIdx < newEndIdx) {} // 旧节点先遍历结束,将剩余的新节点添加到DOM中
if(oldStartIdx < oldEndIdx) {} // 新节点先遍历结束,将剩余的旧节点删掉
}
}
Роль ключей в Vue
Ключ — это единственная метка vnode в Vue, и ключ используется в том же самом Vnode и updateChildren в нашем алгоритме сравнения.
sameVnode используется, чтобы определить, является ли это одним и тем же узлом. Обычный бизнес-сценарий — это список.Если значением ключа является индекс списка, возникнет проблема повторного использования на месте в случае добавления или удаления. (Короче говоря, состояние последнего элемента в текущей позиции используется повторно). Таким образом, уникальное значение ключа обеспечивает более точное сравнение.
Когда четыре предположения в updateChildren не совпадают, вам нужно полагаться на ключ и индекс старого узла, чтобы создать таблицу сопоставления отношений, а затем использовать ключ нового узла, чтобы перейти к таблице сопоставления отношений, чтобы найти индекс для обновления, что гарантирует, что алгоритм diff работает быстрее.
Что такое динамические компоненты Vue
динамические компоненты черезis
Реализация функции. Он подходит для сценариев, которые рендерятся динамически на основе данных, то есть тип компонента не определен.
Возьмем пример страницы сведений о новостях, как показано на рисунке ниже.
Но порядок компонентов на странице сведений каждой новости может быть разным, поэтому нам приходится динамически отображать компоненты с помощью данных, а не жестко прописывать порядок каждого компонента.
<template>
<div v-for="val in componentsData" :key="val.id">
<component :is="val.type">
</div>
</template>
<script>
import CustomTitle from './CustomTitle'
import CustomText from './CustomText'
import CustomImage from './CustomImage'
export default {
data() {
return {
componentsData: [{
id: 1,
type: 'CustomTitle'
},{
id: 2,
type: 'CustomText'
},{
id: 3
type: 'CustomImage'
}]
}
}
}
</script>
Была ли написана Vue.directive и каковы сценарии применения?
Vue.directive может регистрировать глобальные директивы и локальные директивы.
Функция определения инструкций предоставляет следующие функции ловушек
- bind: вызывается, когда директива впервые привязывается к элементу (вызывается только один раз)
- вставленный: используется, когда связанный элемент вставляется в родительский узел (родительский узел может быть вызван, когда родительский узел существует)
- update: вызывается при обновлении шаблона, в котором находится связанный элемент, независимо от того, изменяется ли значение привязки. Путем сравнения значений привязки до и после обновления.
- componentUpdated: вызывается, когда шаблон, содержащий связанный элемент, завершает цикл обновления.
- unbind: вызывается только один раз, когда инструкция отсоединяется от элемента.
В моем проекте копирование в один клик и управление разрешениями можно контролировать с помощью инструкций, цель которых — упростить нашу рабочую нагрузку.
Рекомендовать одинПоделитесь 8 очень полезными пользовательскими инструкциями Vue. 👍
Вы понимаете фильтры Vue?
Фильтры Vue можно использовать в двух случаях: интерполяция двойными фигурными скобками и выражения v-bind.
Эта функция устарела в Vue3.
Фильтры делятся на глобальные фильтры и локальные фильтры.
локальный фильтр
<template>
<div>{{ message | formatMessage }}</div>
</template>
<script>
export default {
filters: {
formatMessage: function(value) {
// 可基于源值做一些处理
return value
}
}
}
</script>
глобальный фильтр
Vue.filter('formatMessage', function(value) {
// 可基于源值做一些处理
return value
})
Фильтры могут быть каскадированы и выполняться слева направо, при этом входное значение второго фильтра является выходным значением первого фильтра.
<div>{{ message | formatMessage1 | formatMessage2 }}</div>
Каковы сценарии применения для понимания миксина
Определение: примеси обеспечивают очень гибкий способ распространения повторно используемой функциональности в компонентах Vue.
Миксины делятся на глобальные миксины и локальные миксины, суть которых заключается в объектах JS, таких как данные, компоненты, вычисляемые, методы и т.д.
Глобальные примеси не рекомендуются и повлияют на создание каждого последующего экземпляра Vue. Частичное смешение позволяет извлечь один и тот же код из компонентов для логического повторного использования.
Применимые сценарии: если несколько страниц имеют相同
Плавающее позиционирование плавающего окна, вы можете попробовать инкапсулировать его с помощью миксина.
// customFloatDialog.js
export const customFloatDialog = {
data() {
return {
visible: false
}
},
methods: {
toggleShow() {
this.visible = !this.visible
}
}
}
</script>
//需要引入的组件
<template>
<div></div>
</template>
<script>
import { customFloatDialog } from './customFloatDialog.js'
export default {
mixins: [customFloatDialog],
}
</script>
Представляем поддержку активности
keep-alive — это встроенный компонент Vue, который может кэшировать состояние компонента, чтобы избежать повторного рендеринга и повысить производительность.
Встроенный компонент keep-alive имеет 3 свойства.
- include: Строка или регулярное выражение, компоненты с совпадающими именами будут кэшироваться.
- exclude: Строка или регулярное выражение, компоненты с совпадающими именами не будут кэшироваться.
- max: пороговое значение количества компонентов кэша.
Установка компонентов keep-alive добавит два лайфхука (активированы/деактивированы).
Первый вход в компонент: beforeCreate -> создано -> beforeMount -> смонтировано ->activated
Запускается при выходе из компонентаdeactivated
, так как кеш компонента не уничтожается, хуки жизни beforeDestroy и destroy не сработают. После повторного входа в компонент начните прямо с активированного спасательного крюка.
Типичные бизнес-сценарии: перейдите на страницу сведений на второй странице страницы списка, вернитесь на страницу сведений и по-прежнему оставайтесь на второй странице без повторного рендеринга. Но вход на страницу списка с других страниц по-прежнему требует повторного рендеринга.
Идея: vuex использует массив для хранения имени страницы списка, а страница списка оставляет хук beforeRouteLeave, чтобы определить, нужно ли ее кэшировать и менять глобальный массив.
Используйте следующее в позиции метки просмотра маршрутизатора
<template>
<keep-alive :include="cacheRouting">
<router-view></router-view>
</keep-alive>
</template>
<script>
export default {
computed: {
cacheRouting() {
return this.$store.state.cacheRouting
}
}
}
</script>
Страница списка используется следующим образом
<template>
<div></div>
</template>
<script>
export default {
beforeRouteLeave(to, from, next) {
if(to.name === '详情页') {
// ... 向全局缓存路由数组添加列表页
next()
} else {
// ... 向全局缓存路由数组删除列表页
next()
}
}
}
</script>
Реализация поддержки активности
Основной исходный код: vue/src/core/components/keep-alive.js
Основная идея стратегии замены LRU (наименее недавно использованная) заключается в замене наименее использовавшегося.
/**
* 遍历 cache 将不需要的缓存的从 cache 中清除
*/
function pruneCache (keepAliveInstance, filter) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode = cache[key]
if (cachedNode) {
const name = getComponentName(cachedNode.componentOptions)
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
/**
* 删除 cache 中键值为 key 的虚拟DOM
*/
function pruneCacheEntry (cache, key, keys, current) {
const entry = cache[key]
if (entry && (!current || entry.tag !== current.tag)) {
// 执行组件的 destroy 钩子
entry.componentInstance.$destroy()
}
// cache 中组件对应的虚拟DOM置null
cache[key] = null
// 删除缓存虚拟DOM的 key
remove(keys, key)
}
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
// 缓存虚拟 DOM
this.cache = Object.create(null)
// 缓存虚拟DOM的键集合
this.keys = []
},
destroyed () {
// 删除所有的缓存内容
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
// 监听 include、exclude 参数变化,调用 pruneCache修改缓存中的缓存数据
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
// 由 render 函数决定渲染结果
render () {
const slot = this.$slots.default
// 获取第一个子组件虚拟DOM
const vnode: VNode = getFirstComponentChild(slot)
// 获取虚拟 DOM 的配置参数
const componentOptions: ? VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// 获取组件名称
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
// 若不在include或者在exclude中,直接退出,不走缓存机制
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
// 获取组件key
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
// 命中缓存
if (cache[key]) {
// 从 cache 中获取缓存的实例设置到当前的组件上
vnode.componentInstance = cache[key].componentInstance
// 删除原有存在的key,并置于最后
remove(keys, key)
keys.push(key)
// 未命中缓存
} else {
// 缓存当前虚拟节点
cache[key] = vnode
// 添加当前组件key
keys.push(key)
// 若缓存组件超过max值,LRU 替换
if(this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 设置当前组件 keep-alive 为 true
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
Vue-Router настроить страницу 404
* Представляет подстановочный знак. Если он размещен перед любым маршрутом, он будет сопоставлен первым, что приведет к переходу на страницу 404, поэтому следующую конфигурацию необходимо разместить в конце.
{
path: '*',
name: '404'
component: () => import('./404.vue')
}
Какие виды навигационных охранников есть у Vue-Router?
Глобальная передняя защита
Запускается перед переходом по маршруту, вы можете выполнить некоторую логику аутентификации перед выполнением следующего метода.
const router = new VueRouter({})
router.beforeEach((to, from, next) => {
...
// 必须执行 next 方法来触发路由跳转
next()
})
глобальная защита синтаксического анализа
Подобно beforeEach, он также срабатывает перед скачком маршрута, разница в том, что его все еще нужно所有组件内守卫和异步路由组件被解析之后
, который вызывается после beforeRouteEnter внутри компонента.
const router = new VueRouter({})
router.beforeResolve((to, from, next) => {
...
// 必须执行 next 方法来触发路由跳转
next()
})
глобальный почтовый хук
В отличие от охранников, эти крюки не принимаютnext
Функция также не меняет саму навигацию.
router.afterEach((to, from) => {
// ...
})
- Эксклюзивная защита маршрутизации
BeforeEnter можно определить непосредственно в конфигурации маршрутизации.
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
beforeEnter: (to, from, next) => {
}
}
]
})
Ограждения внутри компонентов
Следующие средства навигации маршрутизации могут быть определены непосредственно в компоненте.
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 不能获取组件实例 this
// 当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 当前路由改变,但是组件被复用时调用
// 可访问实例 this
},
beforeRouteLeave(to, from, next) {
// 导航离开组件时被调用
}
}
Vue-Router завершает процесс парсинга навигации
- Навигация срабатывает
- Вызовите охрану beforeRouteLeave в деактивированном компоненте.
- Вызовите глобальный перед каждым передним охранником
- Повторно используемый компонент вызывает защиту beforeRouteUpdate (2.2+).
- Маршрутизация вызовов конфигурации перед вводом
- Анализ компонентов асинхронной маршрутизации
- Вызвать охрану beforeRouteEnter в активированном компоненте
- Вызвать глобальную защиту beforeResolve (2.5+)
- Навигация подтверждена
- вызвать глобальный afterEach
- Запустить обновление DOM
- Вызовите функцию обратного вызова, переданную next в стороже beforeRouteEnter, и созданный экземпляр компонента будет передан в качестве параметра функции обратного вызова.
Сколько режимов имеет маршрут Vue-Router? Скажите разницу между ними?
Vue-Router имеет 3 режима маршрутизации: хэш, история, абстракция.
хэш-режим
Vue-Router по умолчанию использует хеш-режим, основанный на браузере.onhashchange
событие, когда адрес меняется, черезwindow.location.hash
Получите хэш-значение адреса; сопоставьте содержимое компонента, соответствующее объекту маршрута, в соответствии с хеш-значением.
Функции
- Хэш-значение хранится в URL-адресе, содержащем
#
, изменение значения хеша не приведет к перезагрузке страницы. - изменение хэша вызовет
onhashchange
События, которые могут быть записаны браузером, чтобы реализовать движение вперед и назад браузера. - Хэш-параметры основаны на URL-адресе, и при передаче сложных параметров существуют ограничения по объему.
- Хорошая совместимость, поддерживает браузеры более ранних версий и браузеры IE.
Код случая, сервис должен быть включен локально(http-server)
Доступ, был протестирован, и может быть непосредственно получен опыт.
Принцип реализации
<div class="main">
<a href="#/home">home</a>
<a href="#/detail">detail</a>
<div id="content"><span>暂无内容</span></div>
</div>
<script>
const routers = [{
path: '/',
component: `<span>暂无内容</span>`
},
{
path: '/home',
component: `<span>我是Home页面</span>`
}, {
path: '/detail',
component: `<span>我是Detail页面</span>`
}]
function Router(routers) {
console.log('执行')
this.routers = {}
// 初始化生成 routers
routers.forEach((router) => {
this.routers[router.path] = () => {
document.getElementById("content").innerHTML = router.component;
}
})
this.updateView = function(e) {
let hash = window.location.hash.slice(1) || '/'
console.log('hash更新', hash, this.routers[hash])
this.routers[hash] && this.routers[hash]()
}
// 路由加载触发视图更新
window.addEventListener('load', this.updateView.bind(this))
// 路由改变触发视图更新
window.addEventListener('hashchange', this.updateView.bind(this))
}
// 实例化 hash 模式的 Router
let router = new Router(routers)
</scrip
режим истории
На основе недавно добавленных pushState и replaceState в HTML5 историей браузера можно манипулировать без обновления. Первый — это новый исторический рекорд, а второй — прямая замена исторического рекорда.
Функции
- URL не несет
#
, используйте pushState и replaceState для выполнения перехода по URL-адресу без перезагрузки страницы. - Изменения URL-адреса вызывают HTTP-запросы. Поэтому ресурс-кандидат, который охватывает все случаи, необходимо добавить на стороне сервера: если URL-адрес не соответствует ни одному из статических ресурсов, должен быть возвращен тот же самый.
index.html
. Эта страница является страницей, от которой зависит приложение.
// nginx 服务端配置
location / {
try_files $uri $uri/ /index.html;
}
- Совместимость IE10+
Принцип реализации
<div class="main">
<a href="javascript:;" path="/home">home</a>
<a href="javascript:;" path="/detail">detail</a>
<div id="content"><span>暂无内容</span></div>
</div>
<script>
const routers = [{
path: '/home',
component: `<span>我是Home页面</span>`
}, {
path: '/detail',
component: `<span>我是Detail页面</span>`
}, {
path: '/',
component: '<span>暂无内容</span>'
}]
function Router(routers) {
this.routers = {}
// 初始化生成 routers
routers.forEach((router) => {
this.routers[router.path] = () => {
document.getElementById("content").innerHTML = router.component;
}
})
const links = [...document.getElementsByTagName('a')]
links.forEach(link => {
link.addEventListener('click', () => {
window.history.pushState({}, null, link.getAttribute('path'))
this.updateView()
})
})
this.updateView = function() {
let url = window.location.pathname || '/'
this.routers[url] && this.routers[url]()
}
// 路由加载触发视图更新
window.addEventListener('load', this.updateView.bind(this))
// 路由改变触发视图更新
window.addEventListener('popstate', this.updateView.bind(this))
}
// 实例化 history 模式的 Router
const router = new Router(routers)
</script>
абстрактный режим
Поддерживаются все режимы работы JS.Vue-Router сам проверит среду.Если обнаружит, что API браузера отсутствует, маршрут будет автоматически переведен в абстрактный режим. Абстрактный режим также используется в собственной среде мобильного терминала.
Метод параметра маршрутизации Vue
Маршрутизация Vue имеет три способа передачи параметров
- Вариант первый
// 路由配置
{
path: '/detail/:id',
name: 'Detail',
component: () => import('./Detail.vue')
}
// 路由跳转
let id = 1
this.$router.push({ path: '/detail/${id}'})
// 获取参数
this.$route.params.id
- Вариант 2
Вариант 2, хотя URL-адрес не отображает наши параметры, есть возможность получить параметры из подкомпонентов. Конечно, есть и проблема: будет параметр потери обновления.
Если вы хотите не потерять его, вам нужно настроить тот же маршрут, что и в схеме 1. Причина в том, что второй способ передачи параметров реализован в функции push предыдущей страницы, и нет действия push для обновления.
// 路由配置
{
path: '/detail',
name: 'Detail',
component: () => import('./Detail.vue')
}
// 路由跳转
let id = 1
this.$router.push({ name: 'Detail', params: { id: id } })
// 获取参数
this.$route.params.id
- третье решение
// 路由配置
{
path: '/detail',
name: 'Detail',
component: () => import('./Detail.vue')
}
// 路由跳转
let id = 1
this.$router.push({ name: 'Detail', query: { id: id } })
// 获取参数
this.$route.query.id
Понимание и использование Vuex
Vuex — это модель управления состоянием, специально разработанная для приложений Vue.js, которая использует централизованное хранилище для управления состоянием всех компонентов приложения.
В основном решить следующие две проблемы
- Несколько представлений зависят от одного и того же состояния.
- Действия из разных представлений требуют изменения одного и того же состояния.
Включает в себя следующие модули, перейти на карту официального сайта
Состояние: определите и инициализируйте глобальное состояние.
Getter: полагаясь на состояние в State, вторичная упаковка не повлияет на исходные данные State.
Мутация: функция, которая изменяет состояние состояния, она должна быть синхронной.
Действие: используется для отправки мутации, может содержать любую асинхронную операцию.
Модуль: Если приложение сложное, Магазин сосредоточится на относительно большом объекте и станет раздутым Модуль позволяет нам управлять Магазином по модульному принципу.
Конечно, если приложение относительно простое, а общее состояние относительно небольшое, Vue.observe можно использовать для замены Vuex, и это хорошо для сохранения библиотеки.
Что делать, если данные потеряны после обновления Vuex
Постоянный кеш: localStorage, sessionStorage
Как Vuex узнает, изменено ли состояние путем мутации или извне?
Единственный способ изменить состояние в Vuex — выполнить метод фиксации.Нижний уровень может изменить состояние, выполнив this._withCommit(fn) и установив для идентификатора _committing значение true.После модификации идентификатор необходимо установить на ложный. Невозможно установить бит флага для внешней модификации, поэтому наблюдайте за изменением состояния, чтобы судить о достоверности модификации.
Вы понимаете Vue SSR?
В проекте Vue SSR пока не используется, в будущем демо напишу отдельно. Вот другие ответы.
Рендеринг SSR на стороне сервера, после завершения работы по рендерингу HTML на стороне сервера, HTML возвращается на сторону браузера.
Плюсы: SSR лучше SEO и быстрее загружается в верхней части страницы.
Недостаток: большая нагрузка на сервер.
Если это внутренняя система, SSR на самом деле не нужен. Если это внешний проект, трудно поддерживать высокодоступный сервер узла.
В чем разница между Vue2 и Vue3? Каковы точки оптимизации Vue3?
Самостоятельно произведено и продано:[Непрерывное обновление] В чем «отличия» Vue3 от Vue2?
Оптимизация производительности Vue
- Не отвечающие данные замораживают данные через Object.freeze
- Не вкладывать слишком глубоко
- Разница между вычислениями и часами
- Разница между v-if и v-show
- Избегайте использования v-for с v-if, а значение ключа привязки должно быть уникальным.
- Слишком много данных списка с использованием пейджинга или виртуального списка
- Очистить таймеры и события после уничтожения компонента
- Ленивая загрузка изображения
- Отложенная загрузка маршрута
- Защита от сотрясений, дросселирование
- Импорт внешних библиотек по запросу
- использование кэша поддержки активности
- Рендеринг на стороне сервера и предварительный рендеринг
Суммировать
Статья объемом 10 000 слов подводит итоги. Если вы считаете ее полезной, ставьте лайки, подписывайтесь на нее и добавляйте в избранное. 😘
Ссылаться на
Передовые знания 3+1
Банк вопросов для фронтенд-интервью
Гигантам, у которых есть очень четкая интерпретация исходного кода VUE, рекомендуется:Ли Юннин