2018年01月18日

Vue开发单页面网站学习要点

作者 非鱼

接上一篇,开发环境建好了,就好好写代码吧。后端的django没什么特别的,定义业务类,一个一个写业务接口就可以了。倒是Vue这个东西,有不少弯路要走。因此零零散散学习到的一些要点放在这儿,记录一下:

1、项目核心结构:一个空白的index.html文件,body中只有一个id=”app”的div定义,运行起来这个div会被App.vue文件中的template定义替换掉。因此,页面的实际结构是在App.vue中实现的。假设你的网站是标准的bootstrap架构,那么它的content/header/footer/sidebar等等都是在App.vue中定义的。(或者分别抽象成一个独立的components组件再在App.vue中引用起来,实际逻辑是一样的。)然后,App.vue中有一个router-view的定义,每个子页面的路由,会将该子页面的代码替换进来,比如首页,列表页,明细页,编辑页这样。

2、每个子页面是一个.vue文件,其中有三部分,template定义html内容,script定义页面的数据逻辑,style定义页面的元素样式。微信小程序也是这样的结构,只是把这三块分别放到了三个不同的文件里。

3、router/index.js负责了整个项目的页面逻辑定义,将一个一个的.vue文件跟URL映射对应起来,点击到哪个URL的时候,就调用哪个.vue文件来执行并将页面渲染到上级的中。在routes中的每一个route里定义children子路由,它就可以做成多层route嵌套。

4、router实现默认是hash格式,也就是整个站点的URL实际都是根/,其它子页面都是在它后面加#/home/这样来实现的,页面的变化变的是#后面的部分。但是它也支持history模式,在router/index.js里面routes定义前面加一行 mode: ‘history’, 就可以了,URL就会变成正常的样式,但是对nginx的配置要求就会高一些。要在nginx里使用这样的转发配置:

location / {
  try_files $uri $uri/ /index.html;
}

5、添加全局jquery对象:在build/webpack.base.conf.js文件头部添加一行

const webpack = require('webpack')

然后在文件最后node:段的定义前面加上一段plugins定义:

plugins:[
    new webpack.ProvidePlugin({
        jQuery: "jquery", $:"jquery"
    })
],

这样就可以在任意vue文件里的script里面直接使用$来使用jquery的代码了。

6、如果在vue文件里使用jquery来处理ajax事件,要小心this的使用。比如在Home.vue里,页面加载完成后调用一个ajax请求,将返回结果更新到某个字段定义里,那么在$.get的回调里面直接使用this.json就不行,需要在$.get之前将this对象保存下来使用。

export default {
  name: 'Home',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      json:{}
    }
  },
  mounted(){
    var vm = this;
    $.get('/api/', function(json){
      vm.json = json;
    });
  }
}

7、全局调用更新页面Title。Vue的定义是body里面的div,页面的标题Title属性是在这个范围之外的,没有办法通过Vue的变量去改变它。所以一般只能使用document.title方法直接修改。但是这样修改在微信里会不起作用,微信取不到修改后的标题。因此要使用一个特殊的hack技巧让微信里生效。定义一个utils.js文件:

let setPageTitle = function (t) {
    document.title = t;
    let iframe = document.createElement('iframe');
    iframe.style.visibility = 'hidden';
    iframe.style.width = '1px';
    iframe.style.height = '1px';
    iframe.src = '//m.baidu.com/favicon.ico';
    iframe.onload = function () {
        setTimeout(function () {
            iframe.remove();
        }, 10);
    };
    document.body.appendChild(iframe);
};

module.exports = {
    setPageTitle
};

然后在其它需要修改页面标题的地方引用它,比如在上面的Home.vue里面,在export之前加上(假设utils.js放在router目录下)

import { setPageTitle } from '../router/utils';

然后在mounted方法里面加上一行:

setPageTitle('Home page.');

这样每次回到首页的时候页面标题就会被设置为Home page.

8、在子页面中修改App.vue里定义的变量。通常在App.vue里定义的Sidebar或者Header,Footer之类的,里面都会有一些动态变量的字段,将字段定义在App.vue里,在下级页面里想要修改这个变量值的时候,只需要使用

this.$parent.headerTitle = 'Home Header title.';

9、打包方式:默认的配置下npm run build调用webpack打包会把所有的页面内容打包成一个app.js文件和一个app.css文件,HTML内容也包含在js文件里,index.html文件直接引用了这个js文件。这样导致的问题是如果项目比较大,几十甚至几百个页面,打包出来的js文件特别大,比如十几M,那index.html首次加载的时候就慢的没法用了。因此,这时候要使用lazy load的方式,拆分打包后的文件,并且让它按需加载。

首先将Router路由定义里面各个js的引用方式改一下,将

import Home from '@/pages/Home'

这种引用方式改成

const Home = () => import('./Home.vue')

也就是将这个变量变成一个Promise的方法声明,然后下面的Route定义不用动,还是{ path: ”, component: Home }。
然后在frontend/build/webpack.base.conf.js里面的output定义里增加一个配置:

chunkFilename: '[name].js',

同时在下面的modules/rules里面的test: /\.js$/段增加一个配置:

options: {
          plugins: [require('babel-plugin-syntax-dynamic-import')]
        }

这样打包的时候webpack才知道怎么命名每一个块文件(保持其原有文件名)。

但是这样又会导致打包后每一个页面都是一个独立的js文件,而通常我们更希望将一组相关的模块打包成一个js文件一起加载,webpack还提供了一个特殊的注释语法来实现:

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

这样三个文件就会打包一同一个js文件里去一起加载完成。

10、这里有个开源的Vue模板项目,以单页面的方式实现了全套的饿了么手机端网站,可以作为最佳实践或者类似项目的基础结构来使用。
https://github.com/bailicangdu/vue2-elm