VUE3学习笔记 持续补充中...

153 阅读4分钟

搭建工程

  1. 使用vue-cli 内部用的是webpack(需要把该cli升级到最新版)

vue create vue3-app-vue-cli

npm run server

  1. 使用vite 不仅可以搭建vue 还可以搭建React 速度比webpack快很多 下载:npm init(自动下载,安装完后自动卸载,保证每次运行都用的最新的vite) vite-app vue3-app-vite(项目名称)

yarn 安装依赖

yarn dev

差异:页面文件放在根目录下,不是public目录

VUE几点重大变化

入口文件 创建VUE实例的方式


//vue2 
const app = new Vue(options)
app.$mount("#app")

//vue3
//不存在构造函数VUE 没办法从vue中导出VUE 没有默认导出

createApp(App).mout("#app");

是非常纯净的vue对象,只保留了必须要用到的方法,包括mount方法。使用插件的方式也发生了变化,是通过实例对象去注册插件的。

组件的this不再指向组件实例

而是输出一个代理Proxy(Es6知识)还是可以使用大部分VUE2的this功能

composition(组合) API

过去是option API配置式写法,整个组件的代码比较零散,CURD分布到各个位置,获取数据和修改都分布到不同的位置,很难看到一个功能的全貌,不利于复杂组件的编写。 现在是composition API组合API。符合高内聚低耦合,相同的代码聚在一起,也便于相同的代码提取出去成为一个模块。 从图一变成了图二。

image.png image.png

VUE3中依然允许用VUE2的方式,方便简单的组件简单写。使用组合API时,可以完全代替配置API。

但是应对复杂组件就要用composition API的方式去编写,composition API的方式,导出的对象中需要有一个setup,自动执行setUp,这个组件的属性被赋值完成之后会立即执行,在所有生命周期钩子函数之前调用。

这个函数中是不能用this的, this指向undifined, 提供了更好的方式获取组件的实例(很多时候没必要获取组件的实例),setUp中返回的对象里可以有属性或者方法,返回的东西会被添加到组件实例里去,可以直接在元素里去用。

定义变量的方式

以普通方式定义的变量没有响应式,需要按照Vue3的约束去定义变量。 以ref定义的变量是具有响应式的(比如挂载在Windows上,控制台修改,页面会改变),会把变量封装到对象里。会提供一个访问器(getter)value,设置值触发响应式,接受一个内部值,返回一个响应式的、可更改的 ref 组件代理对象,此对象只有一个指向其内部值的属性 .value,直接走代理。所以不需要再.value了。直接在元素里实例代理中去用的话,是拆包后的对象。在setup里,还是一个对象,没有经过拆包(目前情况不太办得到),所以要在setup里用.value,所以建议变量名加Ref。建议通用的提出去的方法以useXX命名(不是强制),可以把方法提到外面或者是单独的文件,然后通过use引入或者导出。

watchEffect 相当于React中的useEffect, 监控一个数据然后做相应的处理。

mixin Vue的聚合是扁平化的,很多限制,不像组合API是一个普通的函数。

组件不是最小的MV了,组合API才是最小的MV,可以进一步在组件内进行细分。多个组件出现了相同的功能性,可以抽取出来。尤雨溪鼓励更多关注逻辑内聚问题,逻辑关注点分离。其实VUE3的组合API方式也降低了编程质量的下限。

// 1. 组件挂载完成的生命周期函数
onMounted(() => {
  window.addEventListener("hashchange", onHashChange);
});
// 2. 组件销毁过后的生命周期函数
onUnmounted(() => {
  window.removeEventListener("hashchange", onHashChange);
});

计算属性

