学习目标
- 5.1 Vue组件
- 5.1.1 为什么要使用组件
- 5.1.2 组件入门
- 5.1.2.1 全局组件
- 5.1.2.2 局部组件
- 5.2 组件通讯
- 5.2.1 父子组件定义
- 5.2.2 父组件给子组件传值
- 5.2.3 子组件给父组件传值
- 5.2.4 单向数据流
- 5.3 插槽
- 5.3.1 匿名插槽
- 5.3.2 具名插槽
- 5.4 生命周期钩子函数
- 5.4.1 生命周期图
- 5.4.2 常用生命周期函数
- 5.5 Axios 网络请求库
- 5.5.1 Axios 库简介
- 5.5.2 Axios 基本使用
(如果没有了解前面的知识可以去我主页看看 第四章的内容来学习)上一章我们学习了 Vue 框架的基本知识,大家一定重点掌握并熟练的使用,本章将开始讲解 Vue 的核心内容—组件。
5.1 Vue组件
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架。Vue 组件是 Vue 应用的基本构建块,它们允许你将界面拆分成可重用的、独立的、可测试的部分
5.1.1 为什么要使用组件
Vue.js 使用组件的原因主要是为了提高代码的可重用性、可维护性、模块化、灵活性以及促进团队协作等。现在,我将通过代码示例来进一步说明为什么要在Vue.js中使用组件。
首先,考虑一个简单的Vue.js应用,它可能包含一个按钮和一个显示消息的文本区域。如果我们不使用组件,代码可能会像这样:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><script src="https://cdn.jsdelivr.net/npm/vue@2"></script><title></title></head><body><div id="app"><message-button></message-button></div><script>javascript">// 定义一个名为 'MessageButton' 的Vue组件Vue.component('message-button', {template: `<div><button @click="showMessage">点击</button><p v-if="isVisible">你好!</p></div>`,data() {return {isVisible: false};},methods: {showMessage() {this.isVisible = true;}}});// 创建Vue实例并挂载到 '#app'new Vue({el: '#app'});</script></body>
</html>
在这个重构后的例子中,我们创建了一个名为 message-button 的Vue组件。这个组件包含了按钮和消息显示的逻辑,并且它是完全独立的。我们可以在任何Vue实例中通过<message-button>
</message-button>
标签来使用这个组件,而无需复制和粘贴代码.
使用组件的好处在于:
- 封装:组件封装了相关的HTML、CSS和JavaScript代码,使得功能更加模块化。
- 重用:一旦定义了组件,就可以在任何Vue实例中重复使用它。
- 维护:由于组件是独立的,所以修改一个组件的代码不会影响其他组件。
- 扩展:你可以通过添加属性(props)和事件(events)来扩展组件的功能,使其更加灵活和强大。
总之,Vue.js使用组件是为了提高代码的可重用性、可维护性和模块化程度,从而帮助开发者构建出更加复杂而易于管理的Web应用。
5.1.2 组件入门
组件是Vue.js框架中的一个核心概念,它允许开发者将界面拆分成独立的、可复用的部分。每个组件都封装了自己的模板、逻辑和样式,使得代码更加模块化和易于维护。
- 组件的 data 必须定义为函数,通过返回对象实现每个组件数据的独立性。如果 data 未定义为函数则 Vue 会找不到数据而报错。
- 定义组件名称时可以用使用帕斯卡命名法,也可以使用 kebab-case(短横线分割)命名。 在前面示例当中使用了帕斯卡命名法(MessageButton),而 kebab-case(短横线分割)要求单词全小写,多个单词用短横线分割,示例中也可写为(message-button)。
- 引用组件标签时统一使用 kebab-case(短横线分割)。 类似于普通标签的使用,例如示例中MyComponent 的 C 为大写,分割为两个单词,所以对应在引用组件元素时,应该使用
<message-button>
</message-button>
,不能使用<messagebutton>
。 - 组件名称不能使用 DOM 本身存在的原生元素名称。 例如 div,text,input 等,否则 Vue优先识别为原生标签,而不是你自定义的组件。
以下是一个简单的Vue组件入门示例,包括Vue 2和Vue 3的写法。
Vue 2 组件入门示例
- 创建一个Vue实例作为根组件
<!DOCTYPE html>
<html>
<head><title>Vue 2 组件入门</title><script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<body><div id="app"><my-component></my-component></div><script>javascript">// 定义一个名为 my-component 的新组件Vue.component('my-component', {template: '<div>这是一个自定义组件!</div>'});// 创建 Vue 实例,并挂载到 #app 元素上new Vue({el: '#app'});</script>
</body>
</html>
- 使用单文件组件(SFC)
在Vue 2中,单文件组件通常使用.vue文件,并通过Vue CLI或Webpack等工具进行构建。以下是一个简单的单文件组件示例:
<!-- MyComponent.vue -->
<template><div>这是一个单文件组件!</div>
</template><script>
export default {name: 'MyComponent'
};
</script><style scoped>
/* 组件的样式 */
</style>
然后,在你的主文件中引入并使用这个组件:
javascript">// main.js
import Vue from 'vue';
import MyComponent from './components/MyComponent.vue';new Vue({el: '#app',components: {'my-component': MyComponent}
});
Vue 3 组件入门示例
- 使用全局注册组件
在Vue 3中,你通常会使用createApp方法来创建应用实例,并通过这个实例来注册组件。
<!DOCTYPE html>
<html>
<head><title>Vue 3 组件入门</title><script type="module">javascript">import { createApp } from 'https://unpkg.com/vue@next';const MyComponent = {template: '<div>这是一个Vue 3的自定义组件!</div>'};createApp({components: {'my-component': MyComponent}}).mount('#app');</script>
</head>
<body><div id="app"><my-component></my-component></div>
</body>
</html>
- 使用单文件组件(SFC)
在Vue 3中,单文件组件的写法与Vue 2非常相似,但你可能需要使用Vue 3特有的功能,如Composition API。
<!-- MyComponent.vue -->
<template><div>这是一个Vue 3的单文件组件!</div>
</template><script>
import { defineComponent } from 'vue';export default defineComponent({name: 'MyComponent'
});
</script><style scoped>
/* 组件的样式 */
</style>
然后,在你的主文件中引入并使用这个组件:
javascript">// main.js
import { createApp } from 'vue';
import MyComponent from './components/MyComponent.vue';
import App from './App.vue'; // 假设你有一个根组件App.vuecreateApp(App).component('my-component', MyComponent) // 可以在这里注册全局组件,但通常在根组件内注册更好.mount('#app');
注意:在Vue 3中,更推荐在根组件内通过
<script setup>
语法和Composition API来组织你的组件逻辑,但这超出了这个简单入门示例的范围。
5.1.2.1 全局组件
在Vue.js中,全局组件是在Vue实例或应用实例之外定义的,并且可以在整个应用中任何地方使用的组件。这意味着一旦你注册了一个全局组件,你就可以在应用的任何组件模板中通过标签形式来使用它,无需在每个单独的组件中再次注册。
以下是Vue 2和Vue 3中注册全局组件的代码示例:
Vue 2 全局组件
在Vue 2中,你可以使用Vue.component方法来注册一个全局组件。这个方法接受两个参数:组件的标签名(或称为“名称”)和一个包含组件选项的对象。
javascript">// 定义一个全局组件
Vue.component('my-global-component', {template: '<div>这是一个全局组件!</div>',// 你可以在这里添加data、methods、computed等选项
});// 创建Vue实例并挂载到DOM元素上
new Vue({el: '#app'
});
在HTML模板中,你现在可以像这样使用这个全局组件:
<div id="app"><my-global-component></my-global-component>
</div>
Vue 3 全局组件
在Vue 3中,由于引入了createApp方法,全局组件的注册方式有所变化。你需要在创建应用实例之后,但在挂载之前,使用应用实例的.component方法来注册全局组件。
javascript">import { createApp } from 'vue';// 定义一个组件
const MyGlobalComponent = {template: '<div>这是一个Vue 3的全局组件!</div>',// 你可以在这里添加setup函数、data、methods、computed等选项
};// 创建Vue应用实例
const app = createApp({});// 注册全局组件
app.component('my-global-component', MyGlobalComponent);// 挂载应用到DOM元素上
app.mount('#app');
在HTML模板中,使用方式与Vue 2相同:
<div id="app"><my-global-component></my-global-component>
</div>
请注意:在Vue 3中,由于引入了Composition API和
<script setup>
语法,组件的定义方式可能会更加灵活和复杂。但是,注册全局组件的基本步骤如上所示。
另外,虽然你可以在创建应用实例后注册全局组件,但通常建议在创建应用实例之前定义好所有要使用的全局组件,并在创建实例时立即注册它们,以保持代码的整洁和可维护性。
5.1.2.2 局部组件
在Vue.js中,局部组件(也称为局部注册组件)是在单个Vue组件内部注册的,并且只能在该组件的模板中使用。这与全局组件相反,全局组件可以在应用的任何地方使用。局部组件的注册通常发生在组件的components选项中。
以下是Vue 2和Vue 3中注册局部组件的代码示例:
Vue 2 局部组件
在Vue 2中,你可以在父组件的components选项中注册一个局部组件。这个选项是一个对象,其键是组件的标签名,值是包含组件选项的对象。
javascript">// 定义一个局部组件
var MyLocalComponent = {template: '<div>这是一个局部组件!</div>',// 你可以在这里添加data、methods、computed等选项
};// 创建Vue实例并挂载到DOM元素上
new Vue({el: '#app',components: {'my-local-component': MyLocalComponent // 在这里注册局部组件},template: '<div><my-local-component></my-local-component></div>' // 使用局部组件
});
在上面的代码中,MyLocalComponent是一个局部组件,它只在父Vue实例的模板中可用。
Vue 3 局部组件
在Vue 3中,局部组件的注册方式非常相似,但通常是在使用<script setup>
语法或Composition API的组件内部进行的。以下是一个使用<script setup>
语法的示例:
<!-- ParentComponent.vue -->
<template><div><MyLocalComponent /> <!-- 使用局部组件 --></div>
</template><script setup>
import { defineComponent } from 'vue';// 定义一个局部组件
const MyLocalComponent = defineComponent({template: '<div>这是一个Vue 3的局部组件!</div>',// 你可以在这里添加setup函数、data、methods、computed等选项
});// 在<script setup>中,你不需要显式地注册组件,因为它会自动注册到当前组件的上下文中
// 但如果你需要在其他地方(如模板中)使用它,并且它不是通过<script setup>的顶级变量导入的,
// 你可能需要将它导出并在父组件的components选项中注册(尽管这在<script setup>中通常不是必需的)。
// 然而,在这个特定的例子中,由于MyLocalComponent是在<script setup>中定义的,
// 它会自动可用于模板,无需额外的注册步骤。
</script>
请注意:在上面的Vue 3示例中,由于
<script setup>
的特殊性,你实际上不需要在components选项中注册MyLocalComponent,因为它会自动变得在模板中可用。这是<script setup>
语法的一个关键特性。
如果你不使用<script setup>
,而是在标准的<script>
标签中定义组件,你将需要在父组件的components选项中注册它,就像Vue 2那样:
<!-- ParentComponent.vue -->
<template><div><my-local-component></my-local-component> <!-- 使用局部组件 --></div>
</template><script>
import MyLocalComponent from './MyLocalComponent.vue'; // 假设组件定义在单独的文件中export default {components: {'my-local-component': MyLocalComponent // 在这里注册局部组件}
};
</script>
在这个例子中,MyLocalComponent是从一个单独的文件中导入的,并在父组件的components选项中注册。
5.2 组件通讯
组件编写的原则是高内聚、低耦合,但是难免要与其他组件进行组合完成更复杂的功能,
于是组件与组件需要协同合作就必须要通讯传递数据。下面我们来学习如何定义父子组件、父
组件传值给子组件,子组件传值给父组件
5.2.1 父子组件定义
在Vue.js中,父子组件的定义涉及创建一个父组件和一个或多个子组件,并通过props将数据从父组件传递给子组件。以下是一个简单的示例,展示了如何定义和使用父子组件。
父组件(ParentComponent.vue)
<template><div><h1>我是父组件</h1><!-- 使用子组件,并通过props传递数据 --><ChildComponent :parentData="dataFromParent" /></div>
</template><script>
// 导入子组件
import ChildComponent from './ChildComponent.vue';export default {name: 'ParentComponent', // 组件名称(可选,但有助于调试)components: {ChildComponent // 注册子组件},data() {return {dataFromParent: '这是从父组件传递来的数据' // 父组件的数据};}
};
</script>
子组件(ChildComponent.vue)
<template><div><h2>我是子组件</h2><!-- 显示从父组件接收的数据 --><p>{{ parentData }}</p></div>
</template><script>
export default {name: 'ChildComponent', // 组件名称(可选)props: {parentData: {type: String, // 指定props的类型required: true // 指定props是否必需}}
};
</script>
注意事项
- 组件名称:虽然组件名称是可选的,但在调试和阅读代码时,为组件提供有意义的名称会很有帮助。
- props验证:在子组件中,通过props选项可以指定接收的数据类型和是否必需。这有助于在开发过程中捕获潜在的错误。
- 数据传递:在父组件的模板中,通过:prop-name=“data”(或简写为v-bind:prop-name=“data”)将数据传递给子组件。在子组件中,通过this.propName(在模板中直接使用{{ propName }})访问传递的数据。
- 组件注册:在父组件的components选项中注册子组件,以便在父组件的模板中使用它。
- 单文件组件:上述示例使用了单文件组件(.vue文件),这是Vue.js推荐的组织组件的方式。每个.vue文件都包含模板、脚本和样式(可选)。
- Vue实例:在Vue 3中,你可能会使用createApp来创建Vue实例,并挂载到DOM元素上。但在上述父子组件的示例中,我们主要关注组件本身的定义和通讯,而不是整个应用的创建和挂载过程。
- 组合式API与选项式API:Vue 3引入了组合式API,它提供了一种新的方式来组织和重用逻辑。然而,上述示例使用了Vue 2和Vue 3都支持的选项式API。如果你对组合式API感兴趣,可以查阅Vue 3的官方文档。
5.2.2 父组件给子组件传值
在Vue.js中,父组件可以通过props向子组件传递数据。以下是一个简单的示例,展示了父组件如何向子组件传递值。
父组件(ParentComponent.vue)
<template><div><h1>我是父组件</h1><!-- 使用子组件,并通过props传递数据 --><ChildComponent :message="parentMessage" /></div>
</template><script>
// 导入子组件
import ChildComponent from './ChildComponent.vue';export default {name: 'ParentComponent', // 组件名称(可选)components: {ChildComponent // 注册子组件},data() {return {parentMessage: '这是从父组件传递给子组件的消息' // 父组件的数据};}
};
</script>
子组件(ChildComponent.vue)
<template><div><h2>我是子组件</h2><!-- 显示从父组件接收的数据 --><p>{{ message }}</p></div>
</template><script>
export default {name: 'ChildComponent', // 组件名称(可选)props: {message: {type: String, // 指定props的类型为字符串required: true // 指定这个props是必需的}}
};
</script>
在这个例子中,父组件ParentComponent有一个数据属性parentMessage,它包含了要传递给子组件的消息。在父组件的模板中,<ChildComponent :message="parentMessage" />
这行代码通过props将parentMessage的值传递给子组件ChildComponent。注意,这里的:message是v-bind:message的简写,它告诉Vue将这个属性的值绑定到子组件的message属性上。
在子组件ChildComponent中,通过props选项接收来自父组件的数据。在这个例子中,props对象有一个message属性,它指定了接收的数据的类型(String)和是否必需(true)。然后,在子组件的模板中,可以通过{{ message }}来访问和显示这个数据。
这样,父组件就可以成功地向子组件传递值了。
5.2.3 子组件给父组件传值
在Vue.js中,子组件可以通过事件向父组件传递数据。这通常是通过自定义事件来实现的,子组件触发一个事件,并传递所需的数据作为事件的参数。父组件监听这个事件,并在事件触发时接收数据。
以下是一个简单的示例,展示了子组件如何向父组件传递值:
父组件(ParentComponent.vue)
<template><div><h1>我是父组件</h1><!-- 使用子组件,并监听自定义事件 --><ChildComponent @sendData="handleData" /><p>从子组件接收到的数据: {{ receivedData }}</p></div>
</template><script>
// 导入子组件
import ChildComponent from './ChildComponent.vue';export default {name: 'ParentComponent',components: {ChildComponent},data() {return {receivedData: null // 用于存储从子组件接收到的数据};},methods: {handleData(data) {// 这个方法会在子组件触发'sendData'事件时被调用this.receivedData = data;}}
};
</script>
子组件(ChildComponent.vue)
<template><div><h2>我是子组件</h2><!-- 一个按钮,用于触发向父组件发送数据的事件 --><button @click="sendDataToParent">发送数据到父组件</button></div>
</template><script>
export default {name: 'ChildComponent',methods: {sendDataToParent() {// 触发自定义事件'sendData',并传递数据this.$emit('sendData', '这是从子组件发送到父组件的数据');}}
};
</script>
在这个例子中,子组件ChildComponent有一个方法sendDataToParent,它使用this.$emit来触发一个名为sendData的自定义事件,并传递一个字符串作为参数。父组件ParentComponent在模板中监听这个事件(@sendData=“handleData”),并在事件触发时调用handleData方法,该方法接收事件传递的数据并将其存储在receivedData属性中。
这样,子组件就可以成功地向父组件传递值了。父组件可以通过监听子组件触发的事件来接收数据,并在自己的逻辑中使用这些数据。
5.2.4 单向数据流
在Vue.js中,单向数据流是一种数据流动模式,它确保了数据的流向是从父组件到子组件,而不是反过来。这有助于保持组件的独立性和可预测性。在单向数据流中,子组件不应该直接修改从父组件接收到的props。
下面是一个简单的示例,展示了如何在Vue.js中实现单向数据流:
父组件(ParentComponent.vue)
<template><div><h1>我是父组件</h1><p>父组件的数据: {{ parentData }}</p><!-- 传递数据给子组件,但不监听子组件的事件来修改这个数据 --><ChildComponent :data="parentData" /></div>
</template><script>
// 导入子组件
import ChildComponent from './ChildComponent.vue';export default {name: 'ParentComponent',components: {ChildComponent},data() {return {parentData: '这是父组件的数据'};}
};
</script>
子组件(ChildComponent.vue)
<template><div><h2>我是子组件</h2><p>从父组件接收到的数据: {{ data }}</p><!-- 注意:这里我们没有提供一个按钮或方法来修改`data`,因为它是一个`prop` --></div>
</template><script>
export default {name: 'ChildComponent',props: {data: {type: String,required: true}}// 注意:我们没有在子组件中修改`data`属性,这保持了单向数据流的原则。
};
</script>
在这个例子中,父组件ParentComponent有一个数据属性parentData,并通过props传递给了子组件ChildComponent。子组件接收这个prop并显示它,但没有提供任何方式来修改它。这确保了数据的流向是单向的,从父组件到子组件。
如果子组件需要基于prop的值进行一些操作,并且这些操作的结果需要反馈到父组件,那么应该通过事件来实现。子组件可以触发一个事件,将需要的数据作为事件的参数传递给父组件,然后父组件可以监听这个事件并相应地更新自己的数据。但是,这并不意味着子组件直接修改了父组件的prop,而是通过一种解耦的方式来通知父组件进行更新。
5.3 插槽
组件通讯中学习了父组件如何传递变量给子组件,如果需求是改变子组件的部分HTML 结构,又该如何实现呢?插槽(slot)就是 Vue 提供的一种让父组件能够向子组件传递模板或者组件的方法,官方称为组件内容分发。这使得开发者可以创建可复用的组件,第 5 章 Vue 组件与生命周期并自定义这些组件的外观和行为。
插槽分为匿名插槽和具名插槽
5.3.1 匿名插槽
在Vue.js中,插槽(Slots)是一种让父组件能够向子组件指定内容插入点的机制。匿名插槽(也称为默认插槽或普通插槽)是最基本的插槽类型,它允许父组件向子组件的一个未命名的插槽中插入内容。
以下是一个简单的示例,展示了如何在Vue.js中使用匿名插槽:
子组件(ChildComponent.vue)
<template><div class="child-component"><h2>我是子组件</h2><!-- 这里定义了一个匿名插槽 --><slot></slot></div>
</template><script>
export default {name: 'ChildComponent'
};
</script><style scoped>
.child-component {border: 1px solid #ccc;padding: 10px;
}
</style>
父组件(ParentComponent.vue)
<template><div><h1>我是父组件</h1><!-- 使用子组件,并向其匿名插槽中插入内容 --><ChildComponent><p>这是父组件通过匿名插槽传递给子组件的内容。</p></ChildComponent></div>
</template><script>
// 导入子组件
import ChildComponent from './ChildComponent.vue';export default {name: 'ParentComponent',components: {ChildComponent}
};
</script>
在这个例子中,子组件ChildComponent定义了一个匿名插槽(<slot>
</slot>
)。当父组件ParentComponent使用这个子组件时,它可以在<ChildComponent>
标签内部直接插入内容。这个内容会被渲染到子组件的匿名插槽位置。
渲染后的HTML结构大致如下:
<div><h1>我是父组件</h1><div class="child-component"><h2>我是子组件</h2><p>这是父组件通过匿名插槽传递给子组件的内容。</p> <!-- 插槽内容 --></div>
</div>
通过这种方式,父组件可以灵活地向子组件的指定位置插入内容,而无需修改子组件的代码。这是Vue.js组件化开发中的一个强大特性,有助于实现组件的高复用性和灵活性。
5.3.2 具名插槽
在Vue.js中,具名插槽(Named Slots)允许你向子组件的特定插槽插入内容。与匿名插槽不同,具名插槽在子组件中通过name属性进行标识,父组件则通过标签的slot属性指定要插入到哪个具名插槽中。
以下是一个简单的示例,展示了如何在Vue.js中使用具名插槽:
子组件(ChildComponent.vue)
<template><div class="child-component"><h2>我是子组件</h2><!-- 这里定义了一个具名插槽,名为"header" --><slot name="header"></slot><p>这是子组件的默认内容。</p><!-- 这里定义了另一个具名插槽,名为"footer" --><slot name="footer"></slot></div>
</template><script>
export default {name: 'ChildComponent'
};
</script><style scoped>
.child-component {border: 1px solid #ccc;padding: 10px;
}
</style>
父组件(ParentComponent.vue)
<template><div><h1>我是父组件</h1><!-- 使用子组件,并向其具名插槽中插入内容 --><ChildComponent><!-- 向名为"header"的插槽插入内容 --><template slot="header"><p>这是父组件通过具名插槽"header"传递给子组件的内容。</p></template><!-- 也可以向匿名插槽(默认插槽)插入内容,但在这个例子中我们没有这样做 --><!-- 向名为"footer"的插槽插入内容 --><template v-slot:footer><p>这是父组件通过具名插槽"footer"传递给子组件的内容。</p></template><!-- 注意:Vue 2.6.0+ 支持简写语法 v-slot:name 可以简写为 #name --><!-- <template #footer><p>这是父组件通过具名插槽"footer"传递给子组件的内容(简写语法)。</p></template> --></ChildComponent></div>
</template><script>
// 导入子组件
import ChildComponent from './ChildComponent.vue';export default {name: 'ParentComponent',components: {ChildComponent}
};
</script>
在这个例子中,子组件ChildComponent定义了两个具名插槽:header和footer。父组件ParentComponent在使用子组件时,通过<template>
标签的slot属性(或Vue 2.6.0+的v-slot指令)指定了要插入到哪个具名插槽中的内容。
渲染后的HTML结构大致如下(注意,实际的HTML结构可能会因为Vue的虚拟DOM渲染机制而有所不同,但逻辑上是这样的):
<div><h1>我是父组件</h1><div class="child-component"><h2>我是子组件</h2><p>这是父组件通过具名插槽"header"传递给子组件的内容。</p> <!-- header插槽内容 --><p>这是子组件的默认内容。</p><p>这是父组件通过具名插槽"footer"传递给子组件的内容。</p> <!-- footer插槽内容 --></div>
</div>
通过这种方式,父组件可以灵活地向子组件的不同插槽位置插入内容,而无需修改子组件的代码。具名插槽在构建复杂组件结构时非常有用,它们允许组件的各部分内容被独立地管理和复用。
5.4 生命周期钩子函数
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
5.4.1 生命周期图
5.4.2 常用生命周期函数
在前端开发中,特别是在使用框架和库(如 React、Vue.js 和 Angular)时,生命周期函数(或生命周期钩子)扮演着重要角色。它们允许你在组件的不同阶段执行代码,比如初始化、更新和销毁。以下是这三个主流框架中常用的生命周期函数及其代码示例。
React
在 React 中,生命周期函数分为三类:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。
import React, { Component } from 'react';class MyComponent extends Component {constructor(props) {super(props);console.log('Constructor');this.state = {// 初始化 state};}componentDidMount() {console.log('Component Did Mount');// 组件挂载后执行的代码,比如数据获取}shouldComponentUpdate(nextProps, nextState) {console.log('Should Component Update');// 返回一个布尔值,决定是否更新组件return true; // 默认行为是返回 true}getSnapshotBeforeUpdate(prevProps, prevState) {console.log('Get Snapshot Before Update');// 在最近一次渲染输出(提交到 DOM 节点)之前调用return null;}componentDidUpdate(prevProps, prevState, snapshot) {console.log('Component Did Update');// 组件更新后执行的代码}componentWillUnmount() {console.log('Component Will Unmount');// 组件卸载前执行的代码,比如清理定时器}render() {return (<div>{/* 组件的 JSX */}</div>);}
}export default MyComponent;
Vue.js
在 Vue.js 中,生命周期钩子也是分为创建、挂载、更新和销毁几个阶段。
javascript"><template><div><!-- 组件的模板 --></div>
</template><script>
export default {data() {return {// 初始化 data};},beforeCreate() {console.log('Before Create');// 实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用},created() {console.log('Created');// 实例已经创建完成之后被调用。在这一步,实例已完成数据观测、属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见},beforeMount() {console.log('Before Mount');// 在挂载开始之前被调用:相关的 render 函数首次被调用},mounted() {console.log('Mounted');// el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子},beforeUpdate() {console.log('Before Update');// 数据更新时调用,发生在虚拟 DOM 打补丁之前},updated() {console.log('Updated');// 由于数据改变导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用这个钩子},beforeDestroy() {console.log('Before Destroy');// 实例销毁之前调用。在这一步,实例仍然完全可用},destroyed() {console.log('Destroyed');// 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑,所有的事件监听器会被移除,所有的子实例也会被销毁}
};
</script>
Angular
在 Angular 中,生命周期钩子通过实现特定的接口来定义。
import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked } from '@angular/core';@Component({selector: 'app-my-component',templateUrl: './my-component.component.html',styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnInit, OnDestroy, OnChanges, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked {ngOnChanges(changes: SimpleChanges) {console.log('ngOnChanges');// 当 Angular 设置或重置数据绑定的输入属性时调用。该方法接收一个 SimpleChanges 对象,该对象包含了上一个和当前的属性值的对比}ngOnInit() {console.log('ngOnInit');// 在 Angular 初始化组件/指令/管道之后调用。只调用一次}ngDoCheck() {console.log('ngDoCheck');// 检测输入属性的变化和执行变更检测。当检测到输入属性的变化时调用,适用于实现自定义变更检测算法}ngAfterContentInit() {console.log('ngAfterContentInit');// 在 Angular 完成外部内容投影之后调用。该方法用于当需要添加或移除投影的内容时}ngAfterContentChecked() {console.log('ngAfterContentChecked');// 每次完成被投影组件内容的变更检测之后调用}ngAfterViewInit() {console.log('ngAfterViewInit');// 在 Angular 初始化完组件视图及其子视图之后调用}ngAfterViewChecked() {console.log('ngAfterViewChecked');// 每次做完组件视图及其子视图的变更检测之后调用}ngOnDestroy() {console.log('ngOnDestroy');// 在 Angular 销毁组件/指令/管道之前调用。用于执行清理操作}
}
这些生命周期函数允许你在组件生命周期的不同阶段插入自定义逻辑,以优化性能和用户体验
生命周期钩子函数 | 触发的时机 |
---|---|
beforeCreadted | vue 实例的挂载元素$el 和数据对象 data 都为 undefined,还未初始化。 |
created | vue 实例的数据对象 data 有了,$el 属性还不存在 |
beforeMount | vue 实例的$el 和 data 都初始化了,但还是虚拟的 dom 节点 |
mounted | vue 实例挂载完成 |
beforeUpdate | data 更新之前触发,此时拿到的数据是改变之前的数据 |
updated | data 更新完成触发,此时数据和 DOM 结构已经是修改后的 |
beforeDestroy | 组件销毁之前触发,用于销毁前的准备工作 |
destroyed | 组件销毁时触发,vue 实例解除了事件监听以及和 dom 的绑定(无响应了) 但 DOM 节点依旧存在 |
5.5 Axios 网络请求库
Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。它提供了简单且灵活的 API,用于执行各种 HTTP 请求(如 GET、POST、PUT、DELETE 等)。
5.5.1 Axios 库简介
在 JavaWeb 中我们学习了 Ajax 请求,使用的是 jQuery 的$.ajax 系列方法,但是在 Vue 框架中,由于编程风格和思路完全不同,通常 Vue 和 jQuery 是不会混用的,那么 Ajax 请求我们就需要用到一个独立的 HTTP 客户端
5.5.2 Axios 基本使用
关键特性
- 基于 Promise:Axios 使用 Promise API,这使得它易于与 async/await 语法结合使用。
- 支持拦截器:请求和响应拦截器允许你在请求或响应被处理之前对其进行操作。
- 自动转换数据:自动将请求数据和响应数据转换为 JSON 格式。
- 取消请求:可以使用 CancelToken 取消请求。
- 支持并发请求:使用 axios.all 方法可以处理多个并发请求。
- 客户端支持防止 XSS:通过配置 xsrfCookieName 和 xsrfHeaderName 来防止跨站请求伪造。
安装
在 Node.js 环境中,你可以使用 npm 或 yarn 安装 Axios:
npm install axios
# 或者
yarn add axios
在浏览器环境中,你可以通过 CDN 引入:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
基本用法
发送 GET 请求
javascript">const axios = require('axios');axios.get('https://api.example.com/data').then(response => {console.log(response.data);}).catch(error => {console.error('Error fetching data:', error);});
发送 POST 请求
javascript">axios.post('https://api.example.com/data', {key1: 'value1',key2: 'value2'}).then(response => {console.log(response.data);}).catch(error => {console.error('Error posting data:', error);});
使用 async/await
javascript">async function fetchData() {try {const response = await axios.get('https://api.example.com/data');console.log(response.data);} catch (error) {console.error('Error fetching data:', error);}
}fetchData();
配置
你可以创建一个 Axios 实例,并对其进行全局配置:
javascript">const axios = require('axios');const instance = axios.create({baseURL: 'https://api.example.com',timeout: 1000,headers: {'X-Custom-Header': 'foobar'}
});instance.get('/data').then(response => {console.log(response.data);}).catch(error => {console.error('Error fetching data:', error);});
拦截器
请求拦截器
javascript">axios.interceptors.request.use(config => {// 在发送请求之前做些什么console.log('Request Interceptor:', config);return config;
}, error => {// 对请求错误做些什么return Promise.reject(error);
});
响应拦截器
javascript">axios.interceptors.response.use(response => {// 对响应数据做点什么console.log('Response Interceptor:', response);return response;
}, error => {// 对响应错误做点什么return Promise.reject(error);
});
取消请求
javascript">const CancelToken = axios.CancelToken;
const source = CancelToken.source();axios.get('/user/12345', {cancelToken: source.token
}).catch(function (thrown) {if (axios.isCancel(thrown)) {console.log('Request canceled', thrown.message);} else {// handle error}
});// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');