Подробно поговорим о vue — переходной группе

Vue.js
Подробно поговорим о vue — переходной группе

Эта статья является четвертой статьей в серии vue, но само собой разумеется, что эта статья является первой статьей."Рассказываем о vue - переходе в деталях"в отдельную большую главу. Однако последняя статья получилась слишком длинной, поэтому пришлось написать ее отдельно. Те, кто заинтересован в предыдущих статьях этой серии, могут щелкнуть ссылку ниже, чтобы отправить

Книга является продолжением предыдущей статьи В предыдущей статье мы в основном представили<transition>пара компонентовpropsа такжеvnode hooksиз输入 => 输出Дизайн процесса, нацеленный на один элементenterтак же какleaveЭффект перехода инкапсулирован в сцену, так что нам нужно только обратить внимание наcssа такжеjsДостаточно бизнес-реализации функции ловушки.

Однако в нашей реальной разработке неизбежно, что многие элементы должны отображаться с использованием эффектов перехода.<transition>Компоненты не удовлетворяют потребности моего бизнеса. В настоящее время,vueупакованный внутри<transition-group>Такие встроенные компоненты для удовлетворения наших потребностей, это помогает нам добиться хорошего списка эффектов переходных эффектов.

1. Приведите пример

Как обычно, перейдем непосредственно к официальному примеру

<template>
  <div id="list-demo">
    <button v-on:click="add">Add</button>
    <button v-on:click="remove">Remove</button>
    <transition-group name="list" tag="p">
      <span v-for="item in items" v-bind:key="item" class="list-item">
        {{ item }}
      </span>
    </transition-group>
  </div>
</template>

<script>
export default {
  name: 'home',
  data () {
    return {
      items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
      nextNum: 10
    }
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    }
  }
}
</script>

<style lang="scss">
.list-item {
  display: inline-block;
  margin-right: 10px;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}
</style>

Эффект следующий

Далее я возьму вас исследовать вместе<transition-group>дизайн компонентов

Во-вторых, реализация переходной группы

а также<transition>компоненты по сравнению с<transition>Является абстрактным компонентом и работает только с одним элементом. а также<transition-group>Компонент реализует переход списка и отображает реальный узел элемента.

Но их концепции дизайна одинаковы, и они также предоставят намpropsи ряд функций хука для нас как输入интерфейс, который осуществляется внутри输入 => 输出преобразование или обработка привязки

export default {
  props,
  beforeMount () {
    // ...
  },
  render (h: Function) {
    // ...
  },
  updated () {
    // ...
  },
  methods: {
    // ...
  }
}

1. реквизит и другой импорт

<transition-group>изpropsа также<transition>изpropsПо сути то же самое, только еще одинtagа такжеmoveClassатрибут удаленmodeАтрибуты

// props
import { transitionProps, extractTransitionData } from './transition'
const props = extend({
  tag: String,
  moveClass: String
}, transitionProps)

delete props.mode

// other import
import { warn, extend } from 'core/util/index'
import { addClass, removeClass } from '../class-util'
import { setActiveInstance } from 'core/instance/lifecycle'

import {
  hasTransition,
  getTransitionInfo,
  transitionEndEvent,
  addTransitionClass,
  removeTransitionClass
} from '../transition-util'

2. визуализировать

Во-первых, нам нужно определить ряд переменных для облегчения последующих операций.

  • tag: Из общего контекста приведенного выше дизайна мы можем видеть, что<transition-group>нисколькоabstractсвойство, т. е. он будет отображать реальный узел, затем узелtagявляется обязательным, и его значение по умолчанию равноspan.
  • map: Создает пустой объект
  • prevChildren: используется для хранения последнего дочернего узла
  • rawChildren:Получать<transition-group>Обернутые дочерние узлы
  • children: используется для хранения текущего дочернего узла
  • transitionData: получить данные рендеринга компонента.
const tag: string = this.tag || this.$vnode.data.tag || 'span'
const map: Object = Object.create(null)
const prevChildren: Array<VNode> = this.prevChildren = this.children
const rawChildren: Array<VNode> = this.$slots.default || []
const children: Array<VNode> = this.children = []
const transitionData: Object = extractTransitionData(this)

Далее следует операция обхода узла, вот привязка анимации перехода для каждого узла в списке

  • правильноrawChildrenпройти и положить каждыйvnodeУзел вынуть;
  • Если узел существует с__vlistперсонажkey, тоvnodeбросатьchildrenсередина;
  • Данные перехода, которые будут извлеченыtransitionDataдобавить вvnode.data.transition, чтобы можно было добиться анимации перехода одного элемента в списке
