Vue3详解

vue3的效率提升主要表现在哪些方面

  • 静态提升
  • 预字符串化
  • 缓存事件处理函数
  • PatchFlag

在这里插入图片描述

上图中会对h1,h2标签做静态提升,在render函数外定义_hoisted_1,_hoisted_1 在render函数中可以重复使用;
当存在连续大量的(测试元素数量>10)静态元素时候,vue3会进行预字符串化,通过_createStaticVNode生成字符串;
当有事件处理时vue3会读取render中_cache缓存,而不需要每次都重新创建一个事件;
vue2中不会区分节点是动态的还是静态的,需要层层比较,而vue3中会在_createElementVNode中对节点进行标记,动态节点标记为1,静态节点标记为-1

 _createElementVNode(type, props, children, patchFlag, dynamicChildren, shapeFlag, hooks, data){
	/**
	type:组件的构造函数、选项式组件的 VNode 数据对象或者 HTML 标签字符串。
	props:一个对象,包含 (可能是 reactive 的) 属性,这些属性会被传递到组件。
	children:子 VNode 或者静态内容的数组。
	patchFlag:用于优化渲染的标记位。
	dynamicChildren:动态子 VNode 的数组,用于动态 children。
	shapeFlag:标记 VNode 的形状信息,比如是否是元素、文本、Fragment 等。
	hooks:生命周期钩子对象。
	data:VNode 数据对象,包含了与平台相关的属性,比如 props、attrs、on。
	*/
}

export const enum PatchFlags {
  
  TEXT = 1,// 1 动态的文本节点
  CLASS = 1 << 1,  // 2 动态的 class
  STYLE = 1 << 2,  // 4 动态的 style
  PROPS = 1 << 3,  // 8 动态属性,不包括类名和样式
  FULL_PROPS = 1 << 4,  // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较
  HYDRATE_EVENTS = 1 << 5,  // 32 表示带有事件监听器的节点
  STABLE_FRAGMENT = 1 << 6,   // 64 一个不会改变子节点顺序的 Fragment
  KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment
  UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
  NEED_PATCH = 1 << 9,   // 512  表示只需要non-props修补的元素 (non-props不知道怎么翻才恰当~)
  DYNAMIC_SLOTS = 1 << 10,  // 1024 动态的solt
  DEV_ROOT_FRAGMENT = 1 << 11, //2048 表示仅因为用户在模板的根级别放置注释而创建的片段。 这是一个仅用于开发的标志,因为注释在生产中被剥离。
 
  //以下两个是特殊标记
  HOISTED = -1,  // 表示已提升的静态vnode,更新时调过整个子树
  BAIL = -2 // 指示差异算法应该退出优化模式
}

setup原理

setup函数在组件生命周期中是最早执行的函数,它在beforeCreate和created生命周期钩子之前调用。这意味着setup在函数内部,无法访问到组件实例this; setup接收两个参数props 和 context(attrs, slots, emit, expose.暴露公共属性);setup返回一个对象,对象中的属性和方法将被暴露给组件的模板和其他组合式API函数;

export function renderComponentVNode(
  vnode: VNode,
  parentComponent: ComponentInternalInstance | null = null,
  slotScopeId?: string,
): SSRBuffer | Promise<SSRBuffer> {
  const instance = createComponentInstance(vnode, parentComponent, null)
  // 源码中在渲染组件节点时候调用setupComponent
  const res = setupComponent(instance, true /* isSSR */)
  const hasAsyncSetup = isPromise(res)
  const prefetches = instance.sp /* LifecycleHooks.SERVER_PREFETCH */
  if (hasAsyncSetup || prefetches) {
    ...
    return p.then(() => renderComponentSubTree(instance, slotScopeId))
  } else {
    return renderComponentSubTree(instance, slotScopeId)
  }
}

......

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false,
) {
  isSSR && setInSSRSetupState(isSSR)

  const { props, children } = instance.vnode
  const isStateful = isStatefulComponent(instance)
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)
 //  创建setup函数
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined

  isSSR && setInSSRSetupState(false)
  return setupResult
}

