美文网首页
Vue3 学习笔记 (基础)

Vue3 学习笔记 (基础)

作者: 夹板儿孩 | 来源:发表于2022-11-01 22:54 被阅读0次

    Vue3 学习笔记 (基础)

    看视频学习的笔记,自用
    bilibili 尚硅谷禹神

    setup 一切的开始

    1. 组建中所用到的:数据、方法等都 应当 配置在 setup 中。没有写到 setup 中的函数或者属性在组件模板中无法访问。
    2. vue2 中的 data、computed等生命周期组件在 vue3 中可以写,但是不建议写
    3. setup 中没有 this
    4. 通常情况下 setup 组件是不能够返回 promise 的
    5. 当组件返回 promise 时,要确定被引用时,要使用 defineAsyncComponent 接收组件,并且使用 Suspense 来报过组件进行配合(后面文档有记载)
    <script lang="ts">
    export default {
      setup() {
        const name = '张三'
        const age = 18
        function sayHello() {
          alert("hello world")
        }
        return {
          name,     // 抛出 name 属性
          age,      // 抛出 age 属性
          sayHello  // 抛出 sayHello 函数
        }
      }
    }
    </script>
    <template>
      <h1>人员信息</h1>
      <h2>姓名: {{ name }}</h2>
      <h2>年龄: {{ age }}</h2>
      <button @click="sayHello">sayHello</button>
    </template>
    

    ref 动态响应

    ref 函数的作用是用于动态渲染。

    1. ref 会给普通的值类型属性更改为 RefImpl 对象,并且实现 get、set 两个函数。
    2. ref 会吧引用类型的对象更改为 Proxy 对象

    使用 ref 时,除了在模板中,都需要操作实际的 value 否则无效

    <script lang="ts">
    import {ref} from "vue";
    
    export default {
      setup() {
        const name = ref('张三')  // 使用 ref 来定义一个属性
        const age = ref(18)      // 使用 ref 来定义一个属性
        const job = ref({        // 使用 ref 来定义一个对象 ref 实际是通过 ES6 中的 Proxy 实现了这个对象的代理,进行了数据内部的处理
          type: '前端工程师',
          year: 2
        })
    
        function sayHello() {
          alert("hello world")
        }
    
        function changeInfo() {
          name.value = '李四' // 更改 name 属性的值
          age.value = 30     // 更改 age 属性的值
        }
    
        function changeJob() {
          const work = job.value    // 获取到 job 的这个对象
          work.type = '全栈'        // 更改 job 中 type 这个属性
          work.year = 10           // 更改 job 中 year 这个属性
        }
    
        return {
          name,
          age,
          job,
          sayHello,
          changeInfo,
          changeJob
        }
      }
    }
    </script>
    <template>
      <h1>人员信息</h1>
      <h2>姓名: {{ name }}</h2>
      <h2>年龄: {{ age }}</h2>
      <h1>工作信息</h1>
      <h2>岗位类型: {{job.type}}</h2>
      <h2>工作年限: {{job.year}}</h2>
      <button @click="sayHello">sayHello</button>
      <br><br>
      <button @click="changeInfo">改变信息</button>
      <br><br>
      <button @click="changeJob">更换工作</button>
    </template>
    
    

    reactive 动态响应对象

    • 定义一个 对象类型 的响应式数据(基本类型不要用 reactive)
    • reactive 定义的响应式数据是深层响应的
    • 基于 ES6 的Proxy 实现。通过代理对象操作源对对象内部数据进行操作
    <template>
      <h1>人员信息</h1>
      <h2>姓名: {{ name }}</h2>
      <h2>年龄: {{ age }}</h2>
      <h1>工作信息</h1>
      <h2>岗位类型: {{job.type}}</h2>
      <h2>工作年限: {{job.year}}</h2>
      <button @click="sayHello">sayHello</button>
      <br><br>
      <button @click="changeInfo">改变信息</button>
      <br><br>
      <button @click="changeJob">更换工作</button>
    </template>
    <script lang="ts">
    import {ref, reactive} from "vue";
    
    export default {
      setup() {
        const name = ref('张三')
        const age = ref(18)
        // 使用 reactive 来初始化对象
        const job = reactive({
          type: '前端工程师',
          year: 2
        })
    
        function sayHello() {
          alert("hello world")
        }
    
        function changeInfo() {
          name.value = '李四'
          age.value = 30
        }
    
        function changeJob() {
          // 直接更改属性,不需要再 .value 了
          job.type = '全栈'
          job.year = 10
        }
    
        return {
          name,
          age,
          job,
          sayHello,
          changeInfo,
          changeJob
        }
      }
    }
    </script>
    
    

    Proxy 代理

    Proxy 是 Es6 提供的对象代理函数。在在做对象时直接操作 Proxy 的代理,它会同步修改原来对象中的属性。使用代理可以完成对对象的原生监听

    Proxy 参数 ( 源对象,操作对象)

    如:

        const person = {
          name: '张三',
          age: 18
        }
        const student = new Proxy(person, {})
        student.name = '李四' // 修改代理中的name属性 相当于 person.name = '李四'
        console.log(student)  // Proxy {name: '李四', age: 18}
        console.log(person) // {name: '李四', age: 18}
    

    get / set / delete

    
        const person = {
          name: '张三',
          age: 18
        }
    
        const student = new Proxy(person, {
          get(target, propName: string): unknown {
            console.log(`读取了 student 中的 ${propName} 属性`)
            return Reflect.get(target, propName)
          },
          set(target, propName: string, value): boolean {
            console.log(`写入了 student 中的 ${propName} 值为 ${value}`)
            return Reflect.set(target, propName, value)
          },
          deleteProperty(target, propName: string): boolean {
            console.log(`删除了 student 中的 ${propName} 属性`)
            return Reflect.deleteProperty(target, propName)
          }
        })
    
        student.name = '李四' // 修改代理中的name属性 相当于 person.name = '李四'
        console.log(student.name)  // 李四
        // 读取了 student 中的 name 属性
        console.log(student)  // Proxy {name: '李四', age: 18}
        Reflect.deleteProperty(student, 'age')  // 删除 age 属性
        console.log(JSON.stringify(student)) // Proxy {name: '李四'}
        console.log(JSON.stringify(person)) // {name: '李四'}
    
    

    computed 计算属性

    需要从 vue 中进入 computed 函数

    import { reactive, computed } from "vue";

    <script lang="ts">
    import { reactive, computed } from "vue";
    
    export default {
      setup() {
        const person = reactive({
          firstName: '张',
          lastName: '三'
        })
        // 设置一个叫 fullName 的计算属性
        Reflect.set(person, 'fullName', computed({
          get() {
            return person.firstName + '-' + person.lastName
          },
          set(value: string) {
            const split = value.split('-')
            person.firstName = split[0]
            person.lastName = split[1]
          }
        }))
        return {
          person
        }
      }
    }
    </script>
    
    <template>
      <div>
        姓: <input type="text" v-model="person.firstName">
        名: <input type="text" v-model="person.lastName">
        全名: <input type="text" v-model="person.fullName">
      </div>
    </template>
    
    

    watch 监听

    需要从 vue 中心如 watch 函数

    注意:

    1. reactive 定义的对象在使用 watch 监听时无法正确的获得 oldVal
    2. vue3 目前版本中 watch 监听对象是默认深度监听的,且 deep 也无法关闭

    watch 的三个参数

    1. 被监听的值/对象
    2. 监听触发回调函数。两个回调参数 ( 改变后的值/对象,改变前的值/对象 )
    3. 配置项。两个配置 {deep: boolean, immediate: boolean} ; deep: 深度监听; immediate: 初始触发监听
    <script lang="ts">
    import { ref, reactive, watch } from "vue";
    
    export default {
      setup() {
        let sum = ref(0)
        let msg = ref(0)
        const person = reactive({
          firstName: '',
          lastName: '',
          fullName: '',
          job: {
            year: 10
          }
        })
        // 1. 普通监听
        watch(sum, (newVal, oldVal) => {
          console.log(newVal, oldVal)
          msg.value = newVal
        })
        // 2. 普通监听
        watch(msg, (newVal, oldVal) => {
          console.log(newVal, oldVal)
          sum.value = newVal
        })
        // 3. 组合监听
        watch([sum, msg], (newVal, oldVal) => {
          console.log(newVal, oldVal) // newVal: ['sum', 'msg'],  oldVal: ['sum', 'msg']
        }, { immediate: true }) // 初始触发 与 vue2 中 watch 里的 immediate 一致
          
        // 4. 监听 reactive 生成对象中的某一个属性
        watch(() => person.job.year, (newVal, oldVal) => {
          console.log(newVal, oldVal)
        })
          
        // 5. 监视的是 reactive 对象中某个对象属性时,超过 1 层的属性对象将会无法监听,需要添加 deep:true 配置才有效
        watch(() => person.job, (newVal, oldVal) => {
          console.log(newVal, oldVal)
        }, {deep: true})  
          
        // 6. 监听 ref 生成的对象
        const person2 = ref({
          firstName: '',
          lastName: '',
          fullName: '',
          job: {
            year: 10,
            work: {
              address: '南京'
            }
          }
        })
        // 当被监听的是一个由 ref 生成的对象时,那么监听时需要监听它实际的值
        watch(person2.value, (newVal, oldVal) => {
          console.log(newVal, oldVal)
        })
        return {
          person,
          sum,
          msg
        }
      }
    }
    </script>
    
    <template>
      <div>
        sum: <input type="text" v-model="sum">
        smg: <input type="text" v-model="msg">
          
        姓: <input type="text" v-model="person.firstName">
        名: <input type="text" v-model="person.lastName">
        全名: <input type="text" v-model="person.fullName">
        工作年限: <input type="text" v-model="person.job.year">
      </div>
    </template>
    
    

    watchEffect 监听

    <script lang="ts">
    import { ref, reactive, watch, watchEffect } from "vue";
    
    export default {
      setup: function () {
        const person = reactive({
          firstName: '',
          lastName: '',
          fullName: '',
          job: {
            year: 10,
            work: {
              address: '南京'
            }
          }
        })
    
        // watchEffect 默认开启了 immediate: true
        // watchEffect 不需要指定监视谁,它更类似于一个computed,在函数中用到的属性一旦发生改变,都将会触发这个函数
        // watchEffect 更注重程序的执行过程,没有返回值
        watchEffect(()=> {
          const name = person.fullName
          const address = person.job.work.address
          console.log("watchEffect 被执行了")
        })
    
        return {
          person,
        }
      }
    }
    </script>
    
    <template>
      <div>
        姓: <input type="text" v-model="person.firstName">
        名: <input type="text" v-model="person.lastName">
        全名: <input type="text" v-model="person.fullName">
        工作年限: <input type="text" v-model="person.job.year">
        工作信息: <input type="text" v-model="person.job.work.address">
      </div>
    </template>
    
    

    setup 进阶

    setup 提供了两个参数

    1. props 属性

    2. context 上下文

      属性有 attrs, emit, expose, slots

      attrs: 值为对象,收录了所有 props 中没有声明但被传递的属性。相当于 Vue2 中的 $attrs

      emit: 分发自定义时间的函数。相当于 Vue2 中的 $emit

      expose:

      slots: 收到的插槽内容。相当于 Vue2 中的 $slots

    hook

    hook 本质是一个函数,用于复用可用代码

    hook 文件 .../usePoint.ts

    import {reactive, onMounted, onUnmounted} from "vue";
    
    export default function () {
    const point = reactive({
    x: 0,
    y: 0
    })
    
    function onclick(event: MouseEvent) {
    point.x = event.pageX
    point.y = event.pageY
    }
    
    onMounted(() => window.addEventListener('click', onclick))
    onUnmounted(() => window.addEventListener('click', onclick))
    return point
    }
    
    

    引入 hook 的文件

    <script lang="ts">
    import usePoint from "@/hooks/usePoint";
    
    export default {
      setup: function () {
        const point = usePoint()
        return {
          point
        }
      }
    }
    </script>
    
    <template>
      <div>
        鼠标位置 x {{point.x}} y {{point.y}}
      </div>
    </template>
    
    

    toRef 函数

    <script lang="ts">
    import { reactive, toRef } from "vue";
    
    export default {
      setup: function () {
        const person = reactive({
          job: {
            year: 10,
            work: {
              address: '南京'
            }
          }
        })
        // 使用 toRef 可以将Proxy中的属性与变量名进行桥接,更改被桥接的名字或者这个变量名时,两边都会进行同样的更改
        let address = toRef(person.job.work, 'address')
        return {
          person,
          address
        }
      }
    }
    </script>
    
    <template>
      <div>
        我的地址: <input type="text" v-model="address">
        person 中的地址: <input type="text" v-model="person.job.work.address">
      </div>
    </template>
    
    

    toRefs 函数

     <script lang="ts">
    import { reactive, toRefs } from "vue";
    
    export default {
      setup: function () {
        const person = reactive({
          job: {
            year: 10,
            work: {
              address: '南京'
            }
          }
        })
        // toRefs 可以将对象中的第一层全部 toRef 出来为一个新的对象
        const obj = toRefs(person)
        return {
          person,
          ...toRefs(person) // 直接展开这个对象,那么即可以立即在模板中直接使用
        }
      }
    }
    </script>
    
    <template>
      <div>
        我的地址: <input type="text" v-model="job.work.address">
        person 中的地址: <input type="text" v-model="person.job.work.address">
      </div>
    </template>
    
    

    不常用函数

    readonly 只读函数

    对象、值一旦使用了 readonly 函数,那么这个对象、值将不能再被修改。只能被读取

    <script lang="ts">
    import { reactive, readonly } from "vue";
    
    export default {
      setup: function () {
        // 浅层次的 reactive 声明一个对象。这种声明方式只会考虑第一层数据的响应式。超过一层的数据改变时将不会改变源对象
        let person = reactive({
          firstName: '',
          lastName: '',
          fullName: '',
          job: {
            year: 10,
            work: {
              address: '南京'
            }
          }
        })
        person = readonly(person)
        return {
          person,
        }
      }
    }
    </script>
    
    <template>
      <div>
    <!--    下面都无法更改到源数据-->
        姓: <input type="text" v-model="person.firstName">
        名: <input type="text" v-model="person.lastName">
        全名: <input type="text" v-model="person.fullName">
        工作年限: <input type="text" v-model="person.job.year">
        工作信息: <input type="text" v-model="person.job.work.address">
        <br>
        <span>{{JSON.stringify(person)}}</span>
      </div>
    </template>
    
    

    shallowReactive、shallowRef、shallowReadonly

    浅响应式。相当于弱化了的 reactive 和 ref

    • shallowReactive 只处理对象最外层的响应式
    • shallowRef 只处理基本的数据类型响应式,不进行对象的响应式处理
    • shallowReadonly 将对象的第一层设置为只读,其他可读
    <script lang="ts">
    import { shallowReactive, shallowRef } from "vue";
    
    export default {
      setup: function () {
        // 浅层次的 reactive 声明一个对象。这种声明方式只会考虑第一层数据的响应式。超过一层的数据改变时将不会改变源对象
        const person = shallowReactive({
          firstName: '',
          lastName: '',
          fullName: '',
          job: {
            year: 10,
            work: {
              address: '南京'
            }
          }
        })
        return {
          person,
        }
      }
    }
    </script>
    
    <template>
      <div>
        姓: <input type="text" v-model="person.firstName">
        名: <input type="text" v-model="person.lastName">
        全名: <input type="text" v-model="person.fullName">
        工作年限: <input type="text" v-model="person.job.year">
        工作信息: <input type="text" v-model="person.job.work.address">
        <br>
        <span>{{JSON.stringify(person)}}</span>
      </div>
    </template>
    
    

    toRaw 回归原始

    reactive 生成的对象一旦被 toRaw 以后,就不在是 Proxy 代理对象了。也不再具有动态响应的作用

    <script lang="ts">
    import { reactive, toRaw } from "vue";
    
    export default {
      setup: function () {
        // 浅层次的 reactive 声明一个对象。这种声明方式只会考虑第一层数据的响应式。超过一层的数据改变时将不会改变源对象
        let person = reactive({
          firstName: '',
          lastName: '',
          fullName: '',
          job: {
            year: 10,
            work: {
              address: '南京'
            }
          }
        })
        person = toRaw(person)
        return {
          person
        }
      }
    }
    </script>
    
    <template>
      <div>
    <!--    下面都无法更改到源数据-->
        姓: <input type="text" v-model="person.firstName">
        名: <input type="text" v-model="person.lastName">
        全名: <input type="text" v-model="person.fullName">
        工作年限: <input type="text" v-model="person.job.year">
        工作信息: <input type="text" v-model="person.job.work.address">
        <br>
        <span>{{JSON.stringify(person)}}</span>
      </div>
    </template>
    
    

    markRaw 标记原始

    对象的某个属性被标记标记了原始以后,将不会被动态响应

    <script lang="ts">
    import { reactive, markRaw } from "vue";
    
    export default {
      setup: function () {
        // 浅层次的 reactive 声明一个对象。这种声明方式只会考虑第一层数据的响应式。超过一层的数据改变时将不会改变源对象
        let person = reactive({
          firstName: '',
          lastName: '',
          fullName: '',
        })
        let job = {
          year: 10,
          work: {
            address: '南京'
          }
        }
        job = markRaw(job)
        Reflect.set(person, 'job', job)
        return {
          person
        }
      }
    }
    </script>
    
    <template>
      <div>
        姓: <input type="text" v-model="person.firstName">
        名: <input type="text" v-model="person.lastName">
        全名: <input type="text" v-model="person.fullName">
    <!--    下面都无法更改到源数据-->
        工作年限: <input type="text" v-model="person.job.year">
        工作信息: <input type="text" v-model="person.job.work.address">
        <br>
        <span>{{JSON.stringify(person)}}</span>
      </div>
    </template>
    
    

    响应式数据的类型判断

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

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

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

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

    感觉很重要,但是用的不多

    customRef 自定义 ref 的实现

    这是一个官方的实现案例,延迟更新新效果

    <script lang="ts">
    import { customRef } from "vue";
    import { NodeJS } from "timers";
    
    export default {
      setup: function () {
    
        function myRef(value:string) {
          let timer:NodeJS.Timeout = undefined
          return customRef((track, trigger) => {
            return {
              get() {
                track() // 1,告诉Vue跟踪这个值,使它每次变化都更新
                return value
              },
              set(newValue:string) {
                timer && clearTimeout(timer)
                timer = setTimeout(()=>{
                  // 延迟更新
                  value = newValue
                  trigger() // 2. 通知Vue数据更新了
                }, 500)
              }
            }
          })
        }
        let name = myRef('你好啊')
        return {
          name
        }
      }
    }
    </script>
    
    <template>
      <div>
        请输入: <input type="text" v-model="name">
        <br>
        输入内容为: {{name}}
        <br>
      </div>
    </template>
    
    

    provide/inject 后代组件传值

    provide

    // 父组件定义
    import {provide} from 'vue';
    export default({
      name: 'HomeView',
      setup() {
        let heirloom = ref('双鱼玉佩')
        // 通过 provide 将 car 传递给后代
        provide('heirloom', heirloom)
          
        return {
        }
      }
    })
    

    inject

    <script lang="ts">
    import {inject} from "vue";
    
    export default {
      setup: function () {
        // 通过 inject 接收祖辈组件传值
        const heirloom = inject('heirloom')
        return {
          heirloom
        }
      }
    }
    </script>
    
    <template>
      <div>
        <br>
        传家宝: {{heirloom}}
        <br>
      </div>
    </template>
    
    

    组件

    teleport 传送组件

    在这个组件内部写的所有组件都可以通过 teleport 的 to 属性传送到指定的位置去

    如,这里传送到了 body 中

    <script lang="ts">
    import {inject, isRef, isReactive} from "vue";
    
    export default {
      setup: function () {
        const car = inject('car')
        console.log(isRef(car))
        console.log(isReactive(car))
        return {
          car
        }
      }
    }
    </script>
    
    <template>
      <div>
        <h1>Vue3</h1>
      </div>
      <teleport to="body">
        <div>传家宝: {{car}}</div>
        <div>传送到body去</div>
      </teleport>
    </template>
    
    

    如果想要传送到指定的 div 中,那么需要先在 index.html 中实现定义好这个div

    如:

    <!DOCTYPE html>
    <html lang="">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <title><%= htmlWebpackPlugin.options.title %></title>
      </head>
      <body>
        <noscript>
          <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
        </noscript>
        <div id="app"></div>
        <div class="dialog"></div> <!-- 传到这个div 中 -->
        <!-- built files will be auto injected -->
      </body>
    </html>
    
    
    <script lang="ts">
    import {inject, isRef, isReactive} from "vue";
    
    export default {
      setup: function () {
        const car = inject('car')
        console.log(isRef(car))
        console.log(isReactive(car))
        return {
          car
        }
      }
    }
    </script>
    
    <template>
      <div>
        <h1>Vue3</h1>
      </div>
      <teleport to=".dialog">
        <div>传家宝: {{car}}</div>
        <div>传送到body去</div>
      </teleport>
    </template>
    
    

    Suspense 异步组件

    用于需要从后台加载数据后才渲染的组件

    <template>
      <div class="home">
        <Suspense>
    <!--      suspense 提供了两个槽位
          default  是默认槽位,是代码正确运行后让用户看到的结果
          fallback 是代码异常或者加载缓慢时让用户看到的结果-->
          <template v-slot:default>
            <test-vue3></test-vue3>
          </template>
          <template v-slot:fallback>
            <div>加载中</div>
          </template>
        </Suspense>
      </div>
    </template>
    <script lang="ts">
    // defineAsyncComponent 异步引入组件
    import {defineAsyncComponent} from 'vue';
    
    const TestVue3 = defineAsyncComponent(()=> import('@/components/TestVue3.vue'));
    export default({
      name: 'HomeView',
      components: {
        TestVue3
      }
    })
    </script>
    
    

    当组件是使用 defineAsyncComponent 来接收时,那么被使用的组件 setup 可以返回 promise

    父组件

    <template>
      <div class="home">
        <Suspense>
    <!--      suspense 提供了两个槽位
          default  是默认槽位,是代码正确运行后让用户看到的结果
          fallback 是代码异常或者加载缓慢时让用户看到的结果-->
          <template v-slot:default>
            <test-vue3></test-vue3>
          </template>
          <template v-slot:fallback>
            <div>加载中</div>
          </template>
        </Suspense>
      </div>
    </template>
    <script lang="ts">
    // defineAsyncComponent 异步引入组件
    import {defineAsyncComponent} from 'vue';
    
    const TestVue3 = defineAsyncComponent(()=> import('@/components/TestVue3.vue'));
    export default({
      name: 'HomeView',
      components: {
        TestVue3
      }
    })
    </script>
    
    

    子组件

    <script lang="ts">
    import {inject, isRef, isReactive} from "vue";
    
    export default {
      setup: function () {
        const car = inject('car')
        console.log(isRef(car))
        console.log(isReactive(car))
        return new Promise((resolve, reject) => {
          setTimeout(()=> {
            resolve(1)
          }, 2000)
        })
      }
    }
    </script>
    
    <template>
      <h1>Vue3</h1>
    </template>
    
    

    End

    相关文章

      网友评论

          本文标题:Vue3 学习笔记 (基础)

          本文链接:https://www.haomeiwen.com/subject/fijstdtx.html