入门请参考这篇文章:Vue构建单页应用最佳实战
在此记录下在我使用Vue.js 2.0开发较大型的单页应用时遇到的困难。
写文章不容易,如果这篇文章对你有帮助,请给我的github仓库加个star~
github项目地址
项目结构
.
├── build/ # webpack config files
│ └── ...
├── config/
│ ├── index.js # main project config
│ └── ...
├── src/
│ ├── main.js # app entry file
│ ├── App.vue # main app component
│ ├── components/ # ui components
│ │ └── ...
│ └── assets/ # module assets (processed by webpack)
│ └── ...
├── static/ # pure static assets (directly copied)
├── test/
│ └── unit/ # unit tests
│ │ ├── specs/ # test spec files
│ │ ├── index.js # test build entry file
│ │ └── karma.conf.js # test runner config file
│ └── e2e/ # e2e tests
│ │ ├── specs/ # test spec files
│ │ ├── custom-assertions/ # custom assertions for e2e tests
│ │ ├── runner.js # test runner script
│ │ └── nightwatch.conf.js # test runner config file
├── .babelrc # babel config
├── .editorconfig.js # editor config
├── .eslintrc.js # eslint config
├── index.html # index.html template
└── package.json # build scripts and dependencies
参考链接:
Handing Static Assets
static/
目录下的文件不会被webpack处理,可以在index.html中直接引用其中的资源
参考链接:
Transition Effects
Vue提供多种在items被插入,更新或者从DOM中移除时应用过渡效果的方式。它包括以下的方式:
在此处,我主要讲讲使用transition为mdl-spinner添加CSS动画的经验。
Vue提供4种应用在 enter/leave 过渡上的class:
v-enter
: 进入的初始状态。在元素被插入前应用,一帧后被移除。v-enter-active
: 进入的激活和结束状态。在元素被插入前应用,在过渡/动画结束后移除。v-leave
: 离开的初始状态。在离开过渡效果触发的时候应用,一帧后被移除。v-leave-active
: 离开的激活和结束状态。在离开过渡效果触发的时候应用,在过渡/动画结束后移除。上代码:
<transition name="spinner-move">
<div v-show="$store.state.common.loading">
<div class="mdl-spinner mdl-js-spinner is-active loading">div>
div>
transition>
.spinner-move-enter-active {
position: relative;
animation: move-in .5s;
}
.spinner-move-leave-active {
position: relative;
animation: move-out .5s;
}
@keyframes move-in {
0% {
top: -50px;
}
50% {
top: 10px;
}
100% {
top: 0px;
}
}
@keyframes move-out {
0% {
top: 0px;
}
50% {
top: 10px;
}
100% {
top: -50px;
}
}
参考文章:
vm.$watch( expOrFn, callback, [options] )
vuejs怎么watch对象里某个属性的变化呢?
观察Vue实例上的一个表达式或者computed function的改变。
我的需求是,设置Tip组件出现的条件。
1.在vuex中的state中设置一个属性及其mutation:
const state = {
tip: {
message: '',
actionHandler: function (event) {},
timeout: 2000,
actionText: ''
}
//
}
const mutations = {
[SET_TIP] (state, tip) {
state.tip = tip
}
//
}
2.新建Tip.vue:
<template>
<div id="snackbar" class="mdl-js-snackbar mdl-snackbar">
<div class="mdl-snackbar__text">div>
<button class="mdl-snackbar__action" type="button">button>
div>
template>
<script>
export default {
data () {
return {
//将Store.state绑定在data上
data: this.$store.state
}
},
watch: {
// 但state中的tip改变时调用showTip方法
'data.doc.tip': 'showTip'
},
methods: {
showTip () {
let snackbarContainer = document.querySelector('#snackbar')
snackbarContainer.MaterialSnackbar.showSnackbar(this.data.doc.tip)
}
}
}
script>
3.在需要时commit:
commit(types.SET_TIP, {
message: err.statusText,
actionHandler: function (event) {},
timeout: 2000,
actionText: 'Undo'
})
参考链接:
Computed Properties
我想使loading组件根据当前路由清空展示不同的样式,由于判断表达式比较复杂且不止在一个地方调用了该判断表达式,我使用了computed属性:
computed: {
forLogin: function () {
return this.$route.path === '/login'
}
}
<div>
name="spinner-move">
<div v-show="$store.state.doc.loading && forLogin" class="loading-wrapper loading-login-wrapper">
<div class="mdl-spinner mdl-js-spinner is-active loading">div>
div>
name="spinner-move">
<div v-show="$store.state.doc.loading && !forLogin" class="loading-wrapper">
<div class="mdl-spinner mdl-js-spinner is-active loading loading-common">div>
div>
div>
子组件通信参考链接:
Props
通过阅读官方文档可知,Vue.js可以通过Pros将数据从父组件中传递到子组件,但官网给的例子一般是这样的:
Vue.component('child', {
// camelCase in JavaScript
props: ['myMessage'],
template: '{{ myMessage }}'
})
props传递的数据直接在模板中使用,但是,开发中还会经常遇到另外一种情况,在中引用props的数据,事实上,官方文档也给了例子:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
然而,非常遗憾的是,这段代码在我这里跑不通,于是,我就去研究Vue实例的生命周期图:
注意右下角的圆环处,当数据改变时,virtual DOM 会重新渲染更新,结合上面提到的props传递的数据直接在模板中使用可以正常引用,所以我想到了下面这种方式:
<template>
<div>
<span style="display:none;">
{{initialCounter}}
span>
div>
template>
<script>
export default {
props: ['initialCounter'],
updated () {
// props中的值只有在updated钩子中才能正常访问
// next(this.initialCounter)
}
}
script>
模板中引用props的值,会触发updated钩子,此时this.initialCounter已经被初始化可以被引用了,至于为什么要多次一举,也许就要问尤大大了。
正确渲染<div id="app">
<h1>Hello App!h1>
<p>
<router-link to="/foo">Go to Foorouter-link>
<router-link to="/bar">Go to Barrouter-link>
p>
<router-view>router-view>
div>
组件是一个 functional 组件,渲染路径匹配到的视图组件。
渲染的组件还可以内嵌自己的
,根据嵌套路径,渲染嵌套组件。
踩到的坑:
vue-router组件渲染和virtual DOM更新构成了竞争情况
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。参考链接:
懒加载 · GitBook
今天在试图在使用百度地图api的时候,动态加载远程script遇到了大麻烦的回调,最后用组件内的钩子的钩子解决。 代码:参考链接:
导航钩子 · GitBook
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
next(() => {
loadScript()
initialize()
})
}
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用『动态路径参数』(dynamic segment)来达到这个效果:参考链接:
动态路由匹配 · GitBook
<div id="app">
<p>
<router-link to="/user/foo">/user/foorouter-link>
<router-link to="/user/bar">/user/barrouter-link>
p>
<router-view>router-view>
div>
const User = {
template: `User {{ $route.params.id }}`
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
const app = new Vue({ router }).$mount('#app')
上面是官方文档中的例子,可以发现在传递路径参数的时候,使用的是固定的值,那么,当需要传递动态的路径参数时该如何做呢? 答案是使用v-bind 的 JS 表达式,直接上例子:
<table>
<tbody>
<tr v-for="(item, index) in items">
<th >{{ index }}th>
<td>
<router-link v-bind:to="{ name: 'example', params: { id: index }}">{{ item }}router-link>
td>
tr>
tbody>
table>
解释一下,`:to`属性中的值会被解析成js表达式,所以例子中的`index`可以被识别成当前循环元素的索引,进而体现路径匹配中的动态性。
/user/foo/profile /user/foo/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
借助 `vue-router`,使用嵌套路由配置,就可以很简单地表达这种关系。 在由`vue-cli`创建的项目中, `` 是最顶层的出口,渲染最高级路由匹配到的组件。同样地,一个被渲染组件同样可以包含自己的嵌套 ``。例如,在 User 组件的模板添加一个 ``:
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}h2>
<router-view>router-view>
div>
`
}
要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置:
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 中
path: 'posts',
component: UserPosts
}
]
}
]
})
上面的例子可以符合简单的业务逻辑,但是一旦业务逻辑变得复杂起来,就需要自己摸索一定的技巧了。这就是这篇文章下一个探讨的话题
/user/:id/posts/:id/comments
+------------------+
| User |
| +--------------+ |
| | Posts | |
| | ------------ |
| | Comments | |
| | ------------ | |
| +--------------+ |
+------------------+
上面的例子嵌套了2层路由,且包含了2层的动态路由匹配,一般的方法无法解决这个问题,需要一些特殊的方法。 其实上述提到过,:to中包含的是js表达式,所以可以以此为切入点,将:to的值与`computed`中的变量绑定:
<router-link :to="link">
test
router-link>
computed: {
link () {
return '/user/' + this.$route.params.id + '/chart/' + this.id
}
},
前面已经提到过,APP.vue中包含的
作为顶层路由渲染出口,其子组件中可以包含嵌套的
路由渲染出口。按照上面的思路,我们可以轻松的实现二级菜单甚至多级菜单。
现在考虑一下这种情况,在我们的应用中,侧边一个Drawer作为一级菜单,页面顶部一个TapBar作为二级菜单,类似这样:
我们可以把二级菜单的内容放置在顶层路由渲染出口中渲染,这么做有几个好处:
层级减少,提高渲染性能;.router-link-active
属性添加链接被点击后效果的CSS样式;那么,该如何实现呢?
可以使用重定向进行模拟
'/children1' => '/parent/children1'
'/children2' => '/parent/children2'
{
path: '/parent',
redirect: '/parent/children1'
}
<router-link to="/parent">一级菜单</router-link>
以上!
setTimeout
中的this
// 因为setTimeout中的this指向window,所以得耍个trick,保存当前对象this
let that = this
setTimeout(function () {
that.loadingDisplay = 'none'
}, 1000)
classList
改变element
的class
if (this.lastElement !== null) {
this.lastElement.classList.remove('activate')
}
this.lastElement = event.target
event.target.classList.add('activate')
使用 `stroke-dasharray` 属性实现如下效果:参考文章:
小tip: 使用SVG寥寥数行实现圆环loading进度效果
图中的小圆点使用是用 `` 画的小实心圆:
<circle cx="240" cy="94" r="3" fill="#9e9e9e">circle>
虚线使用 `` :
"240" y1="88" x2="240" y2="55" stroke="#9e9e9e" stroke-width="2" stroke-dasharray="2 2" />
let scrollTop = document.getElementById('scroll').scrollTop
let scrollBottom = scrollTop + window.innerHeight
if (scrollTop > 20 && scrollBottom < (ribbonHeight + servicesHeight / 2)) {
intro1.classList.add('vertical-anim')
device1.classList.add('vertical-anim')
device2.classList.add('vertical-anim')
}
大功告成!
overflow 属性规定当内容溢出元素框时发生的事情。 语法参考链接:
overflow - CSS | MDN
/* 默认值。内容不会被修剪,会呈现在元素框之外 */
overflow: visible;
/* 内容会被修剪,并且其余内容不可见 */
overflow: hidden;
/* 内容会被修剪,浏览器会显示滚动条以便查看其余内容 */
overflow: scroll;
/* 由浏览器定夺,如果内容被修剪,就会显示滚动条 */
overflow: auto;
/* 规定从父元素继承overflow属性的值 */
overflow: inherit;
当我在设计登录界面的卡片框的时候,我想在基本的卡片框上方露出一小截白边,这个技术使用`:before`伪类可以坐到,但也需要在改容器中设置`overflow`属性的值。 **`overflow: hidden;`效果:**
**`overflow: visible;`效果:**
::before 会创建一个作为当前元素子元素的伪元素。常通过 content 属性来为一个元素添加修饰性的内容。 此元素默认为行内元素。参考链接:
::before (:before) - CSS | MDN
/* CSS3 语法 */
element::before { 样式 }
/* (单冒号)CSS2 过时语法 (仅用来支持 IE8) */
element:before { 样式 }
/* 在每一个p元素前插入内容 */
p::before { content: "Hello world!"; }
::after伪元素匹配一个作为当前元素最后一个子元素的伪元素。常通过 content 属性来为一个元素添加修饰性的内容。 此元素默认为行内元素。 我使用::after伪类为当前激活链接添加一个标记,效果如图:参考链接:
::after (:after) - CSS | MDN
使用下面这个box-shadow属性:参考链接:
CSS3 box-shadow 属性
box-shadow: #666 0px 0px 10px;
为载入进度圆环加上如下阴影效果:
一开始采用方式是` `使用相对布局,父元素加上属性`text-align: center;`,但是无效,而且相对布局中的元素就算被改变位置之后,依旧会占据原来的位置,造成大面积留白,非常丑,故弃之。 后来尝试使用绝对布局,因为给` `设定的宽度和高度是固定的,于是就可以结合left属性进行水平居中。
left: 50%;
margin-left: -50px;
这两行代码的意思是,首先设置`
`的最左边距离左边框50%,然后因为半径是50px,所以通过设置margin-left将元素整体左移50px,使圆心水平居中。
我使用下面的代码:参考文章:
Flex 布局教程:语法篇
justify-content: center;
对齐我的TapBar:
两端对齐,项目之间的间隔都相等,并且在交叉轴上居中对齐:
justify-content: space-between;
align-items: center;
sudo useradd xxx
sudo passwd xxx
sudo
命令,如果你按照上述的步骤,你应该会遇到下面这个错误:XXX is not in the sudoers file
这是因为 ‘xxx’用户不在`sudoers`组中。 我参考这篇文章解决了这个问题:
现在,一个具有sudo权限的普通用户已经创建好了。Linux有问必答:怎样解决“XXX is not in the sudoers file”错误
我参考了这篇文章:
在centOS 6下部署node
vue-cli生成的项目可以使用npm run dev
命令直接让程序跑起来,但是生产环境中还使用开发环境的服务器是不行的,我们需要的服务器性能要高,占用内存要小,并发性要好,于是我的目光投向了服务器界大名鼎鼎的Nginx.
在安装Nginx的工程中遇到的问题和安装Node.js类似,我参考下面这篇文章成功的安装的Nginx:
CENTOS 6.5 配置YUM安装NGINX
Beginner’s Guide
在工程目录下使用如下代码生成dist目录
npm run build
参考官方教程添加一个server节点,其中的root参数绑定dist文件夹的绝对路径即可。
其中一开始遇到一个bug,
nginx 403 forbidden
查证后发现是权限问题,参考下面这篇文章,把nginx的启动用户改成目录的所属用户即可。
nginx 403 forbidden 二种原因
未完待续……