总结:
通信:
注:组件中的数据共有三种形式:data、props、computed;
1、父传子 props、(插槽)
2、 子传父 自定义事件、vuex
3、组件之间 全局事件总线、 vuex
vuex全能
(当然 $refs $attrs也有用的情景)
Vue脚手架
Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpakc配置
请执行:vue inspect > output.js
├──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:包版本控制文件
ref属性
1、被用来给元素或子组件注册引用信息(id的替代者)
2、应用在html标签上获取到时真实dom元素,应用在组件标签上是组件实例对象
3、使用方式:
打标识:<h1 ref="xxx">...</h1>或者<School ref="xxx" />
获取:this.$refs.xxx
配置props
功能:让组件接收外部传过来的数据
(1)传递数据: <Demo name="xxx" />
(2)接收数据:
第一种(只接收)
props:['name']
第二种方式(限制类型)
props:{
name:String
}
第三种方式(限制类型、限制必要性、指定默认值)
props:{
name:{
type:String, //类型
required:true, //必要性
default:'老王' // 默认值
}
}
备注:props是只读的,vue底层会监测你对props的修改,如果你进行了修改,就会发出警告。
若业务需求确实需要修改,那么请 复制props的内容到data中一份,然后去修改data中的数据。
minxin(混入)
功能:把多个组件共用的配置提取成一个混入对象
使用方式:
第一步:定义混合,例如:
{
data(){
...
},
methods:{...},
...
}
}
第二步:使用混合,例如:
(1)全局混入:Vue.mixin(xxx)
(2)局部混入:mixins:[‘xxx’]
插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件传递者传递的数据。
定义插件:
对象:install=function(Vue,options){
// 全局过滤器
Vue.filter(){
...
})
// 自定义全局指令
Vue.directive('xxx',{
...
}) ,
// 全局混入
Vue.mixin({
...
}),
//添加实例方法
Vue.prototype.$myMothod=function(){...}
Vue.prototype.$myProterty=xxx
}
使用 :Vue.use()
scoped
作用域,所有的样式最终会汇总到一起,如果使用了相同的class或者id,就会产生冲突(按照引用的顺序进行覆盖), scoped 限制了使用范围
总结TodoList案例
1.组件化编码流程:
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
①一个组件在用:放在组件自身即可。
②一些组件在用:放在他们共同的父组件上(状态提升)
(3)实现交互:从绑定事件开始。
2.props适用于:
(1)父组件 ==> 子组件 通信
(2)子组件 ==> 父组件 通信 (要求父先给子一个函数)
3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
本地存储 webstorage
存储内容大小一般支持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
组件的自定义事件:
一种组件间通信的方式,适用于:子组件 ==> 父组件
A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
绑定自定义事件:
第一种方式,在父组件中:<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>
第二种方式,在父组件中:
<Demo ref="demo"/>
...
mounted(){
this.$refs.demo.$on('atguigu',data)
}
若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
触发自定义事件:this.$emit('atguigu',数据)
解绑自定义事件:this.$off('atguigu')
组件上也可以绑定原生DOM事件,需要使用native修饰符
注意:通过`this.$refs.xxx.$on('atguigu',回调)`绑定自定义事件时,
**回调要么配置在methods中,要么用箭头函数**,否则this指向会出问题
全局事件总线
1、一种组件间的通信方式
2、安装全局事件总线
new Vue({
...
beforCreate(){
Vue.prototype.$bus=this //安装全局事件总线,$bus就是当前应用的vm
},
...
})
3、使用事件总线:
3.1、接收数据:A组件想接收数据,则在A组件给$bus绑定自定义事件,- 事件的回调在A组件自身 -
methods(){
demo(data){
...
}
}
...
mounted(){
this.$bus.$on('xxx',tihs.demo) //this.demo为回调函数,如果在钩子这里写必须写成箭头函数
},
beforeDestory(){
this.$bus.$off('xxx')
}
3.2、提供数据: this.$bus.$emit('xxx',数据)
4、最好在beforeDestory 钩子中,用$off去解绑 当前组件用到的事件
消息的订阅与发布 pubsub
1、一种组件间的通信方式,**适用于 任意组件通信 **
2、引入:npm i pubsub-js
3、接收数据:A组件想接收数据,则A 订阅消息 ,订阅的回调发生在A身上
另外,值得一提的是 因为pubsub-js是第三方库,vue不会帮我们管理,所以this的指向,要么用箭头函数完成回调,要么在methods里创建一个方法(比如demo)使用this.demo;
methods(){
demo(data){...}
},
mounted(){
this.pid=pubsub.subscribe('xxx',this.demo) //订阅消息
}
4、提供数据:pubsub.publish('xxx',数据)
5、最好在beforeDestory钩子
中,用pubsub.unsubscribe(pid)
去 取消订阅
nextTick
1、语法:this.$nextTick(回调函数)
2、作用:在下一次更新结束之后执行指定回调
3、什么时候用:当改变数据后,- 要基于更新后的dom进行某些操作时 -,要在nextTick所指定的回调函数中执行
vue中的ajax
vue脚手架配置代理服务器:
方法一:在vue.config.js中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
说明:
1、优点:配置简单,请求资源时直接发给前端即可
2、缺点:不能配置多个代理,不能灵活的控制请求是否走代理
3、工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二:
devServer: {
proxy: {
'/api1': { // 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': { // 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
// changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
// changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
说明:
优点:可以配置多个代理,且可以灵活的控制请求是否走代理
缺点:配置略微繁琐,请求资源时必须加前缀
插槽:
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于==父组件 > 子组件
分类:默认插槽、具名插槽、作用域插槽
使用方式:
1、默认插槽:
父组件中:
<Category>
<div>html结构1</div>
</Category>
子组件中:
<template>
<div>
<slot>插槽默认内容...</slot>
</div>
</template>
2、具名插槽:
父组件中:
<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组件决定)
具体编码:
父组件中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<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>
vuex
什么是vuex
概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
什么时候用vuex?
多个组件依赖于同一状态
来自不同组件的行为需要变更同一状态
搭建vuex环境
下载 Vuex:npm i vuex (需要注意版本匹配 vue2匹配vuex3 vue3 匹配vuex4)
vuex的基本使用
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)
const actions = {
//响应组件中加的动作
jia(context,value){
// console.log('actions中的jia被调用了',miniStore,value)
context.commit('JIA',value)
},
}
const mutations = {
//执行加
JIA(state,value){
// console.log('mutations中的JIA被调用了',state,value)
state.sum += value
}
}
//初始化数据
const state = {
sum:0
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
组件中读取vuex中的数据:$store.state.sum
组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据) 或 $store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit
getters配置项的使用:
1-概念:当state中的数据需要经过加工后再使用时,可以使用getters加工
2-在store.js中追加getters配置
...
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并暴露store
export default new Vuex.Store({
...
getters
})
3-组件中读取数据:$store.getters.bigSum
mapState方法:用于帮助我们映射state中的数据
computed: {
//借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},
mapGetters方法:用于帮助我们映射getters中的数据
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
备注:mapActions与mapMutations使用时,若需要传递参数,则需要在模板中绑定事件时传递好参数,否则参数是事件对象
模块化+命名空间
1、目的:让代码更好维护,让多种数据分类更加明确
2、修改 store.js
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
}
})
3、开启命名空间后,读取组件state的数据
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']),
4、开启命名空间后,读取getters的数据
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
### 5、开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
6、开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
路由
1、安装vue-router,命令 npm i vue-router
2、应用插件:Vue.use(VueRouter)
3、编写router配置项
//引入VueRouter
import VueRouter from 'vue-router'
//引入Luyout 组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
4、实现切换(active-class 可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>
5、指定展示位:
<router-view></router-view>
6、几个注意点
1、路由组件通常存放在pages文件夹,一般组件存放在components文件夹
2、通过切换,‘隐藏’了的路由组件,默认是被销毁的,需要的时候再去挂载;
3、每个组件只有一个$router
属性,里面存储自己对路由信息;
4、整个应用只有一个routers,可以通过组件的$router
属性获取
多级路由
1、配置路由规则,使用 children 配置项:
routes:[
{
path:'/about', //一级路由
component:About,
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message, //二级路由
}
]
}
]
2、跳转(要写完整路径):
<template>
<div>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<router-link
class="list-group-item"
active-class="active"
to="/home/news"
>News</router-link
>
</li>
<li>
<router-link
class="list-group-item"
active-class="active"
to="/home/message"
>Message</router-link
>
</li>
</ul>
<router-view></router-view>
</div>
</div>
</template>
路由守卫
全局路由守卫
to : 要跳转到?(即将进入的目标 路由对象)
from :从哪里?(当前导航正要离开的路由)
next :放行
//全局前置路由守卫————初始化的时候、每次路由切换之前被调用
router.beforeEach((to,from,next) => {
console.log('前置路由守卫',to,from)
if(to.meta.isAuth){
if(localStorage.getItem('school')==='atguigu'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
})
//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
console.log('后置路由守卫',to,from)
document.title = to.meta.title || '硅谷系统'
})
独享路由守卫
{
name:'zhuye',
path:'/home',
component:Home,
meta:{title:'主页'},
children:[
{
name:'xinwen',
path:'news',
component:News,
meta:{title:'新闻'},
//独享守卫,特定路由**切换之后**被调用
beforeEnter(to,from,next){
console.log('独享路由守卫',to,from)
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
}
}
},
组件内路由守卫
<template>
<h2>我是About组件的内容</h2>
</template>
<script>
export default {
name:'About',
//通过路由规则,离开该组件时被调用
beforeRouteEnter (to, from, next) {
console.log('About--beforeRouteEnter',to,from)
if(localStorage.getItem('school')==='atguigu'){
next()
}else{
alert('学校名不对,无权限查看!')
}
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
console.log('About--beforeRouteLeave',to,from)
next()
}
}
</script>
路由的两种模式
1、对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值
2、hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器
3、hash模式:
地址中永远带着#号,不美观
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
兼容性较好
4、history模式:
地址干净,美观
兼容性和hash模式相比略差
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
组件库
Vant
Cube UI
Mint UI
Element UI
IView UI