......

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean,
) {
  const Component = instance.type as ComponentOptions

  if (__DEV__) {
    ...
  }
  // 0. create render proxy property access cache
  instance.accessCache = Object.create(null)
  // 1. create public instance / render proxy
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
  if (__DEV__) {
    exposePropsOnRenderContext(instance)
  }
  // 2. call setup()
  const { setup } = Component
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    const reset = setCurrentInstance(instance)
    pauseTracking()
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [
        __DEV__ ? shallowReadonly(instance.props) : instance.props,
        setupContext,
      ],
    )
    resetTracking()
    reset()

    if (isPromise(setupResult)) {
      ...
    } else {
  	 // instance 将上下文暴露给其他模板使用
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
   // instance 将上下文暴露给其他模板使用
    finishComponentSetup(instance, isSSR)
  }
}

  1. 初始化组件
  2. 创建组件实例:每个组件都独立存储相关实例信息
  3. 设置组件实例:频道当前组件实例是否是一个有状态组件,初始化(props,slots),执行setup处理函数
  4. 创建渲染上下文:instance.ctx做一层proxy,对渲染上下文属性的访问和修改,代理到对setupState、ctx、data、props中的数据的访问和修改
  5. 创建setup函数上下文:对setup函数参数context的处理,也就是初始化context里的四个属性
  6. 执行setup函数获取结果:获取setup函数执行结果setupResult
  7. 处理setup函数执行结果
  8. 完成组件实例设置

watchEffect watch区别

  • watchEffect
function watchEffect(
  /*
  要运行的副作用函数。用来注册清理回调。清理回调会在该副作用的下一次执行前被调用,
  可以用来清理无效的副作用
  */
  effect: (onCleanup: OnCleanup) => void,
  /*
  可以用来调整副作用的刷新时机或调试副作用的依赖。
  默认情况下,侦听器在组件渲染之前执行,设置flust: 'post'将会使侦听器延迟到组件渲染之后再执行。
  在某些特殊情况(列如要使缓存失效),可能有必要在响应式依赖放生改变时立即出发侦察器,可以使用flush: 'sync'
  当多个属性同时更新时,这将会导致一些性能和数据一致性的问题
  */
  options?: WatchEffectOptions
): StopHandle

type OnCleanup = (cleanupFn: () => void) => void

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

type StopHandle = () => void

const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 输出 0
count.value++
// -> 输出 1

// 副作用清除
watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel)
  data.value = await response
})

//停止侦听器:
const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

  • watch
    侦听一个或多个响应式数据源,并在数据源变化时调用所给的会带哦函数
// 侦听单个来源
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): StopHandle

// 侦听多个来源
function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): StopHandle

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

type WatchSource<T> =
  | Ref<T> // ref
  | (() => T) // getter
  | T extends object
  ? T
  : never // 响应式对象

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认:false
  deep?: boolean // 默认:false
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  once?: boolean // 默认:false (3.4+)
}

与 watchEffect() 相比,watch() 使我们可以:

懒执行副作用;
更加明确是应该由哪个状态触发侦听器重新执行;
可以访问所侦听状态的前一个值和当前值。

// 监听reactive数据
const state = reactive({ count: 0 })
watch(
  () => state.count,
  // 当直接监听一个响应式对象时,监听器会自动启用深度监听
  (count, prevCount) => {
    /* ... */
  }
)

// 监听ref数据
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

// 当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

script setup 基本写法

<template>
	<MyComponent ref="myCompRef"/>
  	<button @click="count++">{{ count }}</button>
  	<button @click="onClick"></button>
</template>

<script setup>
import { ref,defineProps,defineEmits,provide,inject,defineExpose,onMounted, useSlots, useAttrs  } from 'vue'
import MyComponent from './MyComponent.vue'

// props
const props = defineProps({
  foo: String
})
//attrs
const attrs = useAttrs()

// slots
const slots = useSlots()

// emit
const emits = defineEmits(['change', 'delete'])
function onClick(){
	emits('change', '点击')
}

// expose
const myCompRef = ref()
onMounted(() => {
	myCompRef.value._expose()
})
// 子组件对应的暴露方式
function _expose(){}
defineExpose({
	_expose
})

// 提供响应式的值
const count = ref(0)
provide('count', count)

// 注入响应式的值
const count = inject('count')

// 注入一个值,若为空则使用提供的默认值
const bar = inject('path', '/default-path')

// 注入一个值,若为空则使用提供的函数类型的默认值
const fn = inject('function', () => {})


</script>

自定义指令

