vue 3.0学习

  • Post author:
  • Post category:vue


关于学习Vue3.0的一些个人心得体会,分享给同学们


创建一个Vue3.0的项目

1、npm init vite-app vue3.0

2、cd vue3.0

3、npm install

4、npm run dev


3.0的一些亮点


  • Performance

    :性能比

    Vue 2.0

    更强。

  • Tree shaking support

    :可以将无用模块剪辑,仅打包需要的。

  • Composition API

    :组合

    API

  • Fragment, Teleport, Suspense



    Fragment



    Teleport



    Suspense

  • Better TypeScript support

    :更优秀的Ts支持

  • Custom Renderer API


组合API的一些简单介绍


1、setup()

beforeCreate 、created 之前执行

setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口, vue3取消了beforeCreate 、created两个钩子,统一用setup代替, 该函数相当于一个生命周期函数,vue中过去的data,methods,watch,computed等全部都用对应的新增api写在setup()函数中


2、ref()

声明单一基础数据类型  返回值是响应式对象

创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 value 属性, 只在setup函数内部访问ref函数需要加

.value


3、reactive()

声明单一对象  (注意:解构会破坏代理proxy即双向绑定)

reactive() 函数接收一个普通对象,返回一个响应式的数据对象, 想要使用创建的响应式数据也很简单,创建出来之后,在setup中return出去,直接在template中调用即可; 在reactive()函数中访问ref()函数创建的对象不用

.value


4、isRef()   判断是否是通过ref()函数创建出来的对象


5、toRefs()   从组合函数返回反应对象

toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据


6、computed()    计算属性 可创建只读,和可读可写两种

1)  只读           let fullName = computed(() => msg.value + ‘~’ + name)

2)可读可写    let fullName2 = computed({ get: () => number.value + state.step, set: (v) => number.value = v })


7、watch()

watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。

1) 监听 reactive( )声明的数据

单数据监听:    watch( ( )=> reactiveV.name, (n, o)=>{ })

多数据监听:     watch([( )=> reactive.name, ( )=> reactive.sex], ([name, sex], [oldName, oldSex])=> { })

2)   监听 ref( )声明的数据

单数据监听:     watch(age,(n, o)=>{ })

多数据监听:     watch([age, weight], ([newAge, newWeight], [oldAge, oldWeight])=> { })

3)取消监听

const stop = watch(age,(newValue, oldValue)=>{ })

setTimeout(() => { stop() }, 1000)              // 一秒后取消对age的监听


选项 API 生命周期选项和组合式 API 之间的映射

setup()内部调用生命周期钩子,只需在原有钩子函数前加on


  • beforeCreate

    -> use

    setup()

  • created

    -> use

    setup()

  • beforeMount

    ->

    onBeforeMount

  • mounted

    ->

    onMounted

  • beforeUpdate

    ->

    onBeforeUpdate

  • updated

    ->

    onUpdated

  • beforeUnmount

    ->

    onBeforeUnmount

  • unmounted

    ->

    onUnmounted

  • errorCaptured

    ->

    onErrorCaptured

  • renderTracked

    ->

    onRenderTracked

  • renderTriggered

    ->

    onRenderTriggered


refs的使用变化

    <div ref="div1">hello,world</div>
复制代码

要获取div的DOM元素在vue2.x中:

    this.$refs.div1
复制代码

而在vue3.0中:

    import {ref} from 'vue'
    setup(){
        let div1 = ref(null) //括号内为初始值,可以通过.value来读取
        onMounted(()=> console.log(div1)) //打印出<div data-v-xxxxxxx="">hello,world</div>
        return div1
    }
复制代码


global Properties

vue 2.x  通过 Vue.prototype扩展

vue3.0 通过app.config.globalProperties.xxx进行配置

例如:app.config.globalProperties.$api = ‘

www.baidu.com

然后,在setUp函数中通过

getCurrentInstance()

函数获取

import {getCurrentInstance} from ‘vue’

const { ctx } = getCurrentInstance()

console.log(ctx.$api) //打印出

www.baidu.com


provide / inject

vue2.x

const app = Vue.createApp({})
app.component('grandFather', {
data() {
    return {
        list: ['吃饭', '睡觉']
    }
},
provide: {
    user: '李华'
},
template: `
<div>
{{ list.length }}
<!-- rest of the template -->
</div>
`
})

