萝三画室

仿网易云音乐PC-总结Part4(VUE 2.x)

本篇是仿网易云音乐PC-总结的第四部分,Part4-VUE。本项目仅使用了VUE这一个前端框架,由于其简单易用,具有十分友好的文档,使新手如我也可以直接上手。本文主要总结在项目过程中遇过的问题和使用心得,全部官方文档请戳https://cn.vuejs.org/v2/guide/
仿网易云音乐PC全部项目请戳->>>>> https://github.com/love3forever/gogo3

使用VUE

使用VUE.js有两种方法:

  • 直接在HTML文件中引入

    1
    <script src="https://unpkg.com/vue"></script>
  • 通过脚手架vue-cli创建vue项目(需要Node环境)

本项目使用第二种方法。

配置环境

安装node.js

进入https://nodejs.org/en/download/直接下载并安装。
安装完成后,可以命令行工具中输入 node -v 和 npm -v,如果能显示出版本号,就说明安装成功。如果已有node环境,建议更新版本(低版本node可能无法识别vue)。

1
npm install -g npm

安装vue-cli

1
npm install -g vue-cli

安装完成后,可以命令行工具中输入 vue -V,如果能显示出版本号,就说明安装成功。如果安装失败,先清理缓存再重新安装。

1
npm cache clean

新建项目

  1. 通过命令行进入项目目录,然后输入
    1
    vue init webpack Vue-Project

执行后,按照提示手动输入项目名称、描述、作者、打包方式、是否安装vue-router、是否使用ESLint检查代码、是否建立单元测试等内容后,会在当前目录生成一个以输入的项目名称为文件名的项目文件夹。

  1. 然后进入项目文件件夹,安装依赖项

    1
    npm install
  2. 安装vue-router和vue-resource模块(可选)
    vue-router和vue-resource是官方提供的扩展,分别用于配置路由和处理http请求,可根据实际情况选择是否安装。

    1
    cnpm install vue-router vue-resource --save
  3. 安装完成后,启动项目

    1
    npm run dev

这时应自动启动chrome,并跳转到localhost:8080页面了。

  1. 关于修改配置文件
    项目默认使用8080端口,如果已被占用,可以进入config->index.js中修改配置文件使用其他端口。

打包项目

项目开发完成后,进入项目文件夹输入

1
npm run build

将项目打包,打包成功后会生成dist文件夹,这个文件夹里的文件就是最终的成果,它部署到服务器上就OK辣。

项目结构

项目的目录结构如所示。我们需要关注的是src目录、static目录和index.html。

  • src文件夹是项目源码目录,其中初始包括三个文件夹和两个文件:
    • assets文件夹 一般存放图片等资源
    • components文件夹 存放以.vue为后缀的组件
    • router文件夹 其中的index.js用于配置路由
    • App.vue 默认主组件,可以理解为其他所有组件的父组件
    • main.js 入口js文件,主要作用是初始化vue实例并使用需要的插件
  • static文件夹是纯静态资源文件夹,其中的文件不会被webpack构建
  • index.html是入口页面

组件结构

一个vue组件就是一个以.vue为后缀的文件,它包括template、script、style三个部分,分别写入HTML、JavaScript、css代码。结构例子直接戳下边链接中的任何一个文件
https://github.com/love3forever/gogo3/tree/master/src/components
下面说说每个部分需要注意的地方

  • template
    template标签内是html元素的书写区域,这里需要注意的是,下面只能有一个孩子节点(独生子女哇)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--正确-->
    <template>
    <div>
    <div></div>
    <div></div>
    </div>
    </template>
    <!--错误-->
    <template>
    <div></div>
    <div></div>
    </template>
  • script

    1. script标签内是javascript的书写区域,可以通过import…from引入其他vue组件或方法。如果引用了其他vue组件,则需要在export default中注册组件,不注册就使用会报错:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <script>
      import mode from './user-fansfavmode'
      export default {
      components: {
      mode
      },
      }
      </script>
    2. 通过vue-cli方式搭建的项目,export default中如果有data,则一定要写成函数的形式,数据写在函数return的对象中。 如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <script>
      export default {
      data (){
      return {
      a:1,
      b:2,
      }
      }
      }
      </script>
  • style
    style标签中写的就是css样式啦,唯一需要注意的一定就是,如果在style标签中加入了scoped属性,那么这个标签里的所有样式仅供它所在的组件使用,其他组件无法使用;而如果不加scoped属性,那么这个标签里的样式可供全局使用。

data

前面已经说过,通过vue-cli创建的vue项目,组件中的data只能以函数的形式return数据。关于data还需要注意的一点是:只有在data中声明的数据才是响应式的。在data之外,可以通过vue.set将响应式属性添加到data中已有的响应式数据上,但无法创建一个根级别的响应式属性。

对于数组,则有另外的问题。受JS限制,Vue 不能检测以下变动的数组:

  1. arr[index] = newValue;
  2. arr.length = newLength;

也就是说,通过以上两种方法改变数组成员,即便数组是响应式的,console.log也确实发现了变化,但这个变化不会被vue识别到。想要将数据的改动具有响应式,可以通过arr.splice()或vue.set解决。

props

props 是定义在子组件中的属性,用于接收父组件传来的数据。格式:

1
2
3
4
5
<script>
export default {
props:['prop1','prop2']
}
</script>

关于props,需要注意:

  • 数据流是单向的,props是用来接收父组件的数据的,并不能向父组件传递数据

  • 通过v-bind可以实现动态props,父组件传递的值发生变化,子组件也会同步变化。这就意味着,可以用watch观测动态props的变化。

  • 不能直接修改props的数据。直接修改props接收的数据是不被允许的,在子组件中的props是父组件传递的数据的引用,在子组件中改变props的值也会导致父组件变化。如果想对props进行修改,可以定义一个计算属性处理props并返回。

