【study】Vue3.x的进阶之路

618 阅读3分钟

官方文档: [v3.cn.vuejs.org/guide/insta…]

一.Vue3.x的六大亮点

  • Preformance:性能比Vue2.x好
  • Tree shaking support:按需编译,体积比Vue2.x更小
  • Composition API: 组合API(类似React Hooks)
  • Better Typescript support:更好的TS支持
  • Custom Renderer API:暴露了自定义渲染API
  • Fragment,Teleport(Protal),Suspense:更先进的组件

二.Vue3.x是如何变快的

1.diff算法优化
  • Vue2中的虚拟dom是进行全量的对比
  • Vue3新增了静态标记(PatchFlag):在与上次虚拟节点进行对比的时候,只对比带有patch flag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容
2.静态提升
  • Vue2中无论元素是否参加更新,每次都会重新创建,然后在渲染
  • Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
3.事件侦听器缓存
  • 默认情况下事件被视为动态绑定,所以每次都会追踪它的变化
  • 但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可

三.安装

1.通过vue-cli脚手架安装vue-three
  • 对比vite更稳定一些
  • 和安装vue2相同,只不过在选择的时候,选择安装3即可
// 安装vue-cli
npm install -g @vue/cli    
cnpm install -g @vue/cli 
yarn global add @vue/cli

vue create vue-three
cd vue-three
npm run serve
2.通过vite脚手架安装vue-three
  • 对比vue-cli更轻量一些
// npm或者cnpm
npm init vite-app vue-three
cd vue-three
npm install
npm run dev

// yarn
yarn create vite-app vue-three
cd vue-three
yarn
yarn dev

四.composition API

1.set up :组合式API入口

image.png

2.reactive: 定义对象的双向绑定
setup () {
  const myInform = reactive ({
      age: 18, // 定义一个age属性,属性值为18
      nextAge:computed (() => myInform.age + 1) // 计算属性,age的值每次加1
    })
  return {myInform} // 定义的值要返回出去
}
3.ref:定义js基础类型/定义对象的双向绑定
setup () {
  const myName = ref('棠梨煎雪1226')
  const myInform = ref ({
      name: '棠梨煎雪1226',
      age: 18
    })
  return {myName, myInform} // 定义的值要返回出去
}
4.toRefs: 解构对象
// 直接返回这个对象,那么在使用的时候,需要打点访问
<div>{{myInform.name}}</div>
<div>{{myInform.age}}</div>

setup () {
  const myInform = reactive ({
      name: '棠梨煎雪1226',
      age: 18
    })
  return {myInform} // 定义的值要返回出去
}
  • 使用toRefs解构对象
// 直接访问
<div>{{name}}</div>
<div>{{age}}</div>

setup () {
  const myInform = reactive ({
      name: '棠梨煎雪1226',
      age: 18
    })
  return {...toRefs(myInform)}
}
5.watch: 监听
watch(source,callback,[options])
// source:指定侦听的响应式变量/String,Object,Function,Array
// callback:回调函数
// options: deep,immediate,flush
<template>
  <div class="HelloWorld">
    <div>{{age}}</div>
    <div>{{nextAge}}</div>
  </div>
</template>

<script>
import {reactive,computed,onMounted,onUnmounted,toRefs,watch} from "vue";
export default {
  name: "HelloWorld",
  setup() {
    const { age, nextAge } = userCounter(); 
    // 侦听器
    watch (age, (newValue,oldValue) =>{
      console.log('新值:' + newValue,'旧值:' + oldValue)
    })
    return {
      age, 
      nextAge,
    };
  },
};
// 可以把方法提出来
function userCounter() {
  const data = reactive({
    age: 2,
    nextAge: computed(() => data.age + 1),
  });
  let timer;
  onMounted(() => {
    timer = setInterval(() => {
      data.age++;
    }, 1000);
  });
  onUnmounted(() => {
    clearInterval(timer);
  });
  return toRefs(data);
}
</script>

image.png

6.watchEffect

image.png

7.computed

image.png

五.生命周期钩子

1.beforeCreate,created,setup执行顺序以及vue3.x中的使用

image.png

vue3.x是往下兼容vue2.x的,也就是说,虽然vue3.x里面已经将beforeCreate和created变更为了set up,但使用这两个也不会报错
beforeCreate ----- set up
created ----- set up
beforeMount ----- onBeforeMount
mounted ----- onMounted
beforeUpdate ----- onBeforeUpdate
update ----- onUpdate
beforeDestory ----- onBeforeUnmount
destory ----- onUnmounted
activated ---- onActivated
deactivated ---- onDeactivated
errorCaptured ---- onErrorCaptured
vue3.x新加的两个生命周期
onRenderTracked
onRenderTriggered