app.component('son', {
inject: ['user'],
created() {
console.log(`我收到来自祖父的数据: ${this.user}`) //我收到来自祖父的数据: 李华
}
})
复制代码

但是如果我们直接在provide中用这种方式访问data中数据的话就会报错

app.component('grandFather', {
data() {
    return {
        list: ['吃饭', '睡觉']
    }
},
provide: {
    listLenth: this.list.length //error: Cannot read property 'length' of undefined
},
template: `
<div>
{{ list.length }}
<!-- rest of the template -->
</div>
`
})
复制代码

应将provide写成一个函数返回一个对象的形式:

app.component('grandFather', {
data() {
    return {
        list: ['吃饭', '睡觉']
    }
},
provide() {
    return {
        listLenth: this.list.length
    }  
},
template: `
<div>
{{ list.length }}
<!-- rest of the template -->
</div>
`
})
复制代码

vue3.0

1、inject()只能放在setup()生命周期里运行,不能放在别的周期里运行,也不能放在事件周期里运行。 这意味着inject只能在setup的同步代码中运行,无法在setTimeout或者Promise.then等其他异步代码中运行。

2、需要修改祖先组件中数据时,使用祖先组件提供的修改数据的方法而不能直接在子组件里修改。

祖先组件:
setup() {
    let grandFatherName = ref('李华')
    function changeName(val){
        grandFatherName.value = val
    }
    provide('grandFatherName',grandFatherName)
    provide('changeName',changeName) //子组件需要修改值时调用该函数即可
}
子组件:
setup() {
    const grandFatherName = inject('grandFatherName') //子组件接受值
    const changeName = inject('changeName') //子组件接受修改函数
    changeName('韩梅梅')  //子组件调用修改函数修改值
}
复制代码

3、禁止子组件修改变量可以将祖先组件中变量定义为只读的。可以是

readonly

,也可以是

shallowRef

,这样即使子组件调用修改函数修改变量也不会有反应。

祖先组件:
setup() {
    let grandFatherName = shallowRef('李华') //使用shallowRef将变量定义为只读
    function changeName(val){
        grandFatherName.value = val
    }
    provide('grandFatherName',grandFatherName)
    provide('changeName',changeName)
}
子组件:
setup() {
    const grandFatherName = inject('grandFatherName')
    const changeName = inject('changeName')
    changeName('韩梅梅')  //子组件调用修改函数修改值(无效,变量为只读)
}
复制代码


Performance

  • 重写了虚拟

    Dom

    的实现(且保证了兼容性,脱离模版的渲染需求旺盛)。
  • 编译模板的优化。
  • 更高效的组件初始化。

  • update

    性能提高 1.3~2 倍。

  • SSR

    速度提高了 2~3 倍。

编译模板时节点打PatchFlag

静态节点<span>msg</span>
动态节点<span>{{msg}}</span> //会生成number(大于 0)值的PatchFlag,用作标记
复制代码

vue3.0底层,会自动识别某个节点是否是动态的,如果是动态的会自动生成标识(不同的动态会有不同的标识对应,如内容文本的动态,或者id的动态),从而在每次更新dom时,直接跳过那些静态的节点,直接定位到动态的节点,大大节省效率

事件监听缓存

cacheHandlers

vue2.x默认情况下onClick 会被视为动态绑定,所以每次都会去追踪它的变化;

vue3.0 因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可;

hoistStast 静态提升

vue2.x无论元素是否参与更新,每次都要重新创建,然后渲染;

vue3.0中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可;

ssr渲染

vue2.x 当有大量静态的内容时,这些内容会被当做纯字符串推进一个buffer里面,

即使存在动态的绑定,会通过模板 插值嵌入进去,这样会比通过虚拟dom来渲染的快很多

vue3.0 当静态文件大到一定量的时候,会用_ceratStaticVNode方法在客户端去生成一个static node, 这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染


Tree shaking support

一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫

Dead code elimination

其基于ES6的import和exports,借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量.

实际上就是做了2件事:

  • 编译阶段利用

    ES6 Module

    判断哪些模块已经加载
  • 判断那些模块和变量未被使用或者引用,进而删除对应代码

例如:

vue2.x

import Vue from 'vue'
//在组件中使用data属性
export default {
    data: () => ({
        count: 1,
    }),
};
复制代码

项目打包后体积如下:

image.png

