书接上文
Vue组件及脚手架
- 初始化脚手架
- 说明
- 步骤
- 分析脚手架结构
- render函数
- 修改默认配置
- ref属性
- props配置
- mixin 混入/混合
- 定义混合
- 局部混合
- 全局混合
- 插件
- scoped样式
- 安装less-loader
- 浏览器的本地存储 webStorage
- localStroage 本地存储
- sessionStorage 会话存储
- 组件自定义事件
- 绑定
- 解绑
- 组件绑定原生事件
- 全局事件总线 GlobalEventBus
- 安装
- 绑定事件 接收数据
- 触发事件 传递数据
- 消息订阅与发布 pubsub
- $nextTick
- transition
- 动画效果
- 过渡效果
- transition-group
- 集成第三方动画
初始化脚手架
说明
Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
CLI: command line interface (命令行接口工具) 俗称脚手架
步骤
备注:
- 如果下载缓慢可以配置npm淘宝镜像:
npm config set registry https://registry.npm.taobao.org
- Vue脚手架隐藏了所有webpack相关配置,弱项查看具体的webpack配置,请执行
vue inspect > output.js
-
(仅第一次执行)全局安装@vue/cli, 安装好就可以使用vue命令了
npm install -g @vue/cli
-
切换到要创建项目的目录(可用cd命令,也可以直接文件下shift+右键选择’在此处打开窗口’),然后使用命令创建项目
vue create xxxxx(项目名称)
会出现一个选项
目前先选择vue2
又来一个问题,先选NPM吧
-
启动项目(运行的是官方给写好的HelloWorld代码)
来到项目目录下,执行命令
npm run serve
至此准备完成
如果要停止服务的话,命令行按Ctrl+c(两遍)
分析脚手架结构
整个脚手架内容如下,
.gitignore
git忽略文件配置babel.config.js
babel的配置文件package-lock.json
&package.json
符合npm规范的工程都会有这两个文件
package.json 应用包配置文件,包含名称,版本,依赖等
package-lock.json 包版本控制文件README.md
应用描述文件src/main.js
当执行npm run serve
命令后直接运行的就是这个文件,是整个项目的入口文件
逐行分析↓
import Vue from "vue"; //引入Vue
import App from "./App.vue"; //引入App组件,是所有组件的父组件
Vue.config.productionTip = false; //关闭Vue的生产提示
// 创建Vue实例对象
new Vue({
render: (h) => h(App), //将App组件放入容器中
}).$mount("#app"); //绑定模板
src/assets
存放静态资源,如图片src/components
存放组件文件,除了App.vuepublic/favicon.ico
网站页签图标public/index.html
页面文件
接着分析
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的一个特殊配置,目的是让IE浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标 -->
<!-- <%= BASE_URL %>指的是public所在位置 尽量不要用相对路径 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页标题 -->
<!-- ><%= htmlWebpackPlugin.options.title %> 将package.json中的name取来使用 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持js时 noscript标签中的元素就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
render函数
main.js中有一行render: (h) => h(App),
按我们的想法应该是
template: `<App></App>`,
components: { App },
替换成我们的写法运行
报错了
翻译一下就是"嘿,你这用的vue不是完全版的,要不你就用完全版的,要不就用render方法"
嗯?不是完全版的vue?那先找一下目前用的是哪个vue
因为我们引入写的是文件夹名称,默认会找到index
但其实引入的是哪个js文件呢
来到node_modules/vue/package.json
文件进行查到,搜索module,这个module就是控制当使用es6模块化语法引入的时候的具体引入文件
这个vue.runtime.esm.js
就是我们真正引入的残缺版的vue
而完整版的就是相同目录下的vue.js
那么为什么要用一个残缺版的vue呢?
关于不同版本的Vue:
- vue.js与vue.runtime.js的区别:
(1). vue.js是完整版的Vue,包含:核心功能+模板解析器
(2). vue.runtime.xxx.js是运行版的Vue,只包含核心功能,没有模板解析器- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接受到的createElement函数去指定具体内容
vue.runtime.esm.js
缺少的是模板解析器,用来识别template属性内容
而打包后就已经将vue翻译成js文件了,不需要解析器
而解析器占整个vue体积的1/3,懂的都懂
render: (h) => h(App),
完整写法
render(createElement) {
return createElement(App);
},
修改默认配置
图中红色框出的文件或文件夹名称是不可以更改了, 是vue配置并将配置文件隐藏了
其他的就是可以修改的
官网有配置项文档
修改配置项后需要重启服务
ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式: 打标识
<h1 ref="xxx">...</h1>
或<School ref="xxx">...</School>
获取this.$refs.xxx
<div ref="h1">Hello World!</div>
<div ref="h2">Hello World!</div>
<div ref="h3">Hello World!</div>
console.log(this.$refs);
输出结果
给组件添加ref,就能获取到vc
而原生的document.getElementById获取到的只能是组件模板结构
props配置
让组件接收外部传过来的数据
props是只读的,Vue底层会检测你对props的修改,如果进行了修改,就会发出警告.若业务需求确实需要修改,那么请复制props的内存到data中一份,然后去修改data中的数据
注意:v-model绑定的值不能是props传过来的值,因为props是不可以修改的
App.vue中
<HelloWorld name="Qiu" :age="20"></HelloWorld>
<HelloWorld name="Cai" :age="18"></HelloWorld>
子级组件需要接收这些数据,
有三种方法
props: ["name", "age"],
这样数据类型就是传进来的类型props: { name: String, age: Number, },
这样就会对类型进行限制- 还有一个更加完整的
可以控制类型,是否必输和默认值
props: {
name: {
type: String,
required: true,
},
age: {
type: Number,
default: 999,
},
},
props的优先级高于data
当我们想更改传过来的数据时,是不允许直接修改props内数据
所以可以再定义一个变量存放这个数据,再根据需求进行更改
data(){
return {
myName:this.name
}
}
props:{
name: String,
age: Number,
}
mixin 混入/混合
可以把多个组件共用的配置提取成一个混入对象
定义混合
创建一个js脚本,名称可以随便取,这里就用mixin了,里面可以配置所有组件可配置的属性
// mixin.js
// 分别暴露
export const mix = {
data() {
return {
sex: "girl",
};
},
mounted() {
console.log("你好啊,我是mixin");
},
};
局部混合
组件中引用
import { mix } from "../mixin.js";
export default {
mixins: [mix],
};
直接就可以在组件里使用了
<h2>i am a {{ sex }}</h2>
组件和混入数据冲突时,会优先使用组件数据
声明周期钩子函数冲突时,如两边都定义了mounted,那都会执行, 混合的先执行
模块化小知识
export default xxx
是默认暴露,引用时是import xxx from "xxx"
export const xxx = {}
是分别暴露,引用是是import {xxx} from "xxx"
全局混合
在main.js中引入混合并配置
import { mix } from "./mixin.js"; //引入混合
Vue.mixin(mix); //全局混合
这样所有组件都不需要在引入,直接就可以只用混合的内容
插件
用于增强Vue,添加一些全局使用的功能
本质:包含install方法的一个对象,install的第一个参数是Vue(Vue构造函数),之后的参数是插件使用者传递的数据
定义插件(一般命名为plugins.js)
里面必须写install方法,默认参数是Vue构造函数
// 插件
const obj = {
// 固定钩子函数
install(Vue) {
//install(Vue, author) { //接收多个参数
console.log("我是衩件儿", Vue);
// 这里就可以对Vue进行各种操作
// Vue.directives() 自定义指令
// Vue.mixin() 混入
// Vue.prototype.$myMthod = function{...} //添加实例方法
// 等等...
},
};
// 默认暴露
export default obj;
在main.js引入并使用插件
import plugins from "./plugins"; //引入插件
Vue.use(plugins); //使用插件
// Vue.use(plugins, "Cai"); //使用插件 添加参数 可多个
这样所有组件都可以使用插件里配置的东西了
相当于外挂
scoped样式
作用是让样式在局部生效,防止冲突
<style scoped>
所有组件写的样式最终都是会汇总到一起的,这就导致了相同类名的样式会互相影响
后引用的组件会覆盖前面引用的组件的样式
这时可以给style标签加上scoped标记,就可以让这个样式只服务于当前组件
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
怎么做到的?
其实就是编译时会给每一个容器添加一个随机生成的属性
在通过属性选择器来绑定样式
安装less-loader
如果想用less写样式,会发现报错说需要安装less-loader
而当我们用npm install less-loader
会报错,是因为版本兼容的问题
可以看到我们的脚手架用的webpack版本是4.47,而用npm view webpack versions
命令可以查看最新版本是5.88
而less-laoder的最新版本适用于webpack5.x,所以直接安装最新版本就会有问题,所以要选择低一些的版本,推荐的是7.x
使用命令npm i less-loader@7
安装完成
浏览器的本地存储 webStorage
不必借助Vue
- 存储内容大小一般支持5MB左右(不同浏览器不同)
localStroage 本地存储
本地存储的数据关闭浏览器后不会丢失
只有当用户执行remove或clear 或者 进行清缓存操作才会失去数据
存储的键值对都必须是string类型,如不是会自动转化
<h2>localStorage</h2>
<button onClick="saveData()">点我保存数据</button>
<button onClick="readData()">点我读取数据</button>
<button onClick="deleData()">点我删除数据</button>
<button onClick="deleAllData()">点我清空数据</button>
<script type="text/javascript">
function saveData(){
let obj = {name:'cai',age:'18'}
// 存储数据
localStorage.setItem("msg",JSON.stringify(obj))
}
function readData(){
// 读取数据
// 读取不到数据返回null JSON.parse(null)还是null
let obj = localStorage.getItem("msg")
console.log(JSON.parse(obj));
}
function deleData(){
// 删除数据
localStorage.removeItem("msg")
}
function deleAllData(){
// 清空数据
localStorage.clear()
}
</script>
sessionStorage 会话存储
关闭浏览器窗口后数据就会被清理
和localStorage的api完全一样就是把localStorage改为sessionStorage
// sessionStorage
function saveData(){
let obj = {name:'cai',age:'18'}
// 存储数据
sessionStorage.setItem("msg",JSON.stringify(obj))
}
function readData(){
// 读取数据
// 读取不到数据返回null JSON.parse(null)还是null
let obj = sessionStorage.getItem("msg")
console.log(JSON.parse(obj));
}
function deleData(){
// 删除数据
sessionStorage.removeItem("msg")
}
function deleAllData(){
// 清空数据
sessionStorage.clear()
}
组件自定义事件
一种组件间的通信方式,适用于:子组件 ===> 父组件
绑定
子组件给父组件传递数据之前学的是通过props父组件传给子组件一个函数,子组件通过调用函数并传参给父组件传递数据
还有一种就是组件自定义事件
父组件绑定自定义事件
<HelloWorld
v-on:getStudentName="showStudentName"
name="Qiu"
:age="20"
></HelloWorld>
<!--简写@getStudentName="showStudentName"-->
另一种绑定自定义事件方法(灵活性强)
mounted() {
// 绑定组件自定义事件
this.$refs.hw.$on("getStudentName", this.showStudentName);
},
父组件实现响应方法
showStudentName(name) {
alert(name);
},
子组件触发自定事件
this.$emit("getStudentName", this.name);
有个一需要注意的点
当我们在父组件绑定自定义事件时这样写
将子组件传来的数据赋值到父组件的数据上
this.$refs.hw.$on("getStudentName", function (name) {
alert(name);
this.studentName = name;
});
这样不对,因为this.studentName
中的this指向的是子组件实例对象,谁触发的函数this指向谁
而当我们把响应方法放到methods中是,vue的规则是普通函数中的this均指向当前实例对象(高优先级)
所以上面改成箭头函数就可以了
解绑
除了组件或上级被销毁时自定义事件会一起被销毁
有时需求可能需要保留组件条件下主动解绑事件
子组件解绑自定义事件
this.$off("getStudentName"); //解绑一个自定义事件
this.$off(["getStudentName"]); //解绑多个自定义事件
this.$off(); //解绑所有自定义事件
组件绑定原生事件
<HelloWorld @click="showStudentName" :name="Tiger"></HelloWorld>
直接这样写,点击组件是不会触发事件的
因为vue人为click是一个自定义事件,但并没有被触发
<HelloWorld @click.native="showStudentName" :name="Tiger"></HelloWorld>
而添加了native修饰符,就会把事件添加给组件的根元素,就会正常触发点击事件
全局事件总线 GlobalEventBus
一种组件间通信的方式,适用于任意组件间通信
安装
这个总线需要满足两个条件
- 能被所有组件获取到
- 能调用自定义事件方法
还记不记得VueComponent.prototype.__proto__ === Vue.prototype
Vue.prototype上的数据,所有vc都可以拿到
而vm可以调用自定义事件方法
// main.js
new Vue({
render: (h) => h(App), //将App组件放入容器中
// render(createElement) {
// return createElement(App);
// },
// template: `<App></App>`,
// components: { App },
beforeCreate() {
Vue.prototype.$bus = this; //安装全局事件总线
},
}).$mount("#app"); //绑定模板
绑定事件 接收数据
记得要解绑,如不解绑即使组件被销毁$bus依然存在,事件就会越来越多
也不能直接this.&bus.%off()解绑,会把所有&bus上的事件都解除
//Student.vue
mounted() {
this.$bus.$on("hello", (name) => {
console.log("这里是Student组件,我收到了数据:", name);
});
},
beforeDestroy() {
this.$bus.$off("hello");
},
触发事件 传递数据
//HelloWorld.vue
this.$bus.$emit("hello");
消息订阅与发布 pubsub
一种组件间通信的方式,适用于任意组件间通信
pubsub (publish subscribe)是一个js第三方库,用于实现订阅发布模式
其实和自定义事件效果是一样的,一般还是用自定义事件
安装pubsub-js
npm i pubsub-js
订阅
//HelloWorld.vue
// 引入
import pubsub from "pubsub-js";
mounted() {
// 订阅消息
this.pubId = pubsub.subscribe("yoyo", function (magName, data) {
console.log("有人发布了yoyo消息,回调执行了", magName, data);
});
},
要记得解除订阅
//HelloWorld.vue
beforeDestroy(){
pubsub.unsubscribe(this.pubId)
}
发布
// Student.vue
pubsub.publish("yoyo", 12);
输出
$nextTick
有一个需求是,显示一段文字,有一个编辑按钮,点击后展示可编辑文本框并获取焦点,文本框失去焦点显示文本
正常写就是
<div class="left">
<input
v-show="isEdit"
ref="inputText"
type="text"
v-model="inputContent"
@blur="blurInput"
/>
<span v-show="!isEdit">{{ inputContent }}</span>
</div>
<button @click="focusInput">编辑</button>
focusInput() {
this.isEdit = true;
this.$refs.inputText.focus();
},
blurInput() {
this.isEdit = false;
},
但效果如下,点击后展示了编辑文本框,但是没有获取焦点,需要再次点击文本框或编辑按钮
这是因为点击编辑按钮后,先执行了this.isEdit = true;
后接着执行this.$refs.inputText.focus();
注意此时并没有重新解析模板,文本框还是隐藏状态,所以此时focus并没有效果
怎么解决呢
就要用到$nextTick,
$nextTick指定的回调会在DOM更新之后再执行
focusInput函数改为
(箭头函数或普通函数中this都是指向vc)
focusInput() {
this.isEdit = true;
this.$nextTick(function () {
console.log(this);
this.$refs.inputText.focus();
});
},
transition
可以看官网文档
作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名
写法:
1.准备好样式
- 元素进入的样式
1)v-enter:进入的起点
2)v-enter-active:进入过程中
3)v-enter-to:进入的终点- 元素离开的样式
1)v-leave:离开的起点
2)v-leave-active:离开过程中
3)v-leave-to:离开的终点
- 使用
<transition>
包裹要过渡的元素,并配置name属性
备注:若有多个元素需要过渡,则需要使用<transition-group>
,且每个元素都要指定key值
动画效果
希望效果如下(不知道为啥录制有灰块)
用<transition></transition>
标签包裹需要展示动画的部分,就会在合适的时候加上对应名称的样式
样式名称是固定的,如果transition没有加name那么就是v-xxx-active,如果加了name属性如<transition name="hello">
那样式名称就要改为hello-xxx-active
<div class="fadeDemo">
<button @click="isShow = !isShow">显示/隐藏</button>
<transition>
<h1 v-show="isShow">Hello World</h1>
</transition>
</div>
样式
/* 渐入动画 */
@keyframes fade {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
/* 展示后激活样式 */
.v-enter-active {
animation: fade 1s;
}
/* 隐藏前激活样式 */
.v-leave-active {
animation: fade 1s reverse;
}
过渡效果
希望效果同上
<div class="transitionDemo">
<h2>过渡效果</h2>
<button @click="isShow2 = !isShow2">显示/隐藏</button>
<transition name="trans">
<h1 v-show="isShow2" class="trans-h1">Hello World</h1>
</transition>
</div>
.trans-h1 {
/* 需要配置时间 */
transition: 0.5s;
}
/* 过渡起点 */
.trans-enter {
transform: translateX(-100%);
}
/* 过渡终点 */
.trans-enter-to {
transform: translateX(0);
}
.trans-leave {
transform: translateX(0);
}
.trans-leave-to {
transform: translateX(-100%);
}
其实也可以简写,将进入的终点和离开的起点整合,进入的起点和离开的重点整合
.trans-enter,
.trans-leave-to {
transform: translateX(-100%);
}
.trans-enter-to,
.trans-leave {
transform: translateX(0);
}
transition-group
就是可以包裹多个元素,但元素上必须有key
文档
集成第三方动画
一个第三方动画库 Animate.css
怎么用呢↓
安装: npm install animate.css --save
引入: import 'animate.css';
使用: 在网站右侧找想要的动画效果,配置在代码里
<transition
name="animate__animated animate__bounce"
enter-active-class="animate__backInLeft"
leave-active-class="animate__backOutRight"
>
<h1 v-show="isShow3" class="trans-h1">Hello World</h1>
</transition>
下一篇:Vue中的ajax