Vue3 基础知识

Vue2 的缺点

Vue 作为一款 MVVM 框架,因为其响应式数据的特性,避免了开发者频繁操作 DOM 元素,大幅提高了页面的性能,然鹅,Vue2 在做数据响应式时,只是使用了 Object.defineProperty() API,对数据属性或者某些方法进行拦截与 getter,setter 重写,这种代理仍然是有缺陷的,比如数组中某一项内容的删除、对象中某一个属性的直接定义,如 a.b.c = 'x',存在修改了内容但并不能使页面数据响应的情况,需要辅助 API 配合才能监听到 由于 Vue2 中是将当前页所有数据、方法都写进 data、methods 中,在复杂业务的页面下,庞大的变量或方法会使得代码阅读难度大大提升,修改某处内容容易造成开发者在代码里上下反复横跳的情况。

Vue3 的出现

使用 Proxy 代理,对对象的属性值进行读写、添加、删除并进行劫持,通过 Reflect(反射)动态对被代理的对象属性进行特定的操作,由于 Proxy 和 Reflect 不支持 IE 浏览器,这也是 Vue3 不支持 IE 浏览器的主要原因之一 推出了 SetUp,setup 是 Composition API 的入口,可以实现数据与方法的代码捆绑在一块,大大提高了复杂业务逻辑中代码的可阅读性,它在 beforeCreate 之前执行一次,beforeCreate 这个钩子 的任务就是初始化,在它之前执行,那么 this 就没有被初始化 this = undefined 这样就不能通过 this 来调用方法 和 获取属性(注:Vue3 的生命周钩子都比 Vue2 同级的生命钩子执行的早) Vue3 的好处远不止以上两点,许多新特性还需要一步一步学

创建 Vue3 项目

推荐使用 Vite,Vite 是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动

通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目

# npm 6.x
$ npm init vite@latest <project-name> --template vue

# npm 7+,需要加上额外的双短横线
$ npm init vite@latest <project-name> -- --template vue

$ cd <project-name>
$ npm install
$ npm run dev

Composition API

前面提到,Vue2 使用的是 OptionAPI,即 data、methods、watch、computed..,给后期维护造成了一定的压力,Vue3 提供了 Compotion API,将一个逻辑里的代码都收集在一起,统一写入一个 hook,然后再引入,这样管理起来开发者就不用再上下横跳了

setup 是 Composition API 的入口

<template>
  <p>姓名{{ name }}</p>
  <p @click="say>年龄{{ age }}</p>
</template>
<script>
export default {
  name: 'App',
  setup(){
   let name = '流星'
   let age = 18
   //方法
   function say(){
    console.log(`我叫${name},今年${age}岁`)
   }
   //返回一个对象
   return {
    name,
    age,
    say
   }
  }
 }
</script>

Vue3 中,template 可以不用再用一个根元素来包裹内容了~~ 因为 Vue3 中会将多个标签包含在一个Fragment虚拟元素中 , 还有一点,如果是在<script setup>语法糖中引入的组件,可以不用在 components 中注册即可直接在 template 中使用

我们需要 setup 返回数据那么它肯定就不能使用 async 修饰,这样返回 promise 是我们不想看见情况,如果我们硬要用 async 修饰,我们就得用的在它的父组件外层需要嵌套一个 suspense(不确定)内置组件,里面放置一些不确定的操作,比如我们就可以把异步组件放入进去

<!-- 子组件  -->
<template>
  {{ res }}
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'Son',
  async setup() {
    const res = await axios.get('地址')
    return {
      res,
    }
  },
})
</script>
<!-- 父组件  -->
<template>
    <Suspense>
        <!-- 子组件-->
        <Son></Son>
    </Suspense>
</template>

