imagen
Síguenos si te gusta


Autor |

https://juejin.cn/post/7011372376969445413

Espero que este artículo pueda ayudarlo a profundizar su comprensión de Vue y pueda jurar que domina Vue2/3. Además, también espero que los amigos que pasan puedan ayudarme a revisar y llenar los vacíos.


El contenido es mixto 用法 ++ , se recomienda recopilarlo y verlo despacio. 原理  使用小心得

la diferencia

cambios en el ciclo de vida

En general, no hay muchos cambios, pero la mayoría de los nombres necesitan + y las funciones son similares. La API combinada de Vue3 debe introducirse primero; la API de opción de Vue2 se puede llamar directamente, como se muestra a continuación. on

// vue3
<script setup>     
import { onMounted } from 'vue'

onMounted(() => {
  ...
})
// 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不被覆盖
onMounted(() => {
  ...
})
</script>


// vue2
<script>     
   export default {         
      mounted() {             
        ...         
      },           
   }
</script>
 

A continuación se muestra una tabla de ciclo de vida común.

Vue2.x vue3
antes de crear Innecesario*
creado Innecesario*
antes del monte onBeforeMount
montado en Montado
antes de Actualizar onBeforeUpdate
actualizado onActualizado
antes de destruir onBeforeUnmount
destruido en Desmontado

Sugerencias: funciona y se enlaza con el ciclo de vida, por lo que no es necesario definirlo explícitamente. setupbeforeCreatecreated

Múltiples nodos raíz

Vue3 admite componentes de nodos multiraíz, es decir, fragment.

En Vue2, al escribir una página, debemos envolver el componente en <div>, de lo contrario, se informará una advertencia de error.

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

Con Vue3, podemos incluir múltiples nodos raíz en nuestros componentes y podemos escribir una capa menos, ¡muy bien!

<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

Componentes asíncronos

Vue3 proporciona componentes que permiten que los programas representen el contenido final, como la carga, mientras esperan componentes asincrónicos, lo que hace que la experiencia del usuario sea más fluida. Para usarlo, declárelo en la plantilla e incluya dos ranuras con nombre: y . Asegúrese de mostrar la ranura predeterminada cuando se cargue el contenido asíncrono y utilice la ranura como estado de carga. SuspensedefaultfallbackSuspensefallback

<tempalte>
   <suspense>
     <template #default>
       <todo-list />
     </template>
     <template #fallback>
       <div>
         Loading...
       </div>
     </template>
   </suspense>
</template>

He pisado el pozo en proyectos reales. Si desea llamar a una solicitud asíncrona en la configuración, debe agregar una asyncpalabra clave antes de la configuración. En este momento, se le advertirá async setup() is used without a suspense boundary.

Solución: envuelva una capa de componentes alrededor de la página principal que llama al Suspensecomponente actual.

teletransportarse

Vue3 proporciona Teleportcomponentes para mover partes del DOM fuera de la aplicación Vue. Tales como los Dialogcomponentes comunes en el proyecto.

<button @click=dialogVisible = true>点击</button>
<teleport to=body>
   <div class=dialog v-if=dialogVisible>
   </div>
</teleport>

API compuesta

Vue2 es que una lógica se dispersará en diferentes posiciones del archivo (datos, accesorios, computado, reloj, funciones de ciclo de vida, etc.), lo que resultará en una mala legibilidad del código y tendrá que saltar arriba y abajo en la ubicación del archivo. . Vue3 resuelve bien este problema y puede escribir juntos el contenido de la misma lógica. 选项式API(Option API) 组合式API(Composition API)

Además de mejorar la legibilidad y la cohesión del código, la API combinada también proporciona una solución de reutilización lógica relativamente perfecta. Por ejemplo, el ejemplo de las coordenadas públicas del mouse se muestra a continuación.

// main.vue
<template>
  <span>mouse position {{x}} {{y}}</span>
</template>

<script setup>
import { ref } from 'vue'
import useMousePosition from './useMousePosition'

const {x, y} = useMousePosition()

}
</script>

// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'

