Vue3+TypeScript基础

Vue3+Ts 入门

一、组合式API

1、计算属性API

  • 单向响应
    <template>
        姓:<input v-model="username.surname"> <br>
        名:<input v-model="username.name"> <br>
        单向响应:<input v-model="username.nick"> <br>
    </template>
    
    <script setup lang="ts">
    import { computed } from '@vue/reactivity';
    import { reactive } from 'vue';
    const username = reactive({
        surname: "张",
        name: "三"
    })
    username.nick = computed(()=>{
       return username.surname + '*' + username.name
    })
    </script>
    
  • 双向响应
    <template>
        姓:<input v-model="username.surname"> <br>
        名:<input v-model="username.name"> <br>
        双向响应:<input v-model="username.nick"> <br>
    </template>
    
    <script setup lang="ts">
    import { computed } from '@vue/reactivity';
    import { reactive } from 'vue';
    const username = reactive({
        surname: "张",
        name: "三"
    })
    username.nick = computed({
       get(){
        return username.surname + '*' + username.name
       },
       set(value){
        const arr = value.split('*')
        username.surname = arr[0]
        username.name = arr[1]
       }
    })
    </script>
    

2、监听属性API

  • 监听整个对象
    # 监听整个对象,由于是浅拷贝,他们新旧指向的是同一个对象
    <template>
        <h2>年龄:{{ userInfo.age }}</h2>
        <button @click="userInfo.age++"> 年龄+1</button>
    </template>
    
    <script setup lang="ts">
    import { reactive, watch } from 'vue';
    
    const userInfo = reactive({
        name: '张三',
        age: 20
    })
    watch(userInfo, (newVal, oldVal) => {
        console.log('新:');
        console.log(newVal);
        console.log('************************');
        console.log('旧:');
        console.log(oldVal);
    })
    </script>
    
  • 监听对象中单个属性
    # 监听对象中单个属性可以检测到新旧值的变化
    <template>
        <h2>年龄:{{ userInfo.age }}</h2>
        <button @click="userInfo.age++"> 年龄+1</button>
    </template>
    
    <script setup lang="ts">
    import { reactive, watch } from 'vue';
    
    const userInfo = reactive({
        name: '张三',
        age: 20
    })
    watch(()=>userInfo.age, (newVal, oldVal) => {
        console.log('新:');
        console.log(newVal);
        console.log('************************');
        console.log('旧:');
        console.log(oldVal);
    })
    </script>
    
  • 监听多个对象
    # 监听对象中多个属性
    <template>
        <h2>姓名:{{ userInfo.name }}</h2>
        <button @click="userInfo.name+='是个傻逼'">改名</button>
        <br>
        <h2>年龄:{{ userInfo.age }}</h2>
        <button @click="userInfo.age++"> 年龄+1 </button>
    </template>
    
    <script setup lang="ts">
    import { reactive, watch } from 'vue';
    
    const userInfo = reactive({
        name: '张三',
        age: 20
    })
    watch([()=>userInfo.name,()=>userInfo.age], (newVal, oldVal) => {
        console.log('新:');
        console.log(newVal);
        console.log('************************');
        console.log('旧:');
        console.log(oldVal);
    })
    </script>
    
  • 监听对象中的对象(深度监听)
    <template>
        <h2>年龄:{{ userInfo.hobby.like }}</h2>
        <button @click="userInfo.hobby.like += '很快乐'">点我</button>
        <br>
    </template>
    
    <script setup lang="ts">
    import { reactive, watch } from 'vue';
    
    const userInfo = reactive({
        name: '张三',
        age: 20,
        hobby: {
            like: '打球'
        }
    })
    watch(() => userInfo.hobby.like, (newVal, oldVal) => {
        console.log('新:');
        console.log(newVal);
        console.log('************************');
        console.log('旧:');
        console.log(oldVal);
    }, { deep: true })
    </script>
    

