后端已经有golang的模板渲染,后来自己又大概学了下vue,看了一会会就动手写了,感觉还是挺简单的,虽然我不是前端:)
安装很简单,基本就是以下几步:
这样安装算是完成了
首先先用vue-cli
来快速创建项目,命令为
vue init webpack <project-name>
然后选择一些选项后,项目结构就生成好了。
main.js是入口js文件,基本就是导入各种依赖模块,创建vue实例之类的,基本最后就是new一个Vue实例(js我不是太熟悉,应该是生成一个新的实例):
new Vue({
el: '#app',
store,
router,
components: { App },
template: '<App/>'
})
其中el
是要注入的html中的标签,一般为一个div,这个是在注入在哪里的呢?我们可以看下根目录中的index.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>cabin</title>
</head>
<body style="background: #f4f4f4;">
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
最终vue的代码会被注入到<div id="app"></div>
中,而根vue对象,则是在App.vue中的,代码大致如下:
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style scoped>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/*text-align: center;*/
color: #2c3e50;
margin-top: 60px;
/*padding: 3em;*/
}
</style>
我们写的页面,基本就是在app中,通过router-view来切换页面。
在上面我们可以看到一个router-view
,通过该标签和创建Vue实例的router对象,就可以动态的调整该区域的显示了。
我们在路由中的一级页面,都会在app中的router-view
中展现,比如如下的一个定义:
export default new Router({
routes: [
{
path: "/register",
name: "register",
component: Register
},
{
path: "/user",
name: "user"
},
{
path: "/error",
name: "error"
}
]
})
我们在访问/#/register
后,嵌入到app中的router-view
就会展示Register
组件里的内容。
路由支持嵌套,比如如下的例子:
export default new Router({
routes: [
{
path: '/blog',
component: Blog,
children: [
{
path: '',
name: "blogIndex",
component: BlogSummary,
meta: {}
},
{
path: 'article/:id',
name: "blogArticle",
component: BlogArticle,
meta: {
scrollToTop: true
}
}
}
]
})
我们在/#/blog
的时候,会显示默认的BlogSummary
组件,而在/#/blog/article/1
的时候,则会显示BlogArticle
组件,然而它们不是直接显示在app这一层的router-view
中的,我们可以看下Blog
组件的template:
<template>
<div id="wrapper">
<nav-header @showReview="showReview" ref="nav">Header</nav-header>
<div id="main">
<router-view></router-view>
</div>
<sidebar></sidebar>
<el-dialog title="审核回复" :visible.sync="reviewVisible">
<el-table :data="reviewGridData">
<el-table-column property="date" label="日期" width="180"></el-table-column>
<el-table-column property="name" label="昵称" width="120"></el-table-column>
<el-table-column property="comment" label="评论"></el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button @click="handleReview(scope.row, true)" type="text" size="small">通过</el-button>
<el-button @click="handleReview(scope.row, false)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
子路由还是靠父路由显示的,Blog
组件始终会显示,而它之中的router-view
则可以加载子路由的组件。
用vue写页面,感觉最多的就是数据绑定,这个的确很方便,以前用jquery的时候,都是直接获取dom元素,然后直接操作dom元素,而在vue的开发思想下,则是组件与数据绑定,然后直接操作数据,数据一旦发生改变,则dom元素也会进行相应的改变。
这个东西其实和mfc的DDX有点像,但是DDX的数据和控件绑定之后,还是需要手写UpdateData来控制数据刷新,而这里vue自己会做监听,实现应该是在get、set上又封装了一层,这里没有仔细研究过,毕竟我是个业余前端:)
这里的数据绑定相当的简单,所有的子组件,都要export一个object,里面有个data方法用于返回一个object,里面是组件内的数据,简单的例子如下:
export default {
data: function() {
return {
reviewVisible: false,
reviewGridData: []
}
},
}
然后我们就可以相当自由的绑定dom元素和data中的数据成员了,最简单的方式,就是给子组件传值:
<el-table :data="reviewGridData">
这个简单的语句,就可以把reviewGridData
传给子控件了,这个传值会被子控件的props
拿到。
还有一种则是动态显示内容,当data中的数据变更后,页面中的值也会相应的进行变更:
<div id="comment-count"><h3>{{replyCount}} 条评论</h3></div>
还有一种数据绑定也很方便,假设我们有个按钮有个状态,假设满足了某个状态后,需要不断的闪烁,最简单的当然是动态的为按钮添加一个css属性,加一个class,在vue中很简单,可以绑定class与数据成员的关系:
<img id="user-image" :src="getUserAvatar" :class="userImageClass">
在这里,我们使用了两个:,冒号是v-bind
的缩写,下面还会知道,当父子组件有通信的时候,@则是v-on
的缩写。
这里,第一个冒号是为了动态的获取头像信息,其实也可以写很复杂的表达式,但是用了computed
后,则相当的方便,可以直接使用对应函数的返回值:
computed: {
getUserAvatar() {
let user = this.$store.state.userInfo
if ("" == user.avatar) {
return "static/image/my.png"
}
return "static/image/" + user.avatar + ".png"
}
}
第二个冒号则是绑定一个object,通过object的值,也就是true或者false来决定某个class是否需要:
userImageClass: {
'msg-tip': false
}
当我们需要某个class的时候,只需要动态的改变data中相应object的key,让它变为true就可以了。
任何ui,包括各种原生的gui和网页,都逃不开通讯。在vue中,至少在2.5版本中,父子组件通讯还是有点儿不一样的。
父组件向子组件传递数据,上面也提过了,可以用props来传递,比如子组件有以下的props:
props: ["articleId", "summary"]
父组件可以通过
<xxx :articleId="articleId" :summary="summary"></xxx>
来绑定自己data中数据成员的值,假设值发生改变,那么子控件中的值也会发生改变。当然这个值理论上应该在子控件中应该是只读的,子组件向父组件传递事件,不要改变props中的值。
还有一种方式可以通知子组件,就是直接调用子组件中的方法,我们在父组件中定义子组件的时候,给它一个ref
:
<mk-editor :ishowTitle="showTitle" @save="saveContent" ref="editor"></mk-editor>
那么我们可以在父控件中通过以下方式来访问子控件,同时可以调用子控件中methods中定义的各种方法:
self.$refs.editor.updateEditorContent(self.title, self.mkcontent)
updateEditorContent
就是子控件中methods中定义的方法。
这里有一点必须要注意,用这个来获取子控件,必须在子控件已经创建完成的时候来获取,在created
和mounted
中获取都无法获取到对象,有一个方法是在mounted
后通过一次延迟调用来获取子控件对象:
mounted() {
let self = this
this.$nextTick(() => {
self.getArticleContent()
})
}
methods: {
getArticleContent() {
let rname = this.$route.name;
let self = this;
this.articleId = this.$route.params.id;
this.showTitle = true;
// Pull content from article
api.getArticle(
function(res) {
if (res.success) {
self.title = res.res.title;
self.mkcontent = res.res.content;
self.$refs.editor.updateEditorContent(self.title, self.mkcontent)
} else {
self.$message.warning(res.error);
}
},
this.articleId,
0, 1
);
},
在这里通过延迟调用,正确的获得了子控件对象并调用其方法。
子控件通知父控件,通过emit
一个事件来通知,这个和qt的事件与槽机制有点像,qt可以emit一个时间出来,假设emit的事件被slot注册了,那么会被回调,当然这是我以前接触过的qt 4.7
之类的版本的机制了,现在有没有改变我也不清楚了。
在vue中,我们可以通过:
self.$emit("deleteComment", self.reply.id);
来向父组件发出消息,参数分别是事件名称和参数,父组件假设需要处理子组件的消息,则必须监听该事件:
<comment-item v-for="item in replys" :reply="item" :key="item.id" :uri="uri"
@updateReply="updateSubReply" @deleteComment="deleteComment">
这样我们就可以在methods中定义相应的方法来处理该事件了:
deleteComment(commentId) {
let final = []
for (let reply of this.replys) {
if (reply.id == commentId) {
continue
}
final.push(reply)
}
this.replys = final
}
比如上面贴的这个例子,我们获得到了emit出来的commentId
并进行相应的处理。
至于跨层级的消息通讯,比如子组件向兄弟组件发消息,这里就比较麻烦,可以通过新建一个vue对象来专门负责事件注册和回调:
import Vue from 'vue'
let eventBus = new Vue()
export default {
bus: eventBus,
eventUpdateCategories: "updateCategories"
}
当我们想要监听一个事件的时候,我们可以注册一个回调函数在这个对象上:
eventbus.bus.$on(eventbus.eventUpdateCategories, this.pullCategories)
当然注册了,我们也需要把这个事件回调在当前组件被销毁前移除,不然肯定会有问题,所以我们在beforeDestroy
方法里面移除当前的回调函数:
eventbus.bus.$off(eventbus.eventUpdateCategories, this.pullCategories)
然后,我们在想要发出事件的地方,可以这样来发出一个事件,同时让监听者们的回调执行:
eventbus.bus.$emit(eventbus.eventUpdateCategories)
这个其实是一个很清晰的观察者模式,所有的事件注册以及回调,都在一个eventbus
里面。
上面其实是一个解决办法,但是在新版本的vue中,推荐使用vuex来解决这个问题,我在博客的实现中也使用了vuex,这里就不介绍了。
在部署中遇到不少问题,有一种方案是以前就试过的,所以立马可以部署起来,这种方法就是golang开一个静态文件服务的handler
,然后就可以把编译好后的文件放进去,就可以访问了。
其实在部署的时候,不是部署在根的,我目前部署在了/view/
下,所以引用静态资源需要注意,我的静态资源都放在了static
文件夹下,在源码中引用静态资源的话,推荐使用相对路径来引用。还有一个注意的是,这种部署方式,在config/index.js
中的assetsPublicPath
,必须也要设置为/view/
,这样assets
目录中的源码才会被正确引用。
在实际的线上服务,我使用nginx
做了一层反代,web
服务监听8080端口,然后写规则,让一部分请求反代到web
服务上。然后我们继续写规则,让/view/
这个路径下的所有请求,都访问vue
打包生成的静态文件上。于是我们需要编写以下的规则:
location ^~ /view/ {
alias /home/view/;
}
这样所有的带/view/
前缀的,最终都会到指定的静态文件目录中去。
还有一个问题,假如访问/view
的话,无法访问到我们/view/
下的东西,所以我们得有一个重写规则:
rewrite ^/view$ /view/ permanent;
这样就OK了,在nginx
上的部署也完成了。
其实到这里,大概的也介绍的差不多了,很多细节其实使用后才会发现问题,然后再去查问题、解决问题,很多问题都可以找到答案,像我这么业余的都能用,相信对大家更不是问题了。