// 局部指令
<script setup>
// 在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令
const vFocus = {
  mounted: (el) => el.focus()
}
const vTest = {
  mounted: (el: any, binding: any, vnode: any, prevVnode: any) => {
    /**
     * el:指令绑定到的元素。这可以用于直接操作 DOM。
     * binding:一个对象,包含以下属性。
        value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2。
        oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
        arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"。
        modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
        instance:使用该指令的组件实例。
        dir:指令的定义对象。
     * vnode:代表绑定元素的底层 VNode。
     * prevVnode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
     */
  }
}
</script>

<template>
  <input v-focus />
  <div class="msg" v-test></div>
</template>


// 全局指令
const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
  /* ... */
})

defineAsyncComponent 异步加载组件

定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。

function defineAsyncComponent(
  source: AsyncComponentLoader | AsyncComponentOptions
): Component

type AsyncComponentLoader = () => Promise<Component>

interface AsyncComponentOptions {
  loader: AsyncComponentLoader
  loadingComponent?: Component
  errorComponent?: Component
  delay?: number
  timeout?: number
  suspensible?: boolean
  onError?: (
    error: Error,
    retry: () => void,
    fail: () => void,
    attempts: number
  ) => any
}


<template>
  <div>
    <AsyncComponent />
  </div>
</template>
 
<script>
import { defineAsyncComponent } from 'vue';
import Loading from "./components/Loading.vue"
import Error from "./components/Error.vue"
export default {
  components: {
    AsyncComponent: defineAsyncComponent(() =>
      loader: async () => {
      	 await getDataList()
      	 return import('./components/AsyncComponent.vue')
      },
      loadingComponent: Loading 
	  errorComponent: Error
    )
  }
}
</script>

vite和webpack区别

vite

vite使用原生ES模块进行开发,无需在编译时将所有代码转换为JS打包,从而提供了更快的热更新和自动刷新功能;
vite在开发模式下没有打包步骤,而是利用浏览器的ES Module Imports特性实现按需编译,这种模式极大的提高了开发环境的相应速度,在生产模式下,vite使用Rollup进行打包,提供更好的tree-shaking和代码压缩。
由于ES Module需要高版本浏览器支持,所以本地编译选用使用高版本浏览器

webpack

webpack拥有丰富的插件系统和高度可配置性,但由于全量的热更新策略导致编译速度慢;
webpack将所有模块打包为一个或多个文件,初次加载速度较慢
webpack插件生态非常成熟,使得在大型复杂项目中具有很高的灵活性

webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。而vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。由于现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。vite充分利用这一点,将开发环境的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。由于vite再启动时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。当浏览器请求某个模块时候,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译的时间,项目越复杂、模块越多、vite的优势越明显。
在HMR方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像webapck那样需要把该模块的相关依赖模块全部编译一次,效率更高。
当需要打包到生产环境时,vite使用传统的rollup进行打包,因此vite的主要优势在开发阶段,由于vite使用的是ES Module,因此在代码中不可以使用CommonJS

Pinia

与Vuex相比,Pinia提供了一个更简单的API,具有更少的仪式, 提供了Composition-API风格的API;更重要的是,在与TypeScript一起使用时具有可靠的类型推断支持

  • mutations 不再存在
  • 更友好的支持TypeScript支持
  • 不再又modules的嵌套结构,可以灵活的使用每一个store,它们通过扁平化的方式来相互使用
  • 也不再有命名空间的概念,不需要记住它们的复杂关系
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // 也可以这样定义
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++
    },
  },
})


<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

counter.count++
// 自动补全! ✨
counter.$patch({ count: counter.count + 1 })
// 或使用 action 代替
counter.increment()
</script>

<template>
  <!-- 直接从 store 中访问 state -->
  <div>Current Count: {{ counter.count }}</div>
</template>
import { defineStore } from 'pinia'

export const useTodos = defineStore('todos', {
  state: () => ({
    /** @type {{ text: string, id: number, isFinished: boolean }[]} */
    todos: [],
    /** @type {'all' | 'finished' | 'unfinished'} */
    filter: 'all',
    // 类型将自动推断为 number
    nextId: 0,
  }),
  getters: {
    finishedTodos(state) {
      // 自动补全! ✨
      return state.todos.filter((todo) => todo.isFinished)
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished)
    },
    /**
     * @returns {{ text: string, id: number, isFinished: boolean }[]}
     */
    filteredTodos(state) {
      if (this.filter === 'finished') {
        // 调用其他带有自动补全的 getters ✨
        return this.finishedTodos
      } else if (this.filter === 'unfinished') {
        return this.unfinishedTodos
      }
      return this.todos
    },
  },
  actions: {
    // 接受任何数量的参数,返回一个 Promise 或不返回
    addTodo(text) {
      // 你可以直接变更该状态
      this.todos.push({ text, id: this.nextId++, isFinished: false })
    },
  },
})

