生命周期
Vue会在一些特殊时刻去调用一些特殊的函数
这些要经过的特殊时刻就是生命周期,要调用的特殊函数就是生命周期函数(生命周期钩子,生命周期回调函数)
**为什么叫钩子:**vm在某个步骤上已经写了执行该名称的函数,但函数内容未定义,得程序员来定义。即生命周期函数的名字不可更改,但函数的具体内容需要程序员根据业务需求编写
Vue实例的生命周期: 1、初始化显示
beforeCreate()=> 初始化:生命周期、事件,但数据代理还未开始create()=> 初始化:数据监测、数据代理beforeMount()=> 模板解析完毕,虚拟DOM建立,但还未挂载mounted()=> 挂载完毕,虚拟DOM转为真实DOM
2、更新状态: this.xxx = value
beforeUpdate()=> 数据已更新,但页面还未更新,新旧虚拟dom还未对比updated()=> 数据和页面都是新的
3、销毁 vue 实例: vm.$destroy() 触发下面两个钩子
beforeDestroy()=> 此时vm上所有东西都是可用的,但不再解析模板更新页面destroyed()=> vm彻底被销毁
还有三个钩子不在图中,和路由相关
常用钩子:
- mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
- beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
关于销毁Vue实例:
- 在大多数场景中不应该调用 vm.$destroy() 。最好使用 v-if 和 v-for 指令以数据驱动的方式控制子组件的生命周期。
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生D0M事件依然有效
- 在beforeDestroy中做好善后工作
组件
组件:应用中局部功能代码和资源的集合
在 Vue 中,组件是可复用的 Vue实例
组件体现了封装的思想,组件也可以嵌套封装
组件化:编写一套组件,在所需的页面引入组件,当应用中的功能都是多组件的方式来编写的, 这个应用就是一个组件化的应用
**为什么要组件化:**传统的三件套可以只能做到拆分css、js模块化,而html结构不复用,且文件依赖关系(N-1-N)复杂,组件化后,页面的每个部分都对应一个组件,组件中的小功能部分也能对应更多组件,组件逐层嵌套,最终由一个Vue实例管理,依赖关系清晰,通过引入组件构建页面,代码复用率高,后续维护也能针对性找到组件进行操作
非单文件组件
**非单文件组件:**一个文件中包含n个组件,实际开发几乎不用这种写法
使用组件流程:
- 定义组件:
Vue.extend()- 配置项中不能写el属性,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器 - 组件中的data必须用函数式,避免组件被复用时,数据存在引用关系。 - 使用 template 配置组件模板结构,必须有一个根元素包裹
const school = Vue.extend({ // 模板,template中的模板必须有一个根元素包裹 template: ` <div> <h3>{{name}}</h3> <h3>{{city}}</h3> </div> `, // 组件中的data必须用函数式 // 因为对象是引用类型数据,函数式返回一个新对象才能保证多次引用组件的data互不影响 data() { return { name: '五道口', city: '京海' } }})- 注册组件:
-
Vue.component()全局注册 - 局部注册,在vm配置项的 components 属性中
// 全局注册组件,多个vue实例都能使用该组件Vue.component('school', school)// 组件由vm管理new Vue({ el: '#root', data: { msg: '信息' }, // 局部注册组件 components: { // 组件名 student }})- 使用组件:在容器中写组件标签
案例:
<div id="root"> <h2>{{msg}}</h2> <!-- 引用组件,组件标签 --> <school></school> <hr /> <!-- 多次引用组件,且数据独立互不影响 --> <student></student> <student></student></div><script> // 使用Vue.extend定义一个组件,传入一个配置项 const school = Vue.extend({ // 模板,template中的模板必须有一个根元素包裹 template: ` <div> <h3>{{name}}</h3> <h3>{{city}}</h3> </div> `, // 组件中的data必须用函数式 // 因为对象是引用类型数据,函数式返回一个新对象才能保证多次引用组件的data互不影响 data() { return { name: '五道口', city: '京海' } } }) const student = Vue.extend({ template: ` <div> <h3>{{name}}</h3> <h3>{{age}}</h3> <button @click="age++">年龄加一</button> </div> `, data() { return { name: 'chuckle', age: '19' } } }) // 全局注册组件,多个vue实例都能使用该组件 Vue.component('school', school) // 组件由vm管理 new Vue({ el: '#root', data: { msg: '信息' }, // 局部注册组件 components: { // 组件名 student } })</script>注意事项
关于组件名:
- 组件名只有一个单词时,引用组建时写小写
<student>或大写<Student>Vue只会去找小写的组件名,即组件名只有一个单词时需使用小写 - 组件名由多个单词组成时,有两种写法:横杠写法、双驼峰写法(只能在脚手架环境中使用)
<!-- 横杠写法 --><my-school></my-school><script> Vue.component('my-school', MySchool)</script><!-- 双驼峰写法(只能在脚手架环境中使用) --><MySchool></MySchool><script> Vue.component('MySchool', MySchool)o[]</script>- 不能使用html已有标签名作为组件名
- 在定义组件时添加 name 配置,无论注册使用时是什么名,在开发者工具中显示的都是该 name(首字母同样会自动显示大写)
const school = Vue.extend({ // 设置 name name: 'MySchool', template: ``, data() { return {} }})在脚手架环境下,可以使用组件时可以写双标签 <school></school> 或自闭合标签 <school/>,不用使用脚手架时,<school/> 会导致后续组件不能渲染。
定义组件可以简写: 注册组件时底层有判断,若是一个对象,Vue会帮我们调用Vue.extend,并将对象作为配置项传入
// 简写,实际上也调用了Vue.extendconst school = { template: ``, data() { return {} }}// 写上 Vue.extend 不简写const school = Vue.extend({ template: ``, data() { return {} }})组件嵌套
组件嵌套:一个组件也会用到其它多个组件,组件关系逐层嵌套,形成父子组件关系
**使用方式:**在父组件中 components 配置项注册其子组件,组件在哪注册就要在哪使用
开发中的技巧:定义一个名为 app 的组件,用于管理页面中最上层的所有组件,而 vm 本身只管理 app 组件
<div id="root"></div><script> // 子组件的定义要放到父组件前 const student = { template: ` <div> <h3>学生姓名:{{name}}</h3> <h3>学生年龄:{{age}}</h3> </div> `, data() { return { name: 'chuckle', age: '19' } } } // 定义父组件 const school = { // 在父组件中使用子组件 template: ` <div> <h2>学校名:{{name}}</h2> <hr /> <student></student> </div> `, data() { return { name: '五道口' } }, // 注册子组件 components: { student, } } // 定义一个欢迎语组件,和school组件同级 const hello = { template: ` <div> <h3>{{welcome}}</h3> <hr/> </div> `, data() { return{ welcome: '欢迎!' } } } // 定义app组件,管理所有组件 const app = { template: ` <div> <hello></hello> <school></school> </div> `, // 管理下一层的父级组件 components: { school, hello } } new Vue({ el: '#root', // 应用app组件,替换掉root容器 template:`<app></app>`, components: { // vm只管理app组件 app } }) </script>VueComponent构造函数
每个组件的本质都是一个 VueComponent() 构造函数,它由 Vue.extend() 生成并返回
每次调用 Vue.extend() ,返回的都是一个全新的 VueComponent() 构造函数
打印app组件:
const app = Vue.extend({ template: ``, components: {}})console.log(app)ƒ VueComponent(options) { this._init(options); }既然是构造函数,使用它就需要 new 创建实例
只需要在合适的位置写 <school></school> ,Vue 在解析模板时会自动去创建对应的VueComponent构造函数的实例
即Vue会在解析模板时自动执行:new VueComponent(options)
1、this的指向:
(1) Vue.extend(options) 组件配置中,this 指向 VueComponent 实例对象
(2) new Vue(options) 配置中,this 指向 vm 实例对象
2、Vue对组件的关系设计:
- 每种组件定义时都需要调用
Vue.extend(),它返回一个新的VueComponent()构造函数,即不同种类的组件,其对应的 VueComponent 构造函数不同,以此来区分组件的种类 - Vue在解析模板时,会对每个组件标签都调用一次对应的构造函数,返回的实例都是互不影响的,以此来区分同种组件的不同实例(同种组件在出现在页面不同位置,展示不同内容)
vm与vc
VueComponent() 的实例对象,简称vc(也可称之为:组件实例对象)
Vue() 的实例对象,简称vm。
对vm和vc的理解:
1、vm 和 vc 长得差不多(属性和方法都一样的),因为 Vue(options) 和 VueComponent(options) 两个构造函数,在源码中都是调用了其原型上的 _init(options)
2、vm 或 vc 的 $children 属性以数组形式保存着 vm 或 vc 所管理的组件或子组件的实例对象
3、组件是可复用的 Vue 实例(vm),所以它们与 new Vue() 接收相同的配置选项 options,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
**4、**vc 由 vm 管理,vc 没有 el 配置,只有 vm 能通过 el 配置来指定为谁服务
重要内置关系
VueComponent.prototype.__proto__ = Vue.prototype
复习原型与原型链:JavaScript基础笔记(3)--原型与原型链
$mount、$watch 等属性和方法都在 Vue.prototype 上,也就是 Vue 的原型上
作用:这个重要的内置关系将组件的原型与Vue的原型通过 __proto__ 相连,使 VueComponent 继承 Vue原型,让组件实例顺着原型链也能使用$mount、$watch 等属性和方法,
目的:让组件实例对象(vc)可以访问到Vue原型上的属性、方法
单文件组件
**单文件组件:**一个文件中仅包含一个组件,文件后缀为 .vue
浏览器并不认识.vue文件,需要通过 webpack 或脚手架,将其加工为JS文件
文件名推荐规则:单个单词大写,多个单词双驼峰
.vue文件中代码分为三个标签:
<template>=> 组件的结构<script>=> 组件交互相关代码,包含组件所用的数据、方法<style>=> 组件的样式
**主要的文件:**需放入脚手架中才能运行 1、xxx.vue 各种组件 2、App.vue 汇总所有组件 3、main.js 入口文件,创建Vue实例,注册App组件并应用 4、index.html 页面
<template> <!-- 组件的结构 --> <div class="student"> <h2>{{ name }}}</h2> <h3>{{ age }}}</h3> </div></template>
<script>// 组件交互相关代码// 省略Vue.extend,直接暴露组件的配置对象export default { name: 'Student', //最好与文件名保持一致 data() { return { name: 'chuckle', age: '19' } }}</script>
<style>/* 组件的样式 */.student { background: #ccc; border-radius: 8px;}</style>必须有 App.vue 汇总所有组件
<template> <div> <School></School> </div></template>
<script> // 引入Student组件 import School from "./Student.vue"
export default { name: "App", components: { School } }</script>
<style></style>main.js 入口文件,创建Vue实例,注册App组件并应用
import App from "./App.vue" // 引入App组件
new Vue({ el: '#root', template: '<App></App>', components: { App }})index.html 页面
<!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>Document</title></head><body> <!-- 准备一个容器 --> <div id="root"></div> <!-- 引入vue文件 --> <script src="../../js/vue.js"></script> <!-- 引入入口文件 --> <script src="./main.js"></script></body></html>脚手架Vue-cli
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
Vue 有多种脚手架,Vue-cli 是其中一种
起步
安装:该全局模块会暴露一个全局命令vue
npm install -g @vue/cli创建一个项目
vue create <项目名>选择项目的vue版本,babel用于ES6转ES5,eslint语法检查
Vue CLI v5.0.8? Please pick a preset: Default ([Vue 3] babel, eslint)> Default ([Vue 2] babel, eslint) Manually select features等待安装完毕,运行项目
npm run serve项目文件结构
默认 HelloWord 项目结构:
├─ public│ ├─ favicon.ico│ └─ index.html│├─ src│ ├─ assets│ │ └─ logo.png│ ├─ components│ │ └─ HelloWorld.vue│ ├─ App.vue│ └─ main.js│├─ .gitignore├─ vue.config.js├─ babel.config.js├─ jsconfig.json├─ package-lock.json├─ package.json文件解释:
1、根目录中的一些配置文件:
- babel.config.js babel控制文件,脚手架已写好,无需再动
- package.json 定义了当前项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等)
- package-lock.json 锁定所有模块的版本号,包括主模块和所有依赖子模块
- .gitignore git忽略文件配置
- jsconfig.json 指定根文件和JavaScript语言服务提供的功能选项,明确项目的文件范围
- vue.config.js 一个可选的脚手架配置文件,进行vue项目本地运行和构建相关配置,若根目录中存在这个文件,它会被 @vue/cli-service 自动加载
**2、src文件夹:**有些熟悉的文件,main.js入口文件、app.vue组件等 - **assets文件夹:**存放静态资源 - **components:**存放组件,除了App组件
// 整个项目的入口文件// 引入Vueimport Vue from 'vue'// 引入App组件,是所有组件的父组件import App from './App.vue'
// 关闭Vue的生产环境提示Vue.config.productionTip = false
// 创建Vue实例对象new Vue({ // 将App组件放入容器中 render: h => h(App),}).$mount('#app') // 绑定容器**3、public文件夹:**页面的根目录,存放了index.html和ico图标
**应该知道:**main.js之所以是入口文件,而且它能找到public中的index.html去绑定容器,是脚手架配置、规定的
render函数
在 main.js 中,render: h => h(App) 作用是将App组件放入容器中
若使用之前的模板写法,控制台会报错
import App from "./App.vue" // 引入App组件new Vue({ el: '#root', template: '<App></App>', components: { App }})报错内容: [vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build. **意思是:**正在使用Vue的仅运行时版本(runtime模式),其中模板编译器不可用。将模板预编译为render函数,或者使用包含编译器的内部版本。
问题原因: Vue包含两个部分,核心部分与模板解析器,核心部分是生命周期与各种属性与函数,模板解析器用于解析模板,当通过cdn引入来使用Vue时,应该将两者都引入,但如果使用脚手架、webpack将.vue文件都翻译为.js文件,且模板都已经解析为html,模板解析器在生产环境就没有用了
因为Vue中不是所有部分都在生产环境中用到,且模板解析器占Vue体积的三分之一,所以Vue除了完整版,还有许多精简版,带模板解析器的称为 compiler(模板)模式,反之称为 runtime(运行时)模式
(1) vue.js 是完整版Vue,包含:核心功能+模板解析器 (2) vue.runtime.xxx.js 是运行版Vue,只包含核心功能,没有模板解析器
通过 import Vue from 'vue' 引入的就是不带模板解析器的精简版,vue模块的package.json的main/model字段默认为runtime模式,指向了”dist/vue.runtime.common.js”。
解决办法一:引入完整版Vue,不推荐
import Vue from 'vue/dist/vue.js'解决办法二:使用渲染函数 render() 代替模板写法
render 配置项是一个函数,传入 createElement 参数也是一个函数
createElement() 可以创建具体的元素,可以传入一个组件或是 ('标签名','标签内容')的形式 ,返回创建好的元素
Vue会调用 render() 并接收其返回值,将 createElement() 的返回值(创建好的元素)返回即可
// 完整写法render(createElement) { return createElement('h1', '你好啊')}// 箭头函数简写render: h => h(App)**注意:**在开发环境中有 vue-cli 自带有 vue-template-compiler 插件,能解析 .vue 文件中的 <template></template> 模板,但写在配置对象 template 属性中的模板没有插件能解析,所以要使用 render()
以后一般也只有在创建vm时配置项里会用到render
自定义脚手架配置
脚手架默认使用main.js作为入口文件等默认配置都在一个文件中,该文件被隐藏了起来
使用命令导出、查看脚手架配置文件的拷贝
vue inspect > output.js在vue-cli配置参考项中查看可配置项
在 vue.config.js 文件中进行自定义配置
const { defineConfig } = require('@vue/cli-service')module.exports = defineConfig({ // 配置选项})ref标签属性
替代 id 属性给元素或子组件注册引用信息(打标识)
给元素标签或组件标签添加 ref 属性,就能在组件实例对象的 $refs 上获取dom元素或子组件实例对象
<!-- 打标识 --><h1 v-text="msg" ref="xxx"></h1><School ref="xxx"></School><!-- 获取dom元素或子组件vc --><script> this.$refs.xxx</script><template> <div> <h1 v-text="msg" ref="title"></h1> <button @click="showTitle">显示/隐藏标题</button> <School></School> </div></template>
<script>import School from "./components/school.vue";
export default { name: "App", components: { School, }, data() { return { msg: "学校信息", }; }, methods: { showTitle() { this.$refs.title.classList.toggle('hide'); }, },};</script>
<style>.hide { opacity: 0;}</style>props配置项
子组件的 props 配置项接收父组件通过组件标签传入的数据
**作用:**实现父组件与子组件的通信
通过标签属性给子组件传数据:
注意:
- 普通属性传的是字符串,要传入其它数据类型的属性,需要 v-bind 绑定属性,传入表达式的值
- 传入引用数据类型时,传的是引用,浅拷贝
<Student name="chuckle" sex="男" :age="18"></Student>子组件接收数据,三种写法
// 简单接收,不对数据类型进行限制props: ['name', 'age', 'sex']// 接收同时限制数据类型props: { name: String, age: Number, sex: String}// 更多配置,必须的、默认值等props: { name: { type: String, required: true // 必须的 }, age: { type: Number, required: true // 必须的 }, sex: { type: String, default: "男" }}props 接收到的数据会代理到 vc 身上,但不允许修改只能读取(直接修改也有效果,但控制台会发出警告),所以也不能使用 v-model 绑定
若需修改,应先将数据拷贝到data中,用一个新属性接收
data() { return { myAge: this.age + 1 };},mixins混入
多个相似组件中通常会有重复的配置项,可以将这些配置项提取成一个混入对象,给这些组件引入复用(混入)
将相同的配置项提取至 mixin.js
export const mixin1 = { data(){ return { x: 100, y: 200 } }}export const mixin2 = { data() { return { name: '', } }, methods: { showName(){ console.log(this.name); } },}组件通过 mixins 配置项引入 mixin.js 中的配置
Vue会将这些外部的配置混入组件的同名配置中(混合合并),配置中重复的属性保留组件中的
mixins: [] 局部混入
// 引入混入对象文件import {mixin1, mixin2} from '../mixin.js';export default { // 局部混入 mixins: [mixin1,mixin2],};Vue.mixin() 全局混入
import {mixin1, mixin2} from '../mixin.js';Vue.mixin(mixin1)Vue.mixin(mixin2)插件
Vue中插件本质是一个对象,对象中必须有一个 install() 方法
在 plugins.js 或其它名字的独立js文件中定义插件
export default { install(){ console.log('这是一个插件'); }}在 main.js 中导入并使用 Vue.use() 应用插件
// 导入插件import plugins from "./plugins";// 使用插件Vue.use(plugins);插件对象的 install() 方法默认第一个参数接收 Vue构造函数 作为参数,在插件中可以进行各种全局配置以及在原型上添加属性和方法
import {mixin1, mixin2} from './mixin.js';export default { // 第一个参数接收Vue构造函数作为参数 install(Vue){ // 配置全局过滤器 Vue.filter('interceptStr',(value, num=4)=>{ return value.slice(0,num) })
// 注册全局自定义指令 Vue.directive('inHtml', function(el, binding){ el.innerHTML = binding.value })
// 应用全局混入 Vue.mixin(mixin1) Vue.mixin(mixin2)
// 在Vue原型上添加属性和方法 Vue.prototype.hello = function(){ console.log('Hello') }
}}自定义参数:
Vue.use() 使用插件时也可以传入其它参数,会被 install() 方法接收,第一个参数仍然是 Vue构造函数
// 传入三个自定义参数,三个数字Vue.use(plugins,1,2,3);install(Vue,a,b,c){ console.log(a,b,c);// 1 2 3}scoped样式
多个组件有多个 style 标签,所有组件样式都会汇总到一个 CSS 文件中
当组件中有相同类名时,样式就会冲突,可以给组件的 style 标签加上 scoped 属性解决冲突
**作用:**让样式仅在当前组件中生效,防止样式冲突
**原理:**当style标签加上 scoped 属性后,Vue会给该组件所有标签元素都加上一个随机的 data-v 属性,css选择器也会自动选择 data-v
<div data-v-22321ebb="" class="demo"> <h2 data-v-22321ebb="">学生姓名:chuckle</h2> <h2 data-v-22321ebb="">学生性别:男</h2> <h2 data-v-22321ebb="">学生年龄:19</h2> <hr data-v-22321ebb=""></div>.demo[data-v-22321ebb] { background: rgb(78, 180, 220);}**注意:**一般在 App.vue 的 style 标签中写全局共用样式,所以 App 不适合加 scoped
更多: style 标签的 lang 属性可以指定 CSS 预处理语言,常用 less,使用 less 前需要安装 less-loader 用于解析 less
<style lang="less" scoped></style>
评论