methods

methods是一个对象,以键值对的形式存储所在组件中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
methods:{
addAT:function(){
if (this.cmtContent.length < this.maxlength){
this.cmtContent += "@";
}
},
tabShowMore:function(){
this.songs.isShowMore = !this.songs.isShowMore;
},
}
}
</script>

这里需要注意的是,methods中的方法必须以属性名:函数的形式定义,不能使用箭头函数。因为箭头函数会将this指针绑定到函数定义时所在的作用域。在方法内部可以使用箭头函数。

钩子函数

贯穿一个vue实例全生命周期的有以下钩子函数,它们的作用就是在不同阶段执行某些操作。

  • beforeCreate
    vue实例创建后立即执行这个钩子函数,此时数据和事件都没有被初始化。也就是说,这里无法直接访问data、methods等。
    这个钩子里比较适合写loading和http请求(本项目使用vue-resource)。因为http请求是异步的,通常请求返回时已经vue实例已经created了,可以直接访问data和methods等属性和方法。为了稳妥以及避免造成混乱,还是建议把http请求写在created钩子中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script>
    export default {
    beforeCreate:function(){
    var result = this.result;//错误,此时result未初始化,无法访问到
    this.$http.get('http://123.206.211.77:33333/api/v1/index/detail')
    .then(response => {
    this.result = response.data;//正确,http请求正常返回后,通常result已被初始化,可以访问到
    })
    .catch(response => {
    console.log(response)
    });
    },
    }
    </script>
  • created
    数据、事件等属性和方法初始化完毕,但DOM并未生成,el属性不存在。在这里可以直接访问data和methods等,但无法获取DOM元素。
    这个钩子里比较适合写Loading结束、http请求、一些初始化操作。

  • beforeMount
    模板挂载之前,el属性存在,但并未挂载,仅仅是“占坑”。
  • mounted
    模板挂载之后,el属性存在。
    这个钩子可以配合this.$nextTick实现依赖DOM的操作,如监听事件等。
  • beforeUpdate
    组件更新之前执行
  • updates
    组件更新之后执行
  • beforeDestory
    组件销毁前执行,常用确认是否执行销毁动作。
  • destoryed
    组件销毁后执行,常用于收尾清除数据。
  • activated
    针对keep-alive,组件被激活时执行
  • deactivated
    针对keep-alive,组件被移除时执行

条件渲染 v-if vs v-show

v-if和v-show都是控制DOM元素是否渲染的。他们存在以下区别:

  • v-if在条件为假时,不去渲染元素;v-show始终渲染,仅仅是通过设置display属性来控制元素的显示和隐藏
  • v-if 可以写在template标签内控制整个模板的渲染与否,v-show不可以,只能写在template标签的子元素中
  • v-if 可以与v-else、v-else-if联合使用;v-show不可以

综上,v-if适用于运行时条件不太可能改变的场景,比如:异步请求数据,在数据返回之后渲染页面时使用v-if更好;v-show适合可能频繁切换元素的显示和隐藏的场景,比如hover。

computed属性 vs watch属性

  • computed属性
    简单说,一个computed属性是依赖于data中一个或几个数据“计算”得到的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script>
    export default {
    data () {
    return {
    others: [{ num: 5, name: "title"}],
    }
    },
    computed:{
    cmtFrontMore:function(){
    return this.others[0].num>2;
    },
    },
    }
    </script>

computed属性是响应式的。这里的响应式有以下含义:

  1. computed属性跟随其依赖的变化而变化。如上栗,computed属性cmtFrontMore是依赖于data中的others属性的,others变化也会触发cmtFrontMore的变化。
  2. 默认情况下computed属性的变化只会由其依赖的变化触发,任何在computed属性外部试图更改属性都会被拒绝。如果想实现这一的效果,则需要为属性提供set方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <script>
    export default {
    data () {
    return {
    others: [{ num: 5, name: "title"}],
    }
    },
    computed:{
    cmtFrontMore:{
    get: function(){
    return this.others[0].num === 2;
    },
    set: function(newVal){
    if (newVal){
    this.others[0].num = 2;
    } else {
    this.others[0].num = 5;
    }
    }
    }
    },
    }
    </script>

这样,this.others[0].num变化会引起cmtFrontMore变化;this.cmtFrontMore变化也会触发this.others[0].num变化。

  • watch属性
    watch属性是一种观测并相应数据变化的属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <script>
    export default {
    data () {
    return {
    others: [{ num: 5, name: "title"}],
    cmtFrontMore: false,
    }
    },
    watch:{
    others: function(newVal,oldVal){
    if (newVal[0].num === 2){
    this.cmtFrontMore = true;
    }
    },
    },
    }
    </script>

如上栗,当others属性发生变化,则执行watch中others对应的函数。

  • 比较
    大多数情况下,computed属性更适合,但是在执行异步操作或开销较大的操作时,就需要使用watch了。可以理解为,computed属性是要返回一个依赖于某个数据变化而变化的数据,而watch属性是要在发现某个数据变化后,dosomething。

this.$nextTick

Vue 异步执行DOM更新。也就是说,当你更改了一个响应式数据,它所涉及的组件并不会立即重新渲染。数据更改触发DOM更新的流程是:
数据更改->执行完其它同步命令->DOM更新。
因此,在数据更改后试图立即操作更新后的DOM会失败,此刻DOM还未更新呢!
为了解决这个问题,我们可以使用this.$nextTick(callback)。在数据更改后立刻使用this.$nextTick(),会在DOM更新后立即执行其中的回调函数。