GitHub:https://github.com/liu9183/vue-for-sellApp
历时三个星期,在教程和文档的抽丝剥茧中敲完,中间体会以此为记
本项目使用的vue-cli脚手架,安装过程简单交代, 如想详细了解,请参考官方文档
1.新建工程文件vue-for-sellapp
2.cd vue-for-sellapp
3.npm install vue-cli
4.vue init webpack //项目基于webpack模板
5.npm install //安装依赖
6.npm run dev //启动服务器,默认端口8080
现在挑几个重点难点说一说
一、vue-router
vuejs将多个组件组合为一个应用,却没有解决不同页面的切换问题,也就是说同一个页面区域内,可能需要根据用户的手动切换进行不同的渲染,展示不同的内容。
vue-router提供了很好的路由指向功能,看如下代码
<div class="tab">
<div class="tab-item">
<router-link to="/goods">商品</router-link>
</div>
<div class="tab-item">
<router-link to="/ratings">评论</router-link>
</div>
<div class="tab-item">
<router-link to="/seller">商家</router-link>
</div>
</div>
<router-view :seller="seller"></router-view> <!--路由匹配到的组件将自此处渲染 -->
代码中的<router-link to=""></router-link>标签提供了路由指向功能,当我们点击“商品”时便会把定义好的goods组件渲染到
<router-view :seller="seller"></router-view>
之中,同时将seller对象传入到匹配到的组件中(这里称其为子组件 ),在此子组件中通过props接收到父组件传入的seller对象,一个很巧妙的数据流动方式。
<router-link to="/goods">商品</router-link>默认的渲染结果是<a href="/goods">商品<a>
<router-link>
对应的路由匹配成功,将自动设置 class 属性值
.router-link-active
,我们便可通过此类名为点击事件添加样式。
到此为止,我们都在讲vue-router的原理和使用,那么它是如何引入到项目中的呢,我们看项目的入口文件main.js
import Vue from 'vue';
import VueRouter from 'vue-router'; //首先引入依赖,命名为VueRouter
import VueResource from 'vue-resource';
import App from './App.vue';
import goods from './components/goods/goods.vue';
import ratings from './components/ratings/ratings.vue';
import seller from './components/seller/seller.vue';
Vue.use(VueRouter);//插件注册
Vue.use(VueResource);
const router = new VueRouter({
routes:[
{
path:'/goods',//为每个组件定义路由路径,使用此路径便可以调用对应组件
component:goods
},
{
path:'/ratings',
component:ratings
},
{
path:'/seller',
component:seller
}
]
});
let sellapp=new Vue({
el:'#app',
template: '<App/>',
components:{ App },
router //为实例注入路由,从而让整个应用都有路由功能
})
在这里有的开发者对Vue.use()函数抱有很多幻想,这里推荐一篇文章
Vue.use源码分析
至于vue-resource先不做讨论
二、生命周期钩子
生命周期分八个状态对应八个函数,自始至终分别是
1.beforeCreate()
2.created()-------实例已经被创建完成并调用,数据观测(data observer),属性和方法的运算,watch/event事件回调已准备就绪,但是实例尚未挂载,没有进行DOM树构建 和渲染
3.beforeMount()
4.mounted()-------el
被新创建的vm.$el
替换,组件挂载成功
5.beforeUpdate()-------数据更新时调用,此时还没发生DOM重新渲染以及打补丁,可以在这个钩子中进一步更改状态,这不会触发附加的重渲染过程
6.updated()-------数据更改导致DOM重新渲染和打补丁,在这之后调用该钩子,当这个钩子被调用时,组件DOM已经更新,所以可以执行依赖更新后的DOM节点的操作
7.beforeDestroy()
8.destroyed()
三、深入响应式原理
基于本项目所涉及,这里只提一下vm.$nextTick()
看如下代码
_initScroll() {
this.$nextTick(() => {
if(!this.scroll) {
this.scroll = new BScroll(this.$refs.seller, {
click: true
});
} else {
this.scroll.refresh();
}
})
}
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的
this
自动绑定到调用它的实例上。
此处有坑,看下面代码(seller.vue组件中)
watch: {
'seller' () {
this._initScroll();
//console.log("watch执行了");
//console.log(this.$refs.info);
//console.log(this.seller.infos);
//this._initPics();
}
},
mounted() {
this._initScroll();
//console.log("mounted执行了");
//console.log(this.$refs.info);
//console.log(this.seller.infos);
//this._initPics();
}
_initScroll()
_initScroll() {
this.$nextTick(() => {
if(!this.scroll) {
this.scroll = new BScroll(this.$refs.seller, {
click: true
});
} else {
this.scroll.refresh();
}
})
}
很明显,我要在seller组件中实现页面滚动,为什么需要mounted钩子以及watch监视seller对象共用呢,宝宝心里苦啊,原本以为vue生命周期中mounted钩子调用阶段页面DOM早已构建渲染好,直接滑动便可,确实,我在主页面http://localhost:8080/#/到点击进入http://localhost:8080/#/seller中确实实现了seller页的滚动,可当我再次刷新seller页面(http://localhost:8080/#/seller)时,页面又不能滚动了……
查看控制台输出,发现this.seller.infos获取不到,也就是seller对象并没有传进来,但是this.$refs.info却获取到了,不明白为什么seller对象得不到页面数据渲染却成功了。
然后经过一番纠结困恼,我添加了seller对象监视,在监视到seller对象变化时,再次调用_initScroll(),如此这般,问题竟然奇迹般解决了,不管是seller页面刷新还是从主页面进入seller页,滚动都没问题,我尝试将this.$nextTick方法弃用,发现又不行了……
总结一下:刷新seller页面时,seller对象异步获取,在还没有获取成功便执行了_initScroll(),此时页面依赖seller对象提供的数据撑开,子元素没有超出父元素的界限,自然不会触发滚动,而使用watch监控seller变化,在seller变化完成后又执行_initSrcoll(),便可以触发滚动。
四、父子组件通信
我们知道子组件想获取父组件的对象,可以用props进行传递,那么父组件要获取子组件传递的信息该如何实现呢??
本项目为例,在ratings组件中嵌入了ratingselect子组件,而子组件获取用户的点击信息要传递到父组件进行条件展示,比如客户点击了“满意”按钮,那么父组件要响应展示评论中好评的部分。
看下面代码
<div class="rating-type">
<span @click="select(2,$event)" class="block positive" :class="{'active':selectType===2}">{{desc.all}}<span
class="count">{{ratings.length}}</span></span>
<span @click="select(0,$event)" class="block positive" :class="{'active':selectType===0}">{{desc.positive}}<span
class="count">{{positives.length}}</span></span>
<span @click="select(1,$event)" class="block negative" :class="{'active':selectType===1}">{{desc.negative}}<span
class="count">{{negatives.length}}</span></span>
</div>
select方法
select(type, event) {
if(!event._constructed) {
return;
}
this.$emit('select', type);
}
vue为我们提供了$emit方法,将type绑定到指定的方法(第一个参数指定方法名)上,在这里是select
好了,我们来看父组件如何拿到select方法。
<ratingselect :select-type="selectType" :only-content="onlyContent" :ratings="ratings" @select="select" @toggle="toggle"></ratingselect>
很清楚,在ratingselect组件挂载到父组件ratings时就已经通过v-on传了进来,@select便是将子组件的select方法传到父组件,后面的字符串便是在父组件中对应的函数名,这里还是用select。
最后,看一下在父组件中的函数使用
select(type){
this.selectType = type;
this.$nextTick(() => {
this.scroll.refresh();
});
}
五、web存储
参见我的另一篇博客web storage
六、解析url
项目在部署使用过程中,难免遇到与个人信息绑定的东西,比如id为1234的用户点击收藏后,再次刷新http://localhost:8080/?id=1234#/seller,则依然显示已收藏,这就需要计算机得到用户id,并且缓存相关信息,再次刷新后读取缓存
那么如何从URL中得到用户信息呢
见我一篇博文解析url,生成对应JSON对象
七、初涉Express框架
在本项目中express来获取data.json中的数据,并向客户端传送JSON响应
我已在另一篇博客比较细致地介绍了Express的基本原理和使用方法,请参见——Express框架学习笔记
未完待续……………………………………………………