vue3学习笔记(对比vue2学习)

setup

setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API的入口。
setup是一个函数,组件中用到的数据、方法都要配置在setUp中,它会在beforeCreate之前执行一次

  • 若返回一个对象,那么对象中的属性、方法在模版中都可以直接使用
  • 返回一个渲染函数、可以自定义渲染内容
import { ref, reactive } from 'vue'
export default {
  name: 'Home',
  setup(props, context) {
    const title = ref('标题')
    const data = reactive({
      value: '哈哈哈'
    })
    return {
      title,
      data
    }
  }
}
  • props: 值为对象,包含组件外部传递过来,且组件内部声明接收了的属性
  • context: 上下文对象
    • attrs: 值为对象,包含组件外部传递过来,且没有在props配置中声明的属性,等同于this.$attrs
    • slots: 收到的插槽内容,相当于this.$slots
    • emit: 分发自定义事件的函数,相当于this.$emit
  • v2配置(data,message,computed。。)中可以访问到setup中的属性和方法,但反之不行,如果不同名,setup优先

reactive、ref、和toRefs

在 vue2.x 中, 定义数据都是在data中, 但是 Vue3.x 可以使用reactive和ref来进行数据定义。 那么ref和reactive他们有什么区别呢?

  • 使用ref可以创建一个包含响应式数据的引用对象(简称ref对象),可以是基本类型、也可以是对象
const xxx = ref(value)
//使用
xxx.value
// 在模版中
<div>{{xxx}}</div>
  • 使用reactive定义一个对象类型的响应式数据,但是不能代理基本数据类型
const xxx = reactive({
  yy: ''
})
xxx.yy
  • toRef创建ref
const state = reactive({
  foo: 1,
  bar: 2
})
const fooRef = toRef(state, 'foo')
// 传递props
export default {
  setup(props) {
    useSomeFeature(toRef(props, 'foo'))
  }
}
  • toRefs响应式转换
    • 将响应式对象转换成普通对象
  • shallowReactive响应式外层转换, 适用于一个对象,结构比较深,但变化时只是外层属性变化
const state = shallowReactive({
  foo: 1,
  bar: {
    foo1: 2
  }
})
  • shallowRef基本数据响应式, 不进行对象的响应式处理。适用于一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象
  • readonly 让一个数据变成只读的,应用不希望数据被修改时
const readOnlyData = readonly({
  xxx: 'hello' // 只读
  aa: {
    bb: 2 // 只读
  }
})
  • shallowReadOnly响应式变只读(浅只读)
const shallowData = shallowReadOnly({
  foo: 1, //只读
  nested: {
    bar: 2 // 非只读
  }
})
  • toRaw 将响应式转换成非响应式
  • customRef 依赖更新控制
import { customRef } from 'vue'
export default {
  name: 'Home',
  setup() {
    const fn = function() {
      return customRef((track, trigger)) {
        return {
          get() {
              track()
             return value
          },
          set(newValue) {
            trigger()
          }
        }
      }
    }
  }
}
  • 单Ref
import { ref }  from 'vue'
setup() {
  const node = ref(null)
  onMounted(() => {
    console.log(node.value)
  })
}
  • 循环中的ref,需要自行绑定
setup() {
  const itemsRef = []
  const setItemRef = el => {
    if (el) {
      itemsRef.push(el)
    }
  }
}
  • computed
import { computed } from 'vue';
setup() {
  let fullName = computed(() => {
    return person.firsName + '-' + person.lastName
  })
  let fullName = computed(() => {
    get() {
      return person.firsName + '-' + person.lastName
    },
    set(value) {
      const nameArr = value.split('-');
      person.firstName = nameArr[0]
      person.lastName = nameArr[1]
    }
  })
  return fullName
}
  • watch
import { watch } from 'vue'
watch(sum, (newValue, oldValue) => {
  
}, {
  immediate: true,
  deep: false
})
  • watchEffect不需要那个指明监视哪个属性,监视的回调中用到哪个属性就监视哪个,不需要写返回值
watchEffet(() => {
  const x1 = sum.value
  const x2 = person.age
  console.log('watchEffect')
})

生命周期

在这里插入图片描述
在这里插入图片描述

自定义 Hooks

我们可以将一些公共逻辑封装成一个 hook, 我们约定这些「自定义 Hook」以 use 作为前缀,和普通的函数加以区分。

import { ref、 Ref、computed } from 'vue'
export dafault function useCount(initValue = 1) {
  const count = ref(initValue);
  const multiple = computed(() =>count.value * 2)

  const increase = (delta) => {
     count.value +=1;
  }
  const decrease = (delta) => {
    count.value -= 1;
  }
  return {
    count,
    increase,
    decrease,
    multiple
  }
}