3、高级监听API (watchEffect)

  • 基本使用(默认先执行一次)
    # watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调
    <template>
        <h2>姓名:{{ userInfo.name }}</h2>
        <button @click="userInfo.name += '111'">修改</button>
        <br>
    </template>
    
    <script setup lang="ts">
    import { reactive, watch, watchEffect } from 'vue';
    const userInfo = reactive({
        name: '张三'
    })
    watchEffect(()=>{
        userInfo.name
        console.log('姓名变了');
    })
    </script>
    
  • 监听预前处理oninvalidate参数
    # oninvalidate 监听值修改前的回调
    <template>
        <h2>姓名:{{ userInfo.name }}</h2>
        <button @click="userInfo.name += '111'">修改</button>
        <br>
    </template>
    
    <script setup lang="ts">
    import { reactive, watchEffect } from 'vue';
    const userInfo = reactive({
        name: '张三'
    })
    watchEffect((oninvalidate)=>{
        oninvalidate(()=>{
            console.log("before");
        })
        userInfo.name
        console.log('姓名变了');
    })
    </script>
    
  • 停止监听
    # 停止后不再监听值的变化
    <template>
        <h2>姓名:{{ userInfo.name }}</h2>
        <button @click="userInfo.name += '111'">修改</button>
        <button @click="stop">停止监听</button>
        <br>
    </template>
    
    <script setup lang="ts">
    import { reactive, watchEffect } from 'vue';
    const userInfo = reactive({
        name: '张三'
    })
    const stop = watchEffect((oninvalidate)=>{
        oninvalidate(()=>{
            console.log("before");
        })
        userInfo.name
        console.log('姓名变了');
    })
    </script>
    

4、响应式对象解构API

  • toRef()
    <template>
        <h2>姓名:{{ AA }}</h2>
        <button @click="userInfo.name += '加1'">修改</button>
    </template>
    
    <script setup lang="ts">
    import { reactive, toRef } from 'vue';
    const userInfo = reactive({
        name: '张三'
    })
    const AA = toRef(userInfo,'name')
    </script>
    
  • toRefs()
    <template>
        <h2>姓名:{{ name }}</h2>
        <button @click="name += '加1'">修改姓名</button>
        <br>
        <h2>年龄:{{ age }}</h2>
        <button @click="age++">修改年龄</button>
    </template>
    
    <script setup lang="ts">
    import { reactive, toRef, toRefs } from 'vue';
    const userInfo = reactive({
        name: '张三',
        age: 20
    })
    const {name,age} = toRefs(userInfo)
    </script>
    

5、生命周期API

<script setup lang="ts">
import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated } from 'vue';

console.log('setup执行');

onBeforeMount(() => {
    console.log('*** 挂载前 ***');
})
onMounted(() => {
    console.log('*** 挂载后 ***');
})
onBeforeUpdate(() => {
    console.log('*** 更新前 ***');
})
onUpdated(() => {
    console.log('*** 更新后 ***');
})
onBeforeUnmount(() => {
    console.log('*** 卸载前 ***');
})
onUnmounted(() => {
    console.log('*** 卸载后 ***');
})
</script>

6、ref 获取 dom

<template>
    <div ref="box">我是box</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
let box = ref(null)  // 本质是reactive({value:null})
// dom挂载后的生命周期
onMounted(()=>{
    console.log(box.value);
})
// 这里会输出null,setup是最先执行,此时dom还没生成
console.log(box.value);
</script>

