Vue高级 - Vue全家桶技术
1. vue-cli 脚手架
2. vue-router 路由
3. vuex 多组件状态管理
vue-cli
-
它是vue提供的一个用于构建vue项目的脚手架,目前它的版本已经到了4.x
-
版本
- vue-cli 2.x 版本
- vue-cli 2以上版本【 3 4 】 市面上
-
安装
- 如何让一台电脑既可以使用vue-cli 2 也 可以使用 2以上版本?
$ cnpm/npm i @vue/cli @vue/cli-init -g
- @vue/cli 2以上版本
- @vue/cli-init 调节工具,可以让2以上版本也可以构建2版本的项目
-
创建项目
- 2以上版本创建方式【 两者创建出来的项目完全一样 】
- 标准形式
$ vue create 项目名称
- GUI创建
$ vue ui
- 标准形式
- 2版本创建方式
- 标准形式
$ vue init webpack 项目名称
- 简易形式 【 单案例 】
$ vue init webpack-simple 项目名称
- 标准形式
- 2以上版本创建方式【 两者创建出来的项目完全一样 】
-
项目启动
- serve 表示开发环境项目启动
- build 表示生产环境项目打包
- lint 表示语法检测【 eslint 】
-
yarn vs npm
- yarn 安全性更好
- yarn 可以锁定项目中第三方包的版本号,保证团队项目版本一致
-
vue-cli
- 底层实现: webpack
- webpack底层语言: Node.js
-
版本不同
- 2版本比2以上版本多了两个文件夹,分别为: build 、 config 目录
- 这两个文件是项目中webpack配置文件夹和项目脚本执行执行文件夹
- 2以上版本将这两个文件夹放到了node_modules/@vue/cli-service中
-
四中vue-cli不同创建项目形式总结
- 不同点一
- webpack配置和项目脚本执行文件放在不同地方
- 2以上版本放在了node_modules/@vue/cli-service中
- 2版本标准形式放在了 build \ config
- 2版本建议形式直接放在webpack.config.js文件中
- 不同点二
- 2以上版本性能要更好
- 配置插件自由、插件也更丰富了
- 不同点一
-
熟悉脚手架构建的目录【 2以上版本 】
-
yarn run serve -> node_modules/@vue/cli-service/bin/vue-cli-service.js
-
调用webpack功能
- 用来编译src下所有东西
- . stylus -> stylus-loader
- .vue -> vue-loader
- 高版本es语法 -> babel-config.js优雅降级配置文件来编译
- 用来编译src下所有东西
-
node_modules/@vue/cli-service 会有临时文件来启动一个界面
-
-
src 源代码开发目录【 工作目录 】
- assets目录 静态资源目录【 会被webpack编译 】
- img
- 如果图片 < 4K, 那么这个图片会被编译为 base64 图片
- 如果图片 > 4K, 那么这个图片被会复制到dist/img 中
- js
- 编译压缩js文件
- css文件
- 编译所有css文件
- img
- components目录 -> 项目公共组件【 头部、底部、列表 】
- App.vue组件
- 项目中继跟组件后最大的组件
- 用于放置我们创建的组件
- main.js 项目入口文件
- 最先执行的文件,其他文件依赖于这个文件执行
(用于引入项目自适应文件rem.js,vant组件,路由模块routes,并且在new Vue中全局注入路由,可以获得两个属性 r o u t e 和 route和 route和router)
- 最先执行的文件,其他文件依赖于这个文件执行
- assets目录 静态资源目录【 会被webpack编译 】
-
vue-cli 使用了 es6 模块化规范
-
单文件组件
- xxx.vue文件
- 一个文件就是一个组件
- 通过 vue-loader 来将 单文件组件 编译为 浏览器可以执行 js 文件
- 构成
- template 模板 【 jsx 结构】 必须有
- script 脚本 - 组件的配置 可有可无
- style 样式 可有可无
- vscode 要安装Vetur插件,才能识别vue文件
-
使用vue-cli
-
stylus
-
简化了样式需要符号
-
严格要求缩进
-
scoped
- 可以给样式设置独立作用域,也就是说: 如果我们给vue文件中的样式添加了scoped,那么这个样式只会在当前这个组件生效
- 如果不加,那么这个样式作用域是全局的
- 建议:
- 不要随便使用标签名
- 每一个vue文件样式都加 scoped
-
vue-cli 配置 - vue.config.js
配置文件修改了,必须重启
- 项目启动,自动打开网页
- 反向代理
- 实现跨域
- 标识符: 选取域名后的第一个路径作为标识符
- target: 目标源 , 选择 协议 + 域名
module.exports = {
devServer:{
open:true,
proxy:{
'/ajax:{
target:'http://m.maoyan.com',
changeOrigin:true
}
}
}
}
- 路径别名
- 可以将我们麻烦的 ./ …/ ./…/…/ 去掉了
const path=require('path')
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set('assets',path.join(__dirname,'./src/assets'))
.set('components',path.join(__dirname,'./src/components'))
.set('@',path.join(__dirname,'./src'))
}
}
- eslint语法检测关闭
- 如果.vue文件里不能执行console.log()
- 解决方法
- 用window.console.log()代替
- 增加一个eslintrc.js文件
module.exports = { root: true, env: { node: true }, 'extends': [ 'plugin:vue/essential', 'eslint:recommended' ], rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' }, parserOptions: { parser: 'babel-eslint' } }
构建一个项目
-
webapp 项目
-
移动端自适应配置
- rem + flex
- rem配置
- 网易方案
/* * 配置rem */ function font () { document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + 'px' } font() // 表示页面一开启就转换单位 window.onresize = font // 表示页面尺寸发生改变时,再次计算rem
- 淘宝方案
/* 通过js来动态添加rem */ (function(designWidth, maxWidth) { var doc = document, win = window, docEl = doc.documentElement, remStyle = document.createElement("style"), tid; function refreshRem() { var width = docEl.getBoundingClientRect().width; maxWidth = maxWidth || 540; width>maxWidth && (width=maxWidth); var rem = width * 100 / designWidth; remStyle.innerHTML = 'html{font-size:' + rem + 'px;}'; } if (docEl.firstElementChild) { docEl.firstElementChild.appendChild(remStyle); } else { var wrap = doc.createElement("div"); wrap.appendChild(remStyle); doc.write(wrap.innerHTML); wrap = null; } //要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次; refreshRem(); win.addEventListener("resize", function() { clearTimeout(tid); //防止执行两次 tid = setTimeout(refreshRem, 300); }, false); win.addEventListener("pageshow", function(e) { if (e.persisted) { // 浏览器后退的时候重新计算 clearTimeout(tid); tid = setTimeout(refreshRem, 300); } }, false); if (doc.readyState === "complete") { doc.body.style.fontSize = "16px"; } else { doc.addEventListener("DOMContentLoaded", function() { doc.body.style.fontSize = "16px"; }, false); } })(375, 750); // 备注: 这里的375本身就应该写成750 ,但是写成750之后,我们设计稿的尺寸要/50,不好算,我就想,除以100更好算,所以我改成了375
- 阿里方案
(function(doc, win) { const docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function() { const clientWidth = docEl.clientWidth; if (!clientWidth) return; let max = 24; let min = 9.3125; let size = 20 * (clientWidth / 320); size = Math.min(size, max); size = Math.max(size, min); docEl.style.fontSize = size + 'px'; console.log(docEl.style.fontSize, 'em= =====') }; if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); })(document, window);
-
构建项目目录
- layout/index.vue 项目的布局外壳
- 创建公共组件(在component里创建,文件名和组件名一般统一【大驼峰命名】)
- 例如建—标题栏/tabbar -> LayOut组件中使用(要将组件在LayOut/index.vue中【先引入->注册->使用】)
- utils 项目公共封装库
- rem.js
- request.js
-
数据请求封装 request.js (src/utils/request.js) --在该文件引入import axios from ‘axios’
-
创建路由配置文件夹 router (src/router)
- router
- index.js
-
引入需要的模块(使用前安装 yarn add vue-router)
import Vue from 'vue' import VueRouter from 'vue-router import routerTable from './routes.js' //引入路由表
-
安装插件
Vue.use( VueRouter )
-
创建路由表
const routerTable=[] --可以将它当作一个模块放入一个routes.js文件中
-
创建路由模块
const router=new VueRouter({ mode:'history' routes:routes --此属性名固定为routes })
- 最后导出路由
export default router
- 最后导出路由
-
- routes.js (路由表)
const routerTable=[ { path:'/', redirect:'/home' //重定向 }, { path:'/home', component:Home, //组件名 meta:{ //元信息 include:Home //include:组件名 } children:{ //二级路由 { path:'hot', //不用带斜杠 / component:HotMovieComp name:'hot' //给二级路由取别名, <router-link :to="{name:'hot'}"></router-link>使用 } } } ] export default routerTable
- index.js
- router
-
新建文件夹 pages/views , 它是路由路径对应的组件
- home/index.vue
- home文件夹放二级路由的组件 --HotMovieComp.vue
- mine/index.vue
- home/index.vue
-
新建文件夹 store 它是状态管理 vuex 的配置文件夹
-
新建文件夹mock.serveice, 用于数据模拟
-
创建项目样式全局变量文件
- assets/stylesheets/constant.stylus
- border 0 1px 0 0, #ccc //border是个函数,传两个参数(border,color)
- assets/stylesheets/constant.stylus
-
在public/index.html中引入fontawesome cdn
- fontawesome是一个icon图标库
-
Tab.vue构建
-
TabBar.vue
- 数量 2 - 5 个
-
1px 线兼容问题 ?
-
vue路由模式中 hash 和 history
- hash 浏览器的hashchange事件来实现的,兼容度更好
- history h5的history api 和popstate事件 来实现的
- go back forword pushstate replacestate
-
一级路由、子路由、元信息、router-view 、router-link
-
项目中配置组件库
- Vue组件库【 流行 】
- pc
- element-ui
- iview
- 移动端
- pc
- 他们的配置过程是一样的
-
总结:
- 如果数据存在渲染两次情况,其中一次时undefined/null,那么我们需要通过判断来排除undefined/null
- 例如:return this.floors && this.floors.floors
- 如果数据存在渲染两次情况,其中一次时undefined/null,那么我们需要通过判断来排除undefined/null
-
动态路由中的路由传参 - 路由接参
- $route
- 传参
<router-link :to="{ name: 'list', //通过id跳转指定列表 params:{ id:elm.api_cid }, query:{ a:1 } }"> <img :src="elm.img" alt=""> <span>{{ elm.name }}</span> </router-link>
- 接参
<router-link :to="{ name:'detail', params:{ id:item.id }, query:{ ...item } }"> //同时还要再请求数据 const result=await request({ url:'/index.php', params:{ r: 'class/cyajaxsub', page: 1, cid: this.$route.params.id, //$route传过来的id px: 't', } })
-
编程式导航
- $router
- push vs replace
- push会将我们操作放入历史记录,点击浏览器返回会一层一层返回
- this. r o u t e r . p u s h ( 路 由 名 字 ) 或 者 t h i s . router.push(路由名字)或者this. router.push(路由名字)或者this.route.push({路由名字/参数等})
- replace不会将我们的操作放入历史记录,点击浏览器返回反回2层
- go / back
- push vs replace
- $router
-
组件身上添加原生事件,要加native修饰符
导航守卫
-
别名
- 路由钩子
- 路由守卫
- 路由拦截
-
导航守卫作用
- 对路由的跳转进行拦截
- 白话: 从一个路由跳转到另一个路由,能不能跳转过去,导航守卫决定
- 对路由的跳转进行拦截
-
类型
- 全局导航守卫
- 对整个项目做控制
- 路由独享守卫
- 对单个路由的路由配置做控制
- 写在路由表中
- 组件内守卫
- 对组件对应的路由做控制
- 全局导航守卫
-
使用
- 全局导航守卫(在router文件夹下创建)
- 全局前置守卫 [ router.beforeEach((to,from,next)=>{}) ]
router.beforeEach(( to,from,next ) => { console.log("jwj: to", to) console.log("jwj: from", from) if ( to.path == '/home/hot' ) { next() } const token = getCookie(' _token') console.log('token',token) if ( token || to.path == '/login') {//对所有页面进行拦截 next() } else { next('/login') }
- 全局后置守卫 [ ==router.afterEach((to,from)=>{}) ==]
- 全局更新守卫【 可忽略, 使用和前置守卫一样 】
- 区别: 是否完整遍历路由表
- 更新守卫要遍历完整路由表之后才跳转页面
- 区别: 是否完整遍历路由表
- 全局前置守卫 [ router.beforeEach((to,from,next)=>{}) ]
- 导航守卫参数 /home -> /category
- from
- 表示当前路由 /home
- to
- 表示目标路由 /category
- next [必须要写的]
- next表示两者之间的连接
- 取值
- next() //默认连通
- next( true ) // 表示连通
- next( false ) // 不表示断开连接
- next(路由路径)
- next(’/home’)
- next({ name: ‘home’, params: {},query: {} })
- next( vm => { } ) // vm指的是目标组件
- from
- 路由独享守卫[ ==beforeEnter ==]
- 在路由内写
- 组件内守卫
- 组件前置守卫【 钩子 】[ beforeRouteEnter ]
- 拦截组件的进入
- 执行: 创建组件前(beforeCreate)执行,这个时候没有组件,没有this
- 功能
- 判断是否有token,然后是否能进入当前页面
- 数据预载
- 进入组件前,提前得到数据,并将这个数据赋值给组件
- 对象的合并 Object.assign() -> Vue.set/this.$set( 属性,属性值 ) ----原理(将data选项中的某个对象和请求到的数据合并,因为只有data里的数据是双向绑定的)
- vm.$set(属性名,属性值)
- 对象的合并 Object.assign() -> Vue.set/this.$set( 属性,属性值 ) ----原理(将data选项中的某个对象和请求到的数据合并,因为只有data里的数据是双向绑定的)
async beforeRouteEnter(to,from,next){ //数据预载 console.log(to,from,next) const res= await request({ url: '/ajax/search', params: { kw: '万达', cityId: 1, stype: 2 } }) console.log(res.data.cinemas.list) next(vm => { //vm就是当前组件,解决没有this的问题 //因为只有data选项中的数据是数据绑定的,将data中的cinemas对象和请求到的数据进行合并 // Vue.set/this.$set(属性,属性值),原理:Object.assign() // console.log(vm.cinemas) vm.$set(vm.cinemas,JSON.stringify(res.data.cinemas.list)) })
- 进入组件前,提前得到数据,并将这个数据赋值给组件
- 组件后置守卫【 钩子 】 [ beforeRouteLeave ]
- 拦截组件离开
- 执行: 离开组件前,这个时候有组件,有this
- 组件更新守卫
- 专用于: 动态路由
- 组件前置守卫【 钩子 】[ beforeRouteEnter ]
- 全局导航守卫(在router文件夹下创建)
动画效果
- 实现 -> animate.css css3动画效果
- 使用
- 安装: yarn add animate.css
- 在动画组件/元素外层加一个 Vue 内置组件 transition
<transition mode = "out-in" enter-active-class="animated slideInLeft" leave-active-class="animated slideOutLeft" > <router-view></router-view> </transition>