首页 vue基础知识和概念
文章
取消

vue基础知识和概念

生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<script>
export default {
    // 实例初始化之前
    beforeCreate() {
        console.log('home', 'beforeCreate')
    },
    
    // 初始化实例(DOM还未挂载)
    created() {
        console.log('home', 'created')
    },

    // 组件挂载之前,render函数首次被调用
    beforeMount() {
        console.log('home', 'beforeMount')
    },

    // 组件挂载,DOM已经存在
    mounted() {
        console.log('home', 'mounted')
    },

    // 数据更新时(虚拟DOM打补丁之前)
    beforeUpdate() {
        console.log('home', 'beforeUpdate')
    },

    // 虚拟DOM重新渲染和打补丁之后
    updated() {
        console.log('home', 'updated')
    },

    // 组件实例销毁之前, vue3 中 使用 `beforeUnmount`
    beforeDestroy() {
        console.log('home', 'beforeDestroy')
    },

    // 实例销毁之后, vue3 中 使用 `unmounted`
    destroyed() {
        console.log('home', 'destroyed')
    },
}
</script>

composition API 生命周期钩子

  • onBeforeMount() 在组件被挂载之前被调用
  • onMounted() 在组件挂载完成后执行
  • onBeforeUpdate() 在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
  • onUpdated() 在组件因为响应式状态变更而更新其 DOM 树之后调用
  • onBeforeUnmount() 在组件实例被卸载之前调用
  • onUnmounted() 在组件实例被卸载之后调用
  • onErrorCaptured() 在捕获了后代组件传递的错误时调用
  • onRenderTracked() 当组件渲染过程中追踪到响应式依赖时调用
  • onRenderTriggered() 当响应式依赖的变更触发了组件渲染时调用
  • onActivated() 若组件实例是 <KeepAlive> 缓存树的一部分,当组件被插入到 DOM 中时调用
  • onDeactivated() 若组件实例是 <KeepAlive> 缓存树的一部分,当组件从 DOM 中被移除时调用
  • onServerPrefetch() 在组件实例在服务器上被渲染之前调用

computed和watch

  • computed 有缓存,data不变时不会重新计算
  • watch 是浅监听,如何深度监听?
  • watch 监听引用类型,拿不到oldval 他们的区别: 1、computed用于计算产生新的数据,有缓存 2、watch用于监听现有数据

watch进行深比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
export default {
  data() {
    return {
      obj: {
        name: "vue",
        age: 18,
      },
    };
  },
  watch: {
    obj: {
      // 使用 deep 选项进行深比较
      deep: true,
      handler(val, oldVal) {
        console.log("val:", val);
        console.log("oldVal:", oldVal);
      },
    },
  },
};
</script>

当我们修改 obj 的属性时,watch 的回调函数将会被调用,并传入新的值和旧值。

1
2
3
4
5
6
7
8
val: {
  name: "vue",
  age: 19,
}
oldVal: {
  name: "vue",
  age: 18,
}

vue事件挂载

  • vue事件中 event 是原生的,事件被挂载到当前元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
    <button @click="increment1">+1</button>
    <button @click="increment2($event, 2)">+2</button>
</template>

<script>
    exprot default {
        data() {
            return {

            }
        },
        methdos: {
            increment1(event) {
                // 打印出一个原生的 event对象
                console.log('event', event, event.__proto__.constructor)
                // 事件挂载的目标元素button按钮
                console.log(event.target)
                // 事件被注册到当前元素
                cnsole.log(event.currentTarget)
            }
        }
    }
</script>

修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--阻止单击事件继续传播-->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面-->
<form v-on:submit.prevent="onSubmit"></form>
<!--A-I1修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符-->
<form v-on:submit.prevent></form>
<!--添加事件监听器时使用事件捕获模式-->
<!--即内部元素触发的事件先在此处理,然后才交由内部元素进行处理-->
<div V-on:click.capture="doThis">...</div>
<!--只当在 event.target 是当前元素自身时触发处理函数A--I即事件不是从内部元素触发的-->
<div v-on:click.self="doThat">...</div>

v-for/v-if

官方不推荐一起使用,会影响性能,每次循环时都会进行一次v-if的判断

自定义事件

