vue3面试题【持续更新中】

Vue3 基础

1.Vue3 大体情况

1) 了解相关信息

  • 2 年多开发, 100+位贡献者, 2600+次提交, 600+次 PR
  • Vue3 支持 vue2 的大多数特性
  • 更好的支持 Typescript

2) 性能提升

  • 打包大小减少 41%
    • 通过摇树优化核心库体积
  • 初次渲染快 55%, 更新渲染快 133%
    • 虚拟 DOM 重写
    • 优化 slots 的生成
    • 静态树提升
    • 静态属性提升
    • 基于 Proxy 的响应式系统
  • 内存减少 54%
  • 重写虚拟 DOM 的实现和 Tree-Shaking
  • 更容易维护
    • TypeScript + 模块化
  • 更加友好
    • 跨平台:编译器核心和运行时核心与平台无关,使得 Vue 更容易与任何平台(Web、Android、iOS)一起使用
  • 更容易使用
    • 改进的 TypeScript 支持,编辑器能提供强有力的类型检查和错误及警告
    • 更好的调试支持
    • 独立的响应化模块
    • Composition API

3) Composition(组合) API

  • setup

    • setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口
    • setup 函数会在 beforeCreate 之前执行,所以没有 this 对象
    • 第一个形参 props 用来接收 props 数据,第二个形参 context 用来定义上下文
  • ref 和 reactive

    • reactive() 函数接收一个普通对象,返回一个响应式的数据对象
    • ref() 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 value 属性
  • isRef

    • isRef() 用来判断某个值是否为 ref() 创建出来的对象
  • toRefs

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

  • computed

    • 创建只读的计算属性
    • 创建可读可写的计算属性
  • watch

    • 可以实现普通的监控处理
    • 监视指定的数据源,包括: 监视 reactive 类型的数据源,监视 ref 类型的数据源
    • 监视多个数据源
    • 可以清除监视
  • 新的生命周期函数

Vue3Vue2
use setup()beforeCreate
use setup()created
onBeforeMountbeforeMount
onMountedmounted
onBeforeUpdatebeforeUpdate
onUpdatedupdated
onBeforeUnmountbeforeDestroy
onUnmounteddestroyed
onErrorCapturederrorCaptured
onRenderTrackedrenderTracked
onRenderTriggeredrenderTriggered
  • 自定义 hooks 函数

4) 其它新增特性

  • Teleport - 瞬移组件的位置
  • Suspense - 异步加载组件的 loading 界面
  • 全局 API 的修改

2.创建 vue3 项目

1) 使用 vue-cli 创建(目前还是建议用 Vue-CLI 创建 Vue3 项目)

文档: https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## 安装或者升级
npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project

然后的步骤

  • Please pick a preset - 选择 Manually select features
  • Check the features needed for your project - 多选择上 TypeScript && Router && Vuex,特别注意点空格是选择,点回车是下一步
  • Choose a version of Vue.js that you want to start the project with - 选择 3.x (Preview)
  • Use class-style component syntax - 直接回车
  • Use Babel alongside TypeScript - 直接回车
  • Pick a linter / formatter config - 直接回车
  • Use history mode for router? - 直接回车
  • Pick a linter / formatter config - 直接回车
  • Pick additional lint features - 直接回车
  • Where do you prefer placing config for Babel, ESLint, etc.? - 直接回车
  • Save this as a preset for future projects? - 直接回车

::: tip Vue3 初始项目直观差异
Vue3 与 Vue2 项目细节差异
:::

(1) 入口文件

Vue3

main.ts

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

Vue2

main.js

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#app');
(2) 路由操作

Vue3

router/index.ts(函数化单个拆分引入,方便摇树去除代码,减少项目大小提高性能)

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';

mode 切换成了函数化声明

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

Vue2

router/index.js(整个模块引入,导致项目大小增加,增大损耗)

import VueRouter from 'vue-router';

mode 是字符串设置

const router = new VueRouter({
  routes,
});
(3) 状态管理

Vue3

store/index.ts

import { createStore } from 'vuex';

export default createStore({});

Vue2

store/index.js

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({});
(4) 页面引入组件

Vue3

views/home.vue

<script lang="ts">
import { Options, Vue } from "vue-class-component";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src

@Options({
  components: {
    HelloWorld
  }
})
export default class Home extends Vue {}
</script>

上述代码可以修改成以下代码内容,定义组件的方式也可以利用 defineComponent 来实现

