速懂vue3.0新特性及vue2.0区别

2020年9月份beta版本出来后就一直追3.0,作为第一批吃螃蟹的人,这里把3.0的一些知识分享一下,良心之作。喜欢就收藏吧!

TypeScript传送门


从vue2.0到vue3.0必备知识

一、简单介绍vue3.0(性能比2.x快1.2~2倍)

  • Vue3支持大多数的Vue2的特性。如果3.0项目中2.0跟3.0混搭写法,方法重名的话3.0优先

  • Vue3中设计了一套强大的组合APi(Compostion API)代替了Vue2中的option API ,复用性更强了。可自定义hook函数,不需要代码都写在一个template(再也不怕代码多起来,滚轮滚到手发抖了)—(面向对象编程—>函数式编程)

  • 按需编译,体积比Vue2.x更小

Tree shaking:

在Vue2中,很多函数都挂载到全局 Vue 对象上,如:nextTick、set 函数等,虽然我们不常用,但打包时只要引入 Vue 这些全局函数会打包进 bundle 中

而Vue3源码引入tree shaking特性,将全局 API 进行分块。如果你不使用其某些功能,它们将不会包含在你的基础包中
比如你要用watch 就是import {watch} from 'vue' 其他的computed 没用到就不会给你打包减少体积
  • Vue3更好的支持TypeScript(TS)
  • Vue3中使用了Proxy配合Reflect 代替了Vue2中Object.defineProperty()方法实现数据的响应式(数据代理)
  • Vue3 优化Vdom,响应式性能提升
diff 方法优化:

在Vue2中,每次更新diff,都是全量对比,Vue3则只对比带有标记(静态标记)的,这样大大减少了非动态内容的对比消耗,
数据发生变化时,会生成一个新的dom树,然后和之前的dom树进行比较,找到变更的节点然后更新到真实的dom上。在比较的过程中会对没有发生改变的dom也进行比较,这样消耗了一定的时间。
在vue3中,在创建虚拟dom时会根据dom的内容添加一个静态标记,在数据发生变化时就会带着静态标记的节点去对比,能够快速找到需要更新的dom。

新增了 patch flag(补丁标记)

TEXT = 1 // 动态文本节点
CLASS=1<<1,1 // 2 //动态class
STYLE=1<<2,// 4 //动态style
PROPS=1<<3,// 8 //动态属性,但不包含类名和样式
FULLPR0PS=1<<4,// 16 //具有动态key属性,当key改变时,需要进行完整的diff比较。
HYDRATE_ EVENTS = 1 << 5,// 32 //带有监听事件的节点
STABLE FRAGMENT = 1 << 6, // 64 //一个不会改变子节点顺序的fragment
KEYED_ FRAGMENT = 1 << 7, // 128 //带有key属性的fragment 或部分子字节有key
UNKEYED FRAGMENT = 1<< 8, // 256 //子节点没有key 的fragment
NEED PATCH = 1 << 9, // 512 //一个节点只会进行非props比较
DYNAMIC_SLOTS = 1 << 10 // 1024 // 动态slot
HOISTED = -1 // 静态节点
BALL = -2

patch flag 的强大之处在于,当你的 diff 算法走到 _createBlock 函数的时候,会忽略所有的静态节点,只对有标记的动态节点进行对比,而且在多层的嵌套下依然有效。
  • 新的组件: Fragment(片段) / Teleport(瞬移) / Suspense(不确定)
  • 设计了一个新的脚手架工具—vite,npm run dev 秒开,热重载也很快。这种开发体验真是很爽,拒绝等待。
  • 全局 API 现在只能作为 ES 模块构建的命名导出进行访问
  • Vue3可多个根组件
  • Vue3中v-if优先于v-for,移除过滤器

二、常用Composition API