兄弟组件自定义事件通讯 注意:eventvue3中已经弃用了(vue3 创建实例使用的是工厂函数 createApp() 创建的实例)

vue2.x 使用自定义事件先创建一个 event 文件, event 就是vue的实例

1
2
3
// event.js 文件
import Vue from 'vue'
export default new Vue()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 兄弟组件compents-A.vue
<script>
import event from './event.js'
export default {
    mounted() {
        // 绑定自定义事件
        event.$on('onAddTile', this.addTitleHandler)
    },

    methdos: {
        addTitleHandler(title) {
            console.log(title)
        }
    }
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 兄弟组件compents-B.vue
<script>
import event from './event.js'
export default {
    data() {
        return {
            title: ''
        }
    }

    methdos: {
        addTitle() {
            // 调用自定义事件
            event.$emit('oAddTitle', this.title)
        }
    }
}
</script>

组件A通过$on监听onAddTile,组件B通过event.$emit触发onAddTile

  • 绑定事件后要,不用时要及时销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
    export default {
        mounted() {
            // 绑定自定义事件
            event.$on('onAddTile', this.addTitleHandler)
        },

        methdos: {
            addTitleHandler(title) {
                console.log(title)
            }
        },
        // 生命周期-组件销毁之前
        beforeDestroy() {
            // 组件销毁之前进行事件解绑
            event.$off('oAddTitle', this.addTitleHandler)
        }
    }
</script>

vue3中的自定义绑定事件

在Vue 3中, event.$on 已经被废弃了,因为Vue 3中的事件系统已经发生了一些变化。

Vue 3中的事件系统使用了新的API,包括 onoffonceemit 。这些方法可以通过 app.config.globalProperties 来设置全局属性,或者在组件中通过 setup 函数来访问。

下面是一个示例,展示了如何在Vue 3中使用新的事件API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { createApp } from 'vue';

const app = createApp({});

// 设置全局事件
app.config.globalProperties.$eventBus = {
  listeners: {},
  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  },
  off(event, callback) {
    if (this.listeners[event]) {
      const index = this.listeners[event].indexOf(callback);
      if (index !== -1) {
        this.listeners[event].splice(index, 1);
      }
    }
  },
  once(event, callback) {
    const onceCallback = (...args) => {
      callback(...args);
      this.off(event, onceCallback);
    };
    this.on(event, onceCallback);
  },
  emit(event, ...args) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(callback => {
        callback(...args);
      });
    }
  }
};

// 在组件中使用事件
app.component('my-component', {
  setup() {
    const eventBus = app.config.globalProperties.$eventBus;

    const handleClick = () => {
      eventBus.emit('my-event', 'Hello, world!');
    };

    eventBus.on('my-event', message => {
      console.log(message);
    });

    return {
      handleClick
    };
  },
  template: `
    <button @click="handleClick">Click me</button>
  `
});

app.mount(‘#app’); 在这个示例中,我们创建了一个名为 $eventBus 的全局事件对象,并定义了 onoffonceemit 方法来实现事件的注册、注销、触发和一次性触发功能。然后,在组件中使用 setup 函数来访问 $eventBus 对象,并使用 on 方法来注册事件监听器,使用 emit 方法来触发事件。

父子组件生命周期的执行顺序

1、首先父组件先创建实例 ceated 2、子组件进行create -> mounted 等生命周期的执行 3、父组件中的子组件生命周期渲染完后,父组件开始mounted,执行自己的生命周期

异步组件

在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现此功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
import { defineAsyncComponent } from 'vue'

export default {
  components: {
    AdminPage: defineAsyncComponent(() =>
      import('./components/AdminPageComponent.vue')
    )
  }
}
</script>

<template>
  <AdminPage />
</template>

异步组件两种写法的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
  components: {
    AdminPage: defineAsyncComponent(() =>
      import('./components/AdminPageComponent.vue')
    )
  }
}

// or

export default {
  components: {
    AdminPage: () => import('./components/AdminPageComponent.vue')
  }
}

上面两种写法的主要区别在于,第一种写法使用了 Vue 提供的 defineAsyncComponent() 方法来定义异步组件,而第二种写法直接使用了一个返回 Promise 的函数来定义异步组件。