参数 : setup (props, context)

  • 第一个参数是 props,它接收父组件传递的值,是的就是父子组件信息传递的 props

  • 第二个参数是 context ,里面包含 3 个属性 { attrs, slots, emit },分别对应 this.$attrs,this.$slots,this.$emit,因为 setup 中不存在 this ,Vue3 直接将 setup 中的 this 设为 undefined 了

  • props 的内容为响应式的,不能直接使用 ES6 解构,因为它会消除 props 的响应性,但是可以通过 toRefs 来完成此操作

import { toRefs } from 'vue'

setup(props) {
    const { name, age } = toRefs(props)
    console.log(name.value)
    console.log(age.value)
}

如上图代码所示,setup 中的响应式数据在使用时,需要通过 .value 来获取/修改,可以通过在控制台打印来观察响应式数据与普通数据的区别 ,另外,如果需要在 template 中使用 setup 中的变量的话,需要在 setup 中将使用到的变量 return 出去,亦或者,使用 Vue3 的新语法糖 <script setup> ,此语法糖 相当于在编译运行是把代码放到了 setup 函数中运行,然后把导出的变量定义到上下文中,并包含在返回的对象中 ,就不用再专门去做 return 操作了

toRefs 和 toRef

toRefs 是将某个普通数据转换为响应式数据,toRef 和它差不多,差别是它只能对对象的 某一个 属性变为响应式数据

export default {
  setup(props) {
    useSomeFeature(toRef(props, 'foo'))
  }
}

function useSomeFeature(foo: Ref) {
  // ...
}

拷贝了一份新的数据值单独操作,更新时相互不影响

如何建立响应式数据呢

ref

我们可以通过 ref()来建立响应式数据,并且在修改响应式数据时,应该通过 .value 来进行操作

<template>
    <div class="home">
        <h1>姓名:{{ name }}</h1>
        <h1>年龄:{{ age }}</h1>
        <button @click="say">修改</button>
        <button @click="say2">修改</button>
    </div>
</template>

<script>
    import { ref } from 'vue';
    export default {
        name: 'Home',
        setup() {
            let name = ref('燕儿');
            let age = ref(18);
            console.log(name);
            console.log(age);
            //方法
            function say() {
                name = '苒苒';
                age = 20;
            }
            function say2() {
                name.value = '苒苒';
                age.value = 20;
            }
            return {
                name,
                age,
                say,
                say2,
            };
        },
    };
</script>

执行上面代码,你会发现点击第一个修改,页面视图并没有发生变化,此时再点击第二个按钮控制台还会报错,刷新页面后重新点击第二个按钮,此时页面视图发生变化,注意,虽然在操作响应式数据时,需要通过 .value 来操作其值,但是在 template 中使用变量时并不用加上 .value,此时 Vue3 已经帮我们做处理了

那么用 ref 创建对象呢,如何修改响应式对象的属性值呢

<template>
    <div class="home">
        <h1>姓名:{{ name }}</h1>
        <h1>年龄:{{ job.age }}</h1>
        <h2>职业:{{ job.occupation }}</h2>
        <button @click="say">修改</button>
    </div>
</template>

<script>
    import { ref } from 'vue';
    export default {
        name: 'Home',
        setup() {
            let name = ref('燕儿');
            let job = ref({
                occupation: '程序员',
                age: 18,
            });
            console.log(name);
            console.log(job);
            //方法
            function say() {
                job.value.age = 28;
            }
            return {
                name,
                job,
                say,
            };
        },
    };
</script>

但是我们打印 job.value 会发现,它不再是 RefImpl 实例对象,变成了 Proxy 实例对象,他只是 vue3 底层,把对象都变成了 Proxy 实例对象,对于基本数据类型就是按照 Object.defineProperty 里面的 get 和 set 进行数据劫持然后进行响应式,但是如果是对象类型的话,是用到的 Proxy ,但是 vue3 把它封装在新函数 reactive 里,就相当于,ref 中是对象,自动会调用 reactive

reactive

reactive 的用法与 ref 差不多,需要注意的是 reactive 只能定义对象类型的响应式数据