<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from "@/components/HelloWorld.vue";

export default defineComponent({
  name:'Home',
  components:{
    HelloWorld
  }
})
</script>

Vue2

views/home.vue

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',
  components: {
    HelloWorld
  }
}
</script>
(5) 组件的定义

Vue3

components/helloworld.vue

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({
  props: {
    msg: String
  }
})
export default class HelloWorld extends Vue {
  msg!: string;
}
</script>

上述代码可以修改成以下代码内容,定义组件的方式也可以利用 defineComponent 来实现

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name:"HelloWorld",
  props:{
    msg:String
  }
})
</script>

Vue2

components/helloworld.vue

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

2) 使用 vite 创建(周边支持并不完善)

文档: https://v3.cn.vuejs.org/guide/installation.html

Vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于 Rollup 打包。

npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev

3.Composition API 使用(重要)

compostion API VS Option API

options API

composition API

composition API2

文档:

https://v3.cn.vuejs.org/api/basic-reactivity.html

https://v3.cn.vuejs.org/api/composition-api.html

https://composition-api.vuejs.org/zh/

1) setup

setup 函数是一个新的组件选项。作为在组件内使用 Composition API 的入口点。

创建组件实例,然后初始化 props ,紧接着就调用setup 函数。从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用

入参:

  • {Data} props
  • {SetupContext} context
<script lang="ts">
export default {
  setup(props,context){
    console.log(props,context)
  }
}
</script>

image-20201130111958443

2) ref

接受一个参数值并返回一个响应式且可改变的 ref 对象

ref 对象拥有一个指向内部值的单一属性 .value

如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。

当 ref 作为渲染上下文的属性返回(即在setup() 返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value

(1) 确认 this 对象开始的位置
<script lang="ts">
export default {
  beforeCreate(){
     console.log('beforeCreate()',this) // beforeCreate在setup之后执行,具有this对象
  },
  setup(props,context){
    console.log('setup()',this) // setup在beforeCreate之前执行,没有this对象
  }
}
</script>
(2) 声明 ref 对象

::: tip Vue3 与 Vue2 的 ref 差异

与 Vue2 的 ref 对象指向不同(找 DOM 以及找 Component 组件),Vue3 中的 ref 是创建包含响应式数据的引用对象

:::

<script lang="ts">
import { ref } from "vue"
export default {
  beforeCreate(){
     console.log('beforeCreate()',this)
  },
  setup(props,context){
    console.log('setup()',this)
    // 包含响应式数据的引用对象
    const count = ref(0)
    // count是一个引用对象, 内部包含存储数据的value属性
    console.log(count,count.value)
  }
}
</script>

::: tip 对象需返回才能使用

如果目前 count 对象没有进行 return 返回处理,在模板页中插值显示 count 是无法显示的

:::

<script lang="ts">
import { ref } from "vue"
export default {
  beforeCreate(){
     console.log('beforeCreate()',this)
  },
  setup(props,context){
    console.log('setup()',this)
    // 包含响应式数据的引用对象
    const count = ref(0)
    // count是一个引用对象, 内部包含存储数据的value属性
    console.log(count,count.value)

    return {
    	count
    }
  }
}
</script>
(3) 更新响应式数据的函数
<template>
  <div class="about">
    <h2>{{ count }}</h2>
    <hr />
    <button @click="increment">加1</button>
  </div>
</template>

<script lang="ts">
import { ref } from 'vue';
export default {
  beforeCreate() {
    console.log('beforeCreate()', this);
  },
  // 在beforeCreate()之前执行, 不能通过this访问组件对象
  setup() {
    console.log('setup()', this);

    // 包含响应式数据的引用对象
    const count = ref(0); // count是一个引用对象, 内部包含存储数据的value属性
    console.log(count, count.value);

    // 更新响应式数据的函数
    const increment = () => {
      count.value++; // 是对value值进行操作处理,不是对引用对象进行处理
    };

    return {
      // 对象中的属性和方法, 模板可以直接访问
      count,
      increment,
    };
  },
};
</script>

3) computed

使用 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。

或者,它可以使用具有 getset 函数的对象来创建可写的 ref 对象。

(1) 属性计算需要从 vue 中引入 computed 方法,默认操作是 getter
<template>
  <div class="about">
    <h2>count: {{ count }}</h2>
    <h2>double: {{ double }}</h2>
    <hr />
    <button @click="increment">加1</button>
  </div>