computed 有两种写法 完整写法(getter setter)和简单写法 (只有getter

getter返回的依旧是ref,计算属性是只读的不允许修改。修改要用Set。

<template>
  <div>
    <h1>computed 计算属性</h1>
    <el-input v-model="num1" /> +
    <el-input v-model="num2" /> =
    <el-input v-model="num3" />
    <br>
    <br>
    <el-button type="primary" @click="btn">修改计算属性</el-button>
  </div>
</template>
<script>
  import { computed, reactive, toRefs } from 'vue'
  export default {
    setup() {
      const num1 = ''
      const num2 = ''
      const all = reactive({ num1, num2 })

      // 计算属性求和
      const num3 = computed({
        get: () => {
          return Number(all.num1) + Number(all.num2)
        },
        set:(value) => {
          console.log(value)
          return all.num2 = Number(value) + 1
        }
      })

      function btn() {
        num3.value = '10'
      }

      return { ...toRefs(all), num3, btn }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

  • vue.3.0 中用于从vue 按需导入 computed 计算属性。
  • 如果传入的是一个getter 函数, 会返回一个不允许修改的计算属性。
  • 使用ref 创建一个响应式对象, 在模板中使用不用自动解套。 直接渲染使用(这个其实是因为返回的是ref)。
  • 传入一个对象, 包含get 和 set 函数, 就可以创建一个可以修改的计算属性。
  • 只可以获取值,不允许修改值直接使用 computed 计算属性使用箭头函数。
  • 既可以获取值, 也可以修改值, computed 计算属性中传入一个对象。 get 方法 和 set 方法。

计算属性在VUE3变成了快照概念,官方文档推荐修改原始值,不推荐修改计算属性。最好不用Set,这里也是很迷惑的,又提供修改方法,又不推荐使用(也没有标识说会移除)。

image.png

vite为什么快

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

image.png image.png

父子传值机制:

可以用provide和inject,理解为是context的语法糖,不推荐用

provide("adminMenuState", state);
inject("adminMenuState"

VUE3为什么快

Vue3是一个编译时和运行时相结合的框架。所谓编译时就是把我们编写的模版代码转化成一个render函数,该render函数的返回结果是一个虚拟Node,而运行时的核心工作就是把虚拟Node转化为真实Node进而根据情况对DOM树进行挂载或者更新。

客户端渲染效率比vue2提升了1.32倍 SSR渲染效率比vue2提升了23倍

静态提升

区分静态节点和动态节点 将元素节点或者属性内容保存成变量 提升

预字符串化

对于连续超过20个字符的静态节点 做字符串化

Block Tree

vue2在对比新旧树的时候,并不知道哪些节点是静态的,哪些是动态的,因此只能一层一层比较,这就浪费了大部分时间在比对静态节点上 在Vue3中,编译后的模板会被拆分成多个块(blocks),每个块对应一个节点或一组节点。这些块可以被独立地更新和渲染,从而避免了不必要的渲染操作。但是BLOCK机制只能不能对比动态的块,代码里有各种if-else for等等 Block Tree是将条件渲染和循环渲染的内容封装为一个单独的Block,避免了大量的VNode节点创建和销毁。其实就是把那些DOM结构可能发生改变的地方也作为一个动态节点进行收集。

如果识别出整个块都没有更新 就不递归了

一句话:每个可能改动的点都会被记录在根元素上,动态的子节点会变成块树。就是!只比较动态节点 跳过静态节点

缓存事件处理函数

事件处理函数被认定是不变的 缓存起来 每次从缓存读取

PatchFlag

每个虚拟DOM的节点都有个属性patchFlag 这个属性包括了每个标识(props class style等)是否更新 细化到具体更新了什么。数据细化到极致。

模板编译优化总结

  • 动态节点优化

    • 通过patchFlag标识节点动态部分,也因此可以识别所有的动态节点,将其提取出来,不用递归做节点diff。平级替换
    • 通过block收集动态节点,而其中有可能影响结构的指令,则再创建block将其稳定住,也因此形成了blockTree,实现靶向更新。
  • 静态节点优化

    • 节点提升、节点批量创建、节点属性提升、函数提升

API和数据响应式的变化

去掉了构造函数

转而使用createApp创建vue应用,不再又可以做实例又可以做组件,这样可以减少打包进来无用的API数量,更好地应用树摇优化。减少打包体积。可以支持多个实例,互相之间的配置不被影响。可以链式调用挂载插件等。

vue2的全局构造函数带来了诸多问题:
1. 调用构造函数的静态方法会对所有vue应用生效,不利于隔离不同应用
2. vue2的构造函数集成了太多功能,不利于tree shaking,vue3把这些功能使用普通函数导出,能够充分利用tree shaking优化打包体积
3. vue2没有把组件实例和vue应用两个概念区分开,在vue2中,通过new Vue创建的对象,既是一个vue应用,同时又是一个特殊的vue组件。vue3中,把两个概念区别开来,通过createApp创建的对象,是一个vue应用,它内部提供的方法是针对整个应用的,而不再是一个特殊的组件。

组件实例中的API

vue2和vue3均在相同的生命周期完成数据响应式,但做法不一样 VUE2采用的是Object.defineProperty,VUE3采用的是ES6的代理对象。从底层来说,代理对象的速度本身就比Object.defineProperty快,尤其是Object.defineProperty需要挨个属性挂载,代理对象可以实现动态代理,代理一次就无需深度遍历,Object.defineProperty新增的数据也必须走特定的方法去包一层才能响应式,而Proxy可以监控到成员的新增和删除。

vue3不再使用Object.defineProperty的方式定义完成数据响应式,而是使用Proxy。
除了Proxy本身效率比Object.defineProperty更高之外,由于不必递归遍历所有属性,而是直接得到一个Proxy。所以在vue3中,对数据的访问是动态的,当访问某个属性的时候,再动态的获取和设置,这就极大的提升了在组件初始阶段的效率。
同时,由于Proxy可以监控到成员的新增和删除,因此,在vue3中新增成员、删除成员、索引访问等均可以触发重新渲染,而这些在vue2中是难以做到的。

image.png

模版中的变化

v-model支持绑定多个属性v-model:valueA

代替了vue2中的 v-model、.sync, 本身v-model就是一种语法糖。在vue3中,v-model绑定的默认prop与事件名发生了变化,默认prop为model-value,默认的事件名是update:model-value。我们可以通过传递参数来改变prop。

<input type="text" v-model="name" />
相当于
<input type="text" :value="name" @input="name=$event.target.value" >

在input标签中使用v-model,其根本是将name绑定到value属性上,并监听input事件,一旦监听到input事件,就执行语句 `name = $event.target.value``v-model` 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

-   text 和 textarea 元素使用 `value` property 和 `input` 事件;
-   checkbox 和 radio 使用 `checked` property 和 `change` 事件;
-   select 字段将 `value` 作为 prop 并将 `change` 作为事件。

  
在自定义组件中:
如果没有进行相关的配置,其会默认使用名为**value**的prop,并监听名为**input**的事件,在监听的事件中执行语句`value=$event`,

在vue3中,v-model绑定的默认prop与事件名发生了变化,默认prop为**model-value**,默认的事件名是**update:model-value**。我们可以通过传递参数来改变prop。


作者:拾九十九  
链接:https://juejin.cn/post/7119026274890153997  
来源:稀土掘金  
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

支持修饰符

修饰符分为属性修饰符和事件修饰符。 修饰符需要自己定义。名称有固定的要求。

v-if的优先级比v-for要高,2中v-for优先级比v-if高,导致性能不好,官方不推荐使用,因为每个节点的改动都需要走一次遍历,最好用计算属性去筛选。

组件中的变化: 支持异步组件,可以配置loading,错误展示。 支持异步页面(原理和异步组件一样)

export function getAsyncComponent(path) {
  return defineAsyncComponent({
    loader: async () => {
      await delay();
      if (Math.random() < 0.5) {
        return import(path);
      }
      throw new Error();
    },
    loadingComponent: Loading,
    errorComponent: {
      render() {
        return h(Error, "组件加载出错");
      },
    },
  });
}
export function getAsyncPage(path) {
  return defineAsyncComponent({
    loader: async () => {
      NProgress.start();
      await delay();
      const comp = await import(path);
      NProgress.done();
      return comp;
    },
    loadingComponent: Loading,
  });
}

Teleport(可参照另一篇文章的使用)

ReactivityApi

VUE2的响应式是内置的,vue3支持组合式API,所以响应式的API必须暴露。ref, computed 都是从ReactivityApi中导出出来的,是独立于组件的存在。响应式对象有两种,一种是ref对象,一种是ES6的Proxy对象。reactive只能代理对象,readonly可以代理对象或者proxy,ref则是通过value包住普通类型数据,实现响应式。如果ref代理的是对象,则通过reactive包一层。

API传入返回备注
reactiveplain-object对象代理深度代理对象中的所有成员
readonlyplain-object or proxy对象代理只能读取代理对象中的成员,不可修改
refany{ value: ... }对value的访问是响应式的 如果给value的值是一个对象, 则会通过reactive函数进行代理 如果已经是代理,则直接使用代理
computedfunction{ value: ... }当读取value值时, 会根据情况决定是否要运行函数(开始时执行一次,之后执行时如果数据变化则执行)

应用:

  • 如果想要让一个对象变为响应式数据,可以使用reactiveref
  • 如果想要让一个对象的所有属性只读,使用readonly
  • 如果想要让一个非对象数据变为响应式数据,使用ref
  • 如果想要根据已知的响应式数据得到一个新的响应式数据,使用computed

watchEffect和watch

区别是watchEffect是自动收集依赖。watch可以指定依赖,

// 等效于vue2的$watch

// 监听单个数据的变化
const state = reactive({ count: 0 })
watch(() => state.count, (newValue, oldValue) => {
  // ...
}, options)

const countRef = ref(0);
watch(countRef, (newValue, oldValue) => {
  // ...
}, options)

// 监听多个数据的变化
watch([() => state.count, countRef], ([new1, new2], [old1, old2]) => {
  // ...
});

注意:无论是watchEffect还是watch,当依赖项变化时,回调函数的运行都是异步的(微队列)

应用:除非遇到下面的场景,否则均建议选择watchEffect

  • 不希望回调函数一开始就执行
  • 数据改变时,需要参考旧值
  • 需要监控一些回调函数中不会用到的数据

判断

API含义
isProxy判断某个数据是否是由reactivereadonly
isReactive判断某个数据是否是通过reactive创建的 详细:v3.vuejs.org/api/basic-r…
isReadonly判断某个数据是否是通过readonly创建的
isRef判断某个数据是否是一个ref对象

转换

unref

等同于:isRef(val) ? val.value : val

应用:

function useNewTodo(todos){
  todos = unref(todos);
  // ...
}

toRef

得到一个响应式对象某个属性的ref格式

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo'); // fooRef: {value: ...}

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3

toRefs

把一个响应式对象的所有属性转换为ref格式,然后包装到一个plain-object中返回(比如rative数据,返回的时候,解构的时候会失去响应性)

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs: not a proxy
{
  foo: { value: ... },
  bar: { value: ... }
}
*/

应用:

setup(){
  const state1 = reactive({a:1, b:2});
  const state2 = reactive({c:3, d:4});
  return {
    ...state1, // lost reactivity
    ...state2 // lost reactivity
  }
}

setup(){
  const state1 = reactive({a:1, b:2});
  const state2 = reactive({c:3, d:4});
  return {
    ...toRefs(state1), // reactivity
    ...toRefs(state2) // reactivity
  }
}
// composition function
function usePos(){
  const pos = reactive({x:0, y:0});
  return pos;
}

setup(){
  const {x, y} = usePos(); // lost reactivity
  const {x, y} = toRefs(usePos()); // reactivity

降低心智负担:所有的composition function均以ref的结果返回,以保证setup函数的返回结果中不包含reactivereadonly直接产生的数据

composition api

context对象的成员

成员类型说明
attrs对象vue2this.$attrs
slots对象vue2this.$slots
emit方法vue2this.$emit

生命周期函数

vue2 option apivue3 option apivue 3 composition api
beforeCreatebeforeCreate不再需要,代码可直接置于setup中
createdcreated不再需要,代码可直接置于setup中
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestroy==改== beforeUnmountonBeforeUnmount
destroyed==改==unmountedonUnmounted
errorCapturederrorCapturedonErrorCaptured
-==新==renderTrackedonRenderTracked
-==新==renderTriggeredonRenderTriggered

新增钩子函数说明:

钩子函数参数执行时机
renderTrackedDebuggerEvent渲染vdom收集到的每一次依赖时
renderTriggeredDebuggerEvent某个依赖变化导致组件重新渲染时

DebuggerEvent:

  • target: 跟踪或触发渲染的对象
  • key: 跟踪或触发渲染的属性
  • type: 跟踪或触发渲染的方式
  1. 为了更好的逻辑复用和代码组织

  2. 更好的类型推导(VUE2的this是乱指的)

setup语法糖:

这个语法糖使用是在script标签上直接加setup 直接定义的变量都会被返回,使用props和emit变为通过宏传入,在编译阶段注入 defineProps和defineEmits的内容和非setup语法糖的内容中的props和emit是一样的 cn.vuejs.org/api/sfc-scr…

还有个区别是,不使用setup语法糖,返回的属性会被挂在组件实例上,有被父组件通过ref调用的风险。 使用setup语法糖,组件实例则是干净的,要暴露方法需要通过 defineExpose() cn.vuejs.org/api/sfc-scr…

特殊注意使用ts时定义props的方法,使用withDefaults来设置默认值

interface Props {
  tenantInfo: {
    tenantName: string;
    tenantPostion: string;
    tenantType: string;
    tenantStatus: string;
    contactName: string;
    contactPhone: string;
  };
}

const props = withDefaults(defineProps<Props>(), {
  tenantInfo: () => {
    return {
      tenantName: '',
      tenantPostion: '',
      tenantType: '',
      tenantStatus: '',
      contactName: '',
      contactPhone: '',
    };
  },
});

其他:VUE3支持多个根节点

监控哈希值的变化: window.addEventListener("hashChange")可以监控哈希值的变化组件销毁要把事件取消掉 location.hash读取哈希

vite知识点:

  • public 中的资源不应该被 JavaScript 文件引用。

juejin.cn/post/694638…

juejin.cn/post/708202…

VUE3转换后的结果: template-explorer.vuejs.org/#eyJzcmMiOi…

juejin.cn/post/724177…

juejin.cn/post/711902…