defineAsyncComponent() 方法可以将一个返回 Promise 的函数转换为一个异步组件。该方法会在 Promise 成功 resolve 时返回组件定义,在 Promise 失败 reject 时返回报错组件。

直接使用返回 Promise 的函数来定义异步组件,需要在组件渲染时手动调用 resolve() 方法来 resolve Promise。如果 Promise 失败 reject,则需要在组件中处理错误。

因此,两种写法的区别主要体现在异步组件定义的灵活性和易用性上。defineAsyncComponent() 方法提供了更高的灵活性,可以根据需要自定义异步组件的加载行为。而直接使用返回 Promise 的函数来定义异步组件更加简单易用,但灵活性较差。

具体来说,两种写法的区别如下:

特性defineAsyncComponent()返回 Promise 的函数
灵活性
易用性
自定义性
错误处理自动处理手动处理

在实际使用中,可以根据具体的需求来选择合适的写法。如果需要对异步组件加载行为进行自定义,则可以使用 defineAsyncComponent() 方法。如果只需要简单地异步加载组件,则可以直接使用返回 Promise 的函数来定义异步组件。

总结:defineAsyncComponent() 提供了组件加载错误的自动处理错误功能errorComponent,加载中的提示 loadingComponent, 可以定义这是延迟加载的时间 delay 展示加载组件前的延迟时间,默认为 200ms.

defineComponent()

  • 在定义 Vue 组件时提供类型推导的辅助函数。 类型推导、参数类型定义、正确推断 setup() 组件参数类型、支持泛型

Object.defineProperty

vue2响应式使用的是Object.defineProperty,但它是有缺点:

  • 深度监听,需要递归,一次性计算太大
  • 无法监听新增属性/删除属性(Vue.set / Vue.delete)
  • 无法原生监听数组,需要特殊处理

watch/watchEffect

1、两者都可以监听data属性变化

2、watch需要明确监听哪个属性

3、watchEffect会根据其中的属性,自动监听其变化

  • watch 示例代码

需要指定监听的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<script>
export default {
    setup() {
        const numberRef = ref(100)
        const state = reactive({
            name: '饼干',
            age: 18
        })
        // 第一种监听方式 监听值类型
        watch(numberRef, (newNumber, oldNumber) => {
            console.log('ref watch', newNumber, oldNumber)
        }
        // , {
        //     immdiate: true  // 初始化之前就监听,可选
        // }
        )

        // 第二种监听方式
        watch(
            // 第一个参数,确定要监听那个属性
            () => state.age,
            // 第二个参数,回调函数
            (newState, oldState) => {
                console.log('state watch', newState, oldState)
            },
            // 第三个参数,配置项
            {
                immediate: true,    // 初始化之前就监听,可选
                deep: true  // 深度监听
            }
        )
    }
}
</script>
  • watchEffect 初始化时会执行一次,在方法内使用哪个属性就监听那个属性,不需要提前指定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default {
    setup() {
        const numberRef = ref(100)
        const state = reactive({
            name: '饼干',
            age: 18
        })
        wathcEffect(() => {
            // 初始化时执行,这里使用了属性name所以就直接监听了name
            console.log('state.name', state.name)
        })
        
    }
}
</script>

directives

  • 自定义指令,可以全局自定义,也可以局部自定义指令

局部自定义指令示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
  <input v-focus v-model="val"/>
</template>

<script>
export default {
  data() {
    return {
      val:''
    }
  }
  // 自定义指令
  directives: {
      'focus': {
          mounted(el) {
              el.focus()
              el.value = '6666'
          },
      }
  },

}
</script>

defineExpose

可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Child.vue
<script setup>
  import { ref } from 'vue'
  const a = 1
  const b = ref(2)
  // 将属性暴露给外部
  defineExpose({
    a,
    b
  })
</script>

// Parent.vue
<script setup>
  import { ref, onMounted } from 'vue'
  import Child from 'Child.vue'
  const childRef = ref(null)
  
  onMounted(() => {
    console.log(childRef.value.a) // 输出 1
    console.log(childRef.value.b) // 输出 2
  })

</script>

<template>
  <Child ref="childRef"></Child>
</template>

