1.Vue和Element-UI的关系(区别)
-
Vue 是一套用于构建用户界面的渐进式 JavaScript 框架 ,开发者只需要关注视图层, 它不仅易于上手,还便于与第三方库或既有项目的整合。是基于MVVM(Model-View-ViewModel 即:视图层-视图模型层-模型层)设计思想。提供MVVM数据双向绑定的库,专注于UI层面。
-
Element-UI 是基于 vue 实现的一套不依赖业务的 UI 组件库
-
Vue 与Element-Ui的关系
- Element-Ui是基于vue封装的组件库,简化了常用组件的封装,提高了重用性原则;
- vue是一个渐进式框架,Element-Ui是组件库
2.v-model
-
在Vue2中,在标签元素上绑定一个
v-model
,相当于绑定了一个值和一个事件-
<input v-model="searchText" />
- 等同于
-
<input :value="searchText" @input="searchText = $event.target.value" />
-
<template> <!-- <input :value="value" @change="$emit('change', $event.target.value)" > --> <input :value="searchText" @change="change" > </template> <script> model: { prop: 'searchText', event: 'change' }, props: { searchText: String }, methods:{ change(event){ this.$emit("change",event.target.value) } } </script>
-
-
在Vue3中,在自定义组件中使用v-model,相当于传递一个 prop 并发出一个事件
- 默认情况下,使用v-model,相当于传递给组件一个modelValue
-
<ChildComponent v-model="pageTitle" />
- 相当于
-
<ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />
- 1.将属性的值
modelValue
绑定 - 2.触发事件时,发出具有新值的自定义事件
@update:modelValue
-
// ChildComponent.vue <template> <input :value="modelValue" @input="onChange" /> </template> <script setup> import { defineProps, defineEmits } from 'vue' const props = defineProps({ modelValue: { type: XXX, required: true } }) //若需要修改默认的modelValue名称,则需要将modelValue为修改值即可 const emits = defineEmits(['update:modelValue']) const onChange = () => { emits('update:modelValue', 新值) } </script>
-
多个v-model绑定
- 通过
v-model
参数的使用,可以在一个组件绑定多个v-model
-
<UserName v-model:first-name="first" v-model:last-name="last" />
-
//UserName组件 <script setup> import { defineProps, defineEmits } from 'vue' const props = defineProps({ first-name: { type: String, required: true }, last-name: { type: String, required: true }, }) const emits = defineEmits(['update:first-name','update:last-name']) </script>
- 通过
3.watch 和 watchEffect 的区别是什么
-
两者都可监听 data 属性变化
-
watch
- watch至少要有两个参数(第三个参数是配置项),第一个参数是侦听的数据,第二个参数是回调函数
- watch 需要明确监听哪个属性 它是惰性执行副作用,它不会立即执行,但可以配置 immediate,使其主动触发
- watch可以同时获取更改前和更改后的值
-
watchEffect
- watchEffect只需要传递一个回调函数,不需要传递侦听的数据,它会在页面加载时主动执行一次
- watchEffect 接收一个函数,会根据其中的属性,自动监听其变化(依赖变化就会重新执行函数)
- watchEffect获取不到更改前的值
4.如何理解 ref、toRef 和 toRefs
-
ref
- 生成值类型的响应式数据
- 可用于模板和 reactive
- 通过.value 修改值
-
reactive
- 用 reactive 做对象的响应式
-
toRef
- 针对一个响应式对象(reactive 封装)的 prop
- 创建一个 ref,具有响应式 toRef(state,....)
- 两者保持引用关系
-
toRefs
- 将响应式对象(reactive 封装)转换为普通对象 toRefs(state)
5.vue2和vue3双向绑定数据的原理
-
Vue2使用Object.defineProperty()方法
- 使用Object.defineProperty()方法为每个属性添加getter和setter,从而实现双向绑定
-
Vue3使用Proxy对象来实现双向绑定
- 使用Proxy对象来包装原始对象,并重写getter和setter方法,从而实现双向绑定
6.vue2和vue3的区别
- ref和reactive
-
生命周期
大部分生命周期钩子名称上 + “on”,功能上是类似的
-
多根节点
-
Composition API
-
异步组件
Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。
-
Teleport
Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗
-
虚拟DOM
Vue3 相比于 Vue2,虚拟DOM上增加 patchFlag 字段
调用createElementVNode时,第四个参数即 patchFlag 字段类型
1代表节点为动态文本节点,那在 diff 过程中,只需比对文本对容,无需关注 class、style等
1代表所有的静态节点,都保存为一个变量进行静态提升,可在重新渲染时直接引用,无需重新创建
-
打包优化
vue3移除 JavaScript 上下文中未引用的代码,主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用
7.Vue3中v-for 中使用 ref(通过:ref="(el) => setItemRefs(el, item)"的写法)
- 基本使用
<template>
<div ref="hello">小猪课堂</div>
<ul>
<li v-for="item in 10" ref="itemRefs">
{{item}} - 小猪课堂
</li>
</ul>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
const itemRefs = ref<any>([]);
onMounted(() => {
console.log(itemRefs.value);
});
</script>
- ref绑定函数
//该函数会默认接收一个 el 参数,这个参数就是我们需要获取的 div 元素。假如需求中我们采用这种方式的话,那么完全可以把 el 保存到一个变量中去,供后面使用。
<template>
<div :ref="setHelloRef">小猪课堂</div>
</template>
<script setup lang="ts">
import { ComponentPublicInstance, HTMLAttributes } from "vue";
const setHelloRef = (el: HTMLElement | ComponentPublicInstance | HTMLAttributes) => {
console.log(el); // <div>小猪课堂</div>
};
//上面的代码可以改为
<template>
<ul>
<li v-for="item in 10" :ref="(el) => setItemRefs(el, item)">
{{ item }} - 小猪课堂
</li>
</ul>
</template>
<script setup lang="ts">
import { ComponentPublicInstance, HTMLAttributes, onMounted } from "vue";
let itemRefs: Array<any> = [];
const setItemRefs = (el: HTMLElement | ComponentPublicInstance | HTMLAttributes, item:number) => {
if(el) {
itemRefs.push({
id: item,
el,
});
}
}
onMounted(() => {
console.log(itemRefs);
});
</script>
</script>
8.父组件调用子组件的方法或变量(通过defineExpose)
//子组件
<template>
<div>{{ message }}</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const message = ref<string>("我是子组件");
const onChange = () => {
console.log("我是子组件方法")
};
defineExpose({
message,
onChange
});
</script>
//父组件
<script>
<template>
<child ref="childRef"></child>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import child from "./child.vue";
const childRef = ref<any>(null);
onMounted(() => {
console.log(childRef.value); // child 组件实例
console.log(childRef.value.message); // 我是子组件方法
});
</script>
9.自定义指令的钩子函数(生命周期)
-
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
-
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
10.Vue中$set用法
- 原因:Vue 无法检测 对象property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在
data
对象上存在才能让 Vue 将它转换为响应式的 - 简说(由于Vue无法检测对象和数组的变化,因为Vue会在初始化实例阶段就对data中所定义的数据进行响应式处理,所以Vue提供了
Vue.set(object, propertyName, value)
) - Vue.set可以写成this.$set()
11.Vue中$nextTick用法
- 与$set第三点类似
- 为了在数据变化之后等待 Vue 完成更新 DOM
12.Vue3中setup语法糖
- 它是一个函数(接收
props
和context
(solt
和emit
)参数),会暴露所有的内容给组件的其余部分以及组件的模板 - 在script标签中添加setup后,组件只需引入不用注册,属性和方法也不用return返回,也不用写setup函数,也不用写export default ,甚至是自定义指令也可以在我们的template中自动获得
- setup语法糖中的defineProps:子组件接收父组件中传来的props
- setup语法糖中的defineEmits:子组件调用父组件中的方法
- setup语法糖中的defineExpose:子组件暴露属性,可以在父组件中拿到
13.vue常用组件通信方式及vue2和vue3写法对比
-
props/$emit
-
ref/$parent
-
provide/inject
-
vue2写法
-
// 父组件 export default{ provide(){ return { msg: this.msg } } } // 后代组件 export default{ inject:["msg"] }
- 要注意的是 provide 和 inject 传递的数据不是响应式的,也就是说用 inject 接收来数据后,provide 里的数据改变了,后代组件中的数据不会改变
-
-
vue3写法
-
// Parent.vue <script setup> import { provide } from "vue" provide("name", "小心") </script> // Child.vue <script setup> import { inject } from "vue" const name = inject("name") console.log(name) //小心 </script>
-
-
-
eventBus事件总线
-
vue2写法
- 通过Vue.prototype.$bus = new Vue()穿件事件总线
- 通过emit触发自定义事件,this.emit
- 通过on监听事件,this.on
-
vue3写法
- 借助mitt插件来实现代替,原理还是 EventBus
-
-
Vuex/Pinia
- vuex核心概念:state、mutations、actions、getters、modules
- pinia核心概念:state、actions、getters。没有mutation、modules
14.react和vue的区别
-
核心思想不同:React推崇函数式编程(纯组件),数据不可变以及单向数据流,Vue则
灵活易用的渐进式框架,进行数据拦截/代理,它对侦测数据的变化更敏感、更精确
。 -
组件写法差异:React推荐的做法是
JSX
,也就是把 HTML 和 CSS 全都写进 JavaScript 中,Vue 推荐的做法是 template 的单文件组件格式,即 html,css,JS 写在同一个文件 -
diff算法不同:
-
总结:
- 传统Diff算法是循环递归每一个节点,算法复杂度为O(n*3)
- 采用虚拟DOM,算法复杂度为O(n)
- 不同的组件产生不同的DOM结构,type不同,就会直接删除旧DOM,创建新的DOM
- 同一层次的一组子节点,可以通过唯一的 key 区分
-
React的Diff算法核心实现
- react首先对新集合进行遍历
- 通过唯一key来判断老集合中是否存在相同的节点。如果没有的话创建,
- 如果有的话,会将节点在新集合中的位置和在老集合中lastIndex进行比较
- 如果渲染的节点位置小于老位置,进行移动操作,否则不进行移动操作
- 如果遍历的过程中,发现在新集合中没有,但在老集合中有的节点,会进行删除操作
-
Vue的Diff算法核心实现
旧children
和新children
各有两个头尾的变量StartIdx
和EndIdx
,它们的2个变量相互比较,一共有4种比较方式- 如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦
StartIdx>EndIdx
表明旧children
和新children
至少有一个已经遍历完了,就会结束比较
-
-
响应式原理不同:
-
Vue
- Vue依赖收集,自动优化,数据可变,
- Vue递归监听data的所有属性,直接修改
- 当数据改变时,自动找到引用组件重新渲染
-
React
- React基于状态机,手动优化,数据不可变,需要
setState
驱动新的state替换老的state,数据改变时,React 中会需要shouldComponentUpdate
这个生命周期函数方法来进行控制
- React基于状态机,手动优化,数据不可变,需要
-
15.vue中v-for的key作用是什么
- vue中列表循环需要加
:key='唯一标识'
,唯一标识尽量是id,目的是为了高效地更新虚拟DOM
- key主要用于dom diff算法,diff算法为同级比较,比较当前标签上的key还有他当前的标签名,如果key和标签名都一样时只移动,不会重新创建元素和删除元素
- 尽量不要使用索引值index作key值,一定要用唯一标识的值,如id等。因为若用数组索引index为key,当向数组中指定位置插入一个新元素后,因为这时候会重新更新index索引,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新,就像没有加key一样