<template>
  <div class="home">
    <h1>姓名:{{name}}</h1>
    <h2>职业:{{job.occupation}}
    年龄:{{job.age}}</h2>
    <h3>爱好:{{hobby[0]}},{{hobby[1]}},{{ hobby[2] }}</h3>
    <button @click="say">修改</button>
  </div>
</template>

<script>
import {ref,reactive} from 'vue'
export default {
  name: 'Home',
  setup(){
    let name = ref('燕儿')
    let job = reactive({
      occupation:'程序员',
      age:18
    })
    let hobby=reactive(['刷剧','吃鸡','睡觉'])
    console.log(name)
    console.log(job)
    //方法
    function say(){
      job.age=28
      hobby[0]='学习'
    }
    return {
      name,
      job,
      say,
      hobby
    }
  }
}
</script>

可以发现,此时修改对象的属性值,不用再用到 .value 了,因为 reactive 把 Object 转为了 Proxy,介是个深层次的响应式 ,通过打印出的数据也可以看出一些区别,注意,数组也是可以用 reactive 来创建响应式的,这样就不用每次都用 ref 来创建响应式数据了,节约 N 个写 .value 代码的时间

ref 和 reactive 的区别

  • ref 可以用来定义一些简单类型的数据:字符串、数字
  • ref 通过 Object.defineProperty()来实现响应式,即 Vue2 的响应式实现方式
  • 对于 ref 创建的数据,操作需要通过 .value,尽管创建对象、数组时,会将其转为 reactive 来创建响应式数据,但是在操作时,也需要通过 xx.value.xx = xx 来操作
  • reactive 用来定义对象、数组类型的数据
  • reactive 通过 Proxy 代理来实现响应式,通过 Reflect 来操作源代码内部的数据,操作、读取数据不需要用到 .value

前面说过,Vue2 通过 Object.defineProperty()来实现数据操作拦截,但并不能对属性的删除新增来做出反应,Vue3 通过 Proxy 代理来实现了此功能(也使用了 Windows 上的内置对象 Reflect)

  • 通过 Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等
  • 通过 Reflect(反射): 对源对象的属性进行操作
const p=new Proxy(data, {
// 读取属性时调用
    get (target, propName) {
        return Reflect.get(target, propName)
    },
//修改属性或添加属性时调用
    set (target, propName, value) {
        return Reflect.set(target, propName, value)
    },
//删除属性时调用
    deleteProperty (target, propName) {
        return Reflect.deleteProperty(target, propName)
    }
})

判断某个数据是否是响应式

  • isRef
  • isReactive
  • isReadonly
  • isProxy

isRef 检查一个值是否为一个 ref 对象

isReactive 检查一个对象是否是由 reactive 创建的响应式代理

isReadonly 检查一个对象是否是由 readonly 创建的只读代理

isProxy 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

自己配置一个响应式呢

Vue3 给提供了一个 customRef 来让我们实现自定义 Ref,实现原理是对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 tracktrigger 函数作为参数,并应返回一个带有 getset 的对象

<template>
  <h2>App</h2>
  <input v-model="keyword"/>
  <p>{{keyword}}</p>
</template>

<script lang="ts">
import { customRef } from 'vue'
// 不确定类型所以这里使用泛型
function useDebouncedRef<T>(value: T, delay = 200) {
  // 定时器
  let timeout: number
  return customRef((track, trigger) => {
    return {
      get() {
        // 告诉Vue追踪数据
        track()
        return value
      },
      set(newValue: T) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          // 告诉Vue去触发界面更新
          trigger()
        }, delay)
      }
    }
  })
}

export default {
  setup () {
    const keyword = useDebouncedRef('')
    return {
      keyword
    }
  },
}
</script>

shallowReactive 与 shallowRef

  • 创建浅层次的响应式,比如对象中第一层的数据变为响应式,深层次的不做响应式操作
  • shallowRef如果定义的是基本类型的数据,那么它和ref是一样的不会有什么改变,但是要是定义的是对象类型的数据,那么它就不会进行响应式,之前提到如果ref定义的是对象,那么它会自动调用reactive变为Proxy,但是要是用到的是shallowRef那么就不会调用reactive去进行响应式(这个 api 很怪,感觉基本用不上)