template 模板

  • template 模版编译产出的是一个 render函数,render函数返回 vnode, 最后通过 patch对虚拟 DOM 进行处理。
  • createElement 方法返回 vnode
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    const compileer = require('vue-template-compiler')
    const template =`
      <div id="div1" class="container">
        <img :src="imgUrl"/>
      </div>
    `
    // 返回的 createElement 函数, _c是简写
    // with(this){return _c('div', 
    //   {staticClass: "container", attrs: {"id": "div1"}},
    //   [_c('img', fattrs: {"src" :imgUrl}})]
    // )}
    

setup 中如何获取组件实例

  • setup 和其他 Composition API 中没有 this
  • 可通过 getCurrentInstance 获取当前实例

setup script 中操作 DOM

在 Vue 3 的 script setup 语法中,可以使用 ref 函数创建一个响应式引用,并将其与 DOM 元素相关联,以便在 setup 中获取和操作 DOM 结构。

以下是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
  <div ref="myElement">This is a DOM element</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

// 使用 ref 创建一个响应式引用,并关联到 DOM 元素
const myElement = ref(null);

// 在组件挂载后,通过 ref 获取 DOM 元素并进行操作
onMounted(() => {
  console.log('DOM element:', myElement.value);
  myElement.value.innerText = 'Updated text';
});
</script>

在这个例子中,myElement 是一个通过 ref 创建的响应式引用,它与 <div> 元素关联。在 onMounted 生命周期钩子中,我们通过 myElement.value 获取到实际的 DOM 元素,然后进行操作。

请注意,refscript setup 中的使用方式和在传统的 script 区块中略有不同。在 script setup 中,直接使用 ref 而不是 this.$refs 来访问引用。

hash路由实现原理

说到路由就要先了解一下网页url组成部分

1
2
3
4
5
6
7
8
// http://127.0.0.1:8080/hash.html?a=1&b=2#/aaa/bbb
location.protocol   // http:
location.hostname   // 127.0.0.1
location.host   // 127.0.0.1:8080
location.port   // 8080
location.pathname   // '/hash.html'
location.search    // '?a=1&b=2'
location.hash   // '#/aaa/bbb'

了解了url组成部分后我们就可以通过js去实现一个hash路由功能了 hash的特点:

  • hash 变化会触发网页跳转,即浏览器的前进、后退
  • hash 变化不会刷新页面,SPA必需的特点
  • hash 永远不会提交到server端(前端自生自灭)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// hash 变化:js修改url、手动修改url的hash、浏览器前进/后退

// 监听hash变化事件
window.onhashchange = (event) {
    console.log('old-ulr', event.oldURL)    // 旧的url地址
    console.log('new-url', event.newURL)    // 新的url地址

    console.log('hash', location.hash)  // hash地址
}

// 页面初次加载, 获取 hash
document.addEventListener('DOMContentLoaded', () => {
    console.log('hash', location.hash)
})

H5 history

需要server端配合

  • 用 url 规范的路由,但跳转时不刷新页面
  • history.pushState
  • window.onpopstate
1
2
3
4
5
6
7
8
9
10
11
// 使用 pushState 改变url地址
function routerPush() {
    console.log('切换路由到 page1')
    history.pushState({name: 'page1'}, '', 'page1')
}
// 监听浏览器前进、后退
window.onpopstate = (event) => {
    console.log('onpopstate', event.state, location.pathname)
} 

.snyc修饰符

  • vue 2.3版本新增, vue3.0版本弃用改为v-model

toRef与toRefs区别

  • toRef 针对一个响应式对象(reactive封装)的proptoRef 引用对象中一个属性,直接读写其引用的属性,让其依然保持响应式状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
    <div>{{ ageRef }} 等同于 {{ state.age }}</div>
</template>

<script>
import {ref, toRef, reactive} from 'vue'

export default {
    setup() {
        // 创建一个响应式对象
        const state = reactive({
            name: '饼干',
            age: '18'
        })
        // 引用单个对象属性,简化了`state.age`这种调用,在模板中直接`{{ageRef}}`直接渲染
        const ageRef = toRef(state, "age")

        return {
            // 注意state不可以使用解构方式 ...state 这种将会丧失响应式,reactive创建的响应式不可以被解构直接使用对象属性,`toRef`等同于解构了单个属性直接使用,保持着响应状态
            state
            ageRef
        }
    }
}