六.Teleport

1.传送门组件,提供一种简介的方式,指定内容的父元素
2.例子:
  • 希望在组件内部显示一个弹窗Dialog,又希望渲染的DOM结构不嵌套在组件的DOM中
  • 父组件
<template>
  <!-- 模态框 -->
  <Dialog></Dialog>
</template>

<script>
import Dialog from "./Dialog.vue";
export default {
  name: "HelloWorld",
  components: {Dialog},
  setup() {
  }
};

</script>
  • 子组件
<template>
  <button class="open" @click="modelShow = true">点击打开模态框</button>
  <!-- 用teleport包裹弹窗的内容,to属性指向id属性 -->
  <teleport to="#app"> 
    <div v-if="modelShow" class="content">
      内容
      <button class="open" @click="modelShow = false">关闭打开模态框</button>
    </div>
  </teleport>
</template>

<script>
import { ref } from "vue";
export default {
  name: "dialog_box",
  setup() {
    const modelShow = ref(false);
    return { modelShow };
  },
};
</script>

<style scoped>
.content {
  position: absolute;
  width: 100%;
  height: 100%;
  background: pink;
  top: 30%;
  width: 200px;
  height: 200px;
  left: 40%;
}
</style>
  • 效果图 image.png

image.png

  • content和app同级 image.png

七.Fragment

1.在vue2.x中,只允许有一个根节点
2.在vue3.x中,允许有多个根节点

八.Emits Component Option

1.vue3.x中组件发送自定义事件需要定义在emits选项中
2.例子:
  • 父组件
<template>
  <div class="helloWorld">
    <!-- 子传父的自定义事件 -->
    <Emits @click="emitClick"></Emits>
  </div>
</template>

<script>
import Emits from "./Emits.vue";
export default {
  name: "HelloWorld",
  components: {Emits},
  methods: {
    emitClick() {
      console.log('我被点击了')
    }
  }
};
</script>
<style scoped>
.helloWorld {
  width: 100%;
  /* height: 100%; */
}
</style>
  • 子组件
<template>
  <div @click="$emit('click')">点击我!!!</div>
</template>

<script>
export default {
}
</script>

<style lang="scss" scoped>

</style>
  • 效果

image.png

  • 点击一次,输出两次内容,这里是一个大坑!!!!!!
  • 解决
<template>
  <div @click="$emit('click')">点击我!!!</div>
</template>

<script>
export default {
  emits: ['click'] // 可以更好地解决重复输出的问题
}
</script>

<style lang="scss" scoped>

</style>
3.上述例子,如果不使用emits: ['click']的另一种写法
  • 父组件
<template>
  <div class="helloWorld">
    <!-- 子传父的自定义事件 -->
    <Emits @my-click="emitClick"></Emits>
  </div>
</template>

<script>
import Emits from "./Emits.vue";
export default {
  name: "HelloWorld",
  components: {Emits},
  methods: {
    emitClick() {
      console.log('我被点击了')
    }
  }
};
</script>
<style scoped>
.helloWorld {
  width: 100%;
  /* height: 100%; */
}
</style>
  • 子组件
<template>
  //传值的时候,不要直接传递click即可,就不需要在使用emits: ['click']
  <div @click="$emit('my-click')">点击我!!!</div>
</template>

九.摇树优化

1.例子:nextTick
// vue2.x
// Vue构造函数直接接触
import Vue from 'vue'
Vue.nextTick(()=>{})
// vue3.x
// 提取独立函数,打包可以看出是否被调用
import {nextTick} from 'vue'
nextTick(()=>{})

十.V-model

1.vue2.x的v-model原理
2.vue3.x的v-model原理
  • 父组件
<template>
  <div class="helloWorld">
    <!-- v-model数据的双向绑定 -->
    <Mymodel v-model:myCounter="myCounter"></Mymodel>
  </div>
</template>

<script>
import { ref } from 'vue'
import Mymodel from "./Mymodel.vue";
export default {
  name: "HelloWorld",
  components: {Mymodel},
  setup() {
    const myCounter = ref(0)
    return {myCounter}
  }
};

</script>
<style scoped>
.helloWorld {
  width: 100%;
  /* height: 100%; */
}
</style>
  • 子组件
<template>
  <div @click="$emit('update:myCounter',myCounter + 1)">
    myCounter:{{myCounter}}
  </div>
</template>

<script>
export default {
  props: {
    myCounter: {
      type: Number,
      default: 1
    }
  }
}
</script>