vue+webpack项目实战
概述
--
-
项目中会用到的插件 vue-router vue-resource
-
打包工具 webpack
-
依赖环境 node.js
start 安装vue开发的模板
# 全局安装 vue-cli
$ npm install -g vue-cli # 创建一个基于 "webpack" 模板的新项目 $ vue init webpack my-project # 安装依赖,走你 $ cd my-project $ npm install $ npm run dev
-
文件解释:
-
build中配置了webpack的基本配置、开发环境配置、生产环境配置
-
config中配置了路径端口值等
-
node_modules为依赖的模块
-
src放置组件和入口文件
-
static放置静态资源文件
-
index.html文件入口
-
-
webpack中的一些解释:
-
new HtmlWebpackPlugin 这个插件的作用是把output输出的文件自动插入到html里
-
这里不使用elint检查代码
-
step1 配置路由
-
这里使用vue-router 中文基本用法可参见http://router.vuejs.org/zh-cn...
-
把原来脚手架中的new Vue换成了路由实现,最容易忘记的一点是Vue.use(VueRouter);
-
这里的Vue.extend()暂时先定义两个临时的组件,main.vue为入口文件,组件内要添加路由视图标签<router-view></router-view>
-
这里的router.start(app,"#app")的app是require进来main,'#app'是添加从index.html的id为app入口
现在的效果是:
-this is bar 上面那部分是main.vue里面的,
-this is bar 则是有router-view渲染出来的
step2 提取路由
-
app中要切换多个路由为了不代码耦合将map映射部分提取到一个router.js文件中
-
这里要后续要引用zepto开发,所以这里要在webpack.base.conf.js中做一个配置
externals: {
'zepto': 'Zepto'
},
*上截图解释这个参数,所以要加html中加如zepto的链接,然后在其他地方就可以引用了*
3.只是简单地把组件和映射放到router.js中,然后在app.js中传入router
router.js
app.js增加的代码
现在页面还是和之前一样没有变化,基本框架和路由搭建完成,然后就可以开始封装组件
step3 main.vue组件编写
1.app的主页底部一般都有几个tab键是固定不变的,这里实现四个tab键分别是首页,发现,通知,我 2.这里使用mobile sui搭建ui,在main.vue<style>中引用sui样式
@import './assets/css/sm.css';
@import './assets/css/sm-extend.min.css';
这样已经能呈现一个底部导航,但是不太符合vue组件化的概念,毕竟重复了四次的tab代码,所有这里要用slot进行内容分发
3.这里要理解slot元素,先上一张官方的解释
step4 slot的使用
<slot> 就是外部调用时,标签中的内容。如果外部调用时没有提供内容的话,那么它就会使用自己默认提供的内容,非常方便。
-这个字面意思确实难以理解,用代码解释 -首先定义Bar.vue组件替代最外层的nav
-
然后在main.vue import 进来引用
-
原来的nav标签就会变成这样写
-
先看现在的效果
-
一切正常,但是如果把Bar.vue中的slot注释,就没有这些导航图标了,所以我可以理解为使用了slot可以把不在Bar.vue的template中的代码引进来,不使用就直接使用Bar.vue的template模板了
-
现在可以把里面的item元素也弄成一个BarItem.vue组件
-
这里要知道一个新的指令v-link和它的activeClass配合
v-link 是一个用来让用户在 vue-router 应用的不同路径间跳转的指令。http://router.vuejs.org/zh-cn... 详情看这里
先上代码BarItem.vue
-
script中的props是在main.vue传进来的参数,v-link中的replcace:true 是用了router.replace()而不是router.go()也就是不能后退(首页标签页切换就不让用户有后退的功能了),activeClass是当路由激活时加上的类
-
main.vue现在的代码
-
对了,记得把BarItem.vue引进来喔
-
现在的效果还是像之前一样,但是已经实现组件化
step5 HomeTab 路由切换
-
新建search.vue、message.vue、me.vue、home.vue,然后在router.js中做相应的配置
-
这里动态组件载入就是常说的懒加载组件
当你在使用 Webpack 或者 Browserify 时,在基于异步组件编写的 Vue 项目时,也可以较为容易的实现惰性加载组件。不再是之前所述的直接引用一个组件,现在需要像下面这样通过定义一个函数返回一个组件:
-
resolve这个参数有点难理解,实际就是用异步加载,用AMD风格的写法是
require(['./MyComponent.vue'],
function (MyComponent) {
// code here runs after MyComponent.vue is asynchronously loaded
.})
-
五个路由都写好就可以随意切换tab了
-
想要达到这种效果
-
homeTab这部分也是可以提取出组件作为各个tab的头部
-
赞一下vue的错误提示,一开始死活显示不了,这错误提示还是很明显的
-
用组件记得在js components中注册
-
还有这个提示,注册了变量没
step6 HomeTab 内容+下拉刷新
-
这里的ui用的是sui,包括下拉刷新也是用sui的组件,详情:http://http://m.sui.taobao.or...
-
下拉刷新会有dom元素的操作,不能用jq的思想,所以要用到自定义指令
自定义指令提供一种机制将数据的变化映射为 DOM 行为
pullToRefresh.js
用到的钩子函数:
bind:只调用一次,在指令第一次绑定到元素上时调用。
unbind:只调用一次,在指令从元素上解绑时调用。
在注册之后,便可以在 Vue.js 模板中这样用(记着添加前缀 v-),当作为属性指令的时候
用到的指令实例属性:
-
el: 指令绑定的元素。
-
vm: 拥有该指令的上下文 ViewModel。
-
expression: 指令的表达式,不包括参数和过滤器。
-
params:自定义指令可以接收一个 params 数组,指定一个特性列表,Vue 编译器将自动提取绑定元素的这些特性。this.params[key]会自动保持更新。
data-ptr-distance="55"可以配置下拉刷新的下拉距离,sui的配置
-
在app.js中注册自定义指令
import pullToRefresh from './directives/pullToRefresh'
//directive
Vue.directive('pullToRefresh', pullToRefresh);
-
自定义指令完成
-
然后是要将这部分的ui封装成组件放在两个tab里
-
就是封装官网上的这段代码
-前面已经说过怎么去拆分组件了,只要你觉得合理,子父组件之间能通信想怎么拆都可以,当然以复用为原则,直接上我自己的拆分方式,可以对比一下跟第二篇的源码,都会同步到github,无论怎么定义记得要在components里面注册--vue函数中通过this.$el来获取当前元素
-
千万要注意一个概念叫做片段实例
-
就是模板中只有一个顶级元素,否则会对你下面获取当前元素产生影响,如果你在函数中想要获取当前元素用this.$el得到的不是一个节点,而是一个空文本元素,那么你就要去检查你的模板是不是有问题了
-
-
这里最重要的是把刚刚的指令引入进来了
-
前面有提到作为属性要加v-前缀
-
现在可以去书写你下拉刷新是要执行的函数了,在methods方法中定义
那个$.showIndicator的效果其实就是这个
-
之前有一点没有提到,就是sui需要执行一个init()方法才能有上面的效果,而且是在ready方法中init,这是基本配置,所以要在入口文件main.vue加个.page,否则会报找不到id的错误
-
现在的效果是下拉可以增加一个.card,但是切换路由的时候再回到页面就不见了。所以要绑一个变量存数据。今天这个自定义指令挺难理解的,先keep一份代码吧
step7解决(3)中刷新在两个tab中都添加元素的问题
-
(3)中的情况是无论在动态tab下拉刷新还是在前端tab下拉刷新同时会增加dom元素
-
主要是因为这一句话,这里的this是指向整个template的根目录,这也正好可以解释片段实例为什么获取不到元素了
-
main.vue
-
-
那就必须是添加到当前指令下的.card-container,所以要获取到当前指令元素,可以在自定义指令中获得并传参
-
PullToRefresh.js
-
-
现在可以main.vue 中的函数中获取这个参数,利用它获取当前元素的.card-container
step8不再操作dom只关心数据
-
vue是为数据而生的,不该过多的操作dom,而之前的所有刷新都是append了一次元素
-
为了模拟有真实是的数据时的情况,把将定义两个数组将分别对应两个tab里面的数组task1,task2,并在每次下拉时push进去,这时候我们就不在需要操作dom元素了,只需要关心data的变化
-
为了让存放在不同的数组中在自定义指令的元素上加入了这些属性
-
然后在pullToRefresh.js中将它设置到自定义属性中,当然要记得在params中传入特性,(这里我总感觉我这方法有点麻烦,如果在指令中传参我还没有明确,希望有知道的大神指点一二)
-
每次下拉函数都会push一个数组
-
html中用v-for输出数组
-
基本的home.vue已经全部完成了
-
当我很开心的以为结束了今天的任务的时候,我切换一下底部菜单,然后再回到home tab时,整个人都不好了,刚刚下拉的数据全部没了,回到刚开始的页面
-
我本想着要自定义方法来存储,可是参考资料里面是并不用这么麻烦的,寻寻觅觅了很久很久,终于遇到了神奇的它---keep-alive
-
先来了解一下动态组件
-
当用户关闭component时,将该component卸载,再次打开时重新加载。
-
-
-
-
这个项目里在router-view标签中加上keep-live就可以缓存组件了
-
扩展:根据官方的这个案例,通过:is和keep-alive可以实现随意切换是否缓存这个组件
-
-
-
现在无论怎么点tab键都能保留下拉刷新的那几个内容了
step9 个人中心布局
-
前面step1到step8已经将首页和基本菜单栏完成,现在先做个人中心页也就是底部菜单中‘我’这个tab*
-
首页点击去看详情和写新动态加到列表中放到后面再完成
-
总体预览
-
之前做过一个HeaderTab的组件,现在可以拿来用了
- 如果你遇到底部切换页面可以正常显示,但是直接刷新浏览器却不起作用,那么你看看是不是没有$init()
-
第二部分是图片,sui中1rem等于20px
-
第三部分是个人信息,为了模拟以后从后台获取数据,将这部分抽离出来定义一个UserDetail组件并将数据存储在一个数据中,这个数组存放在me.vue中
-
子父之间的通信:虽然之前也有提到过,但是之前都是在父组件上传递一个字符串,当时的栗子是这样的
-
然后在子组件的props中注册status就可以直接用了,但是今天要传递的是一个数组data 如果直接写成data="userData" vue是不能识别你这个是字符串还是变量数组的
-
所以这里要用v-bind:user-data='userData',这里还有一个坑,如果你写的是v-bind:userData='userData'就会报错了。
-
组件中的样式我定义的比较随便,直接用标签来定义样式,因为这里加了scope只作用在当前组件,vue会自动处理加一串标记数字,这样再也不用为命名担心了
-
第三部分的tab与第二部分类似,也是抽离一个组件,这里定义数组将内容与结构分离,用v-for循环输出结构,如果要改内容直接改数组,不用在一大堆的html结构里面去找文字了
//这里之所以数组套数组是因为考虑到sui的.row样式结构需要
//path就是路由的路径了,都要添加到router.js文件中
lists :[
[{
title:'动态',
icon:'icon-app',
path:'/me/moment'
},
{
title:'访客',
icon:'icon-friends',
path:'/me/friends'
}],
[{
title:'文章',
icon:'icon-menu',
path:'/me/articles'
},
{
title:'最佳实现',
icon:'icon-browser',
path:'/me/practice'
}],
[{
title:'阅读',
icon:'icon-code',
path:'/me/read'
},
{
title:'收藏列表',
icon:'icon-star',
path:'/me/love'
}]
]
<user-refer v-bind:lists='lists'>
</user-refer>
<div v-for="list in lists" class="row">
<div class="col-50">
<a class="tab-item" v-link="{ path: list[0].path}">
<!-- 这里不同于首页的tab标签切换,所以不需要replace这个参数,让路由可以后退 -->
<span class="icon" v-bind:class="list[0].icon"></span><br>
<span class="tab-label">{{list[0].title}}</span>
</a>
</div>
<div class="col-50">
<a class="tab-item" v-link="{ path: list[1].path}">
<span class="icon" v-bind:class="list[1].icon"></span><br>
<span class="tab-label">{{list[1].title}}</span>
</a>
</div>
</div>
</div>
-
第三部分的tab键点击进去的详情还是用路由实现,下面这一部分可以用组件封装,路由不用加replace=true参数,返回按钮加上个人中心的路由,详细内容无非就是下拉列表,大致跟首页一样,可以看step1-step8
step10 利用vue-resource 获取数据
-
将首页的数据变为动态获取的
-
首先定义一个json文件,注意格式,否则解析不了会为null,建议可以把自己的json文件在线检测一下
-
news.json
-
-
这个文件放在static/data,要在app.js中做相应的配置
import VueResource from 'vue-resource'
-
参数解释:
-
proces.env.NODE_ENV 在vue-cli搭建的时候bulid文件里面配置好的可以去研究一下
-
Vue.http.options.emulateJSON = true;
If your web server can't handle requests encoded as application/json , you can enable the emulateJSON option. This will send the request as application/x-www-form-urlencoded MIME type, as if from an normal HTML form.--- 给你个眼神自己领悟吧,相信我的翻译还不如自己百度,哈哈
-
然后使用$http去获取数据,返回值response,利用$set设置 Vue 实例的属性值,也就是之前的假数据task1
-
task1数据改变就会引起视图的变化,现在效果是这样的,那么改变一下以前下拉push进去的假数据
-
先每次刷新都能获取一次数据了,基本模拟了后端传输数据的效果
点击进入详情页面
-
希望每个列表都能点击去看文章详情,那么要监听一下click事件,在子组件中,找到CardCon.vue
<div class="card" v-on:click="goDetail">
<slot></slot>
</div>
-
定义goDetail方法,并且使用dispatch冒泡的父组件,传入当前列表的索引,方便获取数据
methods:{
goDetail (){
this.$dispatch('GoDetailRouter', $(this.$el).index())
}
}
-
$emit $dispatch $brocast
-
在父组件中的events中监听子组件的事件,msg就是传入的参数,利用路由go方法跳转到详情页
events :{//监听子组件
GoDetailRouter (msg){
router.go({
name: 'detail',
params: {
Lcontent:encodeURIComponent(this.$data.task1[msg].Lcontent)
}
});
}
}
router.js
'/home/detail/:Lcontent': {
name:'detail',
component (resolve) {
require(['./views/detail'], resolve)
}
}
-
这里的Lcontent是传入的参数,我这里传入的是详情的文字,实际开发一般是传一个id然后从后台获取相应的数据
-
增加detail.vue,要了解router钩子函数才能了解下面这一段代码
route: {
data: function (transition) {
this.$set('Lcontent',decodeURIComponent(this.$route.params.Lcontent));
}
}
切换钩子子函数
-
由于如果我在ready中去获取参数,因为keep-alive将组件缓存所以只会执行一次,但是router的data切换钩子会在每次路由切换的时候调用,保证了当前的参数是最新的