import Vue from 'vue'
//在组件中使用其他属性
export default {
    data: () => ({
        question:"", 
        count: 1,
    }),
    computed: {
        double: function () {
            return this.count * 2;
        },
    },
    watch: {
        question: function (newQuestion, oldQuestion) {
            this.answer = 'xxxx'
    }
};
复制代码

再一次打包,发现打包出来的体积并没有变化

vue3.0

import { reactive, defineComponent } from "vue";
//简单使用
export default defineComponent({
  setup() {
    const state = reactive({
      count: 1,
    });
    return {
      state,
    };
  },
});
复制代码

将项目进行打包

image.png

import { reactive, defineComponent, computed, watch } from "vue";
//引入watch和computed属性
export default defineComponent({
  setup() {
    const state = reactive({
      count: 1,
    });
    const double = computed(() => {
      return state.count * 2;
    });
    watch(
      () => state.count,
      (count, preCount) => {
        console.log(count);
        console.log(preCount);
      }
    );
    return {
      state,
      double,
    };
  },
});
复制代码

再次对项目进行打包,可以看到在引入

computer



watch

之后,项目整体体积变大了

image.png


Fragment

作用:创建模板时不用写一个根元素了

//原本这样写组件会报错
<template>
  <div>Hello</div>
  <div>World</div>
</template>
复制代码

原因是代表任何Vue组件的Vue实例需要绑定到一个单一的DOM元素中。唯一可以创建一个具有多个DOM节点的组件的方法就是创建一个没有底层Vue实例的功能组件。 结果发现React社区也遇到了同样的问题。他们想出的解决方案是一个名为 Fragment 的虚拟元素。它看起来差不多是这样的:

class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}
复制代码

尽管Fragment看起来像一个普通的DOM元素,但它是虚拟的,根本不会在DOM树中呈现。这样我们可以将组件功能绑定到一个单一的元素中,而不需要创建一个多余的DOM节点。 目前你可以在Vue 2中使用vue-fragments库来使用Fragments,而在Vue 3中,你将会在开箱即用!


teleport

能够直接帮助我们将组件渲染后页面中的任意地方,只要我们指定了渲染的目标对象。

官方示例是模态框是使用场景:

app.component('modal-button', {
  template: `
    <button @click="modalOpen = true">
        Open full screen modal! (With teleport!)
    </button>

    <teleport to="body"> //to属性的值为queryselect选择器指定的位置
      <div v-if="modalOpen" class="modal">
        <div>
          I'm a teleported modal! 
          (My parent is "body")
          <button @click="modalOpen = false">
            Close
          </button>
        </div>
      </div>
    </teleport>
  `,
  data() {
    return { 
      modalOpen: false
    }
  }
})
复制代码

与组件一起使用

如果

<teleport>

包含 Vue 组件,则它仍将是

<teleport>

父组件的逻辑子组件:

app.component('parent-component', {
  template: `
    <h2>This is a parent component</h2>
    <teleport to="#endofbody">
      <child-component name="John" />
    </teleport>
  `
})

app.component('child-component', {
  props: ['name'],
  template: `
    <div>Hello, {{ name }}</div>
  `
})
复制代码

在这种情况下,即使

child-component

被渲染到了id为endofbody的元素上,但它仍将是

parent-component

的子级,并将从中接收

name

prop

在同一元素上使用多个 teleport

顺序是一个先来后到原则:稍后挂载将位于目标元素中较早的挂载之后

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- 渲染结果 -->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
复制代码


Suspense

类似React.lazy,是一个新组件,其作用是当你在进行一个异步加载时,可以先提供一些静态组件作为显示内容,然后当异步加载完毕时再显示原有内容

//这是一个内置组件不用引入直接使用
<Suspense>
      <template #default>
        <AsyncComponent/>
      </template>
      <template #fallback>
        <h1>Loading</h1>
      </template>
</Suspense>


AsyncComponent组件:
import {defineComponent} from 'vue'
export default defineComponent({
  name:"AsyncComponent",
  setup() {
    return new Promise((resolve,reject)=> {
      setTimeout(()=> {
        resolve({result:"yuanxi"})
      },2000)
    })
  }
})
复制代码

根据插槽机制,来区分组件:

#default插槽里面的内容就是你需要渲染的异步组件;

#fallback就是你指定的加载中的静态组件。

需要注意的是:

AsyncComponent组件需要返回一个Promise



版权声明:本文为z2475371077原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。