7、Hooks

  • 官方hooks
    • useAttrs()
      // 父组件
      <template>
          <SonVue tit="父组件传值1" title="父组件传值2"/>
      </template>
      
      // 子组价
      <script setup lang="ts">
      import { useAttrs } from 'vue';
      let attr = useAttrs()
      console.log(attr);
      </script>
      
  • 自定义hooks
    • 鼠标跟踪器示例
      # convertImg.ts
      
      import { ref, onMounted, onUnmounted } from 'vue'
      
      // 按照惯例,组合式函数名以“use”开头
      export function useMouse() {
        // 被组合式函数封装和管理的状态
        const x = ref(0)
        const y = ref(0)
      
        // 组合式函数可以随时更改其状态。
        function update(event) {
          x.value = event.pageX
          y.value = event.pageY
        }
      
        // 一个组合式函数也可以挂靠在所属组件的生命周期上
        // 来启动和卸载副作用
        onMounted(() => window.addEventListener('mousemove', update))
        onUnmounted(() => window.removeEventListener('mousemove', update))
      
        // 通过返回值暴露所管理的状态
        return { x, y }
      }
      
      # 在组件中的使用
      
      <template>
          <div>横轴:{{ x }}</div>
          <div>纵轴:{{ y }}</div>
      </template>
      <script setup lang="ts">
      import { useMouse } from '../../hooks/convertImg'
      const { x, y } = useMouse()
      </script>
      
    • 第三方hooks
      • 安装依赖npm install @vueuse/core

      • 简单使用

        <template>
            <div ref="el" :style="style" style="position: fixed;background-color: pink;">
                 请拖动我 {{ x }}, {{ y }}
            </div>
        </template>
        
        <script setup lang="ts">
        import { ref } from 'vue';
        import { useDraggable } from '@vueuse/core'
        const el = ref<HTMLElement | null>(null)
        const { x, y, style } = useDraggable(el, {
            initialValue: { x: 40, y: 40 }
        })
        </script>
        

8、插槽

8.1、匿名插槽
  • A组件
    <template>
        <B>
            这是XXX数据
            这是YYY数据
        </B>
    </template>
    
    <script setup lang="ts">
    import B from '@/views/A/B.vue'
    </script>
    
  • B组件:
    <template>
        <div>
            <header>
                <div>头部</div>
                <slot></slot>
            </header>
            <footer>
                <div>底部</div>
                <slot></slot>
            </footer>
        </div>
    </template>
    
8.2、具名插槽
  • A组件
    <template>
        <B>
            <!-- 标准写法 -->
            <template v-slot:xxx>这是xxx数据</template>
            <template v-slot:yyy>这是yyy数据</template>
             <!-- 简写 -->
            <template #xxx>这是yyy数据</template>
            <template #yyy>这是yyy数据</template>
        </B>
    </template>
    
    <script setup lang="ts">
    import B from '@/views/A/B.vue'
    </script>
    
  • B组件
    <template>
        <div>
            <h1>
                :A组件
            </h1>
            <header>
                <div>头部</div>
                <slot name="xxx"></slot>
            </header>
            <footer>
                <div>底部</div>
                <slot name="yyy"></slot>
            </footer>
        </div>
    </template>
    
8.3、作用域插槽
  • A组件

    <template>
        <B>
            <!-- 标准写法 -->
            <template v-slot="{ data }">{{ data.name }}==>{{ data.age }}</template>
            <!-- 简写 -->
            <template #default="{ data }">{{ data.name }}==>{{ data.age }}</template>
        </B>
    </template>
    
    <script setup lang="ts">
    import B from '@/views/A/B.vue'
    </script>
    
  • B组件

    <template>
        <div>
            <section>
                <div v-for="item in list" :key="item.id">
                <slot :data="item"></slot>
                </div>
            </section>
        </div>
    </template>
    
    <script setup lang="ts">
    import { ref } from 'vue';
    
    const list = ref([
        {id:1,name:'张三',age:15},
        {id:2,name:'李四',age:18},
        {id:3,name:'王二麻子',age:20},
        {id:4,name:'刘五',age:28},
        {id:5,name:'小六子',age:22},
    ])
    </script>
    
8.4、动态插槽 -【通过数据进行切换】
  • A组件

    <template>
        <B>
            <template #[yyy]>这是yyy数据</template>
        </B>
    </template>
    
    <script setup lang="ts">
    import B from '@/views/A/B.vue'
    import { ref } from 'vue';
    let yyy = ref('yyy')
    </script>
    
  • B组件

     <header>
                <div>头部</div>
                <slot name="xxx"></slot>
                <div>底部</div>
                <slot name="yyy"></slot>
     </header>
    

9、Teleport(传送)

<Teleport> 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。

 <Teleport to=".main">这是传送1</Teleport>
 <Teleport to="#home">这是传送2</Teleport>
 <Teleport to="body">这是传送3</Teleport>

注意:必须dom存在才能传送

10、异步组件(提升性能)

Vue 提供了 defineAsyncComponent 方法来实现异步组件