</template>

<script lang="ts">
import { computed, ref } from 'vue';
export default {
  setup() {
    const count = ref(0);

    // 计算属性本质上也是一个ref对象
    const double = computed(() => {
      console.log('double computed', count.value);
      return count.value * 2;
    });

    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment,
      double,
    };
  },
};
</script>
(2) 可以将 getter 与 setter 进行拆分,实现取值与赋值处理
<template>
  <div class="about">
    <h2>count: {{ count }}</h2>
    <h2>double: {{ double }}</h2>
    <h2>double2: {{ double2 }}</h2>
    <hr />
    <button @click="increment">加1</button>
  </div>
</template>

<script lang="ts">
import { computed, ref } from 'vue';
export default {
  setup() {
    const count = ref(0);

    const double = computed(() => {
      console.log('double computed', count.value);
      return count.value * 2;
    });

    // 包含getter与setter的计算属性
    const double2 = computed({
      get() {
        return count.value * 2;
      },
      set(value: number) {
        count.value = value / 2;
      },
    });

    const increment = () => {
      count.value++;
      setTimeout(() => {
        // 调用computed的setter操作
        double2.value += 2;
      }, 1000);
    };

    return {
      count,
      increment,
      double,
      double2,
    };
  },
};
</script>

4) reactive

接收一个普通对象然后返回该普通对象的响应式代理器对象

响应式转换是“深层的”:会影响对象内部所有嵌套的属性

基于 ES2015 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的

:::tip reactive 区别 ref

  • ref 的作用就是将一个原始数据类型(primitive data type)转换成一个带有响应式特性(reactivity)的数据类型,原始数据类型共有 7 个,分别是:String、Number、Boolean、Null、Undefined、Symbol、BigInt

  • ref 与 reactive 定义基本元素类型数据时,ref 定义的是包装后的响应式数据,而 reactive 定义的还是原来的类型(也就是 reactive 定义基本类型不是响应式的,修改数据不能更新到模板)

  • 使用 ref 还是 reactive 可以选择这样的准则
    第一:像平常写普通的 js 代码,选择原始类型和对象类型一样来选择是使用 ref 还是 reactive。
    第二:所有场景都使用 reactive,但是要记得使用 toRefs 保证 reactive 对象属性保持响应性。

:::

(1) reactive 设置对象并进行插值显示
<template>
  <div class="about">
    <p>msg:{{ state.msg }}</p>
    <p>numbers:{{ state.numbers }}</p>
    <p>name:{{ state.person.name }}</p>
  </div>
</template>

<script lang="ts">
import { reactive } from "vue";
export default {
  setup() {
    // state 对象是reactive中原始对象的代理对象
    // 一旦操作代理对象的属性,内部操作的是原始对象的属性
    const state = reactive({
      msg: "hello Vue3",
      numbers: [1, 2, 3],
      person: {
        name: "Vane",
      },
    });
    return {
      state,
    };
  },
};
</script>
(2) reactive 对于数据的更新处理
<template>
  <div class="about">
    <p>msg:{{ state.msg }}</p>
    <p>numbers:{{ state.numbers }}</p>
    <p>name:{{ state.person.name }}</p>
    <button @click="state.update">update</button>
  </div>
</template>

<script lang="ts">
import { reactive } from "vue";
export default {
  setup() {
    // state 对象是reactive中原始对象的代理对象
    // 一旦操作代理对象的属性,内部操作的是原始对象的属性
    const state = reactive({
      msg: "hello Vue3",
      numbers: [1, 2, 3],
      person: {
        name: "Vane",
      },
      update: () => {
        state.msg = "Vue3 is very good";
        // 通过下标直接替换数组元素
        // Vue3会自动更新,Vue2中不会自动更新,需要用转换后的数组函数
        state.numbers[1] = 100;
        // 通过路径直接替换对象属性
        // Vue3会自动更新,Vue2中需要利用Vue.set进行对象属性更新
        state.person.name = "Chinavane";
      },
    });
    return {
      state,
    };
  },
};
</script>

(3) 解构 reactive 以后产生的问题,注意 numbers 与 person 内容进行更新的模式切换
<template>
  <div class="about">
    <p>msg:{{ msg }}</p>
    <p>numbers:{{ numbers }}</p>
    <p>name:{{ person.name }}</p>
    <button @click="update">update</button>
  </div>
</template>

