组件(实现应用中局部功能代码和资源的集合)
data写成函数式浅拷贝其中一组发生变化后另一个不变
Vue2 组件基础 & 组件嵌套 代码笔记梳理
你贴了两段原生 Vue2(非脚手架)代码:
- 第一段:组件的定义、全局注册、局部注册、组件使用
- 第二段:组件嵌套,组件内部注册子组件
一、组件三大步骤(第一段代码核心)
- 定义组件:
Vue.extend(配置对象)
⚠️组件配置对象和
new Vue()区别两点
- 不要写
el:由 Vue 实例 vm 的el决定挂载哪个容器 data必须是函数:data(){ return {} },防止多个组件实例共用一份对象引用,改一个全部都变template写组件 HTML 结构,只能一个根容器包裹
const school = Vue.extend({ template:`<div>...</div>`, data(){ return { schoolName:'尚硅谷' } }, methods:{} }) //简写:可以省略Vue.extend,直接写对象 const school = { template:`<div>...</div>`, data(){return{}} }- 注册组件
- ✅全局注册:
Vue.component('组件标签名',组件对象),所有 vm 实例都可以直接用
Vue.component('hello', hello)- ✅局部注册:在 new Vue 的
components配置里注册,仅当前 vm 容器可用
new Vue({ el:'#root', components:{ xuexiao: school, //key:页面写的标签名,value:组件对象 xuesheng: student } })- 使用组件:页面写标签
<xuexiao></xuexiao>
注意:无脚手架环境,不要自闭合
<xuexiao/>,会导致后面 DOM 渲染失效,必须成对闭合。
组件名规则
- 单个单词:
school/School - 多单词:
- 短横线命名
my‑school(原生浏览器直接可用) - 大驼峰
MySchool只能脚手架环境使用,原生 html 不识别
- 短横线命名
- 不要和原生 html 重名,比如
div、h1 name配置:给 vue 开发者工具显示组件名字,不影响页面标签使用
二、组件嵌套(第二段代码)
核心:子组件在父组件的 components 中局部注册,父组件 template 中直接写子组件标签
执行顺序:
- 定义 student 子组件
- 定义 school 父组件,在它内部
components:{student}注册子组件,template 写<student></student> - app 组件引入 school、hello 组件
- Vue 实例 vm,不写大量标签,直接用
template:'<app></app>',挂载#root
const school = Vue.extend({ template:` <div> <h2>学校:{{name}}</h2> <student></student> <!-- 使用子组件 --> </div> `, components:{ student } //在父组件内部注册子组件 })整体层级关系:
vm(Vue实例) └── app组件 ├─ hello组件 └─ school组件 └─ student组件重要:组件只能在注册它的那个组件 template 内部使用;student 只在 school 里面注册,app 和 vm 中不能直接写
<student></student>。
三、两段代码易错点总结
- 组件
data必须函数,不要写成对象data:{},复用组件数据会互相污染 - template 模板只能一个根 div,不能并列多个根标签
- 原生浏览器环境,组件标签必须成对
<xxx></xxx>,不要自闭合<xxx/> - 全局注册全部可用;局部注册,只在当前组件生效
- 嵌套:子组件写在父组件
components,不是写在最外层 vm;谁要用,谁注册。
补充小区别
- vm(new Vue):根 Vue 实例,唯一,负责挂载页面
- 普通组件:不能直接挂载,必须被别的组件 /vm 调用
VueComponent 核心知识点
Vue.extend()返回的不是普通对象,是VueComponent 构造函数。
vm:new Vue()得到,Vue 实例vc:组件实例,new VueComponent()得到,Vue 内部自动帮我们 new,不用手动写
1. 关键要点
const school = Vue.extend(options)→school就是VueComponent 构造函数,不是实例。 页面写<school></school>,Vue 底层自动执行new school(),生成组件实例 vc。- 每一次
Vue.extend(),都会生成一个全新独立的 VueComponent 构造函数
const school = Vue.extend({...}) const hello = Vue.extend({...}) // school 和 hello 是两个完全不同的构造函数,互不干扰- this 指向(高频考点)
- ✅组件内 data /methods/computed /watch 里面的 this → vc(组件实例 VueComponent)
methods:{ showName(){ console.log(this) // vc 组件实例对象 } }- ✅
new Vue({})根实例里面 data/methods 中的 this →vm(Vue 实例)
- vm 和 vc 的关系
vc.__proto__ === VueComponent.prototypeVueComponent.prototype.__proto__ === Vue.prototype- 实例.proto→ 指向自己构造函数的
.prototype - 构造函数.prototype→ 是实例的原型对象
笔试经常考:
Q:组件实例 vc 为什么可以调用
this.$emit? A:vc 的原型链:vc.__proto__ → VueComponent.prototype.__proto__ → Vue.prototype,$emit定义在 Vue.prototype 上,沿着原型链查找到。
Q:VueComponent 和 Vue 是什么关系? A:不是相等,是原型继承关系。
小总结
- ✅拿能力:继承 Vue 原型上所有实例方法,组件可以做事件通信
- ✅省代码:不需要复制一套 API
- ✅做隔离:组件和根实例区分开,组件不允许写 el
- ✅面试高频考点
通俗比喻
Vue.prototype好比一个工具箱,里面放着$emit、$watch各种工具。
- vm(根实例):直接拿着这个工具箱。
- vc(组件):不直接拿工具箱,通过一根链子(原型链),可以访问工具箱里面全部工具。 但是 vc 自己不能做根 vm 的事情(不能写 el 挂载页面)。
例子普通 JS:
原型链:vc → VueComponent.prototype → Vue.prototype 所以组件实例 vc,可以直接使用 Vue 原型上的方法,比如
$watch、$emit等。
- 局部注册的子组件:谁要用,谁就在自己的
components选项注册。 示例代码中:
test组件只在hello组件内部注册,所以只有hello的 template 中可以写<test></test>#root容器、school 组件中直接写<test></test>会报错,没有注册。
<div id="root"> <school></school> <hello></hello> <!-- <test></test> 这里直接写会报错!vm没有注册test组件 --> </div>6. 打印验证(打开控制台可以看)
console.log(school) // VueComponent构造函数 console.log(vm) // vm 根Vue实例点击按钮,控制台打印的this就是 vc 组件实例。
7. 简写回顾
//完整写法 const school = Vue.extend({ template:'...', data(){return{} } }) //简写,省略Vue.extend,Vue底层会自动帮我们调用 const school = { template:'...', data(){return{} } }8. vm 和 vc 对比表格
表格
| vm(Vue 实例) | vc(VueComponent 组件实例) | |
|---|---|---|
| 创建方式 | new Vue(options) | Vue 内部自动new VueComponent() |
| el 配置 | ✅可以写 el | ❌组件不能写 el |
| data | 对象 / 函数都可以 | 必须是函数 |
| this | vm 实例 | vc 组件实例 |
| 作用 | 根实例,挂载容器 | 复用组件 |
原型链理解:组件实例可以继承 Vue 原型上所有的属性和方法,所以 vc 也能调用
$emit、$on等。
易错坑
- 不要手动写
new school(),Vue 解析组件标签自动帮你 new。 - 多次复用同一个组件标签,每一个标签都会生成独立 vc 实例,data 函数返回新对象,数据隔离。
- 子组件只对注册它的父组件生效,外部无法直接使用。
Vue 脚手架笔记整理
一、脚手架文件结构
├── node_modules # 项目所有依赖包,npm install自动生成,不要手动改 ├── public │ ├── favicon.ico # 浏览器页签小图标 │ └── index.html # 唯一主页面,整个Vue项目挂载在这里 ├── src # 写代码的源代码文件夹(我们主要操作这里) │ ├── assets # 静态资源:图片、css等 │ │ └── logo.png │ ├── components # 存放.vue组件(自定义组件放这里!) │ │ └── HelloWorld.vue │ ├── App.vue # 根组件,汇总所有其他组件 │ └── main.js # **项目入口文件**,程序从这里开始运行 ├── .gitignore # git忽略哪些文件不做版本管理 ├── babel.config.js # js语法转译配置(ES6转ES5兼容旧浏览器) ├── package.json # 项目配置:项目名字、版本、npm命令、依赖列表 ├── package‑lock.json # 锁定包版本,保证所有人安装版本一致,不要手动修改 └── README.md # 项目说明文档✨重点:自定义组件建议放在
src/components/文件夹导入路径示例:import Student from './components/Student.vue'你刚才报错就是路径写错,没有指向 components 文件夹。
二、Vue 两个版本区别
- vue.js(完整版)包含:Vue 核心功能 +模板解析器可以使用
template:配置项写模板。 - vue.runtime.xxx.js(运行时版,脚手架默认用这个)只包含:Vue 核心功能,没有模板解析器
- ❌不能写
template配置项 - ✅要用
render(createElement)函数生成页面内容
// main.js脚手架默认写法 new Vue({ render: h => h(App) }).$mount('#app')render 函数作用:代替模板解析器,把 App.vue 渲染到页面。
记忆口诀:完整版带解析器,运行版不带解析器,脚手架用运行版靠 render。
三、vue.config.js 配置文件
这个文件脚手架初始不会自动生成,需要自己在项目根目录手动新建。
- 查看脚手架全部默认配置(终端执行)
vue inspect > output.js执行后项目根目录会生成output.js,里面是全部默认配置。
- 常用配置(课堂练习必用)
module.exports = { lintOnSave: false, //关闭eslint代码规范校验,消除一堆波浪警告 }修改完必须:
Ctrl+C停止服务,重新npm run serve才生效。
- 官方文档:https://cli.vuejs.org/zh
配套考点小结
- 整个 Vue 项目只有一个真正的 html 文件:
public/index.html - main.js 入口文件,创建 Vue 实例。
- App.vue 是根组件,所有组件都在它里面汇总。
- 组件放 components 文件夹,导入路径写对,否则报
Module not found找不到模块。
ref 属性总结
作用
ref用来给DOM 元素或者组件实例打标识,之后通过this.$refs.xxx获取对应的对象。
两种使用场景
1. ref 写在普通 HTML 标签上
<h1 ref="title"></h1>this.$refs.title→得到原生 DOM 元素- 可以操作 DOM:获取 innerText、修改样式等
2. ref 写在组件标签上
<School ref="sch"/>this.$refs.sch→得到这个组件的实例对象(VueComponent)- 可以拿到组件内部的 data、调用组件的 methods 方法
你的代码示例拆解
showDom(){ alert(this.$refs.title); // <h1>...</h1> 原生DOM alert(this.$refs.sch); // School组件实例对象 }⚠️注意点
$refs只在挂载完成之后才有值,created()阶段拿不到,mounted()、点击事件里可以正常获取。$refs是对象,key 就是 ref 写的名字。- ref不是响应式,不要拿来做数据绑定,只用于获取 DOM / 组件实例。
- 如果 v-for 循环里用 ref,
this.$refs.xxx拿到的是数组。
记忆口诀
ref 打标记,$refs 来获取; 普通标签得 DOM,组件标签得实例。
props 笔记整理(考试背诵版)以props为主
props 配置项
- 功能:让子组件接收父组件传递过来的数据。
- 父组件传递数据
<!-- 传字符串 --> <Demo name="张三"/> <!-- 传数字、布尔、对象等,必须加 v-bind / : --> <Demo :age="18" :info="{a:1}"/>- 子组件接收数据,三种写法
① 简单接收(只接收,不做校验)
props: ['name','age','sex']② 接收 + 类型限制
props:{ name: String, age: Number, sex: String }③ 接收 + 类型 + 是否必填 + 默认值(最重要)
props:{ name:{ type: String, // 限制数据类型 required: true, // 是否为必填项 true必填 default: '老王' // 默认值,父不传就用这个 } }注意:
required和default不要同时写;必填就不要给默认值。
⚠️重要备注(高频考点)
props 是只读!Vue 底层监视 props,子组件直接修改 props,控制台抛出警告,数据不会生效。
如果业务需要修改这份数据两种方案
- 方案一:复制到 data 副本(仅子组件内部使用,父组件数据不变)
data(){ return { myAge: this.age // 把props的age拷贝到data,之后修改myAge } }局限:仅初始化拷贝一次;后续父组件更新 props,data 副本不会自动同步。
- 方案二:$emit 触发自定义事件(标准单向数据流,修改父源数据)子组件:
this.$emit('change-age', 20)父组件监听事件修改自己的数据:
<StudentInfo :age="a" @change-age="a=$event"/>易错点总结
- 传递非字符串,一定要加
:,否则会当成字符串。
<Demo age=18 /> <!-- ❌age值是字符串"18" --> <Demo :age=18 /> <!-- ✅age值是数字18 -->default如果是对象 / 数组,必须写成函数返回,不能直接写对象字面量。
//对象类型默认值写法 info:{ type:Object, default(){ return {} } }- props 优先级高于 data,template 可以直接使用 props 里的变量。
背诵短句
props 父传子,本身是只读; 简单数组接收,对象可以限制类型必填默认; 要改分两种,拷贝 data 改副本,或者 emit 通知父改源。
mixin 混入 笔记整理
1. 功能
把多个组件重复共用的配置(data、methods、生命周期、computed 等)提取出来,做成混入对象,复用代码,不用每个组件重复写。
2. 使用方式
① 定义混入对象
新建mixin.js
export const myMixin = { data(){ return { msg:'混入的数据' } }, methods:{ show(){ alert('混入的方法') } }, mounted(){ console.log("混入的mounted") } }② 局部混入(组件内使用,只对当前组件生效)
<script> import {myMixin} from './mixin' export default { name:'Demo', mixins:[myMixin], // 数组,可以写多个混入对象 // 组件自己配置 } </script>③ 全局混入(main.js,所有组件全部生效)
import Vue from 'vue' import {myMixin} from './mixin' Vue.mixin(myMixin) //之后所有组件自动合并这个混入配置3. ✨合并规则(考试重点)
- 生命周期钩子:全部执行,混入先执行,组件自身后执行
混入 mounted → 组件 mounted,两个都跑,不会覆盖。
- data、methods、computed:组件自身优先,会覆盖混入的同名属性
- 如果混入和组件有同名 data/methods:组件自己的优先级更高,覆盖混入。
示例: 混入:msg:'混入的数据'组件 data:msg:'组件自己的数据'最终页面显示:组件自己的数据
4. 特点 & 坑点
- 局部混入:只作用写了
mixins:[xxx]的组件。 - 全局混入:所有组件,包括第三方组件都会生效,谨慎使用。
- 可以传入多个混入对象
mixins:[m1,m2,m3];多个混入之间,后面的会覆盖前面同名 data/methods。 - mixin 里面的 data 不是响应式共享,每个组件使用混入都会生成独立副本,互不干扰。
简短背诵版
mixin 抽公共配置,分为局部混入、全局混入; 生命周期钩子全都执行,混入先跑; data/methods 同名,组件自身优先级更高,覆盖混入。
对比
表格
| 局部混入 | 全局混入 | |
|---|---|---|
| 写法 | 组件内mixins:[mixinObj] | Vue.mixin(mixinObj)main.js 写 |
| 生效范围 | 当前组件 | 项目全部组件 |
小考点:
mixins是数组,可以引入多个混入对象。
Vue2 插件 (plugin) 笔记
1. 功能
用来增强 Vue,可以给 Vue 添加全局功能:全局过滤器、全局指令、全局混入、实例方法 / 属性。
2. 本质
插件是带有install方法的对象。
const myPlugin = { install(Vue, options){ // 插件逻辑 } }- 第一个参数:
Vue构造函数 - 第二个参数
options:使用者调用Vue.use(插件, 传入的配置)传过来的数据
3. 自定义插件完整写法
// my-plugin.js export default { install(Vue, options) { //1.全局过滤器 Vue.filter('myFilter', function(val){ return val.toUpperCase() }) //2.全局自定义指令 Vue.directive('focus',{ inserted(el){ el.focus() } }) //3.全局混入 Vue.mixin({ mounted(){ console.log('插件全局mixin') } }) //4.给Vue原型添加实例方法/属性,所有组件可以 this.$xxx 调用 Vue.prototype.$hello = function(){ alert('hello插件') } Vue.prototype.$test = 100 } }4. 使用插件:Vue.use(插件, [配置参数]) 配置的参数可传递给插件
在main.js引入并使用
import Vue from 'vue' import myPlugin from './my-plugin' // 使用插件,第二个参数传给install的options Vue.use(myPlugin, { name:'测试配置' }) new Vue({ el:'#app' })⚠️重要:
Vue.use()要写在new Vue()之前。
install 内部四种增强能力汇总
Vue.filter()— 注册全局过滤器Vue.directive()— 注册全局指令Vue.mixin()— 设置全局混入Vue.prototype.$xxx— 添加实例方法 / 属性,组件内部this.$xxx访问
高频考点 & 坑
- 插件必须有
install方法;没有 install 的对象不能作为插件。 Vue.use(插件, 配置对象),配置对象会作为 install 的第二个参数。Vue.use()会自动缓存,同一个插件多次 use 只会执行一次 install。- 原型上挂载的方法,所有组件实例通过
this.$xxx访问;不要和内置属性重名。
背诵短句
插件增强 Vue,本质带 install 的对象; install 第一个参数是 Vue 构造函数,第二个是配置 options; use 来启用,可以注册过滤器、指令、全局混入、原型方法; use 写在 new Vue 之前。
对比区分
表格
| mixin 混入 | plugin 插件 | |
|---|---|---|
| 作用 | 复用组件配置 (data/methods/ 生命周期) | 全局增强 Vue 能力 |
| 局部使用 | mixins:[xxx] | 无局部,只能 Vue.use 全局注册 |
| 全局 | Vue.mixin() | Vue.use(plugin) |
插件内部可以调用
Vue.mixin()实现全局混入。
scoped 样式笔记
1. 作用
<style scoped>:让样式只作用于当前组件,防止不同组件之间样式冲突(样式污染)。
2. 写法
<style scoped> .box { color: red; } </style>3. 底层原理
Vue 给当前组件所有 DOM 标签自动生成唯一data‑v‑xxx属性,同时编译 css,给每一条选择器加上这个属性筛选。
模板:
<div class="box" data‑v‑7ba2ac34></div>编译后 css:
.box[data‑v‑7ba2ac34] { color:red; }样式只会匹配带这个
data‑v‑xxx属性的元素,别的组件没有该属性,不会被影响。
4. 坑点(考试常考)
- scoped 样式,无法修改子组件内部的 DOM 元素子组件模板的标签会带自己的
data‑v属性,父组件 scoped 的选择器带父的data‑v,匹配不到子组件内部元素。 - 深度选择器
>>>//deep/,用来穿透 scoped,修改子组件内部样式
/* vue2 */ .box >>> .child-inner{ color:blue; } /* 或者 */ .box /deep/ .child-inner{ color:blue; }- 去掉
scoped,样式变成全局样式,整个项目所有组件都会生效,容易类名冲突。 - 一个组件可以写多个 style 标签,可以一部分 scoped 局部,一部分全局:
<style scoped> /* 局部 */ </style> <style> /* 全局,无scoped */ </style>背诵短句
scoped 实现组件样式局部生效,防止样式冲突; 原理添加唯一
data‑v‑xxx属性; scoped 不能直接改子组件内部样式,需要深度选择器/deep/穿透。
对比
表格
| 写法 | 生效范围 |
|---|---|
<style scoped> | 仅当前组件局部 |
<style> | 全局,所有组件 |
考点问答题:为什么加 scoped 就不会样式冲突? 答:Vue 编译时给组件 DOM 添加唯一
data‑v‑xxx属性,css 选择器自动附加该属性,样式只匹配本组件 DOM。
CSS 和 Less 的区别
CSS:原生层叠样式表,浏览器直接识别。Less:CSS 预处理器,不是原生 CSS,浏览器不认识,必须编译转换成普通 CSS 才能运行。
对比表
表格
| 对比 | 原生 CSS | Less(预处理器) |
|---|---|---|
| 本质 | 浏览器可直接解析的样式语言 | CSS 的增强工具,最终编译输出 CSS |
| 变量 | ❌没有变量,写死颜色、尺寸 | ✅支持变量@color:red;,统一管理 |
| 嵌套 | ❌只能平铺写选择器 | ✅支持嵌套,贴合 HTML 结构,代码简洁 |
| 运算 | ❌不能计算 | ✅支持加减乘除width:100px + 20px |
| 样式复用 | ❌复制粘贴重复代码 | ✅Less‑mixin,复用一套样式片段 |
| 导入 | @import "a.css" | @import "a.less",可以引入 less 文件 |
| 浏览器 | 直接运行 | 不能直接运行,需要编译成 CSS |
示例对比
✅原生 CSS
.box { color:#f00; } .box .item { font‑size:16px; } .box:hover { background:#eee; }✅Less 写法
@red: #f00; .box{ color:@red; .item{ font‑size:16px; } &:hover{ background:#eee; } }打包的时候,less 会被编译转换成上面那份普通 CSS 交给浏览器。
在 Vue 中使用
<!-- 普通css --> <style scoped> .box{} </style> <!-- less,必须加 lang="less" --> <style lang="less" scoped> @c:red; .box{ color:@c; } </style>安装依赖:
npm i less less‑loader@7 -D
Less 新增四大能力(CSS 没有)
- 变量
@xxx,统一管理颜色、尺寸 - 嵌套,减少重复写父选择器;
&代表自身 - 运算,尺寸、颜色可以计算
- mixin 样式混入,复用一堆 css 属性
关键点
- Less不能替代 CSS,只是写 CSS 的工具;最终产物依旧是 CSS。
- 浏览器只认识 CSS,不识别
@变量、嵌套这些 less 语法。 - 写 less 只是开发阶段方便,打包自动翻译成 css。
记忆口诀
CSS 浏览器直接跑; Less 是预处理器,写得爽,编译变 CSS; 新增变量、嵌套、运算、样式 mixin。
注意区分两个 mixin:
- Vue 的 mixin:JS 逻辑复用(script)
- Less 的 mixin:样式代码复用(style)