function useMousePosition() {
  let x = ref(0)
  let y = ref(0)
  
  function update(e{
    x.value = e.pageX
    y.value = e.pageY
  }
  
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  
  return {
    x,
    y
  }
}
</script>

Resuelve el peligro oculto de los conflictos de nombres en Vue2 , las dependencias no son claras y la configuración entre diferentes componentes no es lo suficientemente flexible. Mixin

Principios de respuesta

Vue2 se basa en el principio de capacidad de Object.definePropertyrespuesta; Vue3 se basa en el principio de capacidad de respuesta Proxy.

Objeto.defineProperty

Uso básico: defina nuevas propiedades o modifique las propiedades existentes directamente en un objeto y devuelva el objeto.
Consejos: Armonía y Armonía coexisten . writable  value  getter  setter 

let obj = {}
let name = '瑾行'
Object.defineProperty(obj, 'name', {
  enumerabletrue// 可枚举(是否可通过for...in 或 Object.keys()进行访问)
  configurabletrue// 可配置(是否可使用delete删除,是否可再次设置属性)
  // value: '', // 任意类型的值,默认undefined
  // writable: true, // 可重写
  getfunction() {
    return name
  },
  setfunction(value{
    name = value
  }
})

Mueva el código fuente principal de Vue2 y elimínelo ligeramente.

function defineReactive(obj, key, val{
  // 一 key 一个 dep
  const dep = new Dep()
  
  // 获取 key 的属性描述符,发现它是不可配置对象的话直接 return
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) { return }
  
  // 获取 getter 和 setter,并获取 val 值
  const getter = property && property.get
  const setter = property && property.set
  if((!getter || setter) && arguments.length === 2) { val = obj[key] }
  
  // 递归处理,保证对象中所有 key 被观察
  let childOb = observe(val)
  
  Object.defineProperty(obj, key, {
    enumerabletrue,
    configurabletrue,
    // get 劫持 obj[key] 的 进行依赖收集
    getfunction reactiveGetter() {
      const value = getter ? getter.call(obj) : val
      if(Dep.target) {
        // 依赖收集
        dep.depend()
        if(childOb) {
          // 针对嵌套对象,依赖收集
          childOb.dep.depend()
          // 触发数组响应式
          if(Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
    }
    return value
  })
  // set 派发更新 obj[key]
  setfunction reactiveSetter(newVal{
    ...
    if(setter) {
      setter.call(obj, newVal)
    } else {
      val = newVal
    }
    // 新值设置响应式
    childOb = observe(val)
    // 依赖通知更新
    dep.notify()
  }
}

Entonces, ¿por qué Vue3 lo abandonó? Eso debe tener algunos defectos.

Motivo principal: no se pueden monitorear elementos nuevos o eliminados de objetos o matrices.
Solución Vue2: piratee el procesamiento para métodos comunes de prototipos de matrices push, pop, shift, unshift, splice, , sorty reverseproporcione Vue.setnuevas propiedades para escuchar objetos/matrices. En respuesta a la adición/eliminación newde un objeto, también se puede crear un nuevo objeto. Al agregar, el nuevo atributo y el objeto anterior se fusionan; al eliminar, el objeto con el atributo eliminado se copia profundamente en el nuevo objeto.

Sugerencias: es posible monitorear los elementos existentes de la matriz, pero la razón por la que Vue2 no lo proporciona es un problema. Para obtener más detalles, consulte el segundo artículo ~. Object.defineOProperty性能

apoderado

ProxyhandlerEs una nueva característica de ES6, que intercepta el comportamiento del objeto de destino a través del segundo parámetro . Elimina las limitaciones en comparación con Object.definePropertyla capacidad de respuesta en toda la gama de idiomas. Pero renunció a la compatibilidad (por debajo de IE11)

limitación

  1. Agregar y eliminar objetos/matrices.
  2. Supervise las modificaciones de .length.
  3. Compatibilidad con mapas, conjuntos, WeakMap y WeakSet.

Uso básico: cree un proxy para que un objeto implemente la intercepción de operaciones básicas y operaciones personalizadas.

const handler = {
  getfunction(obj, prop{
    return prop in obj ? obj[prop] : ''
  },
  setfunction() {},
  ...
}

Mueva el archivo fuente reactive.ts de Vue3

function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap{
  ...
  // collectionHandlers: 处理Map、Set、WeakMap、WeakSet
  // baseHandlers: 处理数组、对象
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

Tomando baseHandlers.ts como ejemplo, la razón para usar Reflect.get en su lugar es target[key]que el parámetro del receptor puede apuntar esto al objeto cuando se llama al getter, no cuando se construye el Proxy.

// 依赖收集
function createGetter(isReadonly = false, shallow = false{
  return function get(target: Target, key: string | symbol, receiver: object{
    ...
    // 数组类型
    const targetIsArray = isArray(target)
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    // 非数组类型
    const res = Reflect.get(target, key, receiver);
    
    // 对象递归调用
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
// 派发更新
function createSetter() {
  return function set(target: Target, key: string | symbol, value: unknown, receiver: Object{
    value = toRaw(value)
    oldValue = target[key]
    // 因 ref 数据在 set value 时就已 trigger 依赖了,所以直接赋值 return 即可
    if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    }

    // 对象是否有 key 有 key set,无 key add
    const hadKey = hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

DOM virtual

Vue3 agrega patchFlagcampos al DOM virtual en comparación con Vue2. Echemos un Vue3 Template Explorervistazo.

<div id=app>
  <h1>技术摸鱼</h1>
  <p>今天天气真不错</p>
  <div>{{name}}</div>
</div>

La función de renderizado es la siguiente.

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue

const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n)
const _hoisted_1 = { idapp }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(h1null技术摸鱼-1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(pnull今天天气真不错-1 /* HOISTED */))

export function render(_ctx, _cache, $props, $setup, $data, $options{
  return (_openBlock(), _createElementBlock(div, _hoisted_1, [
    _hoisted_2,
    _hoisted_3,
    _createElementVNode(divnull, _toDisplayString(_ctx.name), 1 /* TEXT */)
  ]))
}

Tenga en cuenta que _createElementVNodeel cuarto parámetro del tercero es el patchFlagtipo de campo, y el tipo de campo es el siguiente. 1 significa que el nodo es un nodo de texto dinámico, luego, en el proceso de diferencias, solo necesita comparar el texto, no necesita prestar atención a la clase, el estilo, etc. Además, se encuentra que todos los nodos estáticos se guardan como una variable 静态提升, a la que se puede hacer referencia directamente cuando se vuelve a renderizar sin volver a crear.

export const enum PatchFlags { 
  TEXT = 1// 动态文本内容
  CLASS = 1 << 1// 动态类名
  STYLE = 1 << 2// 动态样式
  PROPS = 1 << 3// 动态属性,不包含类名和样式
  FULL_PROPS = 1 << 4// 具有动态 key 属性,当 key 改变,需要进行完整的 diff 比较
  HYDRATE_EVENTS = 1 << 5// 带有监听事件的节点
  STABLE_FRAGMENT = 1 << 6// 不会改变子节点顺序的 fragment
  KEYED_FRAGMENT = 1 << 7// 带有 key 属性的 fragment 或部分子节点
  UNKEYED_FRAGMENT = 1 << 8,  // 子节点没有 key 的fragment
  NEED_PATCH = 1 << 9// 只会进行非 props 的比较
  DYNAMIC_SLOTS = 1 << 10// 动态的插槽
  HOISTED = -1,  // 静态节点,diff阶段忽略其子节点
  BAIL = -2 // 代表 diff 应该结束
}

caché de eventos

Vue3 puede almacenar en caché nuestros eventos después del primer renderizado. Comparado con Vue2, no hay necesidad de pasar una nueva función cada renderizado. Agrega un evento. cacheHandlerclick

<div id=app>
  <h1>技术摸鱼</h1>
  <p>今天天气真不错</p>
  <div>{{name}}</div>
  <span onCLick=() => {}><span>
</div>

La función de renderizado es la siguiente

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue

const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n)
const _hoisted_1 = { idapp }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(h1null技术摸鱼-1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(pnull今天天气真不错-1 /* HOISTED */))
const _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(span, { onCLick() => {} }, [
  /*#__PURE__*/_createElementVNode(span)
], -1 /* HOISTED */))

export function render(_ctx, _cache, $props, $setup, $data, $options{
  return (_openBlock(), _createElementBlock(div, _hoisted_1, [
    _hoisted_2,
    _hoisted_3,
    _createElementVNode(divnull, _toDisplayString(_ctx.name), 1 /* TEXT */),
    _hoisted_4
  ]))
}

optimización de diferencias

Lleva el código fuente de Vue3 patchChildren. Al combinar lo anterior con el código fuente, patchFlag ayuda a distinguir entre los nodos estáticos y los diferentes tipos de nodos dinámicos cuando se diferencian. Hasta cierto punto, se reduce la alineación del propio nodo y sus atributos.

function patchChildren(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized{
  // 获取新老孩子节点
  const c1 = n1 && n1.children
  const c2 = n2.children
  const prevShapeFlag = n1 ? n1.shapeFlag : 0
  const { patchFlag, shapeFlag } = n2
  
  // 处理 patchFlag 大于 0 
  if(patchFlag > 0) {
    if(patchFlag && PatchFlags.KEYED_FRAGMENT) {
      // 存在 key
      patchKeyedChildren()
      return
    } els if(patchFlag && PatchFlags.UNKEYED_FRAGMENT) {
      // 不存在 key
      patchUnkeyedChildren()
      return
    }
  }
  
  // 匹配是文本节点(静态):移除老节点,设置文本节点
  if(shapeFlag && ShapeFlags.TEXT_CHILDREN) {
    if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      unmountChildren(c1 as VNode[], parentComponent, parentSuspense)
    }
    if (c2 !== c1) {
      hostSetElementText(container, c2 as string)
    }
  } else {
    // 匹配新老 Vnode 是数组,则全量比较;否则移除当前所有的节点
    if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense,...)
      } else {
        unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)
      }
    } else {
      
      if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
        hostSetElementText(container, '')
      } 
      if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        mountChildren(c2 as VNodeArrayChildren, container,anchor,parentComponent,...)
      }
    }
  }
}

El código fuente de patchUnkeyedChildren es el siguiente.

function patchUnkeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized{
  c1 = c1 || EMPTY_ARR
  c2 = c2 || EMPTY_ARR
  const oldLength = c1.length
  const newLength = c2.length
  const commonLength = Math.min(oldLength, newLength)
  let i
  for(i = 0; i < commonLength; i++) {
    // 如果新 Vnode 已经挂载,则直接 clone 一份,否则新建一个节点
    const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as Vnode)) : normalizeVnode(c2[i])
    patch()
  }
  if(oldLength > newLength) {
    // 移除多余的节点
    unmountedChildren()
  } else {
    // 创建新的节点
    mountChildren()
  }
  
}

El código fuente de patchKeyedChildren es el siguiente, que tiene la idea del algoritmo de usar la secuencia creciente más larga.

function patchKeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized{
  let i = 0;
  const e1 = c1.length - 1
  const e2 = c2.length - 1
  const l2 = c2.length
  
  // 从头开始遍历,若新老节点是同一节点,执行 patch 更新差异;否则,跳出循环 
  while(i <= e1 && i <= e2) {
    const n1 = c1[i]
    const n2 = c2[i]
    
    if(isSameVnodeType) {
      patch(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSvg, optimized)
    } else {
      break
    }
    i++
  }
  
  // 从尾开始遍历,若新老节点是同一节点,执行 patch 更新差异;否则,跳出循环 
  while(i <= e1 && i <= e2) {
    const n1 = c1[e1]
    const n2 = c2[e2]
    if(isSameVnodeType) {
      patch(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSvg, optimized)
    } else {
      break
    }
    e1--
    e2--
  }
  
  // 仅存在需要新增的节点
  if(i > e1) {    
    if(i <= e2) {
      const nextPos = e2 + 1
      const anchor = nextPos < l2 ? c2[nextPos] : parentAnchor
      while(i <= e2) {
        patch(null, c2[i], container, parentAnchor, parentComponent, parentSuspense, isSvg, optimized)
      }
    }
  }
  
  // 仅存在需要删除的节点
  else if(i > e2) {
    while(i <= e1) {
      unmount(c1[i], parentComponent, parentSuspense, true)    
    }
  }
  
  // 新旧节点均未遍历完
  // [i ... e1 + 1]: a b [c d e] f g
  // [i ... e2 + 1]: a b [e d c h] f g
  // i = 2, e1 = 4, e2 = 5
  else {
    const s1 = i
    const s2 = i
    // 缓存新 Vnode 剩余节点 上例即{e: 2, d: 3, c: 4, h: 5}
    const keyToNewIndexMap = new Map()
    for (i = s2; i <= e2; i++) {
      const nextChild = (c2[i] = optimized
          ? cloneIfMounted(c2[i] as VNode)
          : normalizeVNode(c2[i]))
      
      if (nextChild.key != null) {
        if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {
          warn(
            `Duplicate keys found during update:`,
             JSON.stringify(nextChild.key),
            `Make sure keys are unique.`
          )
        }
        keyToNewIndexMap.set(nextChild.key, i)
      }
    }
  }
  
  let j = 0
  // 记录即将 patch 的 新 Vnode 数量
  let patched = 0
  // 新 Vnode 剩余节点长度
  const toBePatched = e2 - s2 + 1
  // 是否移动标识
  let moved = false
  let maxNewindexSoFar = 0
  
  // 初始化 新老节点的对应关系(用于后续最大递增序列算法)
  const newIndexToOldIndexMap = new Array(toBePatched)
  for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
  
  // 遍历老 Vnode 剩余节点
  for (i = s1; i <= e1; i++) {
    const prevChild = c1[i]
    
    // 代表当前新 Vnode 都已patch,剩余旧 Vnode 移除即可
    if (patched >= toBePatched) {
      unmount(prevChild, parentComponent, parentSuspense, true)
      continue
    }
    
    let newIndex
    // 旧 Vnode 存在 key,则从 keyToNewIndexMap 获取
    if (prevChild.key != null) {
      newIndex = keyToNewIndexMap.get(prevChild.key)
    // 旧 Vnode 不存在 key,则遍历新 Vnode 获取
    } else {
      for (j = s2; j <= e2; j++) {
        if (newIndexToOldIndexMap[j - s2] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)){
           newIndex = j
           break
        }
      }           
   }
   
   // 删除、更新节点
   // 新 Vnode 没有 当前节点,移除
   if (newIndex === undefined) {
     unmount(prevChild, parentComponent, parentSuspense, true)
   } else {
     // 旧 Vnode 的下标位置 + 1,存储到对应 新 Vnode 的 Map 中
     // + 1 处理是为了防止数组首位下标是 0 的情况,因为这里的 0 代表需创建新节点
     newIndexToOldIndexMap[newIndex - s2] = i + 1
     
     // 若不是连续递增,则代表需要移动
     if (newIndex >= maxNewIndexSoFar) {
       maxNewIndexSoFar = newIndex
     } else {
       moved = true
     }
     
     patch(prevChild,c2[newIndex],...)
     patched++
   }
  }
  
  // 遍历结束,newIndexToOldIndexMap = {0:5, 1:4, 2:3, 3:0}
  // 新建、移动节点
  const increasingNewIndexSequence = moved
  // 获取最长递增序列
  ? getSequence(newIndexToOldIndexMap)
  : EMPTY_ARR
  
  j = increasingNewIndexSequence.length - 1

  for (i = toBePatched - 1; i >= 0; i--) {
    const nextIndex = s2 + i
    const nextChild = c2[nextIndex] as VNode
    const anchor = extIndex + 1 < l2 ? (c2[nextIndex + 1as VNode).el : parentAnchor
    // 0 新建 Vnode
    if (newIndexToOldIndexMap[i] === 0) {
      patch(null,nextChild,...)
    } else if (moved) {
      // 移动节点
      if (j < 0 || i !== increasingNewIndexSequence[j]) {
        move(nextChild, container, anchor, MoveType.REORDER)
      } else {
        j--
      }
    }
  }
}

Optimización de envases

tree-shaking: Conceptos en el empaquetado de módulos webpack, rollupetc. Elimina el código sin referencia del contexto de JavaScript. La dependencia principal importy la exportdeclaración se utilizan para detectar si un archivo JavaScript exporta, importa y utiliza un módulo de código.

nextTickPor ejemplo, en Vue2 tree-shakingpuede eliminar incluso si no se usa.

import Vue from 'vue'

Vue.nextTick(() => {
  // 一些和DOM有关的东西
})

Las API globales e internas se han refactorizado en Vue3 teniendo tree-shakingen cuenta el soporte. Por lo tanto, ahora solo se puede acceder a la API global como exportaciones nombradas de compilaciones de módulos ES.

import { nextTick } from 'vue'

nextTick(() => {
  // 一些和DOM有关的东西
})

tree-shakingCon este cambio, las API no utilizadas en la aplicación Vue se eliminarán del paquete final para obtener un tamaño de archivo óptimo , siempre que el enlace del módulo lo admita . Las API globales afectadas por este cambio son las siguientes.

  • Vue.nextTick
  • Vue.observable (用 Vue.reactive 替换)
  • Vue.version
  • Vue.compile (仅全构建)
  • Vue.set (仅兼容构建)
  • Vue.delete (仅兼容构建)

内部 API 也有诸如 transition、v-model等标签或者指令被命名导出。只有在程序真正使用才会被捆绑打包。

根据 尤大 直播可以知道如今 Vue3 将所有运行功能打包也只有22.5kb,比 Vue2 轻量很多。

自定义渲染API

Vue3 提供的createApp默认是将 template 映射成 html。但若想生成canvas时,就需要使用custom renderer api自定义render生成函数。

// 自定义runtime-render函数
import { createApp } from './runtime-render'
import App from './src/App'

createApp(App).mount('#app')

TypeScript 支持

Vue3 由TS重写,相对于 Vue2 有更好地TypeScript支持。

  • Vue2 Option API中 option 是个简单对象,而TS是一种类型系统,面向对象的语法,不是特别匹配。
  • Vue2 需要vue-class-component强化vue原生组件,也需要vue-property-decorator增加更多结合Vue特性的装饰器,写法比较繁琐。

周边

列举一些 Vue3 配套产物,具体Composition API新语法可见官方迁移文档,参考中有链接~ 。

  • vue-cli 4.5.0
  • Vue Router 4.0
  • Vuex 4.0
  • Element plus
  • Vite

参考

Vue3.0性能优化之重写虚拟Dom[1]
记一次思否问答的问题思考:Vue为什么不能检测数组变动[2]
Vue3 源码解析系列 \- 响应式原理(reactive 篇)[3]
Vue 源码解读(3)—— 响应式原理[4]
Vue 3 Virtual Dom Diff源码阅读[5]
Vue 2 迁移[6]

参考资料

[1]

https://blog.csdn.net/summer_zhh/article/details/108080930: https://link.juejin.cn?target=https%3A%2F%2Fblog.csdn.net%2Fsummer_zhh%2Farticle%2Fdetails%2F108080930

[2]

https://segmentfault.com/a/1190000015783546: https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000015783546

[3]

https://zhuanlan.zhihu.com/p/87899787: https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F87899787

[4]

https://juejin.cn/post/6950826293923414047#heading-12: https://juejin.cn/post/6950826293923414047#heading-12

[5]

https://segmentfault.com/a/1190000038654183: https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000038654183

[6]

https://v3.vuejs.org/guide/migration/migration-build.html#overview: https://link.juejin.cn?target=https%3A%2F%2Fv3.vuejs.org%2Fguide%2Fmigration%2Fmigration-build.html%23overview

imagen

每日分享前端插件干货,欢迎关注!

点赞和在看就是最大的支持❤️