<script lang="ts">
import { reactive } from "vue";
export default {
  setup() {
    // state 对象是reactive中原始对象的代理对象
    // 一旦操作代理对象的属性,内部操作的是原始对象的属性
    const state = reactive({
      msg: "hello Vue3",
      numbers: [1, 2, 3],
      person: {
        name: "Vane",
      },
      update: () => {
        state.msg = "Vue3 is very good";
        // 通过下标直接替换数组元素
        // Vue3会自动更新,Vue2中不会自动更新,需要用转换后的数组函数
        state.numbers = [1, 100, 3];
        // 通过路径直接替换对象属性
        // Vue3会自动更新,Vue2中需要利用Vue.set进行对象属性更新
        state.person = { name: "Chinavane" };
      },
    });

    return {
      // 问题:如果 reactive 响应式对象一旦解析出来,其属性则不再是响应式属性
      ...state,
    };
  },
};
</script>
(4) 利用 toRefs 将 reactive 中的每个属性转成 ref 响应式数据对象
...
<script lang="ts">
import { reactive, toRefs } from "vue";
export default {
  setup() {
    ...
    return {
      // 问题:如果 reactive 响应式对象一旦解析出来,其属性则不再是响应式属性
      // 解决:利用 toRefs 将 reactive 中的每个属性转成 ref 响应式数据对象
      ...toRefs(state),
    };
  },
};
</script>

(5) 利用接口对数据类型内容进行约束
...
<script lang="ts">
import { reactive, toRefs } from "vue";

interface State {
  msg: string;
  numbers: number[];
  person: {
    name?: string;
  };
  update: () => void;
}

export default {
  setup() {
    const state: State = reactive({
      ...
    });
    return {
      ...toRefs(state),
    };
  },
};
</script>

5) toRefs

问题: reactive 对象取出的所有属性值都是非响应式的

解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性

6) watch

与选项 API this.$watch (以及相应的 watch 选项) 完全等效

侦听一个数据: ref 或 reactive 属性值(getter 函数)

侦听多个数据

(1) 设置 ref 值 count,并进行数据更新
<template>
  <div class="about">
    ...
    <p>count:{{ count }}</p>
    <button @click="update">update</button>
  </div>
</template>

<script lang="ts">
import { reactive, ref, toRefs } from "vue";
...
export default {
  setup() {
    const count = ref(0);
    const state: State = reactive({
      ...
      update: () => {
       ...
        count.value++;
      },
    });

    return {
      ...toRefs(state),
      count,
    };
  },
};
</script>
(2) 监控一个 ref 的值
<script lang="ts">
...
export default {
  	...
    // 监视一个ref值
    watch(count, (newVal, oldVal) => {
      console.log("watch count", newVal, oldVal);
      document.title = count.value.toString();
    });

    return {
      ...toRefs(state),
      count,
    };
  },
};
</script>

(3) 监视 reactive 对象中的某个属性,需要用箭头函数的方式指向监控对象
<script lang="ts">
...
export default {
  	...
    // 监视 reactive 对象中的某个属性,注意:指定返回它的getter
    watch(
      () => state.msg,
      (newVal, oldVal) => {
        console.log("watch msg", newVal, oldVal);
      }
    );

    return {
      ...toRefs(state),
      count,
    };
  },
};
</script>

(4) 监视多个对象
export default {
  	...
	// 监视多个对象,利用解构方式输出
	watch(
      [count, () => state.msg],
      ([countNewVal, msgNewVal], [countOldValue, msgOldVal]) => {
        console.log("watch 多值解构count", countNewVal, countOldValue);
        console.log("watch 多值解构msg", msgNewVal, msgOldVal);
      }
    );

	// 监视多个对象,利用单一对象输出
    const stateRef = toRefs(state);
    watch([count, () => state.msg, stateRef.msg, state], (values) => {
      console.log("watch 多值", values);
    });

    return {
      ...stateRef,
      count,
    };
  },
};
</script>

4.比较 Vue2 与 Vue3 的响应式(重要)

1) vue2 的响应式

  • 核心
    • 对象:通过 defineProperty 对对象的已有属性值的读取和修改进行劫持(监视/拦截)
    • 数组:通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
  get() {},
  set() {},
});
  • 问题
    • 对象直接新添加的属性或删除已有属性, 界面不会自动更新
    • 直接通过下标替换元素或更新 length, 界面不会自动更新
    • Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy 可以劫持整个对象,并返回一个新的对象。
    • Object.defineProperty 本身是可以监控到数组下标的变化的,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性。