<template>
  <p>倍数: {{ multiple }}</p>
  <div>
    <button @click="increase()">加一</button>
     <button @click="decrease()">减一</button>
  </div>  
</template>
<script>
  import useCount from './useCount';
  setup() {
    const { count, increase, decrease, multiple } = useCount(10)
    return {
      count,
      increase,
      decrease,
      multiple
    }
  }
  
</script>

简单对比 vue2.x 与 vue3.x 响应式

  1. Object.defineProperty只能劫持对象的属性, 而 Proxy 是直接代理对象
    由于Object.defineProperty只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是 Proxy 直接代理对象, 不需要遍历操作
  2. Object.defineProperty对新增属性需要手动进行Observe
    因为Object.defineProperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象, 对其新增属性再次使用Object.defineProperty进行劫持。也就是 Vue2.x 中给数组和对象新增属性时,需要使用$set才能保证新增的属性也是响应式的, $set内部也是通过调用Object.defineProperty去处理的。

Teleport

Teleport 就像是哆啦 A 梦中的「任意门」,任意门的作用就是可以将人瞬间传送到另一个地方。有了这个认识,我们再来看一下为什么需要用到 Teleport 的特性呢,看一个小例子: 在子组件Header中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,我们可以用包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。

<body>
  <div id="app"></div>
  <div id="dialog"></div>
</body>

<template>
  <telpport to="#dialog"></telpport>
    ...
</template>

Suspense

Suspense 只是一个带插槽的组件,只是它的插槽指定了default 和 fallback 两种状态。
vue2写法

<div v-if="!loading">{{data}}</div>
<div v-else>{{loading... }}</div>

vue3写法

<Suspense>
  <template #default>
      <async-component></async-component>
  </template>
  <template #fallback>
    <div>loading...</div>
  </template>
  
</Suspense>

片段(Fragment)

vue2中一个组件只能定义一个根节点

<template>
  <div></div>
</template>

在vue3中

<template>
  <div></div>
  <div></div>
</template>

更好的 Tree-Shaking

受影响的 API

  • Vue.nextTick
  • Vue.observable(用 Vue.reactive 替换)
  • Vue.version
  • Vue.compile(仅限完整版本时可用)
  • Vue.set(仅在 2.x 兼容版本中可用)
  • Vue.delete(与上同)

内置工具

变更

slot 具名插槽语法

vue2
<slot name="title"></slot>
<template slot="title">
<h1>插槽</h1>
</template>

vue3
<template #content="scoped">
  <div v-for="item in scoped.data"></div>
</template>

自定义指令

vue2
Vue.directive('focus', {
  inserted: function(el) {
    el.focus()
  }
})

vue3 
const { createApp } from 'vue'
const app = createApp({})
app.directive('focus', {
    mounted(el) {
      el.focus()
    }
})

v-model 升级

异步组件

Vue3中使用defineAsyncComponent 定义异步组件

<template>
  <AsyncPage/>
  
</template>
<sciprt>
    import { defineAsyncComponent  } from 'vue';
    components: {
      AsyncPage: defineAsyncComponent(() => import ("./Page.vue"))
      AsyncPageOptions: defineAsyncComponent({
        loader: () => import ("./Page.vue"),
        dealu: 200,
        timeout: 3000,
        errorComponent: () => import("./Error.vue"),
        loadinComponent: () => import("./Loading.vue")
      })
    }
  </script>

Vue3设计思想以及响应式源码

Vue2缺点

  • 对TS支持不友好(所有属性都在this上,难以推倒数据类型)
  • 全局API都在Vue对象的原型上,无法实现TreeShaking
  • 对跨平台渲染支持不友好
  • Vue3设计

  • Vue3模块耦合度低,可以独立使用
  • Vue3框架更小,更易扩展
  • 基于monorepo管理项目,实现模块拆分

vue3源码

paclages

  • reactivity: 响应式系统
  • runtime-core: 与平台(web用的最多的是)无关的运行时核心
  • runtime-dom: 针对浏览器的运行时,包括DOM API,属性。事件处理
  • runtime-test: 测试文件
  • server-renderer: 服务端渲染
  • compiler-core: 与平台无关的编译器核心
  • compiler-dom: 针对浏览器的编译模块
  • compiler-ssr: 服务端渲染

scripts

Vue3在开发环境自己使用的,开发环境使用esbuild打包,生成环境使用rollup打包

Vue3中Reactivity模块

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  if (!isObject(target)) {
    if (
__DEV__
) {
      warn(
        
value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(           target,         )}
,
      )
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )
  proxyMap.set(target, proxy)
  return proxy
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值