前言
- Vue CLI 是一个基于
Vue.js 进行快速开发的完整系统,其通过
@vue/cli
实现交互式的项目脚手架,并致力于将 Vue
生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样可以专注在撰写应用上,而不必花好几天去纠结配置的问题,与此同时,它也为每个工具提供了调整配置的灵活性。
- Vuex
是一个专门为 Vue.js
应用程序开发的一种状态(数据)管理模式。它采用集中式存储管理应用程序的所有组件的状态(数据),并以相应的规则保证状态以可预测的方式变化。
1
| npm install vuex@3 --save
|
- vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。
1
| npm install vue-router@3
|
在本文的最后,补充了Vue
UI组件库Element UI
的相关内容。
一、vue-cli
1.1 脚手架文件结构
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:包版本控制文件
|
1.1.1 main.js
- 这里引入的vue是
node_modules
下的vue.runtime.xxx.js
,是运行版的Vue,只包含核心功能,没有模板解析器
- 因为
vue.runtime.xxx.js
没有模板解析器,所以不能使用template
这个配置项,需要使用render
函数接收到的createElement
函数去指定具体内容。
1 2 3 4 5 6 7 8 9 10 11
|
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({ render: h => h(App), }).$mount('#app')
|
1.1.2 组件名.vue
语法同非单文件组件一样,只不过拆成了template
、script
、style
三个部分
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template>
</template>
<script> export default { name: '组件名' } </script>
<style> </style>
|
1.2 ref属性
- 被用来给元素或子组件注册引用信息(
id
的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
1 2 3 4 5 6
| <h1 ref="xxx">.....</h1> <School ref="xxx"></School>
this.$refs.xxx
|
1.3 $nextTick
- 当改变数据后,要基于更新后的新DOM进行某些操作时,使用nextTick。
- 作用:在下一次 DOM 更新结束后执行其指定的回调
- 语法:
this.$nextTick(回调函数)
1 2 3 4
| this.$nextTick(function(){ this.$refs.inputTitle.focus() })
|
1.4 scoped
让样式在局部组件生效,防止冲突
1.5 props配置项
- 功能:让组件接收外部传过来的数据
- 传递数据:
<Demo name="xxx"/>
- 接收数据:
- 第一种方式(只接收):
props:['name']
- 第二种方式(限制类型):
props:{name:String}
- 第三种方式(限制类型、限制必要性、指定默认值)
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
| props:['name','age','sex']
props:{ name:String, age:Number, sex:String }
props:{ name:{ type:String, required:true, }, age:{ type:Number, default:99 }, sex:{ type:String, required:true } }
|
1.6 webStorage
- 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
- 浏览器端通过
Window.sessionStorage
和
Window.localStorage
属性来实现本地存储机制
sessionStorage
存储的内容会随着浏览器窗口关闭而消失
localStorage
存储的内容需要手动清除才会消失
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| localStorage.setItem('key', 'value')
localStorage.getItem('key')
localStorage.removeItem('key')
localStorage.clear()
sessionStorage.setItem('key', 'value')
sessionStorage.getItem('key')
sessionStorage.removeItem('key')
sessionStorage.clear()
|
1.7 子组件向父组件通信
1.7.1 父组件绑定自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <Demo @事件名="test"/> <Demo v-on:事件名="test"/>
<Demo ref="demo"/> ...... mounted(){ this.$refs.demo.$on('事件名',this.test)
this.$refs.demo.$once('事件名',()=>{ ... }) }
|
1.7.2 子组件触发自定义事件
1
| this.$emit('事件名', '传递数据')
|
1.7.3 子组件解绑自定义事件
1 2 3 4 5 6 7 8
| this.$off('事件名1')
this.$off(['事件名1','事件名2'])
this.$off()
|
1.7.4 给子组件绑定原生DOM事件
组件上也可以绑定原生DOM事件,需要使用native
修饰符
1 2 3 4 5
| <Student @click.native="show"/> ... show(){ alert(123) }
|
1.8 全局事件总线
一种组件间通信的方式,适用于任意组件间通信
1.8.1 安装全局事件总线
1 2 3 4 5 6 7
| new Vue({ ... beforeCreate() { Vue.prototype.$bus = this }, ... })
|
1.8.2 使用全局事件总线
接受数据
1 2 3 4 5 6 7
| methods(){ demo(data){...} } ... mounted() { this.$bus.$on('事件名',this.方法名) }
|
传递数据
1
| this.$bus.$emit('事件名',数据)
|
1.8.3 解绑所用事件
1 2 3
| beforeDestroy() { this.$bus.$off('事件名') }
|
1.9 插槽
让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于
父组件 ===> 子组件
1.9.1 默认插槽
组件中的内容会填充到插槽slot
内,下例父组件的div
会替换子组件的slot
1 2 3 4 5 6 7 8 9 10 11
| 父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <slot>插槽默认内容</slot> </div> </template>
|
1.9.2 具名插槽
1 2 3 4 5 6 7 8 9 10 11
| 父组件中: <Category> <div v-slot:center>html结构</div> </Category> 子组件中: <template> <div> <slot name="center">插槽默认内容</slot> </div> </template>
|
1.9.3 作用域插槽
数据在子组件的自身,但根据数据生成的结构需要父组件的使用者来决定。(下例中foods数据在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
| 父组件中: <Category title="美食"> <template v-slot:food="slots"> <h4 v-for="(f, index) in slots.foods" :key="index">{{f}}</h4> </template> </Category>
子组件中: <template> <div class="category"> <h3>{{title}}分类</h3> <slot name="food" :foods="foods"></slot> </div> </template>
<script> export default { name: 'Category', props:['title'], data(){ return { foods:['火锅', '烧烤', '小龙虾', '牛排'], } } } </script>
|
1.10 脚手架配置代理
1.10.1 方法一
在vue.config.js
中添加如下配置
1 2 3
| devServer:{ proxy:"http://localhost:5000" }
|
- 优点:配置简单,请求资源时直接发给前端(8080)即可
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器
1.10.2 方法二
在vue.config.js
中添加如下配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| module.exports = { devServer: { proxy: { '/api1': { target: 'http://localhost:5000', changeOrigin: true, pathRewrite: {'^/api1': ''} }, '/api2': { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2': ''} } } }
|
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
- 缺点:配置略微繁琐,请求资源时必须加前缀
二、VueX
在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
2.1 搭建VueX环境
2.1.1 创建文件
src/store/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = {}
const mutations = {}
const state = {}
export default new Vuex.Store({ actions, mutations, state })
|
2.1.2 传入store
配置项
在main.js
中创建vm时传入store
配置项
1 2 3 4 5 6 7 8 9 10 11
| ...
import store from './store' ...
new Vue({ el:'#app', render: h => h(App), store })
|
2.2 VueX案例——基本使用
Count.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
| <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, } }, methods: { increment(){ this.$store.commit('JIA',this.n) }, decrement(){ this.$store.commit('JIAN',this.n) }, incrementOdd(){ this.$store.dispatch('jiaOdd',this.n) }, incrementWait(){ this.$store.dispatch('jiaWait',this.n) }, }, mounted() { console.log('Count',this) }, } </script>
<style lang="css"> button{ margin-left: 5px; } </style>
|
/src/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 35 36 37 38 39 40 41 42 43
| import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex)
const actions = { jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') if(context.state.sum % 2){ context.commit('JIA',value) } }, jiaWait(context,value){ console.log('actions中的jiaWait被调用了') setTimeout(()=>{ context.commit('JIA',value) },500) } }
const mutations = { JIA(state,value){ console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state,value){ console.log('mutations中的JIAN被调用了') state.sum -= value } }
const state = { sum:0 }
export default new Vuex.Store({ actions, mutations, state, })
|
2.3 getters的使用
当state
中的数据需要经过加工后再使用时,可以使用getters
加工,有点类似计算属性
src/store/index.js
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 })
|
读取bigSum:$store.getters.bigSum
2.4 四个map方法的使用
1
| import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
|
2.4.1 mapState
用于帮助我们映射state
中的数据为计算属性
1 2 3 4 5 6 7
| computed: { ...mapState({sum:'sum',school:'school',subject:'subject'}), ...mapState(['sum','school','subject']), },
|
2.4.2 mapGetters
用于帮助我们映射getters
中的数据为计算属性
1 2 3 4 5 6 7
| computed: { ...mapGetters({bigSum:'bigSum'}),
...mapGetters(['bigSum']) },
|
2.4.3 mapActions
用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
1 2 3 4 5 6 7
| methods:{ ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
...mapActions(['jiaOdd','jiaWait']) }
|
2.4.4 mapMutations
用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
1 2 3 4 5 6 7
| methods:{ ...mapMutations({increment:'JIA',decrement:'JIAN'}), ...mapMutations(['JIA','JIAN']), }
|
2.4.5 案例完善
mapActions
与mapMutationsz
在使用时,若要传递参数,需在模板中绑定事件时传递好参数,否则参数是事件对象
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> <h1>当前求和为:{{sum}}</h1> <h3>当前求和放大10倍为:{{bigSum}}</h3> <h3>我在{{school}},学习{{subject}}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template>
<script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' export default { name:'Count', data() { return { n:1, } }, computed:{ ...mapState(['sum','school','subject']), ...mapGetters(['bigSum']) }, methods: { ...mapMutations({increment:'JIA',decrement:'JIAN'}), ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) }, } </script>
<style lang="css"> button{ margin-left: 5px; } </style>
|
2.5 store模块化
让代码更好维护,让多种数据分类更加明确
2.5.1 修改store.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const countAbout = { namespaced:true, state:{ ... }, mutations: { ... }, actions: { ... }, getters: { ... } }
const personAbout = { namespaced:true, state:{ ... }, mutations: { ... }, actions: { ... } }
const store = new Vuex.Store({ modules: { countAbout, personAbout } })
|
2.5.2 读取state数据
1 2 3 4 5
| this.$store.state.personAbout.xxx
...mapState('countAbout',['sum','school','subject'])
|
2.5.3 读取getters数据
1 2 3 4 5
| this.$store.getters['personAbout/firstPersonName']
...mapGetters('countAbout',['bigSum'])
|
2.5.4 组件中调用dispatch
1 2 3 4 5
| this.$store.dispatch('personAbout/addPersonWang',person)
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
|
2.5.5 组件中调用commit
1 2 3 4 5
| this.$store.commit('personAbout/ADD_PERSON',person)
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
|
三、Vue-Router
- 理解: 一个路由(route)就是一组映射关系(key -
value),多个路由需要路由器(router)进行管理
- 前端路由:key是路径,value是组件
3.1 基本使用
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息
- 整个应用只有一个router,可以通过组件的
$router
属性获取到
3.1.1 创建文件
src/router/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import VueRouter from 'vue-router'
import About from '../components/About' import Home from '../components/Home'
export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] })
|
3.1.2 注册路由
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import Vue from 'vue' import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router'
Vue.config.productionTip = false
Vue.use(VueRouter)
new Vue({ el:'#app', render: h => h(App), router:router })
|
3.1.3 router-link实现切换
1 2
| <router-link active-class="active" to="/about">About</router-link>
|
3.1.4 router-view指定展示位置
1
| <router-view></router-view>
|
3.2 多级路由
3.2.1 children配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children: [ { path:'news', component: New } ] } ] })
|
3.2.2 路由跳转
1 2
| <router-link to="/home/news">News</router-link>
|
3.3 命名路由
可以简化路由的跳转
3.3.1 给路由命名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| export default new VueRouter({ routes:[ { path:'/home', component:Home, children: [ { path: 'message', component: Message, children: [ { path:'detail', name:'xiangqing', component:Detail, } ] } ] } ] })
|
3.3.2 简化跳转
1 2 3 4 5 6 7 8 9 10 11 12
| <router-link to="/home/message/detail">跳转</router-link>
<router-link :to="{name:'xiangqing'}">跳转</router-link>
<router-link :to="{ name:'hello', }" >跳转</router-link>
|
3.4 query参数
3.4.1 传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13
| <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
<router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>
|
3.4.2 接收参数
1 2
| $route.query.id $route.query.title
|
3.5 params参数
3.5.1 传递参数
路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
1 2 3 4 5 6 7 8 9 10 11 12 13
| <router-link :to="/home/message/detail/666/你好">跳转</router-link>
<router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
|
3.5.2 声明接收params参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export default new VueRouter({ routes:[ { path:'/home', component:Home, children: [ { component: Message, children: [ { name:'xiangqing', path:'detail/:id/:title', component:Detail, } ] } ] } ] })
|
3.5.3 接收参数
1 2
| $route.params.id $route.params.title
|
3.6 路由的props配置
让路由组件更方便的收到参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { name:'xiangqing', path:'detail/:id', component:Detail,
props:{a:900}
props(route){ return { id: route.query.id, title: route.query.title } } props:true }
|
3.7
<router-link>
的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录(一个类似于栈的结构)。路由跳转时候默认为push
开启replace
模式:
1
| <router-link replace>News</router-link>
|
3.8 编程式路由导航
不借助<router-link>
实现路由跳转,让路由跳转更加灵活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } })
this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } })
this.$router.forward() this.$router.back() this.$router.go()
|
3.9 缓存路由组件
让不展示的路由组件保持挂载,不被销毁
1 2 3 4 5 6 7 8 9
| <keep-alive include="News"> <router-view></router-view> </keep-alive>
<keep-alive :include="['News','Message']"> <router-view></router-view> </keep-alive>
|
3.10 两个新的生命周期钩子
- 路由组件所独有的两个钩子,用于捕获路由组件的激活状态
- 具体名字
activated
路由组件被激活时触发
deactivated
路由组件失活时触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script> export default { name:'News', activated() { console.log('News组件被激活了') this.timer = setInterval(() => { this.opacity -= 0.01 if(this.opacity <= 0) this.opacity = 1 },16) }, deactivated() { console.log('News组件失活了') clearInterval(this.timer) }, } </script>
|
3.11 路由守卫
对路由进行权限控制
可以在src/router/index.js
配置路由的元信息meta
字段:
1
| meta:{isAuth:true,title:'新闻'}
|
3.11.1 全局守卫
在src/router/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
| router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') } }else{ next() } })
router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title }else{ document.title = 'vue_test' } })
|
3.11.2 独享守卫
在src/router/index.js
中对应的路由处添加beforeEnter
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { name:'xinwen', path:'news', component:News, meta:{isAuth:true,title:'新闻'}, beforeEnter: (to, from, next) => { console.log('独享路由守卫',to,from) if(to.meta.isAuth){ if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('学校名不对,无权限查看!') } }else{ next() } } },
|
3.11.3 组件内守卫
放在组件内部使用,同钩子使用方法一样
1 2 3 4 5 6 7 8 9
| beforeRouteEnter (to, from, next) { ... },
beforeRouteLeave (to, from, next) { ... }
|
3.12 路由器的两种工作模式
- 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值
- hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器
- hash模式:
- 地址中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
- history模式:
- 地址干净,美观
- 兼容性和hash模式相比略差
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
四、Vue UI组件库
4.1 移动端常用UI组件库
Vant
:https://youzan.gothub.io/vant
Cube UI
: https://didi.github.io/cube-ui
Mint UI
: https//mint-ui.github.io
4.2 PC端常用UI组件库
Element UI
: https://element.eleme.cn
IView UI
: https://www.iviewui.com
4.3 Element UI
4.3.1 安装
4.3.2 完整引入
1 2 3 4 5 6
| import Vue from 'vue'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue';
Vue.use(ElementUI);
|
4.3.3 按需引入
首先,安装babel-plugin-component
:
1
| npm install babel-plugin-component -D
|
接着,修改babel.config.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| module.exports = { presets: [ '@vue/cli-plugin-babel/preset', ["@babel/preset-env", { "modules": false }], ], plugins:[ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
|
最后,main.js
按需引入:
1 2 3 4 5 6
| import Vue from 'vue'; import { Button, Select } from 'element-ui'; import App from './App.vue';
Vue.component(Button.name, Button); Vue.component(Select.name, Select);
|