<!DOCTYPE html>
<html lang="en">
  <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" />
    <title>介绍Object.defineProperty的作用</title>
  </head>

  <body>
    <script>
      const obj = {
        name: '张三',
        age: 18,
      };

      // 取值操作
      console.log(obj.name);
      // 赋值操作
      obj.name = '李四';
      console.log(obj.name);

      // 通过Object.defineProperty可以拦截取值赋值属性操作
      Object.defineProperty(obj, 'name', {
        enumerable: true, // 当前属性允许被循环
        // 如果不允许for-in的时候会被跳过
        configurable: true, // 当前属性允许被配置
        get() {
          // getter
          console.log('有人获取了obj.name的值');
          return '我不是张三';
        },
        set(newVal) {
          // setter
          console.log('我不要你给的值', newVal);
        },
      });

      console.log(obj.name); // get的演示
      obj.name = '李四'; // set的演示
      console.log(obj);
    </script>
  </body>
</html>

2) Vue3 的响应式

  • 核心
    • 通过 Proxy(代理):拦截对 data 任意属性的任意(13 种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
    • 通过 Reflect(反射):动态对代理对象的相应属性进行特定的操作
    • 文档
      • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
      • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
  • Proxy 不仅可以代理对象,还可以代理数组,还可以代理动态增加的属性。
new Proxy(data, {
  // 拦截读取属性值
  get(target, prop) {
    return Reflect.get(target, prop);
  },
  // 拦截设置属性值或添加新属性
  set(target, prop, value) {
    return Reflect.set(target, prop, value);
  },
  // 拦截删除属性
  deleteProperty(target, prop) {
    return Reflect.deleteProperty(target, prop);
  },
});
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Proxy 与 Reflect</title>
  </head>
  <body>
    <script>
      const user = {
        name: 'Vane',
        age: 42,
      };

      /*
    proxyUser是代理对象, user是被代理对象
    后面所有的操作都是通过代理对象来操作被代理对象内部属性
    */
      const proxyUser = new Proxy(user, {
        get(target, prop) {
          console.log('劫持get()', prop);
          return Reflect.get(target, prop);
        },

        set(target, prop, val) {
          console.log('劫持set()', prop, val);
          return Reflect.set(target, prop, val); // (2)
        },

        deleteProperty(target, prop) {
          console.log('劫持delete属性', prop);
          return Reflect.deleteProperty(target, prop);
        },
      });
      // 读取属性值
      console.log(proxyUser === user);
      console.log(proxyUser.name, proxyUser.age);
      // 设置属性值
      proxyUser.name = 'Chinavane';
      proxyUser.age = 18;
      console.log(user);
      // 添加属性
      proxyUser.sex = '男';
      console.log(user);
      // 删除属性
      delete proxyUser.sex;
      console.log(user);
    </script>
  </body>
</html>

5.生命周期

1) vue2.x 的生命周期

lifecycle_2

2) vue3 的生命周期

lifecycle_3

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

Vue3Vue2
use setup()beforeCreate
use setup()created
onBeforeMountbeforeMount
onMountedmounted
onBeforeUpdatebeforeUpdate
onUpdatedupdated
onBeforeUnmountbeforeDestroy
onUnmounteddestroyed
onErrorCapturederrorCaptured
onRenderTrackedrenderTracked
onRenderTriggeredrenderTriggered
<template>
  <div class="about">
    <h2>msg: {{ msg }}</h2>
    <hr />
    <button @click="update">更新</button>
  </div>
</template>

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

export default {
  beforeCreate() {
    console.log('beforeCreate()');
  },

  created() {
    console.log('created');
  },

  beforeMount() {
    console.log('beforeMount');
  },

  mounted() {
    console.log('mounted');
  },

  beforeUpdate() {
    console.log('beforeUpdate');
  },

  updated() {
    console.log('updated');
  },

  beforeUnmount() {
    console.log('beforeUnmount');
  },

  unmounted() {
    console.log('unmounted');
  },

  setup() {
    const msg = ref('abc');

    const update = () => {
      msg.value += '--';
    };

    onBeforeMount(() => {
      console.log('--onBeforeMount');
    });

    onMounted(() => {
      console.log('--onMounted');
    });

    onBeforeUpdate(() => {
      console.log('--onBeforeUpdate');
    });

    onUpdated(() => {
      console.log('--onUpdated');
    });

    onBeforeUnmount(() => {
      console.log('--onBeforeUnmount');
    });

    onUnmounted(() => {
      console.log('--onUnmounted');
    });

    return {
      msg,
      update,
    };
  },
};
</script>

