后端已经有golang的模板渲染,后来自己又大概学了下vue,看了一会会就动手写了,感觉还是挺简单的,虽然我不是前端:)

安装

安装很简单,基本就是以下几步:

  • 安装node.js,同时最好安装cnpm,加快安装速度
  • 安装vue,npm install vue –save
  • 安装个vue-cli,用于快速生成项目结构, npm install vue-cli –global
  • 安装顺手的ide,我直接用vscode了,用了vetur这个插件,但是没配置好,但是也够用了

这样安装算是完成了

开始写代码

首先先用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中定义的方法。

这里有一点必须要注意,用这个来获取子控件,必须在子控件已经创建完成的时候来获取,在createdmounted中获取都无法获取到对象,有一个方法是在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上的部署也完成了。

其实到这里,大概的也介绍的差不多了,很多细节其实使用后才会发现问题,然后再去查问题、解决问题,很多问题都可以找到答案,像我这么业余的都能用,相信对大家更不是问题了。

共 0 条回复
暂时没有人回复哦,赶紧抢沙发
发表新回复

作者

sryan
today is a good day