vue 3.0学习

关于学习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, SuspenseFragmentTeleportSuspense
  • 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,
    };
  },
});
复制代码

再次对项目进行打包,可以看到在引入computerwatch之后,项目整体体积变大了

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值