Vue组件化编程
为什么要用组件?
传统方式编写应用

组件方式编写应用

模块:
- 向外提供特定功能的 js 程序, 一般就是一个 js 文件
- 作用: 复用 js, 简化 js 的编写, 提高 js 运行效率
- 当应用中的js 都以模块来编写的, 那这个应用就是一个模块化的应用。
组件:实现应用中局部功能代码和资源的集合
- 用来实现局部(特定)功能效果的代码集合(html/css/js/image…..)
- 复用编码, 简化项目编码, 提高运行效率
- 当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用,。
组件的编写
非单文件组件
一个文件中包含n个组件
1 2 3 4 5 6 7 8 9 10 11
| <div id="root"> <hello></hello> <hr> <h1>{{msg}}</h1> <hr> <school></school> <hr> <student></student> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| const school = Vue.extend({ template:` <div class="demo"> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> `, data(){ return { schoolName:'尚硅谷', address:'北京昌平' } }, methods: { showName(){ alert(this.schoolName) } }, })
const student = Vue.extend({ template:` <div> <h2>学生姓名:{{studentName}}</h2> <h2>学生年龄:{{age}}</h2> </div> `, data(){ return { studentName:'张三', age:18 } } })
new Vue({ el:'#root', data:{ msg:'你好啊!' }, components:{ school, student } })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const hello = Vue.extend({ template:` <div> <h2>你好啊!{{name}}</h2> </div> `, data(){ return { name:'Tom' } } })
Vue.component('hello',hello)
|
使用组件:定义组件、注册组件、使用组件。
注意点:
- 组件名:
- 一个单词组成
- 第一种写法(首字母小写):school
- 第二种写法(首字母大写):School
- 多个单词组成
- 第一种写法(kebab-case命名):my-school
- 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
- 备注
- 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
- 可以使用name配置项指定组件在开发者工具中呈现的名字。
- 关于组件标签
- 第一种写法:
<school></school>
- 第二种写法:
<school/>
- 备注:不用使用脚手架时,
<school/>
会导致后续组件不能渲染。
const school = Vue.extend(options)
可简写为:const school = options
组件的嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
const student = Vue.extend({ name:'student', template:` <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> `, data(){ return { name:'尚硅谷', age:18 } } })
const school = Vue.extend({ name:'school', template:` <div> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <student></student> </div> `, data(){ return { name:'尚硅谷', address:'北京' } }, components:{ student } })
const hello = Vue.extend({ template:`<h1>{{msg}}</h1>`, data(){ return { msg:'欢迎来到尚硅谷学习!' } } })
const app = Vue.extend({ template:` <div> <hello></hello> <school></school> </div> `, components:{ school, hello } })
new Vue({ template:'<app></app>', el:'#root', components:{app} })
|
VueComponent(重要)
- school组件本质是一个名为
VueComponent
的构造函数,且不是程序员定义的,是Vue.extend生成的。
- 我们只需要写
<school/>
或<school></school>
,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。($children)vc不能写el,只有根实例可以写
一个重要的内置关系:
- 一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
单文件组件:
一个文件中只包含1个组件
代码高亮vetur,单文件组件的结构:
1 2 3 4 5 6 7 8 9 10 11 12
| //School.vue <template>
</template>
<script> </script>
<style>
</style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> <div class="demo"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> </template>
<script> export default { name:'School', data(){ return { name:'尚硅谷', address:'北京昌平' } }, methods: { showName(){ alert(this.name) } }, } </script>
<style> .demo{ background-color: orange; } </style>
|
汇总所有组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <div> <School></School> <Student></Student> </div> </template>
<script> import School from './School.vue' import Student from './Student.vue'
export default { name:'App', components:{ School, Student } } </script>
|
main.js
1 2 3 4 5 6 7
| import App from './App.vue'
new Vue({ el:'#root', template:`<App></App>`, components:{App}, })
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>练习一下单文件组件的语法</title> </head> <body> <div id="root"></div> </body> </html>
|
.vue不能直接在浏览器运行,需要脚手架。
Vue脚手架
初始化脚手架
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。vue cli(command line interface)
官网: https://cli.vuejs.org/zh/
步骤:
- 全局安装@vue/cli
- 切换到你要创建项目的目录,然后使用命令创建项目
- 启动项目
如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry
https://registry.npm.taobao.org
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,
请执行:vue inspect > output.js
模板项目的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ├── 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:包版本控制文件
|
分析
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({ el:'#app', render: h => h(App),
})
|
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <!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"> <link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css"> <title><%= htmlWebpackPlugin.options.title %> </title> </head> <body> <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> </body> </html>
|
render:
1 2 3 4 5 6 7 8 9
| new Vue({ el:'#app', render: h => h(App),
})
|
关于不同版本的Vue:
- vue.js与vue.runtime.xxx.js的区别:
- vue.js是完整版的Vue,包含:核心功能+模板解析器。
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要**使用
render函数接收到的createElement函数去指定具体内容**。
打包时已经有了模板引擎,不需要在引入,这里需要一个第3方的。
修改默认配置
创建vue.config.js,调整脚手架的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| module.exports = { pages: { index: { entry: 'src/main.js', }, }, lintOnSave:false,
devServer: { proxy: { '/test': { target: 'http://localhost:5000', pathRewrite:{'^/atguigu':''}, }, '/demo': { target: 'http://localhost:5001', pathRewrite:{'^/demo':''}, } } } }
|
一些常用属性
ref属性
作用:用于给节点打标识
应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)。
使用:
- 打标识:
<h1 ref="xxx">.....</h1>
或 <School ref="xxx"></School>
- 获取:
this.$refs.xxx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <template> <div> <h1 v-text="msg" ref="title"></h1> <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button> <School ref="sch"/> </div> </template>
<script> import School from './components/School'
export default { name:'App', components:{School}, data() { return { msg:'欢迎学习Vue!' } }, methods: { showDOM(){ console.log(this.$refs.title) console.log(this.$refs.btn) console.log(this.$refs.sch) } }, } </script>
|
props配置项
作用:用于父组件给子组件传递数据
读取方式一: 只指定名称 props: ['name', 'age', 'setName']
读取方式二: 指定名称和类型
1 2 3 4 5
| props: { name: String, age: Number, setNmae: Function }
|
读取方式三: 指定名称/类型/必要性/默认值
1
| props: {name: {type: String, required: true, default:xxx}, }
|
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
app.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div> <Student name="李四" sex="女" :age="18"/> </div> </template>
<script> import Student from './components/Student'
export default { name:'App', components:{Student} } </script>
|
Student.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <template> <div> <h1>{{msg}}</h1> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <h2>学生年龄:{{myAge+1}}</h2> <button @click="updateAge">尝试修改收到的年龄</button> </div> </template>
<script> export default { name:'Student', data() { console.log(this) return { msg:'', myAge:this.age } }, methods: { updateAge(){ this.myAge++ } },
props:{ name:{ type:String, required:true, }, age:{ type:Number, default:99 }, sex:{ type:String, required:true } } } </script>
|
mixin混入
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步定义混合:
1 2 3 4 5
| { data(){....}, methods:{....} .... }
|
第二步使用混入:
全局混入:Vue.mixin(xxx)
局部混入:mixins:['xxx']
mixin.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export const hunhe = { methods: { showName(){ alert(this.name) } }, mounted() { console.log('你好啊!') }, } export const hunhe2 = { data() { return { x:100, y:200 } }, }
|
Student.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div> <h2 @click="showName">学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> </div> </template>
<script>
export default { name:'Student', data() { return { name:'张三', sex:'男' } }, } </script>
|
插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
定义插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 对象.install = function (Vue, options) { Vue.filter(....)
Vue.directive(....)
Vue.mixin(....)
Vue.prototype.$myMethod = function () {...} Vue.prototype.$myProperty = xxxx }
|
使用插件:Vue.use()
scoped样式
作用:让样式在局部生效,防止冲突。
写法:<style scoped>
组件化的流程
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
props适用于:
(1).父组件 ==> 子组件 通信(逐层传递)
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
uuid的库
存储webStorage
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
相关API:
xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。
xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
xxxxxStorage.clear()
该方法会清空存储中的所有数据。
备注:
- SessionStorage存储的内容会随着浏览器窗口关闭而消失。
- LocalStorage存储的内容,需要手动清除才会消失。
xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是null。
JSON.parse(null)
的结果依然是null。
localStorage.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>localStorage</title> </head> <body> <h2>localStorage</h2> <button onclick="saveData()">点我保存一个数据</button> <button onclick="readData()">点我读取一个数据</button> <button onclick="deleteData()">点我删除一个数据</button> <button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript" > let p = {name:'张三',age:18}
function saveData(){ localStorage.setItem('msg','hello!!!') localStorage.setItem('msg2',666) localStorage.setItem('person',JSON.stringify(p)) } function readData(){ console.log(localStorage.getItem('msg')) console.log(localStorage.getItem('msg2'))
const result = localStorage.getItem('person') console.log(JSON.parse(result))
} function deleteData(){ localStorage.removeItem('msg2') } function deleteAllData(){ localStorage.clear() } </script> </body> </html>
|
sessionStorage.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>sessionStorage</title> </head> <body> <h2>sessionStorage</h2> <button onclick="saveData()">点我保存一个数据</button> <button onclick="readData()">点我读取一个数据</button> <button onclick="deleteData()">点我删除一个数据</button> <button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript" > let p = {name:'张三',age:18}
function saveData(){ sessionStorage.setItem('msg','hello!!!') sessionStorage.setItem('msg2',666) sessionStorage.setItem('person',JSON.stringify(p)) } function readData(){ console.log(sessionStorage.getItem('msg')) console.log(sessionStorage.getItem('msg2'))
const result = sessionStorage.getItem('person') console.log(JSON.parse(result))
} function deleteData(){ sessionStorage.removeItem('msg2') } function deleteAllData(){ sessionStorage.clear() } </script> </body> </html>
|
组件的自定义事件
一种组件间通信的方式,适用于:子组件 ===> 父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
绑定自定义事件:
第一种方式,在父组件中:<Demo @getStudent="test"/>
或 <Demo v-on:getStudent="test"/>
子组件通过getStudent进行调用自定义事件,调用父组件的test方法。
第二种方式,在父组件中:
1 2 3 4 5 6
| <Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('getStudent',this.test); this.$refs.xxx.$once('getStudent',this.test) }
|
若想让自定义事件只能触发一次,可以使用once
修饰符,或$once
方法。<Demo @getStudent.once="test"/>
或 <Demo v-on:getStudent.once="test"/>
触发自定义事件:this.$emit('getStudent',数据)
解绑自定义事件this.$off('atguigu')
,只能解绑一个。this.$off(['atguigu'])
组件上也可以绑定原生DOM事件,需要使用native
修饰符。<Demo @click.navie="test"/>
注意:通过this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!在vue里面谁触发的事件fuction中是this就是那个组件,所以必须写成箭头函数。
全局事件总线
一种组件间通信的方式,适用于任意组件间通信。
安装全局事件总线:
1 2 3 4 5 6 7
| new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this }, ...... })
|
使用事件总线:
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
1 2 3 4 5 6 7
| methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) }
|
提供数据:this.$bus.$emit('xxxx',数据)
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。因为傀儡身上的事件一直存在。
消息订阅和发布
一种组件间通信的方式,适用于任意组件间通信。
使用步骤:
安装pubsub:npm i pubsub-js
引入: import pubsub from 'pubsub-js'
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
1 2 3 4 5 6 7
| methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) }
|
提供数据:pubsub.publish('xxx',数据)
最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)
去取消订阅。
订阅消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <script> import pubsub from 'pubsub-js' export default { name:'School', data() { return { name:'尚硅谷', address:'北京', } }, mounted() {
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{ console.log(this) }) }, beforeDestroy() { pubsub.unsubscribe(this.pubId) }, } </script>
|
消息发布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <script> import pubsub from 'pubsub-js' export default { name:'Student', data() { return { name:'张三', sex:'男', } }, mounted() { }, methods: { sendStudentName(){ pubsub.publish('hello',666) } }, } </script>
|
nextTick
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> <span v-show="!todo.isEdit">{{todo.title}}</span> <input v-show="todo.isEdit" type="text" :value="todo.title" @blur="handleBlur(todo.id,$event)" ref="inputTitle" /> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo.id)">编辑</button> </li> </template>
<script> import pubsub from "pubsub-js"; export default { name: "MyItem", props: ["todo"], methods: { handleCheck(id) { this.$bus.$emit("checkTodo", id); }, handleDelete(id) { if (confirm("确定删除吗?")) { this.$bus.$emit("deleteTodo", id); } }, handleEdit(id) { if (this.todo.hasOwnProperty("isEdit")) { this.todo.isEdit = true; } else { this.$set(this.todo, "isEdit", true); } this.$nextTick(function(){ this.$refs.inputTitle.focus() }); }, handleBlur(id, e) { this.todo.isEdit = false; if(!e.target.value.tirm()) return alert('输入不能为空'); this.$bus.$emit("updateTodo", this.todo.id, e.target.value); } } }; </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader @addTodo="addTodo"></MyHeader> <MyList :todos="todos" ></MyList> <MyFooter :todos="todos" @checkAllTodos="checkAllTodos" @clearAllTodo="clearAllTodo"></MyFooter> </div> </div> </div> </template>
<script> import pubsub from 'pubsub-js' import MyHeader from './components/MyHeader' import MyList from './components/MyList' import MyFooter from './components/MyFooter'
export default { name: 'App', components: { MyHeader, MyList, MyFooter }, data(){ return { todos: JSON.parse(localStorage.getItem('todos')) || [] } }, methods:{ addTodo(todoObj){ this.todos.unshift(todoObj) }, checkTodo(id){ this.todos.forEach( todo => { if ( todo.id === id ) { todo.done = !todo.done; } }) }, updateTodo(id, title){ this.todos.forEach( todo => { if ( todo.id === id ) { todo.title = title; } }) }, deleteTodo(id){ this.todos = this.todos.filter( todo => { return todo.id !== id; }) }, checkAllTodos(done){ this.todos.forEach( todo => { todo.done = done; }) }, clearAllTodo(){ this.todos = this.todos.filter( todo => { return !todo.done; }) } }, watch:{ todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)); } } }, mounted(){ this.$bus.$on('deleteTodo',this.deleteTodo) this.$bus.$on('checkTodo',this.checkTodo) this.$bus.$on('updateTodo',this.updateTodo) }, beforeDestroy(){ this.$bus.$off('checkTodo'); this.$bus.$off('deleteTodo'); this.$bus.$off('updateTodo'); }, } </script>
|
Vue封装的过度与动画
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
图示:
写法:
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
使用<transition>
包裹要过度的元素,并配置name属性:
1 2 3
| <transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>
|
备注:若有多个元素需要过度,则需要使用:<transition-group>
,且每个元素都要指定key
值。
动画效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition name="hello" appear> <h1 v-show="isShow">你好啊!</h1> </transition> </div> </template>
<script> export default { name:'Test', data() { return { isShow:true } }, } </script>
<style scoped> h1{ background-color: orange; }
.hello-enter-active{ animation: my-css 0.5s linear; }
.hello-leave-active{ animation: my-css 0.5s linear reverse; }
@keyframes my-css { from{ transform: translateX(-100%); } to{ transform: translateX(0px); } } </style>
|
过渡效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group name="hello" appear> <h1 v-show="!isShow" key="1">你好啊!</h1> <h1 v-show="isShow" key="2">尚硅谷!</h1> </transition-group> </div> </template>
<script> export default { name:'Test', data() { return { isShow:true } }, } </script>
<style scoped> h1{ background-color: orange; } .hello-enter,.hello-leave-to{ transform: translateX(-100%); } .hello-enter-active,.hello-leave-active{ transition: 0.5s linear; } .hello-enter-to,.hello-leave{ transform: translateX(0); }
</style>
|
集成第三方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__swing" leave-active-class="animate__backOutUp" > <h1 v-show="!isShow" key="1">你好啊!</h1> <h1 v-show="isShow" key="2">尚硅谷!</h1> </transition-group> </div> </template>
<script> import 'animate.css' export default { name:'Test', data() { return { isShow:true } }, } </script>
<style scoped> h1{ background-color: orange; }
</style>
|
Vue中的ajax
AJAX跨域请求问题
引入:npm i axios
方法一
在vue.config.js中添加如下配置:
1 2 3
| devServer:{ proxy:"http://localhost:5000" }
|
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| module.exports = { devServer: { proxy: { '/api1': { target: 'http://localhost:5000', ws: true, changeOrigin: true, pathRewrite: {'^/api1': ''} }, '/api2': { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2': ''} } } } }
|
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
vue 项目中常用的 2 个 Ajax 库
插槽
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
分类:默认插槽、具名插槽、作用域插槽
使用方式:
默认插槽:
1 2 3 4 5 6 7 8 9 10 11
| 父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <slot>插槽默认内容...</slot> </div> </template>
|
具名插槽:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 父组件中: <Category> <template slot="center"> <div>html结构1</div> </template>
<template v-slot:footer> <div>html结构2</div> </template> </Category> 子组件中: <template> <div> <slot name="center">插槽默认内容...</slot> <slot name="footer">插槽默认内容...</slot> </div> </template>
|
作用域插槽:
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
具体编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 父组件中: <Category> <template scope="scopeData"> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category>
<Category> <template slot-scope="scopeData"> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category> 子组件中: <template> <div> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
|
默认插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div class="category"> <h3>{{title}}分类</h3> <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> </div> </template>
<script> export default { name: "Category", props: ["title"] }; </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <div class="container"> <Category title="美食" > <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> </Category>
<Category title="游戏" > <ul> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> </Category>
<Category title="电影"> <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> </Category> </div> </template>
<script> import Category from "./components/Category"; export default { name: "App", components:{Category}, data() { return { foods:['火锅','烧烤','小龙虾','牛排'], games:['红色警戒','穿越火线','劲舞团','超级玛丽'], films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》'] } }, }; </script>
|
具名插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <div class="category"> <h3>{{title}}分类</h3> <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot> <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot> </div> </template>
<script> export default { name:'Category', props:['title'] } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <template> <div class="container"> <Category title="美食" > <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> <a slot="footer" href="http://www.baidu.com">更多美食</a> </Category>
<Category title="游戏" > <ul slot="center"> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> <div class="foot" slot="footer"> <a href="http://www.baidu.com">单机游戏</a> <a href="http://www.baidu.com">网络游戏</a> </div> </Category>
<Category title="电影"> <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> <template v-slot:footer> <div class="foot"> <a href="http://www.baidu.com">经典</a> <a href="http://www.baidu.com">热门</a> <a href="http://www.baidu.com">推荐</a> </div> <h4>欢迎前来观影</h4> </template> </Category> </div> </template>
<script> import Category from './components/Category' export default { name:'App', components:{Category}, data() { return { foods:['火锅','烧烤','小龙虾','牛排'], games:['红色警戒','穿越火线','劲舞团','超级玛丽'], films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》'] } }, } </script>
|
作用域插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div class="category"> <h3>{{title}}分类</h3> <slot :games="games" msg="hello">我是默认的一些内容</slot> </div> </template>
<script> export default { name:'Category', props:['title'], data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'], } }, } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <template> <div class="container">
<Category title="游戏"> <template scope="baidu"> <ul> <li v-for="(g,index) in baidu.games" :key="index">{{g}}</li> </ul> </template> </Category>
<Category title="游戏"> <template scope="{games}"> <ol> <li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li> </ol> </template> </Category>
<Category title="游戏"> <template slot-scope="{games}"> <h4 v-for="(g,index) in games" :key="index">{{g}}</h4> </template> </Category>
</div> </template>
<script> import Category from './components/Category' export default { name:'App', components:{Category}, } </script>
|
vuex
是什么
- 概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
- Github 地址: https://github.com/vuejs/vuex
使用时刻:
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
Vuex工作原理图(重要)
State:vuex 管理的状态对象,保存了组件间共享的数据。
Action: 包含多个响应用户动作的回调函数 。通过 commit( )来触发 mutation 中函数的调用, 间接更新 state。可以包含异步代码(定时器, ajax 等等)。可以包含多个执行流程。
1
| $store.dispatch('对应的 action 回调名')
|
Mutations:保存了操作对象,并且在操作里面有着state值和传递过来的值。包含多个直接更新 state 的方法。不能写异步代码、只能单纯的操作 state
1
| commit('对应的 mutations 方法名')
|
这个三个对象需要一个store来管理
Vuex使用
基本使用
安装:npm i vuex
创建并引入
初始化数据、配置actions
、配置mutations
,操作文件store.js
,一般在store文件夹中的index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = { jia(context,value){ context.commit('JIA',value) }, }
const mutations = { JIA(state,value){ state.sum += value } }
const state = { sum:0 }
export default new Vuex.Store({ actions, mutations, state, })
|
在main.js中引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import Vue from 'vue' import App from './App.vue'
Vue.config.productionTip = false
import store from './store'
new Vue({ render: h => h(App), store, beforeCreate(){ Vue.prototype.$bus = this; } }).$mount('#app')
|
使用
定义方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import Vue from 'vue' import Vuex from 'vuex'
Vue.use(Vuex);
const actions = { incrementOdd(context,value){ console.log('actions'); if (context.state.sum % 2){ context.commit('increment',value); } }, incrementWait(context,value){ setTimeout(() => { context.commit('increment',value) }, 500); } }
const mutations = { increment(state, value){ console.log('mulations'); state.sum += value; } }
const state = { sum:0 }
export default new Vuex.Store({ actions, mutations, state })使用
|
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template>
<script> export default { name: "Count", data() { return { n: 1, sum: 0 }; }, methods: { increment() { this.$store.commit("increment", this.n); }, decrement() { this.$store.commit("increment", -this.n); }, incrementOdd() { this.$store.dispatch("incrementOdd", this.n); }, incrementWait() { this.$store.dispatch("incrementWait", this.n); } } }; </script>
|
组件中读取vuex中的数据:$store.state.sum
组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)
或 $store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch
,直接编写commit
getter
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
在store.js
中追加getters
配置
1 2 3 4 5 6 7 8 9 10 11 12 13
| ......
const getters = { bigSum(state){ return state.sum * 10 } }
export default new Vuex.Store({ ...... getters })
|
组件中读取数据:$store.getters.bigSum
四个map方法的使用
哪里使用哪里引入:import {mapState} from ‘vuex’
mapState方法:用于帮助我们映射state
中的数据为计算属性
1 2 3 4 5 6 7
| computed: { ...mapState({sum:'sum',school:'school',subject:'subject'}), ...mapState(['sum','school','subject']), },
|
mapGetters方法:用于帮助我们映射getters
中的数据为计算属性
1 2 3 4 5 6 7
| computed: { ...mapGetters({bigSum:'bigSum'}),
...mapGetters(['bigSum']) },
|
mapActions方法:用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数
1 2 3 4 5 6 7
| methods:{ ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
...mapActions(['jiaOdd','jiaWait']) }
|
mapMutations方法:用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数
1 2 3 4 5 6 7
| methods:{ ...mapMutations({increment:'JIA',decrement:'JIAN'}), ...mapMutations(['JIA','JIAN']), }
|
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
模块化+命名空间
目的:让代码更好维护,让多种数据分类更加明确。
修改store.js
,分别放到2个js文件中,只需要在index.js中引入就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const countAbout = { namespaced:true, state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } }
const personAbout = { namespaced:true, state:{ ... }, mutations: { ... }, actions: { ... } }
const store = new Vuex.Store({ modules: { countAbout, personAbout } })
|
开启命名空间后,组件中读取state数据:
1 2 3 4
| this.$store.state.personAbout.list
...mapState('countAbout',['sum','school','subject']),
|
开启命名空间后,组件中读取getters数据:
1 2 3 4
| this.$store.getters['personAbout/firstPersonName']
...mapGetters('countAbout',['bigSum'])
|
开启命名空间后,组件中调用dispatch
1 2 3 4
| this.$store.dispatch('personAbout/addPersonWang',person)
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
|
开启命名空间后,组件中调用commit
1 2 3 4
| this.$store.commit('personAbout/ADD_PERSON',person)
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
|