for (let i = 0; i < rawChildren.length; i++) {
  const c: VNode = rawChildren[i]
  if (c.tag) {
    if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
      children.push(c)
      map[c.key] = c
      ;(c.data || (c.data = {})).transition = transitionData
    } else if (process.env.NODE_ENV !== 'production') {
      const opts: ?VNodeComponentOptions = c.componentOptions
      const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag
      warn(`<transition-group> children must be keyed: <${name}>`)
    }
  }
}

затем кprevChildrenобрабатывать

  • еслиprevChildrenсуществует, пройти по нему иtransitionDataназначить наvnode.data.transition, после этого, когдаvnodeдочерний узелenterа такжеleaveКогда на сцене есть анимация перехода, будет выполнена соответствующая анимация перехода.
  • тогда позвони родномуgetBoundingClientRectПолучите информацию о местоположении элемента, запишите ее вvnode.data.posсередина
  • затем судитьmapсуществует вvnode.key, если он существует, то будетvnodeпомещатьkept, иначе он выбрасывается вremovedв очереди
  • Наконец, поместите визуализированный элемент вthis.keptсередина,this.removedиспользуется для записи удаленного узла
if (prevChildren) {
  const kept: Array<VNode> = []
  const removed: Array<VNode> = []
  for (let i = 0; i < prevChildren.length; i++) {
    const c: VNode = prevChildren[i]
    c.data.transition = transitionData
    c.data.pos = c.elm.getBoundingClientRect()
    if (map[c.key]) {
      kept.push(c)
    } else {
      removed.push(c)
    }
  }
  this.kept = h(tag, null, kept)
  this.removed = removed
}

наконец<transition-group>оказывать

return h(tag, null, children)

3. обновление и методы

выше мы ужеrenderСтадия привязана к каждому элементу в спискеtransitionСвязанные эффекты перехода, следующий шаг заключается в том, что при динамическом изменении каждого элемента весь списокupdateВремя для динамичного перехода. Итак, как именно это работает? Далее, давайте посмотрим на логику этого произведения.

I. Требуется ли переход движения

  • первый вupdateВ функции ловушки последний дочерний узел будет получен первымprevChildrenа такжеmoveClass; потом судитьchildrenесть иchildrenбудь тоhas move,подобноchildrenне существует илиchildrenнетmoveстатус, то нет необходимости продолжатьupdateизmoveпереход, прямойreturnТолько что
const children: Array<VNode> = this.prevChildren
const moveClass: string = this.moveClass || ((this.name || 'v') + '-move')
if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
  return
}
  • hasMove(): Этот метод в основном используется для определенияelЕсть ли в узлеmoveположение дел.
  • текущий фронтreturnЕсли условия не выполняются, он сначала клонирует DOM-узел, а затем, чтобы избежать CSS-перехода внутри элемента, удалит все клонированные узлы.transitionClasses
  • Затем повторно добавьте узел клонированияmoveClass, и положить егоdisplayустановить какnone, затем добавьте вthis.$elначальство
  • Далее черезgetTransitionInfoвозьмиtransitionсоответствующую информацию, затем изthis.$elудалить его. На данный момент мы получили, имеет ли узелtransformИнформация
export const hasTransition = inBrowser && !isIE9

hasMove (el: any, moveClass: string): boolean {
  // 若不在浏览器中,或者浏览器不支持 transition,直接返回 false 即可
  if (!hasTransition) {
    return false
  }
  // 若当前实例上下文的有 _hasMove,直接返回 _hasMove 的值即可
  if (this._hasMove) {
    return this._hasMove
  }
  const clone: HTMLElement = el.cloneNode()
  if (el._transitionClasses) {
    el._transitionClasses.forEach((cls: string) => { removeClass(clone, cls) })
  }
  addClass(clone, moveClass)
  clone.style.display = 'none'
  this.$el.appendChild(clone)
  const info: Object = getTransitionInfo(clone)
  this.$el.removeChild(clone)
  return (this._hasMove = info.hasTransform)
}

II. Реализация перехода Move

  • Затем на дочерних узлах выполняется волна предварительной обработки.Здесь для обработки дочерних узлов используются три цикла, в основном, чтобы избежать путаницы чтения и записи в DOM в каждом цикле, что помогает предотвратить путаницу в макете.
children.forEach(callPendingCbs)
children.forEach(recordPosition)
children.forEach(applyTranslation)

Обработка трех функций выглядит следующим образом

  1. callPendingCbs(): Определить, была ли выполнена анимация перехода предыдущего кадра каждого узла, если нет, выполнить ее заранее_moveCb()а также_enterCb()
  2. recordPosition(): записать новую позицию каждого узла
  3. applyTranslation(): Получите старое и новое положение узла соответственно и вычислите разницу.Если есть разница, установите узелtransformАтрибут смещает позицию узла, который необходимо переместить на предыдущую позицию, которая является спискомmoveподготовиться к