</script>

  • toRefs 针对一个响应式对象(reactive封装)的prop,转化为一个普通对象(保持响应式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
    <!--toRefs 方式直接可以在模板中使用属性渲染在模板上,省去了对象.属性这中访问方式-->
    <div>名字:{{ name }}  年龄:{{ age }}</div>
</template>

<script>
import {ref, toRef, toRefs, reactive} from 'vue'

export default {
    setup() {
        // 创建一个响应式对象
        const state = reactive({
            name: '饼干',
            age: '18'
        })
        
        // 将响应式对象转化为普通对象(使用解构方式依然保持响应)
        const stateRefs = toRefs(state)

        return stateRefs
    }
}

</script>

为什么需要ref

  • 返回值类型,会丢失响应式 vue3的响应式是proxy它只能拦截对象引用类型,针对值类型无法拦截所以需要ref()方法存在去使用值类型
  • setupcomputed、合成函数,都有可能返回值类型。 值类型如:number, string, boolean, null, undefined, 和 symbol

ref为何需要.value

  • ref 是一个对象(不丢失响应式),value存储值
  • 通过.value属性的get和set实现响应式
  • 用于模版、reactive时,不需要.value,其他情况都需要

ref与reactive

  • ref 可以很好的处理值类型,多个ref组合成一个对象时,可以使用解构方式,省去了对象引用属性调用链条过长,简化属性值的调用。唯一麻烦的是在修改值是需要使用.value如:state.value = 1,模板渲染调用时不需要加value,直接使用声明的变了即可 “{ {sate} }”
  • reactive 将普通对象转化为响应式,它封装的对象不可以使用解构方法,否则将会失去响应式。

vue中diff算法

vue和react的diff不一样,vue是双端diff,头头尾尾双指针。它不像react是单端的它在效率上会比react高一个层级。vue3在双端diff上又加了一个最长递增子序列这个算法,它整个函数的流程通过child NOODE不断去递归查找和上一个diff不一样的地方,再做头尾的一个比较和替换。

diff算法key的作用在比对时必须标记出这次和上次的同一个dom元素替换的位置,如果不加key那么位置扰乱了,不加key它会根据索引进行比较,索引比较有很大一个缺陷,当push一个元素时,后面都会向后错位,重新进行计算,浪费性能及时间。

vue与react区别:vue是MVVM,数据驱动视图,视图驱动数据。react是MVC数据和视图之间需要一个Controller(控制器)它不断去dispath通过事件去驱动数据的改变更新视图。

proxy

vue3中响应式使用的是proxy做代理拦截

  • 深度监听,性能更好
  • 可监听 新增/删除 属性
  • 可监听数组变化

Proxy能规避Object.defineProperty的问题

Proxy无法兼容所有浏览器,无法polyfill

下面是proxy基本使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const data = {
    name: '饼干',
    age: 20
}

const proxyData = new Proxy(data, {
    get(target, key, receiver) {
        const result = Reflect.get(targe, key, receiver)
        console.log('get', key)
        // 返回结果
        return result
    },
    set(target, key, val, receiver) {
        const result = Reflect.set(target, key, val, receiver)
        console.log('set', key, val)
        return result
    },
    deleteProperty(target, key) {
        const result = Reflect.deleteProperty(target, key)
        console.log('delete property', key)
        return result
    }
})

Vue3 (script setup)

  • 基本使用,<script> 写在 <template>前面
  • 定义属性 defineProps, 定义事件 defineEmits
  • defineExpose 暴漏数据给父组件

vue3比vue2优势?

  • 性能更好
  • 体积更小哦
  • 更好的ts支持
  • 更好的代码组织
  • 更多新功能

Composition API和 React Hooks

  • vue中setup只会被调用一次, Hooks函数会被多次调用
  • vue无需useMemo useCallback,因为setup只调用一次
  • vue无需顾虑调用顺序,而react需要保证hooks的顺序一致

Composition API带来了什么

  • 更好的代码组织
  • 更好的逻辑复用
  • 更好的类型推导
转摘分享请注明出处

宏任务和微任务

webpack基础知识