import {
  defineComponent,//目的是定义一个组件,vue3如果用ts,导出时候要用 defineComponent,这俩是配对的,为了类型的审查正确
  defineAsyncComponent,//vue3中动态引入组件
  provide, //祖孙组件传参
  inject, //祖孙组件传参
  reactive, //定义多个数据的响应式,可配合torefs使用方便
  toRefs, //torefs函数可以将reavtive创建的响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref,html模板中直接使用变量显示数据
  ref, //定义一个数据的响应式(一般是基本数据类型),通过.value获取到值,如果是object之类会自动转化为reactive
  computed, //计算属性,如果只传入一个回调函数,表示的是get,可get,set
  watch, //监听一个或多个属性或对象,默认不执行---可加参数immediate表示默认执行一次,deep表示深度监视
  watchEffect, //不需要配置immediate,默认就会执行一次。监视所有回调中使用的数据
  onMounted, //生命周期
  onUnmounted,
  onUpdated,
  onBeforeUpdate,
  onBeforeMount,
  onBeforeUnmount,
  nextTick,//整个视图都渲染完毕后
  customRef,//追踪数据并告诉Vue去触发界面更新,可用于对页面更新进行一个防抖
  readonly,//数据只读
  shallowReactive,// 只处理了对象内最外层属性的响应式(也就是浅响应式)
  shallowRef,//只处理了value的响应式, 不进行对象的reactive处理
  toRaw,//返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。
} from "vue";

1.setup(props,context)

setup是组合api的入口函数,函数返回对象,html模板中可以直接使用。
setup细节问题:
1.setup在beforeCreate之前执行。
在这里插入图片描述

由此推断setup在执行的时候,当前组件还没有创建出来,也意味着组件实例对象this根本不能用(undefined)
2.setup中的返回值是一个对象,内部的属性和方法是给html模板使用的。

  1. props即为父组件传递过来的值,当你在steup函数中获取值不能像vue2.0中this.值,是获取不到的,要props.值
  2. context上下文对象,里面有attrs,slots,emit
    在这里插入图片描述
import {defineComponent} from "vue";
export default defineComponent({
    setup(props, {attrs,slots,emit}){
    	console.log("this=>", this); //undefined
    	console.log(props);//父组件传过来是值
    	console.log(slots);//插槽
    	console.log(attrs); //当前组件标签上的属性,不带冒号的,带冒号是props
    	//如<Child :msg="count" text="我是src" />  text是attrs,mag是props
    	---------------------
    	const addCount = () => {
      	emit("demoHander", "我的子组件传来的");//向父组件传参,这种写法父组件@demoHander='函数'
      	//或者
      	props.demoHander('我的子组件传来的')//这种写法父组件:demoHander='函数'
    };
    return {};//必须为一个对象
  }
  });

2.ref,reactive

<template>
  <div>
    {{ count }}
    {{state.age}}
    <button @click="add"></button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, reactive} from "vue";
export default defineComponent({
  setup() {
    // let count = 0;//这个数据不是响应式数据,(响应式数据:数据变化,页面跟着渲染)
    let count = ref(0); //ref一般定义一个数据的响应式(一般是基本数据类型,单独定义某一个常量),方法中要通过count.value获取到值,如果ref是一个对象也可以,会自动转为reactive
    ---------------------------------------------
    let obj = {
      name: "tom",
      age: 25,
      wife: {
        name: "marry",
        age: 22,
      },
    };
    let state = reactive(obj); //reactive一般定义多个数据(复杂数据类型)的响应式,如果不使用torefs的话,html模板中要使用state.age显示数据
    //方法
    const add = () => {
      count.value++;//操作ref定义的值
      state.age++;//操作reactive定义的值
      ------------------------
      // obj.age++;这种方式页面不会更新渲染,需要操作代理对象才有用
      // delete state.wife//删除某个属性,页面会更新
    };
    return {
      count,
      add,
      state //响应式数据
      //...state 不是响应式数据
      //...toRefs(state) 响应式数据
    };
  },
});
</script>

reactive不能处理简单数据类型。如reactive(10)是无法响应式的,必须是一个对象reactive({ })
ref 是一个对象也可以,会自动转为reactive。如ref({ id:10 })

ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
如果用ref对象/数组, 内部会判断是否是ref类型,如果不是,使用toReactive方法将对象/数组转换为reactive的代理对象
当 ref 在模板中作为顶层 property 被访问时,它们会被自动“解包”,所以不需要使用 .value。

ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
reactive内部: 通过使用Proxy对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据

注意: 解构state 或者 扩展运算符都会使数据失去响应性
在这里插入图片描述