readonly 与 shallowReadonly

  • 类似 const,但是更强大的地方是数据深层也是只读,比如对象的属性或数组内容不能修改
  • shallowReadonly 只对第一层进行只读

取消数据的响应式 toRaw 与 markRaw

toRaw其实就是将一个由reactive生成的响应式对象转为普通对象。如果是ref定义的话,是没有效果的(包括ref定义的对象)如果在后续操作中对数据进行了添加的话,添加的数据为响应式数据,当然要是将数据进行markRaw操作后就不会变为响应式,和readonly不一样的地方是,readonly是根本没办法改,但markRaw是不转化为响应式,但是数据还会发生改变

祖孙组件传值 provide 与 inject

在 Vue2 中,如果想要实现祖孙组件的传值,在不考虑存入状态管理中的情况下,只能通过父子组件一层一层传值,但是在 Vue3 中,提供了一种可实现组祖孙组件传值的方法,不论组件层次结果有多深,父组件都是可以作为其所有子组件的以来提供者,只要让父组件提供 provide 选项提供数据,子组件提供 inject 选项来使用这些数据

//父
import { provide } from 'vue'
setup(){
 let userInfo = reactive({id:'007',name:'靓仔'})
 provide('userInfo',userInfo) //给自己的后代组件传递数据
 return {...toRefs(userInfo)}
}
//后代
import {inject} from 'vue'
setup(){
 let userInfo = inject('userInfo')
 return {userInfo}
}

关于 router

Vue3 的路由跳转是定义了一个vue-router,然后引入其useRouteuseRouter来实现 Vue2 中的 this.$route和this.$router

import {useRouter,useRoute} from "vue-router";
setup(){
  const router= useRouter()
  const route= useRoute()
  function fn(){
    this.$router.push('/about')
  }
  onMounted(()=>{
    console.log(route.query.code)
  })
  return{
    fn
  }
}

另外,Vue3 移除了 filter

嗯,可以通过自定义字典方法实现此功能,无伤大雅

watch 和 computed

Vue3 中的 watch 和 Vue2 中差别不大,引入即可

import { ref,watch } from 'vue'
setup() {
    let name = ref('小浪')
    let age = ref(21)
    let watchName = ref('')
    watch(name, (newName, oldName) => {
        watchName.value = `我是新姓名${newName},我是老姓名${oldName}`
    })
    return {
        name,
        age,
        watchName,
    }
}

Vue3 中的 computed 比之前稍有不同

import { ref,computed } from 'vue'
setup() {
    let name = ref('小浪')
    let age = ref(21)
    //计算属性
    let getInfo = computed(() => {
        return `我的名字:${name.value},今年${age.value},请多多指教`
    })
    return {
        name,
        age,
        getInfo,
    }
}

watchEffect

个人感觉更类似于 Vue2 中的 computed

let user = reactive({
    name: '小浪',
    age: 21,
})
// 只有 user.name 发生改变这个就会执行
watchEffect(() => {
    console.log(user.name)
});

Teleport

看了文档,感觉像是吃鸡里面的定点空投,可以把组件进行传送到指定的位置去

<teleport v-if="flag" to=".test">
    <div class="dog">狗子</div>
</teleport>

to 是目标的地址 body , #XXX , .XXX 这些都是 css 选择器

小结:第一篇关于 Vue3 的学习笔记,暂时记录到此,本篇主要是记录并实验了 Vue3 中出现的新的语法和特性,并大概的了解了其功能实现原理的皮毛,对于程序眼来说,现阶段使用到的任何的语言、框架,对其理解如果只停留于 api 的使用,不管是对于工作还是自身技术发展,都是远远不够的,还是得多学习其功能实现源码才行..

参考链接vue3保姆级教程 - 掘金

参考链接还不会Vue3?一篇笔记带你快速入门 - 掘金

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

圆周率呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值