<template>
  <A></A>
  <B></B>
  <div ref="target">
    <AsyncComp v-if="targetIsShow"></AsyncComp>
  </div>

</template>
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import { useIntersectionObserver } from '@vueuse/core';  // npm插件
import A from './A.vue';
import B from './B.vue';
const AsyncComp = defineAsyncComponent(() =>
  import('./C.vue')
)
const target = ref<any>(null);
const targetIsShow = ref(false);
 useIntersectionObserver(
  target,
  ([{ isIntersecting }]) => {
    if (isIntersecting) {
      targetIsShow.value = isIntersecting
    } 
  }
)
</script>
defineAsyncComponent 打包时做分包处理,异步组件是一个单独的js文件。

11、Mixin(混入)

含义:分发vue组件中可复用的功能。

注意:vue3中使用Hooks

// mixin.ts
import { ref } from "vue";
export default function() {
  let num = ref(1);
  let bel = ref(true)
  const btn = () => {
    num.value += 1
    bel.value = false
    setTimeout(() => {
      bel.value = true
    }, 3000);
  }
  return {
    num,
    bel,
    btn
  }
}

A组件使用

<template>
  <div style="width:300px;height: 200px;background-color: #f60;">
    <h1>A组件</h1>
    {{ num }}
    <button @click="btn">{{ bel }}</button>
  </div>
</template>
<script setup lang="ts">
import mixin from '../mixin/mixin';
let { num, bel, btn } = mixin();
</script>

B组件使用

<template>
   <div style="width:300px;height: 600px;background-color: blue;">
      B组件
         {{ num }}
          <button @click="btn">{{ bel }}</button>
    </div>
</template>

<script setup lang="ts">
import mixin from '../mixin/mixin';
let { num, bel, btn } = mixin();
</script>

12、Vue3生命周期

在组合API中,我们需要将生命周期钩子导入到项目中,才能使用,这有助于保持项目的轻量性。

import { onMounted } from 'vue'

除了 beforecate 和 created (它们被 setup 方法本身所取代),我们可以在 setup 方法中访问的API生命周期钩子有9个选项

onBeforeMount – 在挂载开始之前被调用:相关的 render 函数首次被调用。

onMounted – 组件挂载时调用

onBeforeUpdate – 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。

onUpdated – 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

onBeforeUnmount – 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。

onUnmounted – 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

onActivated – 被 keep-alive 缓存的组件激活时调用。

onDeactivated – 被 keep-alive 缓存的组件停用时调用。

onErrorCaptured – 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

二、组件间通信

1、props父传子

  • 父组件
    <template>
       <SonVue msg="父组件的值" />
    </template>
    
  • 子组件
    <script setup lang="ts">
    const props = defineProps(
        { msg: String }
    )
    console.log(props);
    </script>
    

2、emit子传父

  • 父组件
    <template>
        <SonVue @getuser="getuser" />
    </template>
    
    <script setup lang="ts">
    const getuser = (val:string) => {
        console.log(val)
    }
    </script>
    
  • 子组件
    <template>
        <button @click="buttonClick">传输</button>
    </template>
    
    <script setup lang="ts">
    const emit = defineEmits(['getuser'])
    const buttonClick = ()=>{
        emit('getuser','我是子组件传给父组件的值')
    }
    </script>
    

3、插槽通讯

  • 匿名插槽
    • 子组件

      <template>
          <slot></slot>
      </template>
      
    • 父组件

      <template>
          <SonVue>
              插槽传递
          </SonVue>
      </template>
      
  • 具名插槽
    • 父组件
      <template>
          <SonVue>
              <template #title>这是具名插槽</template>
          </SonVue>
      </template>
      
    • 子组件
      <template>
          <slot name="title"></slot>
      </template>
      
  • 作用域插槽
    • 含义
      # 数据在子组件的自身,但根据数据生成的结构需要父组件决定
      
    • 父组件
      <template>
          <!-- 父组件将信息传递给子组件 -->
          <SonVue :person="person">
              <!-- 子组件接收父组件的插槽中传的值 -->
              <template #tab="scope">
                  <tr v-for="(item, index) in scope.person" :key="index">
                      <th>{{ item.name }}</th>
                      <th>{{ item.age }}</th>
                      <th>
                          <button>编辑</button>
                      </th>
                  </tr>
              </template>
          </SonVue>
      </template>
      
      <script setup lang="ts">
      const person = [
          { name: '小明', age: 20 },
          { name: '小红', age: 18 }
      ]
      </script>
      
    • 子组件
      <template>
          <table border="1">
              <tr>
                  <th>姓名</th>
                  <th>年龄</th>
                  <th>操作</th>
              </tr>
              <slot name="tab" :person="props.person"></slot>
          </table>
      </template>
      <script setup lang="ts">
      const props = defineProps<{ person: { name: string, age: number } }>()
      </script>
      