reactive改用toRefs配合使用

toRefs()函数可以将reactive创建出来的响应式对象,转换为普通对象,只不过这个对象上的每个属性节点,都是ref()类型的响应式数据
(使用…操作符返回的对象,不会失去响应性)

<template>
  <div>
    <!-- 不使用toRefs:{{state.age}} -->
    <!--使用toRefs -->{{ age }}
  </div>
</template>

<script lang="ts">
import { defineComponent,reactive,toRefs} from "vue";
export default defineComponent({
  setup() {
    let obj = {
      name: "tom",
      age: 25,
      wife: {
        name: "marry",
        age: 22,
      },
    };
    let state = reactive(obj);
    return {
         //state
      ...toRefs(state) // toRefs可以在不丢失响应式的情况下对返回的对象进行分解使用
    };
  },
});
</script>

ref获取元素

<template>
  <div>
    <input type="text" ref="inputRef" name="" id="" />
  </div>
</template>

<script lang="ts">
import { defineComponent, ref,} from "vue";
export default defineComponent({
  setup() {
    //通过ref自动获取焦点
    const inputRef = ref<HTMLElement | null>(null);
    onMounted(() => {
      inputRef.value && inputRef.value.focus();
    });
    return {
      inputRef,
    };
  },
});
</script>

如果项目中方法比较多时,可以这样处理,方便看
在这里插入图片描述

3.watch,watchEffect

watch监听一个属性或对象

