vue2.0与vue3.0

Object.defineProperty

我们知道 Vue2 是响应式原理基于 Object.defineProperty 方法重定义对象的 getter 与 setter,vue3 则基于 Proxy 代理对象,拦截对象属性的访问与赋值过程。差异在于,前 者并不能对诸如数组长度变化、增删元素操作、对象新增属性进行感知,在 vue 层面不得不 重写一些数组方法(push、pop、unshift、shift 等),动态添加响应式属性,也要使用 $set 方法等。而 Proxy 则完美地从根上解决了这些问题,不过对于不支持 Proxy 对象的浏览器(如 IE),如果要使用 vue3 依然要进行降级兼容

createApp

import { createApp, defineAsyncComponent } from 'vue'
import App from './App.vue'
import MyApp from './MyApp.vue'

可以挂载两个实例,实现完全隔离

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

异步组件的使用,挂载到哪里就只能在注册那个地方实现

const app = createApp. mount('#root')

const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComp.vue') ) app.component('async-component', AsyncComp);

1.异步组件在打包的时候分离出来了,可以缓存它,多个组件同时使用,可以复用。
2.提前用占位符代替,后面在慢慢加载

const myapp = createApp. mount('#app')

自定义指令更新

// vue2

bind - 指令绑定到元素后发生。只发生一次。
inserted - 元素插入父 DOM 后发生。
update - 当元素更新,但子元素尚未更新时,将调用此钩子。
componentUpdated - 一旦组件和子级被更新,就会调用这个钩子。
unbind - 一旦指令被移除,就会调用这个钩子。也只调用一次。.
// vue3
bind beforeMount
insertedmounted beforeUpdate :新的!这是在元素本身更新之前调用的,很像组件生命周期钩子。
update → 移除!有太多的相似之处要更新,所以这是多余的,请改用 updated。
componentUpdated updated beforeUnmount :新的!与组件生命周期钩子类似,它将在卸载元素之前调用。
unbind -> unmounted

to:body

比如一个弹框组件,要把它放到body 标签里去渲染,避免样式混淆<pop to:body></pop>;比如路由标题每个页面都有一个标题,是页面的属性,逻辑上是全局,业务上属于模块的

data

data 声明不再接收纯 JavaScript object,而必须使用 function 声明返回。

export default { data() { return { state: '', } } }

多根节点组件、函数式组件

多根节点允许 template 标签内直接出现多个子集标签,注意默认根节点需要明确指定。

<template>
<header></header>
<main v-bind $arrtr style={color:red}></main>
<footer><footer>
</template>

如果在main组件中,如果template只有一个子节点,会继承这个样式,如果有两个子节点,就不会继承样式了,需要在继承的那个节点使用$attrs

<template>
 <div id="">
 <div class="MultiNode" v-bind="$attrs">
      1
 </div>
 <div class="" v-bind="$attrs">
      2
 </div>
 </div>
</template>
// $attrs 默认本来绑定在唯一根节点上,多节点时需要明确指定挂在哪个节点下,用来指定继承父节点的属性

在 Vue 2 中,函数式组件有两个主要用例:

  1. 作为性能优化,因为它们的初始化速度比有状态组件快得多
  2. 返回多个根节点

Vue 3 中,函数式组件剩下的唯一用例就是简单组件。只能使用普通函数来声明。

// vue2 Functional.vue
<template>
<div>{{ msg }} </div>
</template>
<script>
export default { 
  functional: true, // 此处需要定义为ture 就可以使用函数式组件
    props: ['msg']
  }
</script>

// vue3 Functional.js 个人感觉好难用
import { h } from 'vue'
export default function Functional(props, context) {
return h('div',
context.attrs,  // 属性
props.msg  // children
)
}
Functional.props = ['msg']

一些其他与开发者关系较为密切的更新

在同一元素上使用的 v-if 和 v-for 优先级已更改。即 if 比 for 的优先级更高了;

vue3渲染函数不再提供 createElement 参数(通常简写为 h),而是从依赖中导入; 标签上的属性与绑定对象的属性冲突时,以排在后面的属性为准,不再给单独属性更高 的优先级。例如:

<your-comp name="Tom" v-bind="{ name: 'Jim' }" />
// vue2 your-comp 的 props:
name:'Tom'
// vue3 your-comp 的 props:
name:'Jim' 如果顺序是 <your-comp v-bind="{ name: 'Jim' }" name="Tom" />
则 vue2 和 vue3 的 props 均为
name:'Tom
如果顺序是 <your-comp v-bind="{ name: 'Jim' }" name="Tom" />
则 vue2 和 vue3 的 props 均为
name:'Tom'

最具有颠覆意义的响应-组合 API

这也是我们学习和转向 vue3 最有意义的一部分内容,可以这么说,如果没有掌握这一 部分内容,那么即使我们底层使用了 vue3,也不过是写 vue2。就像我们拥有了一台铲车, 却还在坚持靠人力把重物装卸到车叉上,只把铲车当运输工具一样。

setup 函数
组件创建前执行的初始化函数,默认参数包含 props 和 context。context 可以理解为 组件实例挂载之前的上下文(虽然这么理解,但不是实例本身,这点要清楚),所以实例上 的 attrs,slots,emit 可以在 context 上访问到。

reactive(深层) | readonly(只读) | shallowReactive(浅层) | shallowReadonly (只读浅层)
用于创建响应式的对象(只能是对象),isReactive 可以判断对象是否为响应式的。isProxy 区分是哪 种方法创建的代理对象,isReadonly 判断对象是否为 readonly(不可被修改) 创建。shallowXXX 根据名称 可知,只将对象自身的浅层属性进行转换,深层属性保持不变。

toRaw | markRaw
前者返回代理对象的源对象,后者将普通对象转换为不可代理的对象。

ref | toRef | unref | toRefs | isRef | shallowRef | triggerRef
常用于包装基本类型值为响应式对象,例如 const box = ref(0), box.value === 0 unref: isRef(data) ? data.value : data shallowRef:创建浅层的包装对象 triggerRef:人为触发订阅了 shallowRef 的副作用。

effect | watch | watchEffect
对数据源的更新订阅,一旦订阅的数据发生变化,将自动执行副作用函数。效果与 vue2 的 watch 配置类似。

computed
基于响应式数据生成衍生数据,且衍生数据会同步响应式数据的变化,效果与 vue2 的 computed 属性一样。

provide,inject
跨组件传递数据的方案。 基于 setup 方法使用的生命周期钩子同样有对应更新

基于 setup 方法使用的生命周期钩子同样有对应更新

beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeUnmount -> onBeforeUnmount
unmounted -> onUnmounted
errorCaptured -> onErrorCaptured // 错误上报钩子,仅做了解
renderTracked -> onRenderTracked // { key, target, type } 仅做了解 renderTriggered -> onRenderTriggered // { key, target, type } 仅做了解

<template>
  <div @click="increment">
    compositionVue: {{data.count}}<br />
  </div>
  <span @click="pintCountRef">count: {{count}}</span>
  <p>f: {{f}}</p>
  <p>computedVal: {{computedVal}}</p>
</template>

