当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
组件是可复用的Vue实例
, 说白了就是一组可以重复使用的模板
, 跟JSTL的自定义标签、Thymeleal的th:fragment
等框架有着异曲同工之妙,通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头
、侧边栏
、内容区
等组件,每个组件又包含了其它的像导航链接
、博文之类的组件。
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">
<script type="text/javascript" src="../js/vue.js">script>
<title>Documenttitle>
head>
<body>
<div id="root">
<school>school>
<student>student>
div>
<script type="text/javascript">
//1创建组件
const school=Vue.extend({
template: `
{{schoolName}}
{{address}}
`,
data() {
return {
schoolName: '一中',
address: '柳岸'
}
},
})
//1创建组件的快捷方式
const student={
template: `
{{studentName}}
{{age}}
`,
data() {
return {
studentName: 'lsc',
age: 22
}
},
}
//2全局注册组件
Vue.component('student',student)
new Vue({
el: '#root',
data: {
msg: 'hello compentments'
},
//2局部注册组件
components: {
school:school //这种可以简写为 school
}
})
script>
body>
html>
总结:
Vue中使用组件的三大步骤:
定义组件(创建组件)——Vue.extend
注册组件——局部和全局
使用组件(写组件标签)
如何定义一个组件?
使用Vue.extend(options)创建
,其中options和new Vue(options)时传入的options几乎一样,但也有点区别:
el不要写,为什么?
data必须写成函数,为什么?
如何注册组件?
全局注册
Vue.component('my-component-name', { // ... 选项 ... })
这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (
new Vue
) 的模板中Vue.component('component-a', { /* ... */ }) Vue.component('component-b', { /* ... */ }) Vue.component('component-c', { /* ... */ }) new Vue({ el: '#app' }) <div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div>
在所有子组件中也是如此,也就是说这三个组件
在各自内部也都可以相互使用。
局部注册
- 全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }
然后在
components
选项中定义你想要使用的组件:new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
对于
components
对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。注意局部注册的组件在其子组件中不可用。例如,如果你希望
ComponentA
在ComponentB
中可用,则你需要这样写:var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }
如果你通过 Babel 和 webpack 使用 ES2015 模块
import ComponentA from './ComponentA.vue' export default { components: { ComponentA }, // ... }
注意在 ES2015+ 中,在对象中放一个类似
ComponentA
的变量名其实是ComponentA: ComponentA
的缩写,即这个变量名同时是用在模板中的自定义元素的名称
- 包含了这个组件选项的变量名
如何使用
编写组件标签:
组件注意事项
{{msg}}
总结:
关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
组件名大小写
定义组件名的方式有两种:
使用 kebab-case
Vue.component('my-component-name', { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如
。
使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说
和
都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
备注:
组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行
可以使用name配置项指定组件在开发者工具中呈现的名字
关于组件标签:
第一种写法:
第二种写法:
备注:不使用脚手架时,
会导致后续组件不能渲染
一个简写方式:const school = Vue.extend({options})可简写为:const school = {options}
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>组件的嵌套title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">
div>
body>
<script type="text/javascript">
Vue.config.productionTip = false
//定义student组件
const student = Vue.extend({
template:`
学生名称:{{name}}
学生年龄:{{age}}
`,
data(){
return {
name:'JOJO',
age:20
}
}
})
//定义school组件
const school = Vue.extend({
template:`
学校名称:{{name}}
学校地址:{{address}}
`,
components:{
student
},
data(){
return {
name:'尚硅谷',
address:'北京'
}
}
})
//定义hello组件
const hello = Vue.extend({
template:`
{{msg}}
`,
data(){
return {
msg:"欢迎学习尚硅谷Vue教程!"
}
}
})
//定义app组件
const app = Vue.extend({
template:`
`,
components:{
school,
hello
}
})
//创建vm
new Vue({
template:`
`,
el:'#root',
components:{
app
}
})
script>
html>
效果:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script language="JavaScript" src="static/script/vue.js">script>
head>
<body>
<div id="app">
<myfirstcomponent>myfirstcomponent>
div>
<script language="JavaScript">
// 定义一个Vue组件
Vue.component("myfirstcomponent", {
template: ' hello,我是一个组件 '
});
var vm=new Vue({
el: "#app",
data: {
}
});
script>
body>
html>
const school = Vue.extend({
name:'atguigu',
template:`
学校名称:{{name}}
学校地址:{{address}}
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
}
})
关于VueComponent:
school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
我们只需要写
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!
关于this指向:
组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是VueComponent实例对象
new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是Vue实例对象
VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)
Vue的实例对象,以后简称vm
只有在本笔记中VueComponent的实例对象才简称为vc
一个重要的内置关系
一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法
School.vue
学校名称:{{name}}
学校地址:{{address}}
Student.vue
学生姓名:{{name}}
学生年龄:{{age}}
App.vue
main.js
import App from './App.vue'
new Vue({
template:` `,
el:'#root',
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>单文件组件练习title>
head>
<body>
<div id="root">div>
<script src="../../js/vue.js">script>
<script src="./main.js">script>
body>
html>
Node.js
:http://nodejs.cn/download/安装就是无脑的下一步就好,安装在自己的环境目录下
Git
:https://git-scm.com/doenloads
具体步骤
-g 就是全局安装
npm install cnpm -g
或使用如下语句解决npm速度慢的问题
npm install --registry=https://registry.npm.taobao.org
- 但是能不用cnpm就不用,npm比较好,因为cnpm可能打包的时候会出现问题
呈现效果——访问http://localhost:8080/
webpack 的主要编程语言是 js,想要在你的机器上跑 js 脚本,就需要一个解释执行 js 的环境,nodejs 出现之前我们只能在浏览器环境下解释执行 js,而 nodejs 基于 V8 引擎进行了一系列封装,使得我们可以在非浏览器环境下解释执行 js。
nodejs 可以支持本地文件的操作,也就是文件读写,webpack 基于此提供了编译前端代码的功能,使得我们可以在前端代码开发的过程中选择我们喜欢的框架和预编译语言。
nodejs 也可以支持搭建网络服务,webpack 基于此提供了开发环境的搭建,使得我们可以轻松的在本地构建服务调试我们的前端代码。
说 webpack 依赖 nodejs 其实并不太准确,应该说 webpack 是用 nodejs 执行的,js 不是机器语言,解释执行 js 必须要一个特定的环境,nodejs 提供了这个环境。
前端资源模块化管理和打包工具
, 它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分离,等到实际需要时再异步加载。通过loader转换, 任何形式的资源都可以当做模块, 比如Commons JS、AMD、ES 6、CSS、JSON、Coffee Script、LESS等;伴随着移动互联网的大潮, 当今越来越多的网站已经从网页模式进化到了WebApp模式。它们运行在现代浏览器里, 使用HTML 5、CSS 3、ES 6等新的技术来开发丰富的功能, 网页已经不仅仅是完成浏览器的基本需求; WebApp通常是一个SPA(单页面应用) , 每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的JS代码,这给前端的开发流程和资源组织带来了巨大挑战。
前端开发和其他开发工作的主要区别,首先是前端基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器的,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。
Script标签
<script src = "module1.js"></script>
<script src = "module2.js"></script>
<script src = "module3.js"></script>
这是最原始的JavaScript文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在window对象中,不同模块的调用都是一个作用域。
这种原始的加载方式暴露了一些显而易见的弊端:
的书写顺序进行加载CommonsJS
服务器端的NodeJS遵循CommonsJS规范,该规范核心思想是允许模块通过require方法来同步加载所需依赖的其它模块,然后通过exports或module.exports来导出需要暴露的接口。
require("module");
require("../module.js");
export.doStuff = function(){};
module.exports = someValue;
服务器端模块便于重用
NPM中已经有超过45万个可以使用的模块包
简单易用
缺点:
实现:
服务端的NodeJS
Browserify,浏览器端的CommonsJS实现,可以使用NPM的模块,但是编译打包后的文件体积较大modules-webmake,类似Browserify,但不如Browserify灵活
wreq,Browserify的前身
AMD
Asynchronous Module Definition规范其实主要一个主要接口define(id?,dependencies?,factory);它要在声明模块的时候指定所有的依赖dependencies,并且还要当做形参传到factory中,对于依赖的模块提前执行。
define("module",["dep1","dep2"],functian(d1,d2){
return someExportedValue;
});
require(["module","../file.js"],function(module,file){});
优点
适合在浏览器环境中异步加载模块
可以并行加载多个模块
缺点
提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不畅
不符合通用的模块化思维方式,是一种妥协的实现
实现
RequireJS
curl
CMD
Commons Module Definition规范和AMD很相似,尽保持简单,并与CommonsJS和NodeJS的Modules规范保持了很大的兼容性。
define(function(require,exports,module){
var $=require("jquery");
var Spinning = require("./spinning");
exports.doSomething = ...;
module.exports=...;
});
优点:
依赖就近,延迟执行
可以很容易在NodeJS中运行
缺点
实现
Sea.js
coolie
ES6模块
EcmaScript 6标准增加了JavaScript语言层面的模块体系定义。ES 6模块的设计思想, 是尽量静态化, 使编译时就能确定模块的依赖关系, 以及输入和输出的变量。Commons JS和AMD模块,都只能在运行时确定这些东西。
import "jquery"
export function doStuff(){}
module "localModule"{}
优点
容易进行静态分析
面向未来的Ecma Script标准
缺点
实现
Babel
大家期望的模块
初始脚手架文件结构:
.文件目录
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件
src/components/School.vue
学校名称:{{name}}
学校地址:{{address}}
src/components/Student.vue
学生姓名:{{name}}
学生年龄:{{age}}
src/App.vue
src/main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
//将App组件放入容器中
render: h => h(App),
}).$mount('#app')
public/index.html
DOCTYPE html>
<html lang="">
<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">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %>title>
head>
<body>
<div id="app">div>
body>
html>
来引入,但是我们可以进行使用
,是因为脚手架帮我们做好了import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:'#app',
// 简写形式
render: h => h(App),
// 完整形式
// render(createElement){
// return createElement(App)
// }
// 因为只有一个参数所以可以把括号去掉 和一条语句只是return,写成render createElement =>return createElement(App)
})
总结:
关于不同版本的函数:
vue.js 与 vue.runtime.xxx.js的区别:
因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render函数接收到的createElement 函数去指定具体内容
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false /*关闭语法检查*/
})
webpack将配置文件放在了webpack.config.js中,这个文件被隐藏,就是防止我们乱改,对于我们想改Vue的配置,我们可以通过新建一个vue.config.js 文件,将这个文件和我们webpack.config.js进行合并,达到我们修改配置的功能
{{msg}}
总结:
ref属性:
被用来给元素或子组件注册引用信息(id的替代者)
使用方式:
或
获取:this.$refs.xxx
像上面那样用组件没有任何意义,所以我们是需要传递参数到组件
的,此时就需要使用props
属性了!注意:默认规则下props属性里的值不能为大写
;
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
src/App.vue
{{msg}}
src/components/Student.vue
学生姓名:{{name}}
学生性别:{{sex}}
学生年龄:{{myage}}
总结:
props配置项:
功能:让组件接收外部传过来的数据
传递数据:
,在使用组件标签的时候,进行传递数据
接收数据:
第一种方式(只接收):props:[‘name’]
第二种方式(限制数据类型):props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, //类型
required:true, //必要性
default:'JOJO' //默认值
}
}
props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
- 经过测试,我们发现对于组件来说,prop的属性的优先级是大于我们的data里面的属性,所以我们复制可以data() return{ mydata: this.prop}
src/mixin.js
//注册组件并暴露出去
export const mixin = {
methods: {
showName() {
alert(this.name)
}
},
mounted() {
console.log("你好呀~")
}
}
src/components/School.vue
学校姓名:{{name}}
学校地址:{{address}}
src/components/Student.vue
学生姓名:{{name}}
学生性别:{{sex}}
src/App.vue
全局混入:
src/main.js:
import Vue from 'vue'
import App from './App.vue'
import {mixin} from './mixin'
Vue.config.productionTip = false
Vue.mixin(mixin)
new Vue({
el:"#app",
render: h => h(App)
})
总结:
mixin(混入):
使用方式:
第一步定义混入:
const mixin = {
data(){....},
methods:{....}
....
}
第二步使用混入:
全局混入:Vue.mixin(xxx)
局部混入:mixins:[‘xxx’]
备注:
组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先。
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data () {
return {
message: 'goodbye',
bar: 'def'
}
},
created () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
created () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
src/plugin.js:
export default {
install(Vue,x,y,z){
console.log(x,y,z)
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
//定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
src/main.js:
import Vue from 'vue'
import App from './App.vue'
import plugin from './plugin'
Vue.config.productionTip = false
Vue.use(plugin,1,2,3)
new Vue({
el:"#app",
render: h => h(App)
})
src/components/School.vue:
学校姓名:{{name | mySlice}}
学校地址:{{address}}
src/components/Student.vue:
学生姓名:{{name}}
学生性别:{{sex}}
总结:
插件:
功能:用于增强Vue
本质:包含install方法的一个对象
,install的第一个参数是Vue,也就vm实例对象的构造函数,第二个以后的参数是插件使用者传递的数据
定义插件:
plugin.install = function (Vue, options) {
// 1.添加全局过滤器
Vue.filter(....)
// 2.添加全局指令
Vue.directive(....)
// 3. 配置全局混入
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
使用插件:Vue.use(plugin)
src/components/School.vue
学校姓名:{{name}}
学校地址:{{address}}
src/components/Student.vue
学生姓名:{{name}}
学生性别:{{sex}}
src/App.vue
总结:
scoped
样式:
scoped
样式一般不会在App.vue
中使用
后面的改进不会再写style样式
MyHeader.vue
MyList.vue
MyItem.vue
MyFooter.vue
src/App.vue
实现效果
总结:
组件化编码流程:
实现动态组件:考虑好数据的存放位置
,数据是一个组件在用,还是一些组件在用:
实现交互:从绑定事件开始
props适用于:
父组件 ==> 子组件 通信
子组件 ==> 父组件 通信(要求父组件先给子组件一个函数)
使用v-model时要切记:v-model绑定的值不能是props
传过来的值,因为props是不可以修改的
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>localStoragetitle>
head>
<body>
<h2>localStorageh2>
<button onclick="saveDate()">点我保存数据button><br/>
<button onclick="readDate()">点我读数据button><br/>
<button onclick="deleteDate()">点我删除数据button><br/>
<button onclick="deleteAllDate()">点我清空数据button><br/>
<script>
let person = {name:"JOJO",age:20}
function saveDate(){
localStorage.setItem('msg','localStorage')
localStorage.setItem('person',JSON.stringify(person))
}
function readDate(){
console.log(localStorage.getItem('msg'))
const person = localStorage.getItem('person')
console.log(JSON.parse(person))
}
function deleteDate(){
localStorage.removeItem('msg')
localStorage.removeItem('person')
}
function deleteAllDate(){
localStorage.clear()
}
script>
body>
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>sessionStoragetitle>
head>
<body>
<h2>sessionStorageh2>
<button onclick="saveDate()">点我保存数据button><br/>
<button onclick="readDate()">点我读数据button><br/>
<button onclick="deleteDate()">点我删除数据button><br/>
<button onclick="deleteAllDate()">点我清空数据button><br/>
<script>
let person = {name:"JOJO",age:20}
function saveDate(){
sessionStorage.setItem('msg','sessionStorage')
sessionStorage.setItem('person',JSON.stringify(person))
}
function readDate(){
console.log(sessionStorage.getItem('msg'))
const person = sessionStorage.getItem('person')
console.log(JSON.parse(person))
}
function deleteDate(){
sessionStorage.removeItem('msg')
sessionStorage.removeItem('person')
}
function deleteAllDate(){
sessionStorage.clear()
}
script>
body>
html>
总结:
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制
相关API:
xxxStorage.setItem(‘key’, ‘value’):该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxStorage.getItem(‘key’):该方法接受一个键名作为参数,返回键名对应的值
xxxStorage.removeItem(‘key’):该方法接受一个键名作为参数,并把该键名从存储中删除
xxxStorage.clear():该方法会清空存储中的所有数据
备注:
SessionStorage存储的内容会随着浏览器窗口关闭而消失
LocalStorage存储的内容,需要手动清除才会消失
xxxStorage.getItem(xxx)如果 xxx 对应的 value 获取不到,那么getItem()的返回值是null
JSON.parse(null)的结果依然是null
之前我们实现子元素给父元素传递数据,是将父元素的方法传递给我们的子元素,然后子元素通过父元素传递的方法来进行传递数据给父元素
学校名:{{name}}
student.vue
学生姓名:{{name}}
学生性别:{{sex}}
App.vue——也是student,school的父组件
student.vue
学生姓名:{{name}}
学生性别:{{sex}}
App.vue
总结:
组件的自定义事件:
一种组件间通信的方式,适用于:==子组件 > 父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
绑定自定义事件:
第一种方式,在父组件中:
第二种方式,在父组件中
mounted(){
this.$refs.student.$on('myevent',this.getStudentName)
// this.$refs.student.$once('myevent',this.getStudentName)//只调用一次
}
触发自定义事件:this.$emit(‘atguigu’,数据)——在对应触发事件的组件中,也就是子组件
解绑自定义事件:this.$off(‘atguigu’)——在对应绑定的组件中,也就是子组件
组件上也可以绑定原生DOM事件,需要使用native修饰符
<Student @click.native="getStudentName"/>
src/App.vue
MyHeader.vue
MyFooter.vue
在前面通过props可以解决父组件传递数据给子组件,也可以通过自定义事件将子组件数据传递给父组件,但是我们没法完成
全局事件总线是一种可以在任意组件间通信的方式,本质上就是一个对象。它必须满足以下条件:
src/main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
}
})
App.vue
<template>
<div class="app">
<School/>
<Student/>
</div>
</template>
<script>
import Student from './components/Student'
import School from './components/School'
export default {
name:'App',
components:{School,Student}
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男'
}
},
methods: {
sendStudentName(){
this.$bus.$emit('demo',this.name)
}
}
}
</script>
<style scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
methods:{
demo(data) {
console.log('我是School组件,收到了数据:',data)
}
},
mounted() {
this.$bus.$on('demo',this.demo)
},
beforeDestroy() {
this.$bus.$off('demo')
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
总结:
全局事件总线(GlobalEventBus):
安装全局事件总线:
new Vue({
...
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
...
})
使用事件总线:
export default {
methods(){
demo(data){...}
}
...
mounted() {
this.$bus.$on('xxx',this.demo)
}
}
methods: {
sendStudentName(){
this.$bus.$emit('demo',this.name)
}
}
export default {
beforeDestroy() {
this.$bus.$off('demo')
},
}
先分析以下改改造那些
src/main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:"#app",
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
})
App.vue
MyItem.vue
需要我们额外去安装对应的包
npm i pubsub-js
其实用法跟全局总线差不多,我们最好还是用全局总线,因为是vue自带的,但是还是可以了解一下
src/components/School.vue
学校名称:{{name}}
学校地址:{{address}}
学生姓名:{{name}}
学生性别:{{sex}}
总结:
消息订阅与发布(pubsub):
消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信
使用步骤:
安装pubsub:npm i pubsub-js
引入:import pubsub from ‘pubsub-js’
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
export default {
methods(){
demo(data){...}
}
...
mounted() {
this.pid = pubsub.subscribe('xxx',this.demo)
}
}
$nextTick(回调函数)
可以将回调延迟到下次 DOM 更新循环之后执行
App.vue
MyItem.vue
总结:
$nextTick:
this.$nextTick(回调函数)
transition需要触发一个事件才会随着时间改变其CSS属性;
animation在不需要触发任何事件的情况下,也可以显式的随时间变化来改变元素CSS属性,达到一种动画的效果
1)动画不需要事件触发,过渡需要。
2)过渡只有一组(两个:开始-结束)关键帧,动画可以设置多个
App.vue
MyAnimation.vue
你好啊!
MyTransition.vue
你好啊!
MyTransitionGroup.vue
你好啊!
大笨蛋
·
ThirdPartAnimation.vue
你好啊!
大笨蛋
总结:
Vue封装的过度与动画:
写法:
准备好样式
元素进入的样式
v-enter:进入的起点
v-enter-active:进入过程中
v-enter-to:进入的终点
元素离开的样式:
v-leave:离开的起点
v-leave-active:离开过程中
v-leave-to:离开的终点
使用
包裹要过度的元素,并配置name属性:
你好啊!
,且每个元素都要指定key值我们的TodoList中会被删除,添加的是我们的MyItem组件
为什么我们需要插槽呢?——说直接点,就是想定制化组件,什么叫定制化组件?
Category.vue
{{title}}分类
我是一些默认值,当使用者没有传递具体结构时,我会出现
- {{g}}
对于一个组件,可能会出现多个插槽的未知,所以我们在使用的时候需要指定用哪个插槽
{{title}}分类
我是一些默认值,当使用者没有传递具体结构时,我会出现1
我是一些默认值,当使用者没有传递具体结构时,我会出现2
App.vue
中使用,其
会在解析之后消失
是新语法>是旧语法
也就是数据在我们的组件中,而我们的使用这个组件使用者在填充对应的插槽的时候,需要其这个组件中的数据,作用域插槽能够实现这个功能
Category.vue
{{title}}分类
我是一些默认值,当使用者没有传递具体结构时,我会出现1
:games=“games”,将数据放入我们的插槽,然后使用者调用插槽的时候,就可以拿到数据
来使用插槽接收数据App.vue
- {{g}}
- {{g}}
{{g}}
插槽:
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于==父组件 > 子组件
分类:默认插槽、具名插槽、作用域插槽
使用方式:
默认插槽:
父组件中:
html结构1
子组件中:
插槽默认内容...
具名插槽:
父组件中:
html结构1
html结构2
子组件中:
插槽默认内容...
插槽默认内容...
作用域插槽:
具体编码:
父组件中:
- {{g}}
{{g}}
子组件中:
具名和作用域同时使用
旧用法
{{ slotProps.msg }}
新用法
{{ slotProps.user.firstName }}