setup() {
  const x = ref(0);
  let state = reactive({
      fristName: "东方",
      laetName: "不败",
      fallName3:""
    });
    ------------方法一,一个对象
   watch(state,({fristName,laetName})=>{
        fallName3.value=fristName+'_'+laetName
      },{
      immediate:true,
      deep:true,
      flush: "post", // 如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: 'post' 选项
     // 后置刷新的 watchEffect() 也有个更方便的别名 watchPostEffect()}
        )
    //immediate表示默认执行一次
    //deep表示深度监视
	---------------------- 单个 ref
    watch(
      x,
      (newVal, oldVal) => {
        console.log(newVal, oldVal);
      });
    ------------方法二 reactive创建的对象
    const stop=watch(() => state.fristName, (val,oldval) => {
    	console.log(val);
   },{deep:true});
   
   //直接调用stop(),可以清除监听
   
   return {
      ...toRefs(state)
    };
}

watch监听多个属性

注意:
ref 直接监听/箭头函数返回
reactive对象必须用箭头函数返回 ,否则不能侦听响应式对象的 property

// watch多个数据用数组

   const x = ref(0);
   const y = ref(0);
   let state = reactive({
      count: 0,
      age: "",
    });
    
 watch([x, () => y.value, () => state.count], ([newX, newY, newCount]) => {
      console.log(newX, newY, newCount);
    });

watchEffect

// 监视所有回调中使用的数据,没有使用的不监听。默认触发一次,不需要配置immediate
      watchEffect(()=>{
          // 这里只监听count,x
      		console.log("watchEffect", state.count, x.value);
      })

vue2.0写法

  watch: {
    /**
     * 监听路由变化刷新页面
     */
    $route(to) {
      logger.log("watch router invoke", to.query);

      this.initPage();
    },
    configureList: {
      handler(newVale) {
        logger.log("watch configureList invoke", newVale);
      },
      deep: true,
      immediate: false,
    },
  },

watch vs. watchEffect
watch 只追踪明确侦听的源。我们能更加精确地控制回调函数的触发时机
watchEffect 自动追踪所有能访问到的响应式 property。代码往往更简洁,但其响应性依赖关系不那么明确

4.computed

注意:计算属性如果只传入一个回调函数,表示的是get。

场景:三个input任意一个值变化,其他两个也会变化

<input type="text" v-model="state.fristName"/>
<input type="text" v-model="state.laetName"/>
<input type="text" v-model="fallName"/>

计算属性如果只传入一个回调函数,表示的是get。即fristName或laetName改变,fallName发生改变。但是fallName发生改变,其他两个值不会变化

 setup() {
    let state = reactive({
      fristName: "东方",
      laetName: "不败",
    });
    //fallName 返回的是一个ref对象
    const fallName = computed(() => {
      return state.fristName + "_" + state.laetName;
    });
  }

解决办法:

computed有不光有get,还有set

 const fallName = computed({
      get() {
        return state.fristName + "_" + state.laetName;
      },
      set(val: any) {
        const names = val.split("_");
        state.fristName = names[0];
        state.laetName = names[1];
      },
    });

5.生命周期

在这里插入图片描述

6. provide 和 inject(实现跨层级组件(祖孙)间通信),也可放在main.ts里面进行全局挂载使用

父组件

<template>
  <h1>父组件</h1>
  <p>当前颜色: {{color}}</p>
  <Son />
</template>
<script lang="ts">
import { provide, ref } from 'vue'
import Son from './Son.vue'
export default {
  name: 'ProvideInject',
  components: {
    Son
  },
  setup() {
    const color = ref('red')
    provide('color', color)
    return {
      color
    }
  }
}
</script>

子组件

<template>
    <h2>子组件</h2>
    <GrandSon />
</template>
<script lang="ts">
import GrandSon from './GrandSon.vue'
export default {
  components: {
    GrandSon
  },
}
</script>

孙组件

<template>
  <h3 :style="{color}">孙子组件: {{color}}</h3>
</template>
<script lang="ts">
import { inject } from 'vue'
export default {
  setup() {
    const color = inject('color')
    return {
      color
    }
  }
}
</script>

7.自定义hook函数(即把函数抽出来复用)

抽离前:

<template>
    <button @click="add">add</button>
    <img :src="src" alt="">
</template>
<script>
import {reactive, toRefs} from "vue";
export default {
  setup() {
   const state = reactive({
    	index: 1,
    	src:''
  });
  	const add = () => {
       console.log(state.index)
    	state.index++;
    	console.log(state.index)
  	};
    return {
      ...toRefs(state),
      add
    };
  },
};

抽离后:

<template>
    <button @click="add">add</button>
    <img :src="src" alt="">
</template>
<script>
import hander from './hooks/demo.ts'
import {toRefs} from "vue";
export default {
  setup() {
 	let {state,add}=hander()
    return {
      ...toRefs(state),
      add
    };
  },
};

demo.ts

import {reactive} from "vue";
export default function useMousePosition () {
	const state = reactive({
    		index: 1,
    		src:''
 	 });
  	const add = () => {
       	console.log(state.index)
    	state.index++;
    	console.log(state.index)
  	};
  	return {state,add}
}

8.defineAsyncComponent配合Suspense异步组件使用

<script  lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
// vue正常引入组件
import SuspensChild from '@/components/Suspense/Son.vue'
// vue2中动态引入组件的写法:(在vue3中这种写法不行)
const AsyncCompotent=()=>import('./Son.vue')
//vue3中动态引入组件
const SuspensChild = defineAsyncComponent(() => import("./Son.vue"));
</script>

注意:由于组件是异步引入的,会有一瞬间的空白,所以可以用Suspense配合loading填充,可以让我们创建一个平滑的用户体验

<template>
  <Suspense>
     //v-slot:default也可以写成#default
    <template v-slot:default><!-- 组件加载完后显示-->
      <SuspensChild/> <!-- 异步组件 -->
    </template>
    <template v-slot:fallback> <!-- 组件加载完前显示-->
      <h1>LOADING...</h1> <!-- LOADING的内容 -->
    </template>
  </Suspense>
</template>
<script  lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
const SuspensChild = defineAsyncComponent(() => import("./Son.vue"));
export default defineComponent({
  name: "Suspense1",
  components: {
    SuspensChild,
  },
  async setup(){}
});
</script>

9.新组件 Fragment及Teleport

Fragment
  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中(类似react)
  • 好处: 减少标签层级, 减小内存占用
Teleport

Teleport 让组件的html在父组件界面外的特定标签(可能是body,也可能是其他)下插入显示

<template>
  <teleport to="body">  <!-- teleport 直接父级为body -->
    <div>
      <div>
        I'm a teleported modal! 
      </div>
    </div>
  </teleport>
</template>

在这里插入图片描述

10.customRef, readonly,shallowReactive,shallowRef, toRaw等

这些api在平常开发很少用到,这里我就不细讲了,有兴趣的朋友可以自行了解

11、全局挂载

在这里插入图片描述

三、setup语法糖,<script lang=“ts“ setup>

script-setup 的推出是为了让熟悉 3.0 的用户可以更高效率的开发组件,只需要给 script 标签添加一个 setup 属性,那么整个 script 就直接会变成 setup 函数,所有顶级变量、函数,均会自动暴露给模板使用(无需再一个个 return 了)。

ElementUI-plus等框架也是用的setup语法糖

如果你使用的是 TypeScript ,还需要借助 defineComponent 来帮助你对类型的自动推导,使用前:

<!-- 标准组件格式 -->
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  setup () {
    // ...

    return {
      // ...
    }
  }
})
</script>