4) 自定义 hooks 函数

  • 作用:对多个组件重复的功能进行提取封装

  • 在 vue2 中,可以使用 mixin 技术,在 vue3 中使用自定义 hooks 函数

需求 1: 收集用户鼠标点击的页面坐标
(1) 在组件中进行功能的实现
<template>
  <div class="about">
    <p>x:{{ x }}</p>
    <p>y:{{ y }}</p>
  </div>
</template>

<script lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    // 初始化坐标数据
    const x = ref(-1);
    const y = ref(-1);

    // 用于收集点击事件坐标的函数
    const updatePosition = (e: MouseEvent) => {
      x.value = e.pageX;
      y.value = e.pageY;
    };

    // 挂载后绑定点击监听
    onMounted(() => {
      document.addEventListener('click', updatePosition);
    });

    // 卸载前解绑点击监听
    onUnmounted(() => {
      document.removeEventListener('click', updatePosition);
    });

    return { x, y };
  },
};
</script>
(2) 将功能抽离成自定义的 hooks

hooks/useMousePosition.ts

/*
  自定义hooks: 收集用户鼠标点击的页面坐标
  */
import { ref, onMounted, onUnmounted } from 'vue';

export default function useMousePosition() {
  // 初始化坐标数据
  const x = ref(-1);
  const y = ref(-1);

  // 用于收集点击事件坐标的函数
  const updatePosition = (e: MouseEvent) => {
    x.value = e.pageX;
    y.value = e.pageY;
  };

  // 挂载后绑定点击监听
  onMounted(() => {
    document.addEventListener('click', updatePosition);
  });

  // 卸载前解绑点击监听
  onUnmounted(() => {
    document.removeEventListener('click', updatePosition);
  });

  return { x, y };
}
(3) 在组件中调用自定义钩子函数
<template>
  <div class="about">
    <p>x:{{ x }}</p>
    <p>y:{{ y }}</p>
  </div>
</template>

<script lang="ts">
import useMousePosition from '../hooks/useMousePosition';
export default {
  setup() {
    const { x, y } = useMousePosition();
    return { x, y };
  },
};
</script>
需求 2:封装异步请求
(1) 将功能抽离成自定义的 hooks

hooks/useUrlLoader.ts

/*
  使用axios发送异步ajax请求
  */
import { ref } from 'vue';
import axios from 'axios';

export default function useUrlLoader(url: string) {
  const result = ref(null);
  const loading = ref(true);
  const errorMsg = ref(null);

  axios
    .get(url)
    .then((response) => {
      loading.value = false;
      result.value = response.data;
    })
    .catch((e) => {
      loading.value = false;
      errorMsg.value = e.message || '未知错误';
    });

  return {
    loading,
    result,
    errorMsg,
  };
}
(2) 在组件中调用自定义钩子函数
<template>
  <div class="about">
    <p>x:{{ x }}</p>
    <p>y:{{ y }}</p>
    <hr />
    <p v-if="loading">Loading...</p>
    <p v-else-if="errorMsg">{{ errorMsg }}</p>
    <p v-else>{{ result }}</p>
  </div>
</template>

<script lang="ts">
import useMousePosition from '../hooks/useMousePosition';
import useUrlLoader from '../hooks/useUrlLoader';
export default {
  setup() {
    const { x, y } = useMousePosition();
    const { result, loading, errorMsg } = useUrlLoader(
      'https://dog.ceo/api/breeds/image/random'
    );
    return { x, y, result, loading, errorMsg };
  },
};
</script>

6. 利用 TS 强化类型检查与提示

1) 使用泛型

hooks/useUrlLoader.ts

export default function useUrlLoader<T>(url: string) {

  const result = ref<T | null>(null)
  ...
}

2) 定义接口,约束信息,单一对象

...
<script lang="ts">
import { watch } from "vue";
import useMousePosition from "../hooks/useMousePosition";
import useUrlLoader from "../hooks/useUrlLoader";

interface DogResult {
  message: string;
  status: string;
}