function callPendingCbs (c: VNode) {
  if (c.elm._moveCb) {
    c.elm._moveCb()
  }
  if (c.elm._enterCb) {
    c.elm._enterCb()
  }
}

function recordPosition (c: VNode) {
  c.data.newPos = c.elm.getBoundingClientRect()
}

function applyTranslation (c: VNode) {
  const oldPos = c.data.pos
  const newPos = c.data.newPos
  const dx = oldPos.left - newPos.left
  const dy = oldPos.top - newPos.top
  if (dx || dy) {
    c.data.moved = true
    const s = c.elm.style
    s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`
    s.transitionDuration = '0s'
  }
}
  • Затем пройдите по дочерним элементам, чтобы достичьmoveпереход. Перед обходом он будет полученdocument.body.offsetHeight, поэтому вычисление происходит, вызывает перекомпоновку и позволяет браузеру перерисовать
  • затем начнитеchildrenпоход, во время которого, еслиvnode.data.movedдляtrue, затем выполнитеaddTransitionClassдобавить дочерние узлыmoveClass, и положить егоstyle.transformАтрибут очищен.Поскольку мы уже сместили дочерний узел в предыдущую старую позицию в предварительной обработке дочернего узла, он перейдет из старой позиции в текущую позицию в это время, что мы и хотимmoveэффект перехода
  • Наконец, узел будет добавленtransitionendПрослушайте событие в конце перехода и выполните некоторые операции очистки в событии.
this._reflow = document.body.offsetHeight

children.forEach((c: VNode) => {
  if (c.data.moved) {
    const el: any = c.elm
    const s: any = el.style
    addTransitionClass(el, moveClass)
    s.transform = s.WebkitTransform = s.transitionDuration = ''
    el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {
      if (e && e.target !== el) {
        return
      }
      if (!e || /transform$/.test(e.propertyName)) {
        el.removeEventListener(transitionEndEvent, cb)
        el._moveCb = null
        removeTransitionClass(el, moveClass)
      }
    })
  }
})

Примечание. Я кратко опишу условия срабатывания для перекомпоновки браузера, такие как изменение окна браузера, стили расчета, добавление или удаление элементов в DOM, изменение классов элементов и т. д.

  1. Добавить или удалить видимые элементы DOM
  2. изменение позиции элемента
  3. Изменения размера элемента - поля, отступы, границы, ширина и высота
  4. Изменения содержимого, например, когда пользователь вводит текст в поле ввода, расчетное значение ширины и высоты изменяется из-за изменения размера текста или изображения.
  5. инициализация рендеринга страницы
  6. Изменение размера окна браузера - когда происходит событие изменения размера
  7. Вычислить свойства offsetWidth и offsetHeight
  8. Установите значение свойства стиля

4. перед монтированием

из-заVDOMв узлеdiffПри обновлении нет гарантии относительного положения удаленного элемента. Так что здесь нужноbeforeMountвнутри функции хукаupdateЛогика рендеринга переписана для достижения желаемого эффекта.

  • Сначала получите сам экземплярupdateметод кэширования
  • Сверху мы знаемthis.keptЭто последний узел кеша, и количество узлов в нем увеличилось на некотороеtransitionпереходные свойства. Здесь сначала черезsetActiveInstanceКэшировать текущий экземпляр, а затемvnodeпровести__patch__Управляйте и удаляйте то, что нужно удалитьvnode, затем выполнитеrestoreActiveInstanceуказать его экземпляр для восстановления
  • будет потомthis.keptназначить наthis._vnode, так что это вызывает переход
  • последний кэшированныйupdateвизуализировать узел
beforeMount () {
  const update = this._update
  this._update = (vnode, hydrating) => {
    const restoreActiveInstance = setActiveInstance(this)
    // force removing pass
    this.__patch__(
      this._vnode,
      this.kept,
      false, // hydrating
      true // removeOnly (!important, avoids unnecessary moves)
    )
    this._vnode = this.kept
    restoreActiveInstance()
    update.call(this, vnode, hydrating)
  }
}
  • setActiveInstance
export let activeInstance: any = null
export function setActiveInstance(vm: Component) {
  const prevActiveInstance = activeInstance
  activeInstance = vm
  return () => {
    activeInstance = prevActiveInstance
  }
}

наконец

Статья почти здесь, даtransitionСвязанные встроенные компоненты<transition>так же как<transition-group>Анализ тоже закончился. Различные типы компонентов, один абстрактный компонент и один, который отображает фактический элемент узла, хотят делать одно и то же, инициализируя их для пользователя.输入интерфейс,输入можно получить после输出переходный эффект.

Внешняя коммуникационная группа: 731175396, сердечно приветствую вас, сестра, газета, газета Хань хочет присоединиться