使用script-setup 后:

<!-- 使用 script-setup 格式 -->
<script setup lang="ts">
  // ...
</script>

注意:
在 script-setup 模式下,新增了 4 个全局编译器宏,他们无需 import 就可以直接使用。
但是默认的情况下直接使用,项目的 eslint 会提示你没有导入,
可以配置一下 eslint

// 项目根目录下的 .eslintrc.js
module.exports = {
  // 原来的lint规则,补充下面的globals...
  globals: {
    defineProps: 'readonly',
    defineEmits: 'readonly',
    defineExpose: 'readonly',
    withDefaults: 'readonly',
  },
}

使用setup语法糖后:

  1. 变量无需进行 return
  2. 子组件无需手动注册
  3. props 的接收方式变化为defineProps接收
  4. emits 的接收方式变化为defineEmits
  5. attrs 的接收方式变化为useAttrs
  6. 顶级 await 的支持,script setup>结果代码会被编译成 async setup()
  7. 通过ref获取子组件的数据,需要子组件通过defineExpose暴露出来
  8. 自定义指令在 script setup> 中不需要注册,但他们必须遵循 vNameOfDirective 这样的命名规范
  9. 代码更简洁
  10. 能够使用纯 Typescript 声明 prop 和 emit

1. 变量无需进行 return

<!-- 标准组件格式 -->
<template>
  <p>{{ msg }}</p>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  setup () {
    const msg: string = 'Hello World!';
    
    // 要给 template 用的数据需要 return 出来才可以
    return {
      msg
    }
  }
})
</script>

在 script-setup 模式下

<template>
  <div class="home">
    <div>加数:<input type="text" v-model="state.addNum1" /></div>
    <div>加数:{{ state.addNum2 }}</div>
    <div>和:{{ state.sum }}</div>
    <button @click="handleAddNum">=</button>
  </div>
</template>

<script lang="ts" setup>
import { reactive, provide, computed,ref } from "vue";

const state = reactive({
  addNum1: 1,
  addNum2: 1,
  sum: 0,
  nav: computed(() => store.state.home.nav),
});
const hellow=ref()
const msg: string = 'Hello World!';

const handleAddNum = () => {
  const { addNum1, addNum2 } = state;
  state.sum = Number(addNum1) + addNum2;
};
</script>

2. 子组件无需手动注册

<!-- 标准组件格式 -->
<template>
  <Child />
</template>

<script lang="ts">
import { defineComponent } from 'vue'

// 导入子组件
import Child from '..//Child.vue'

export default defineComponent({
  // 需要启用子组件作为模板
  components: {
    Child
  },

  // 组件里的业务代码
  setup () {
    // ...
  }
})
</script>

在 script-setup 模式下,只需要导入组件即可,编译器会自动识别并启用。

<!-- 使用 script-setup 格式 -->
<template>
  <Child />
</template>

<script setup lang="ts">
import Child from '@cp/Child.vue'
</script>

3. props 的接收方式变化

由于整个 script 都变成了一个大的 setup function ,没有了组件选项,也没有了 setup 入参,所以没办法和标准写法一样去接收 props 了。

这里需要使用一个全新的 API :defineProps

defineProps([
  'name',
  'userInfo',
  'tags'
])

如果 script 里的方法要拿到 props 的值

const props = defineProps([
  'name',
  'userInfo',
  'tags'
])