export default {
  setup() {
    const { x, y } = useMousePosition();
    const { result, loading, errorMsg } = useUrlLoader<DogResult>(
      "https://dog.ceo/api/breeds/image/random"
    );

    watch(result, (newVal, oldVal) => {
      console.log(result.value?.message); // 原来没有使用接口类型约束没有提示,有接口类型则可以提示
    });

    return { x, y, result, loading, errorMsg };
  },
};
</script>

3) 定义接口,约束信息,数组对象

...
<script lang="ts">
import { watch } from "vue";
import useMousePosition from "../hooks/useMousePosition";
import useUrlLoader from "../hooks/useUrlLoader";

interface CatResult {
  breeds: any[];
  id: string;
  url: string;
  width: number;
  height: number;
}

export default {
  setup() {
    const { x, y } = useMousePosition();
    const { result, loading, errorMsg } = useUrlLoader<CatResult[]>(
      "https://api.thecatapi.com/v1/images/search"
    );

    watch(result, (newVal, oldVal) => {
      if (result.value) {
        console.log(result.value[0].url);
      }
    });

    return { x, y, result, loading, errorMsg };
  },
};
</script>

7.使用 defineComponent 包裹组件

  • 问题: 配置选项没有提示
  • 解决: 使用 defineComponent(options)
<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  setup(props, context) {
    console.log(props.msg);
    console.log(context.attrs, context.slots, context.emit);
    return {};
  },
});
</script>

eds/image/random"
);

watch(result, (newVal, oldVal) => {
  console.log(result.value?.message); // 原来没有使用接口类型约束没有提示,有接口类型则可以提示
});

return { x, y, result, loading, errorMsg };

},
};


### 3) 定义接口,约束信息,数组对象

```typescript {7-13,18-26}
...
<script lang="ts">
import { watch } from "vue";
import useMousePosition from "../hooks/useMousePosition";
import useUrlLoader from "../hooks/useUrlLoader";

interface CatResult {
  breeds: any[];
  id: string;
  url: string;
  width: number;
  height: number;
}

export default {
  setup() {
    const { x, y } = useMousePosition();
    const { result, loading, errorMsg } = useUrlLoader<CatResult[]>(
      "https://api.thecatapi.com/v1/images/search"
    );

    watch(result, (newVal, oldVal) => {
      if (result.value) {
        console.log(result.value[0].url);
      }
    });

    return { x, y, result, loading, errorMsg };
  },
};
</script>

7.使用 defineComponent 包裹组件

  • 问题: 配置选项没有提示
  • 解决: 使用 defineComponent(options)
<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  setup(props, context) {
    console.log(props.msg);
    console.log(context.attrs, context.slots, context.emit);
    return {};
  },
});
</script>
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
文章摘要 智慧医院智能化系统建设方案旨在通过智能化手段提升医院的安全性、舒适性、便捷性和效率。该方案规划了四大类子系统:平台、应用、节能和安全,以打造一个高效的医疗环境。 信息设施系统:包括综合布线系统、信息网络系统、多媒体会议系统等,旨在为医院提供稳定、高速的网络服务。综合布线系统采用6类非屏蔽铜缆和光纤,支持多种业务信息的传输。信息网络系统采用以太网交换技术和树型网络结构,确保网络的稳定性和安全性。 信息化应用系统:包括信息查询系统、分诊排队叫号系统、ICU探视系统等,通过信息技术提高医疗服务的质量和效率。信息查询系统便于病员及家属查询医院信息,分诊排队叫号系统优化就诊流程,ICU探视系统通过音视频技术实现远程探视和监护。 安全防范系统:针对医患关系敏感、医疗纠纷、医护人身安全等问题,设计了安防音视频监控系统、电子巡更系统、门禁系统等,以提高医院的安全管理水平。安防音视频监控系统在关键区域设置监控摄像机,电子巡更系统确保巡更人员按时按路线完成任务,门禁系统通过权限管理控制人员出入。 机房建设工程:包括机房配电系统、防雷接地系统、消防系统等,确保机房设备的安全稳定运行。机房供配电系统采用普通电源和不间断电源,消防系统采用无管网七氟丙烷气体灭火系统,防雷系统采用三级防雷措施,机房空调系统保持适宜的温度和湿度。 方案特色:紧扣标准、安全简便、统一融合、可视操作、事前预防、智能管控。通过智能化系统的设计和实施,医院能够更有效地进行安全管理,提高医疗服务质量,同时降低维护成本和提升运营效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值