<script>
import { reactive, toRaw, ref, effect, toRefs, watch, computed } from 'vue'
export default {
  setup(props, context) {
    const data = reactive({ count: 0, obj: {f: 2} })  // 定义了一个对象类型的 响应式对象
    
    const count = ref(0) // 定义了一个基本类型 响应式对象
    
    const pintCountRef = () => {
      
      console.log(++count.value)  // count 是一个对象, 使用count.value 来取值,标签里可以直接用,模板层面给解析出来了
    }
    
    const origin = toRaw(data) // 返回源对象,不用也不影响
    
    effect(() => {// pagination, search query  vue3 只关心变化了的值,变化之后,比如搜索功能,分页,查询参数,当变化的时候,不需要手动的调用请求
      console.log(data.count); // 副作用,如果使用了响应化数据,如果响应化数据有变化的时候就执行回调
    });
    
    
   /* watch([count, data.count], (value) => { //比vue2 有点变化
      // 第一个参数可以是函数,返回值变了就回调
      // 第一个参数可以是 ref / reactive,ref/reactive 值变了就回调
      // 第一个参数可以是 数组,数组每一项变了就回调
      alert(value)
    }); */
    
    watch(() => count.value > 5, (value) => { // 可以是条件,满足条件执行一次的效果
      // 第一个参数可以是函数,返回值变了就回调
      // 第一个参数可以是 ref / reactive,ref/reactive 值变了就回调
      // 第一个参数可以是 数组,数组每一项变了就回调
      alert(value)
    });
    
    function increment () {
      data.count++
      data.obj.f++ // 页面上的f 不会变,不是响应式变量,
    }
    
    const { f } = toRefs(data.obj);  // 基本值都是包装过的在解构
    // const f = ref(data.obj.f)
    
    const computedVal = computed(() => count.value + data.count); //计算衍生值 惰性的,只有变了才执行,不能对它进行重新赋值
    const computedVal2 = computed(() => count.value * data.count); // vue2里可以写多个,这里可以写一个,执行多次
    

    return { data, increment, count, pintCountRef, f, computedVal } // 在这里return 之后 template里才能获取
  }
  }
</script>

数据传递demo

// 父组件

export default {
  name: 'MyApp',
  // props: [''],
  data() {
    return {
      state: {
        value: 0
      }
    }
  },
  provide() { // 要传递给子组件的变量和方法
    return {
      state: readonly(this.state), // 使用readonly 子组件不能改
      onChange: () => {
        this.state.value = Math.random()
      }
    }
  },
  components: {  composition  }
}

// 子组件

import { h, reactive, inject, onMounted } from 'vue'


export default {
  setup(props, context) {
    const data = reactive({ count: 0 })
    
    const increment = () => {
      data.count++
    }
    
    const state = inject('state')  // 注入state 获取到父组件的state
    const onChange = inject('onChange') // 获取父组件的方法
    /* const onChange = () => {  // 直接变父组件的state,会导致父组件里的state变化,其他引用都会变化,会出问题
      state.value++
    } */
    
    onMounted(() => { // 不是生命周期的定义,是一个注册,可以注册多个;没有this
      console.log('task1')
    })
    
    onMounted(() => {
      console.log('task2')
    })
    
    return () => h('div', {
      onClick: onChange
    }, 'composition: ', data.count, 'state.value', state.value)
  }
}

终极简化:单文件组件 defineProps,defineEmits 直接在

// 在<script setup>内部的顶层变量,均能直接用于模板部分,
// 省略了 setup 函数及其返回值
<template>
<span @click="increment">{{count}}</span>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => {
count.value++
}
</script>

下面是自定义hooks demo

// customHooks

<template>
  customHook: {{loading ? '下载中' : '下载完成'}}<button @click="refresh">重试</button>
  <ul>
    <li v-for="{name, id} in info.value" :key="name">{{name}}</li>
  </ul>
</template>

<script>
  import useUserInfo from './hooks'
  import { ref, useSlots } from 'vue'
  export default {
    setup(props, { slots }) {
      const id = ref(0);
      const { info, loading } = useUserInfo(id);
      const refresh = () => {
        id.value++;
      }
      
      return { info, loading, refresh }
    }
  }
</script>

<style>
</style>

// hooks

import { ref, onMounted, watch, reactive } from 'vue'

const fetch = (id) => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve([
      { name: 'user-' + Math.random(), id: 1 },
      { name: 'user-' + Math.random(), id: 2 },
      { name: 'user-' + Math.random(), id: 3 },
      { name: 'user-' + Math.random(), id: 4 },
      { name: 'user-' + Math.random(), id: 5 },
    ]);
  }, 200)
})

export default function useUserInfo(id) { // id 为响应式数据源
  const info = reactive({value: []})
  const loading = ref(false)

  const getUserInfo = () => {
    loading.value = true
    fetch(id.value).then(user => {
      info.value = user
      loading.value = false
    })
  }
  onMounted(getUserInfo)
  watch(() => id.value, getUserInfo);
  
  return { info, loading }
}
  • 28
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值