console.log(props.name);
const { tags} = toRefs(props)

props校验机制:

defineProps({
  name: {
    type: String,
    required: false,
    default: 'Petter'
  },
  userInfo: Object,
  tags: Array
});

或者使用ts校验

interface UserInfo {
  id: number;
  age: number;
}

defineProps<{
  nname?: string;
  tags: string[];
  userInfo: UserInfo;
}>();

4. emits 的接收方式变化

defineEmits 的用法和原来的 emits 选项差别不大

// 获取 emit
const emit = defineEmits(['chang-name']);

// 调用 emit
emit('chang-name', 'Tom');

5. attrs 的接收方式变化

attrs 和 props 很相似,也是基于父子通信的数据,如果父组件绑定下来的数据没有被指定为 props ,那么就会被挂到 attrs 这边来。

// 标准组件的写法
export default defineComponent({
  setup (props, { attrs }) {
    // attrs 是个对象,每个 Attribute 都是它的 key
    console.log(attrs.class);

    // 如果传下来的 Attribute 带有短横线,需要通过这种方式获取
    console.log(attrs['data-hash']);
  }
})

useAttrs 的基础用法

// 导入 useAttrs 组件
import { useAttrs } from 'vue'

// 获取 attrs
const attrs = useAttrs()

// attrs是个对象,和 props 一样,需要通过 key 来得到对应的单个 attr
console.log(attrs.msg);

6. ref、defineExpose

通过ref获取子组件的数据,需要子组件通过defineExpose暴露出来

标准组件写法里,子组件的数据都是默认隐式暴露给父组件的,也就是父组件可以通过 childComponent.value.foo 这样的方式直接操作子组件的数据

但在 script-setup 模式下,所有数据只是默认隐式 return 给 template 使用,不会暴露到组件外,所以父组件是无法直接通过挂载 ref 变量获取子组件的数据。

在 script-setup 模式下,如果要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由 defineExpose 来完成。

子组件:

<script setup lang="ts">
// 定义一个想提供给父组件拿到的数据
const msg: string = 'Hello World!';

// 显示暴露的数据,才可以在父组件拿到
defineExpose({
  msg
});
</script>

然后你在父组件就可以通过挂载在子组件上的 ref 变量,去拿到暴露出来的数据了

<template>
  <div class="home">
    <HelloWorld ref="hellow" />
    <button @click="handleAddNum">=</button>
  </div>
</template>

<script lang="ts" setup>
import {ref } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
const hellow=ref()
const handleAddNum = () => {
  console.log('通过ref获取子组件defineExpose暴露的数据',hellow.value.msg2);
};

</script>

7.顶级 await 的支持

在 script-setup 模式下,不必再配合 async 就可以直接使用 await 了,这种情况下,组件的 setup 会自动变成 async setup 。

<script lang="ts">
<!-- 标准组件格式 -->
import { defineComponent, withAsyncContext } from 'vue'

export default defineComponent({
  async setup() {
    const post = await withAsyncContext(
      fetch(`/api/post/1`).then((r) => r.json())
    )

    return {
      post
    }
  }
})
</script>
<script setup lang="ts">
<!-- script-setup 模式下 -->
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

四、比较Vue2与Vue3的响应式

vue2的响应式

  • 核心:
    1.对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
    2.数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
		let obj = {}
        //  1.要操作的对象
        //  2.要操作的属性
        //  3.具体值
        //数据劫持
        Object.defineProperty(obj, 'username', {
            get: function () {
                console.log('取值')
            },
            set: function (val) {
                console.log('赋值')
            },
        })
  • 问题:
    1.需要响应化的数据较大,递归遍历性能不好、消耗较大(defineProperty只对当前这一个属性进行监听,所以需要递归遍历每一个属性值。)
    2.新增或删除属性无法监听,对象直接新添加的属性或删除已有属性, 界面不会自动更新,直接通过下标替换元素或更新length, 界面不会自动更新
    3.defineProperty只对当前这一个属性进行监听,所以需要递归遍历每一个属性值。Proxy 的监听是针对一个对象的,那么对这个对象的所有操作会进入监听
    操作,可以代理所有属性