4、Provide / Inject

官网称为依赖注入,Provide 为提供,Inject为注入

  • Provide (提供)
    • 父组件(祖先组件)
      <template>
          <h1>我是父组件</h1>
          <SonVue></SonVue>
      </template>
      
      <script setup lang="ts">
      import { provide, ref } from "vue";
      let flag = ref<number>(10);
      provide("flag", flag);
      </script>
      
  • Inject (注入)
    • 子组件(后代组件)
      <template>
          <button>子组件</button>
          <div> {{ flag }} </div>
          <button @click="flag++">+1</button>
      </template>
      
      <script setup lang="ts">
      import { inject, ref } from 'vue';
      const flag = inject('flag',ref(1))
      </script>
      

注意:Provide / Inject是具有响应式的,而且是单向数据流

三、路由(vue插件)

1、安装vue-router

npm install vue-router

2、创建router文件

// src/router/index.js

import { createRouter, createWebHistory } from "vue-router";
const routes = [
  {
    path: '/',
    redirect: '/index'
  },
  {
    path: '/index',
    name: 'index',
    component: () => import('../views/index/index.vue')
  },
  {
    path: '/account',
    name: 'account',
    component: () => import('../views/account/index.vue')
  }
]
const router = createRouter({
  history: createWebHistory(),
  routes
});
export default router

3、全局引入router

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index.js'

createApp(App).use(router).mount('#app')

4、使用

  • app.vue设置路由出口

    // App.vue
    <template>
      <router-view></router-view>
    </template>
    
  • index.vue 跳转到 account.vue

    <template>
      <div>
        <h1>首页</h1>
        <!-- 路由跳转方式 1 -->
        <router-link to="/account">去个人中心</router-link>
        <!-- 路由跳转方式 2 -->
        <button @click="gotoAccount">去个人中心</button>
      </div>
    </template>
    
    <script setup>
    import { useRoute, useRouter } from 'vue-router';
    // useRoute 类似于 vue2 的 this.$route
    useRoute == this.$route
    // useRouter 类似于 vue2 的 this.$router
    useRouter == this.$router
    
    const router = useRouter();
    const gotoAccount = () => {
      console.log(router);
      router.push('/account')
    }
    </script>
    

四、Typescript的支持

1、模板中的TS - 类型推断

<template>
    {{ (x as number).toFixed(2) }}  // 正确
    {{ (<number>x).toFixed(2) }}  // 正确
    {{ x.toFixed(2) }}  // IDE提示:类型“string | number”上不存在属性“toFixed”
</template>

<script setup lang="ts">
let x:string | number = 1
</script>

2、全局接口的抽取

1.src下定义types文件夹命名xx.d.ts

2.建立全局接口global.d.ts
interface globalInterface{
    name:string
    age:string
}

3.组件中直接使用
<script setup lang="ts">
const props = defineProps<{person:globalInterface[]}>()
</script>

4.如果不是在src下则需要在tsconfig.json中配置全局目录
{
  {
 ...
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], //配置全局目录
  "references": [{ "path": "./tsconfig.node.json" }]
}

3、类型增强

1.使用环境:全局定义的数据,函数在vue组件中直接访问报错

2.index.html中定义数据
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue + TS</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
    <script>
       const  global=1
    </script>
  </body>
</html>

3.定义类型增强
// common.d.ts
declare const global:string;

4.组件中直接读取
<script setup lang="ts">
console.log(global)
</script>