ts

第三方库定义类型声明

  1. 方式一:在自己库中进行类型声明(编写.d.ts文件)
// xx.d.ts
declare module "xx" {
	...
}

// 除了第三方的库外,还能为自己的一些全局变量定义类型声明
declare const shareName: string
declare function fn(): void
declare class Person{
	constructor(name: string)
}

// 声明文件模块
declare module "*.png"
declare module "*.jpg"
declare module "*.svg"
  1. 方式二:通过社区的一个公有库DefinitelyTyped存放类型声明文件
    • GitHub地址
    • 该库查找声明安装方式的地址:查找地址
    • 比如我们安装react的类型声明: npm i @types/react --save-dev

自定义组件类似声明

// 父组件
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
import { ref } from 'vue'

const helloWorldRef = ref<InstanceType<typeof HelloWorld>>()
function onClick() {
  helloWorldRef.value?.emitOnClick()
}
</script>

<template>
  <HelloWorld ref="helloWorldRef" msg="You did it!" />
</template>

<style scoped></style>


// 子组件
<script setup lang="ts">
defineProps<{
  msg: string
  info?: string
}>()

defineExpose({
  emitOnClick
})

function emitOnClick() {
  console.log('this is components')
}
</script>

<template>
  <div class="greetings">
    <h1 class="green">{{ msg }}</h1>
    <h3>
      You’ve successfully created a project with
      <a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
      <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
    </h3>
  </div>
</template>

<style scoped></style>


// env.d.ts
declare module '*.vue' {
  import { DefineComponent } from 'vue'
  const src: DefineComponent
  export default component
}

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue3中的`watch`与Vue2中的`watch`有一些重要的变化。在Vue3中,`watch`仍然允许我们监视响应式数据的变化,并对变化做出相应的操作。不过,Vue3引入了一个新的API——`watchEffect`来取代Vue2中的`watch`。 在Vue3中,`watch`可以通过以下几种方式使用: 1. **基础用法**:可以使用`watch`方法来监视一个响应式数据的变化,并在变化发生时执行回调函数。示例如下: ```javascript import { watch } from 'vue'; watch(() => { // 监视的响应式数据 return state.value; }, (newVal, oldVal) => { // 响应式数据发生变化时执行的回调函数 console.log(`new value: ${newVal}, old value: ${oldVal}`); }); ``` 2. **立即执行**:可以通过在`watch`选项中设置`immediate`为`true`,使得监听函数会在初始创建时立即执行一次。示例如下: ```javascript watch(() => { // 监视的响应式数据 return state.value; }, (newVal, oldVal) => { // 响应式数据发生变化时执行的回调函数 console.log(`new value: ${newVal}, old value: ${oldVal}`); }, { immediate: true }); ``` 3. **深度监听**:可以通过设置`deep`为`true`来进行深度监听,以便检测对象内部值的变化。示例如下: ```javascript watch(() => { // 监视的响应式数据 return state.obj; }, (newVal, oldVal) => { // 响应式数据发生变化时执行的回调函数 console.log(`new value: ${newVal}, old value: ${oldVal}`); }, { deep: true }); ``` 此外,Vue3还引入了`watchEffect`,它可以轻松地监视响应式数据的变化,并在其变化时执行回调函数。与`watch`不同的是,`watchEffect`不需要显式指定要监视的数据,它会自动追踪代码中使用的响应式数据,并在其变化时触发。示例如下: ```javascript import { watchEffect } from 'vue'; watchEffect(() => { // 使用到的响应式数据 console.log(state.value); }); ``` 需要注意的是,在Vue3中,`watch`和`watchEffect`都是基于副作用函数实现的,所以它们可以在组件渲染期间多次执行。 以上是Vue3中`watch`和`watchEffect`的详细说明,希望能对你有所帮助!如有其他问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值