vue3的响应式

  • 核心:
    1.通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
    2.通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
		//目标对象
        let obj = {
            name: "tom",
            age: 25,
            wife: {
                name: "marry",
                age: 22,
            },
        }
        //把目标对象变成代理对象
        //参数1:obj-->目标对象;参数2:handler-->处理器对象,用来监视数据,及数据的操作;
        const proxyUser = new Proxy(obj, {//
            //get获取目标对象的某个属性值
            get(target, prop) {
                console.log('get')
                //这里要通过 Reflect反射返回,否则proxyUser.name是undefiend
                return Reflect.get(target, prop)
            },
            //set方法不仅能修改还能为目标对象增加属性值
            set(target, prop, newval) {
                console.log('set')
                return Reflect.set(target, prop, newval)
            },
            //deleteProperty删除目标对象的某个属性值
            deleteProperty(target, prop) {
                console.log('deleteProperty')
                return Reflect.deleteProperty(target, prop)
            },
        })
        ---
        注意:直接操作目标对象,视图是不会跟新的
        ---
        //-----获取属性值测试
        console.log('结果=>', proxyUser.name);
        //-----更改属性值测试
        proxyUser.name = '小明'
        //-----增加属性值测试
        proxyUser.sax = '男'
        //-----删除属性值测试
        delete proxyUser.name
        //-----更改深层次属性值测试
        proxyUser.wife.name='小红'

五、vue3使用vuex,vue-router

<template>
  <div class="home">
    <button @click="alter">路由</button>
  </div>
</template>

<script>
import {defineComponent} from "vue";
import { useRouter, useRoute,onBeforeRouteLeave } from "vue-router";
//守卫beforeRouteEnter,在composition api中被移除了,解决办法
//1、可以通过watch监听路由实现
//2、beforeRouteEnter不需要导入,只需要写在setup外即可
import { useStore } from "vuex";
export default {
  name: "Home",
  beforeRouteEnter(to,from,next){
    next(vm=>{
      console.log('组件内的守卫===',vm)
    })
  },
  setup() {
    let router = useRouter();//路由实例
    let route = useRoute()//路由信息,query,params等
    let store = useStore();

    const alter = () => {
      console.log(store.state.num);
    };
    onBeforeRouteLeave((to,from,next) => {
		next()
	});
		
    //监听路由变化
    watch(route,(val)=>{
      console.log('watch',val.matched)
    },{immediate:true,deep:true})
    
    const routerto = () => {
      router.push({
        path: "/about",
        query: {
          id: 2,
        },
      });
    };
    return {

    };
  },
};
</script>

在这里插入图片描述

/** vue-router 4.0
 * 1. new Router 变成 createRouter
 * 2. 新的 history 配置取代 2.0 的mode配置
 * 3. 移动了 base 配置,base 配置被作为 createWebHistory (其他 history 也一样)的第一个参数传递
 * 4. 删除了 *(星标或通配符)路由
 * 5. 3.0的 router.onReady() 函数已被 router.isReady() 取代,该函数不接受任何参数并返回一个 Promise
 * 6. 删除 <router-link> 中的 event 和 tag 属性,使用 v-slot API 来完全自定义
 * 7. 所有的导航现在都是异步的
 */
app.use(store).use(router);
router.isReady().then(() => app.mount('#app'))
// 所有的导航现在都是异步的,如果你使用一个 transition,你可能需要等待路由 ready 好后再挂载程序

Vue Router 将更新和离开守卫作为 组合式 API 函数公开,不含前置守卫

动态路由:
router.addRoute() 和 router.removeRoute()
router.hasRoute():检查路由是否存在。
router.getRoutes():获取一个包含所有路由记录的数组。

六、简单总结

数据监听方式变成了Proxy,消除了Object.defineProperty现有的限制(例如无法检测新的属性添加),并提供更好的性能。

vue3解决了vue2的一些问题,大型应用的性能问题、ts支持不友好问题,自定义渲染API解决体系架构存在的问题,Compostion API让代码的组织形式更好。vite开发构建工具让开发体验更好,Tree shaking让包更小、性能更优。

总的来说vue3还是非常棒的,带来了很多非常好的新特性。

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

web前端小龚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值