4、父传子props通讯 - TS

  • 父组件
    <template>
        <SonVue msg="父组件传过来的值"></SonVue>
    </template>
    
  • 子组件
    <template>
        {{ props.msg }}
    </template>
    
    <script setup lang="ts">
    interface msgInterface{
        msg:string
    }
    // withDefaults 帮助程序为默认值提供类型检查,并确保返回的 props 类型删除了已声明默认值的属性的可选标志。
    const props = withDefaults(defineProps<msgInterface>(),{
        msg:'dasdas'
    })
    console.log(props.msg);
    </script>
    

5、子传父emit通讯 - TS

  • 父组件
    <template>
        <SonVue @getuser="getuser"></SonVue>
    </template>
    
    <script setup lang="ts">
    const getuser = (a:number)=>{
        console.log('我是父组件打印的值');
    }
    </script>
    
  • 子组件
    <template>
        <button @click="buttonClick">传输</button>
    </template>
    
    <script setup lang="ts">
    const emit = defineEmits<{(e:'getuser',id:number):void}>()  // (e: 事件名, 键名:类型): void
    const buttonClick = ()=>{
        emit('getuser',1)
    }
    </script>
    

6、依赖注入类型推断

  • 父组件(祖先组件)
    <template>
        <button>我是父组件</button>
        <SonVue></SonVue>
    </template>
    
    <script setup lang="ts">
    import { provide, ref } from 'vue';
    
    let flag = ref<number>(99)
    provide('flag',flag)
    </script>
    
  • 子组件(后代组件)
    <template>
        <h2>我是子组件</h2>
        <div>{{flag}}</div>
        <button @click="(flag as number)++">+1</button>
    </template>
    
    <script setup lang="ts">
    import { Ref, inject, ref } from 'vue';
    
    const flag:Ref<number | string> = inject('flag',ref(1))
    </script>
    

五、指令的重构

1、v-model指令

  • 父组件

    <template>
      {{ num }}
      <HelloWorld v-model="num" />
    </template>
    <script setup lang="ts">
    import { ref } from 'vue';
    import HelloWorld from './components/HelloWorld.vue';
    const num = ref(1)
    </script>
    
  • 子组件

    <template>
      <input type="text" v-model="value">
    </template>
    <script lang="ts" setup>
    import { computed } from "vue";
    const props = defineProps<{modelValue:number}>();
    const emit = defineEmits<{(e:'update:modelValue',id:number):void}>();
    const value = computed({
      get(){
        return + props.modelValue
      },
      set(value){
        emit('update:modelValue',+value)
      }
    })
    </script>
    
  • v-model原理

    <template>
      {{ num }}
      <HelloWorld :modelValue="num" @update:modelValue="num = $event"/>
    </template>
    <script setup lang="ts">
    import { ref } from 'vue';
    import HelloWorld from './components/HelloWorld.vue';
    const num = ref(1)
    </script>
    

2、自定义指令

2.1、自定义指令简单使用
  • 全局自定义指令
    // main.ts
    import { createApp } from 'vue'
    import './style.css'
    import App from './App.vue'
    const app = createApp(App)
    app.directive('focus',{
        mounted(el){
            el.focus()
        }
    })
    app.mount('#app')
    
    
  • 使用自定义指令
    <template>
      <input type="text" v-model="value" v-focus />
    </template>
    <script lang="ts" setup>
    import { ref } from 'vue'
    let value = ref('全局自定义指令')
    </script>
    
  • 局部自定义指令
    <template>
    <input type="text" v-focus />
    </template>
    <script setup lang="ts">
    const vFocus  = {
        mounted: (el: any)=>{
            el.focus()
        }
    }
    </script>
    
2.2、自定义指令详解
  • 自定义指令的生命周期
    <template>
        <!-- 自定义指令,参数,修饰符 -->
        <div v-move:a.x="{ background: 'red' }"></div>
    </template>
    <script setup lang="ts">
    import type { Directive, DirectiveBinding } from 'vue';
    type Dir = { background: string };
    const vMove: Directive = {
        // 元素初始化的时候
        created() { },
        // 指令绑定到元素后调用 只调用一次
        beforeMount() { },
        // 元素插入父级dom调用
        mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
            console.log(dir.value.background);
            el.style.background = dir.value.background
        },
        // 元素被更新之前调用
        beforeUpdate() { },
        // 元素被更新之后调用 改用updated
        updated() { },
        // 在元素被移除前调用
        beforeUnmount() { },
        // 指令被移除后调用 只调用一次
        unmounted() { }
    }
    </script>
    
  • 生命周期的简写
    <template>
        <!-- 自定义指令,参数,修饰符 -->
        <div v-move:a.x="{ background: 'red' }">自定义指令生命周期简写</div>
    </template>
    <script setup lang="ts">
    import type { Directive, DirectiveBinding } from 'vue';
    type Dir = { background: string };
    const vMove: Directive = (el: HTMLElement, dir: DirectiveBinding<Dir>)=>{
        el.style.background = dir.value.background
    }
    </script>
    
  • 自定义拖拽指令
    <template>
        <!-- 自定义指令,参数,修饰符 -->
        <div v-move style="
          background-color: red;
          width: 200px;
          height: 200px;
          position: fixed;
          left: 50%;
          top: 50%;
          transform: translate(-50%, -50%);
        ">
            <div style="background-color: black; width: 200px; color: white">
                自定义指令
            </div>
        </div>
    </template>
    <script setup lang="ts">
    import type { Directive } from 'vue';
    const vMove: Directive = (el: HTMLElement) => {
        const move = (e: MouseEvent) => {
            console.log(e);
            el.style.left = e.clientX + 'px'
            el.style.top = e.clientY + 'px'
        }
        // 鼠标按下
        el.addEventListener('mousedown', () => {
            // 鼠标按下拖拽
            document.addEventListener('mousemove', move);
            // 鼠标松开
            document.addEventListener('mouseup', () => {
                // 清除事件
                document.addEventListener('mousemove', move)
            });
        })
    }
    </script>
    

3、响应式原理

3.1、了解Proxy代理
  • Proxy代理的get方法
    <script>
      let obj = {
        name: "小明",
        age: 26,
      };
      let agent = new Proxy(obj, {
          /*
              target表示obj这个对象
              property表示读取的属性的key
          */
        get(target, property) {
            console.log("执行了get方法");
            return target[property];
        },
      });
      console.log(agent.age);
    </script>
    
  • Proxy代理的set方法
    let obj = {
        name: "小明",
        age: 26,
    };
    let agent = new Proxy(obj, {
        /*
            target表示obj这个对象
            property表示读取的属性的key
            newValue表示设置的值
        */
        set(target, property, newValue) {
            console.log('执行了set方法');
            target[property] = newValue
            return true
        },
    });
    agent.age=30
    console.log(agent.age);
    
    注意:定义 Proxy 代理对象的 set 的时候,要返回 return true ,特别是在严格模式下,否则,
         会报错 'set' on proxy: trap returned falsish for property 'age'
    
3.2、了解Object.defineProperty
  • Object.defineProperty(对象.定义属性,用来为一个对象添加新属性)
    <script>
          let person  = {
              name: "小明",
              sex: '男',
          };
    	  // 为 person对象 传输了一个新属性 “age”,并且设定它的值为 18
          Object.defineProperty(person,'age',{
              value : 18
          })
          console.log(person)
    </script>
    
  • Object.defineProperty() 的get()方法
    <script>
            let person = {
                name: '张三',
                sex: '男',
            }
            function Observer(obj) {
                const keys = Object.keys(obj)
                keys.forEach((key) => {
                    Object.defineProperty(this,key,{
                        get() {
                            return obj[key]
                        }
                    })
                })
            }
            const obs = new Observer(person)
            console.log(obs.sex);
    </script>
    
  • Object.defineProperty() 的set()方法
    <script>
            let person = {
                name: '张三',
                sex: '男',
            }
            function Observer(obj) {
                const keys = Object.keys(obj)
                keys.forEach((key) => {
                    Object.defineProperty(this,key,{
                    
                        set(val) {
                            console.log('set方法调用了')
                            obj[key] = val
                        }
                    })
                })
            }
            const obs = new Observer(person)
            obs.name=15
    </script>
    
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值