"vue APP开发"

vue2

Posted by fairyly on November 29, 2016

Vue2.0+Mint UI+Vue-router+Vux构建移动端APP

** 本该拼搏的年龄,却想太多,做太少!~~~~~~ **


刚开始接触的时候,Vue还是1.0的版本,学习的还是一些很简单的东西,

还不知道什么vuex,vue-router,前端发展的真的很快,几个月的时间,

angular已经更新到2.0,Vue也已经更新2.0了;不管是angular还是react,还是Vue,

他们都是前端MVVM(Model–View–ViewModel)框架;

先到官网学习了Vue2.0的教程,知道Vue的安装、常用的语法、构造器、属性和方法、

生命周期以及渲染、事件、表单控件和组件;

看了尤雨溪写的学习vue2.0的顺序


  • 起步  
  1. 扎实的 JavaScript / HTML / CSS 基本功。这是前置条件。

  2. 通读官方教程 (guide) 的基础篇。不要用任何构建工具,就只用最简单的

    把教程里的例子模仿一遍,理解用法。不推荐上来就直接用 vue-cli 构建项目,尤其是如果没有 Node/Webpack 基础。

  3. 照着官网上的示例,自己想一些类似的例子,模仿着实现来练手,加深理解。

  4. 阅读官方教程进阶篇的前半部分,到『自定义指令 (Custom Directive) 』为止。

    着重理解 Vue 的响应式机制和组件生命周期。『渲染函数(Render Function)』如果理解吃力可以先跳过。

  5. 阅读教程里关于路由和状态管理的章节,然后根据需要学习 vue-router 和 vuex。

    同样的,先不要管构建工具,以跟着文档里的例子理解用法为主。

  6. 走完基础文档后,如果你对于基于 Node 的前端工程化不熟悉,就需要补课了。

    下面这些严格来说并不是 Vue 本身的内容,也不涵盖所有的前端工程化知识,

    但对于大型的 Vue 工程是前置条件,也是合格的『前端工程师』应当具备的知识。


  • 前端生态/工程化
  1. 了解 JavaScript 背后的规范,ECMAScript 的历史和目前的规范制定方式。

    学习 ES2015/16 的新特性,理解 ES2015 modules,适当关注还未成为标准的提案。

  2. 学习命令行的使用。建议用 Mac。

  3. 学习 Node.js 基础。建议使用 nvm 这样的工具来管理机器上的 Node 版本,

    并且将 npm 的 registry 注册表配置为淘宝的镜像源。

    至少要了解 npm 的常用命令,npm scripts 如何使用,语义化版本号规则,

    CommonJS 模块规范(了解它和 ES2015 Modules 的异同),Node 包的解析规则,

    以及 Node 的常用 API。应当做到可以自己写一些基本的命令行程序。

    注意最新版本的 Node (6+) 已经支持绝大部分 ES2015 的特性,可以借此巩固 ES2015。

  4. 了解如何使用 / 配置 Babel 来将 ES2015 编译到 ES5 用于浏览器环境。

  5. 学习 Webpack。Webpack 是一个极其强大同时也复杂的工具,

    作为起步,理解它的『一切皆模块』的思想,并基本了解其常用配置选项和 loader 的概念/使用方法即可,

比如如何搭配 Webpack 使用 Babel。学习 Webpack 的一个挑战在于其本身文档的混乱,建议多搜索搜索,

应该还是有质量不错的第三方教程的。英文好的建议阅读 Webpack 2.0 的文档,比起 1.0 有极大的改善,

但需要注意和 1.0 的不兼容之处。


  • Vue 进阶
  1. 有了 Node 和 Webpack 的基础,可以通过 vue-cli 来搭建基于 Webpack ,

    并且支持单文件组件的项目了。建议用 webpack-simple 这个模板开始,

    并阅读官方教程进阶篇剩余的内容以及 vue-loader 的文档,了解一些进阶配置。

    有兴趣的可以自己亲手从零开始搭一个项目加深理解。

  2. 根据例子尝试在 Webpack 模板基础上整合 vue-router 和 vuex

  3. 深入理解 Virtual DOM 和『渲染函数 (Render Functions)』这一章节(可选择性使用 JSX),

    理解模板和渲染函数之间的对应关系,了解其使用方法和适用场景。

  4. (可选)根据需求,了解服务端渲染的使用(需要配合 Node 服务器开发的知识)。

    其实更重要的是理解它所解决的问题并搞清楚你是否需要它。

  5. 阅读开源的 Vue 应用、组件、插件源码,自己尝试编写开源的 Vue 组件、插件。

作者:尤雨溪
链接:https://zhuanlan.zhihu.com/p/23134551
来源:知乎

做了一个小demo,是在node环境中的,所以就用构建工具安装Vue-cli、mint-ui、vue-router、Vuex;

安装Vue-cli命令:

全局安装 vue-cli

$ npm install –global vue-cli

创建一个基于 webpack 模板的新项目

$ vue init webpack my-project

安装依赖

$ cd my-project

$ npm install

$ npm run dev

这样一个很简单的Vue小项目已经完成。

默认服务器的端口是8080,如果需要改变端口号,

可以到src/config/index.js文件中port端口号就可以;

dev: { env: require(‘./dev.env’), port: 8080, assetsSubDirectory: ‘static’, assetsPublicPath: ‘/’, proxyTable: {}, cssSourceMap: false }

安装Mint UI命令: 文档:http://mint-ui.github.io/docs/#!/zh-cn2
// install# for Vue 1.x
npm install mint-ui@1 -S  

for Vue 2.0  

npm install mint-ui -S  

// import all components
import Vue from ‘vue’;import Mint from ‘mint-ui’;Vue.use(Mint);

安装Vue-router命令:文档:http://router.vuejs.org/zh-cn/
npm install vue-router如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能:
import Vue from ‘vue’
import VueRouter from ‘vue-router’
Vue.use(VueRouter)

安装Vuex命令:Vuex文档:http://vuex.vuejs.org/zh-cn/

npm install vuex在一个模块化的打包系统中,您必须显式地通过 Vue.use() 来安装

Vuex:状态管理模式

import Vue from ‘vue’ import Vuex from ‘vuex’ Vue.use(Vuex)

Vuex是Vue的状态管理模式,每一个 Vuex 应用的核心就是 store(仓库)。
“store” 基本上就是一个容器,它包含着你的应用中大部分的状态(即 state);

State:单一状态树
从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态;
初始 state 对象:
// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)

const store = new Vuex.Store({ state: { count: 0 } })

当一个组件需要获取多个状态时候,使用 mapState 辅助函数帮助我们生成计算属性;

computed: mapState({ // 箭头函数可使代码更简练 count: state => state.count,

// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count' })

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。如:

computed: { …mapState([‘headNav’]) } Getters

Vuex 允许我们在 store 中定义『getters』(可以认为是 store 的计算属性)。

Getters 接受 state 作为其第一个参数:

const store = new Vuex.Store({
  state: {
    audio: {
      songUrl: '',
      imgUrl: 'http://m.kugou.com/v3/static/images/index/logo_kugou.png',
      title: '',
      singer: '',
      currentLength: 0,
      songLength: 0,
      currentFlag: false
    },
    head: {
      toggle: false,
      title: '',
      style: {'background': 'rgba(43,162,251,0)'}
    },
    headNav: 'head-nav1',
    audioLoadding: false,
    detailPlayerFlag: false,
    showPlayer: false,
    listenCount: 0,
    isPlay: true
  },
  getters: {
    audio: state=>state.audio,
    head: state=>state.head,
    audioLoadding: state=>state.audioLoadding,
    detailPlayerFlag: state=>state.detailPlayerFlag,
    showPlayer: state=>state.showPlayer,
    isPlay: state=>state.isPlay
  }
})

mapGetters 辅助函数仅仅是将 store 中的 getters 映射到局部计算属性

Mutations:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

Vuex 中的 mutations 非常类似于事件:

每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。

这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数;

const store = new Vuex.Store({
  state: {
    audio: {
      songUrl: '',
      imgUrl: 'http://m.kugou.com/v3/static/images/index/logo_kugou.png',
      title: '',
      singer: '',
      currentLength: 0,
      songLength: 0,
      currentFlag: false
    },
    head: {
      toggle: false,
      title: '',
      style: {'background': 'rgba(43,162,251,0)'}
    },
    headNav: 'head-nav1',
    audioLoadding: false,
    detailPlayerFlag: false,
    showPlayer: false,
    listenCount: 0,
    isPlay: true
  },
  getters: {
    audio: state=>state.audio,
    head: state=>state.head,
    audioLoadding: state=>state.audioLoadding,
    detailPlayerFlag: state=>state.detailPlayerFlag,
    showPlayer: state=>state.showPlayer,
    isPlay: state=>state.isPlay
  },
  mutations: {
    setAudio(state, audio){
      if (!state.listenCount) {
        state.showPlayer = true;  //首次进入应用时不可打开播放详情
      }
      state.listenCount++;
      state.audio = {...(state.audio), ...audio};
    },
    setAudioTime(state, time){
      state.audio.currentLength = time;
    },
    setCurrent(state, flag){
      state.audio.currentFlag = flag;
    },
    showHead(state, flag){
      state.head.toggle = flag;
    },
    setHeadTitle(state, title){
      state.head.title = title;
    },
    setHeadStyle(state, style){
      state.head.style = style;
    },
    resetHeadStyle: state=> {
      state.head.style = {'background': 'rgba(43,162,251,0)'};
    },
    toggleAudioLoadding: (state, flag)=> {
      state.audioLoadding = flag;
    },
    setHeadNav: (state, index)=> {
      state.headNav = 'head-nav' + index;
    },
    showDetailPlayer: (state, flag)=> {
      state.detailPlayerFlag = flag;
    },
    showPlayer: (state, flag)=> {
      state.showPlayer = flag;
    },
    isPlay: (state, flag)=> {
      state.isPlay = flag;
    },
    setLrc: (state, lrc)=> {
      state.audio = {...(state.audio), lrc}
    }
  }
});

可以在组件中使用 this.$store.commit(‘xxx’) 提交 mutation,

或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

import { mapMutations } from 'vuex'export default {
  // ...
  methods: {
    ...mapMutations([
      'increment' // 映射 this.increment() 为 this.$store.commit('increment')
    ]),
    ...mapMutations({
      add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
    })
  }}

Action 类似于 mutation,不同在于:

Action 提交的是 mutation,而不是直接变更状态。

Action 可以包含任意异步操作。

Action 通过 store.dispatch 方法触发:

const store = new Vuex.Store({
  state: {
    audio: {
      songUrl: '',
      imgUrl: 'http://m.kugou.com/v3/static/images/index/logo_kugou.png',
      title: '',
      singer: '',
      currentLength: 0,
      songLength: 0,
      currentFlag: false
    },
    head: {
      toggle: false,
      title: '',
      style: {'background': 'rgba(43,162,251,0)'}
    },
    headNav: 'head-nav1',
    audioLoadding: false,
    detailPlayerFlag: false,
    showPlayer: false,
    listenCount: 0,
    isPlay: true
  },
  getters: {
    audio: state=>state.audio,
    head: state=>state.head,
    audioLoadding: state=>state.audioLoadding,
    detailPlayerFlag: state=>state.detailPlayerFlag,
    showPlayer: state=>state.showPlayer,
    isPlay: state=>state.isPlay
  },
  mutations: {
    setAudio(state, audio){
      if (!state.listenCount) {
        state.showPlayer = true;  //首次进入应用时不可打开播放详情
      }
      state.listenCount++;
      state.audio = {...(state.audio), ...audio};
    },
    setAudioTime(state, time){
      state.audio.currentLength = time;
    },
    setCurrent(state, flag){
      state.audio.currentFlag = flag;
    },
    showHead(state, flag){
      state.head.toggle = flag;
    },
    setHeadTitle(state, title){
      state.head.title = title;
    },
    setHeadStyle(state, style){
      state.head.style = style;
    },
    resetHeadStyle: state=> {
      state.head.style = {'background': 'rgba(43,162,251,0)'};
    },
    toggleAudioLoadding: (state, flag)=> {
      state.audioLoadding = flag;
    },
    setHeadNav: (state, index)=> {
      state.headNav = 'head-nav' + index;
    },
    showDetailPlayer: (state, flag)=> {
      state.detailPlayerFlag = flag;
    },
    showPlayer: (state, flag)=> {
      state.showPlayer = flag;
    },
    isPlay: (state, flag)=> {
      state.isPlay = flag;
    },
    setLrc: (state, lrc)=> {
      state.audio = {...(state.audio), lrc}
    }
  },
  actions: {
    getSong({commit,state}, hash){
      commit('toggleAudioLoadding', true);
      Vue.http.get('http://lavyun.applinzi.com/apis/getKugouSong.php?hash=' + hash).then(res=> {
        var json_obj = JSON.parse(res.data);
        var songUrl = json_obj.url,
          imgUrl = json_obj.imgUrl.split('{size}').join('100'),
          title = json_obj.songName,
          singer = json_obj.choricSinger,
          songLength = json_obj.timeLength,
          currentLength = 0,
          audio = {songUrl, imgUrl, title, singer, songLength, currentLength};
        commit('setAudio', audio);
        commit('toggleAudioLoadding', false);
      });
    },
    getLrc({commit,state}, hash){
      Vue.http.get('http://lavyun.applinzi.com/apis/getLrc.php?hash=' + hash).then(res=> {
        commit('setLrc', res.data);
      })
    }
  }
});

modules:

Vuex 允许我们将 store 分割到模块(module)。 每个模块拥有自己的 state、mutation、action、getters、甚至是嵌套子模块——从上至下进行类似的分割:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }}const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }}const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB  }})

store.state.a // -> moduleA 的状态

store.state.b // -> moduleB 的状态

这几个state,getters, mutations,actions,modules是Vuex的接个核心概念;

还有一个需要安装的插件,Vue-resource:主要用来发送http请求的时候用的;

安装命令:

npm install vue-resource

这些组件安装后,开始编写demo,前面已经安装了vue-cli,创建了基于webpack模板的新项目,

一般的单页面应用,入口文件就是main.js,以后都是用模板组件来构建项目页面的;

先在App.vue中添加组件,分别创建各个组件对应的模板文件,

<template>
  <div id="app">
    <!--<img src="./assets/logo.png">-->
    <headlogo></headlogo>
    <hello></hello>
    <head-nav></head-nav>
    <div class="main">
      <keep-alive>
        <router-view></router-view>
      </keep-alive>
    </div>
    <!--<conlist></conlist> 原直接加载组件,后改为路由-->
  </div>
</template>

<script>
import Hello from './components/Hello'
import Headlogo from './components/Headlogo'
import HeadNav from './components/HeadNav'
import Conlist from './components/Conlist'
export default {
  name: 'app',
  components: {
    Hello,Headlogo,HeadNav,Conlist
  }
}
</script>

<style>
body{
  margin:0px;
  padding:0px;
}
#app {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: justify;
  color: #2c3e50;
  margin-top:40px;
}
.main{
  margin-top: 5px;
}
</style>

因为用到了路由,所以main.js中需要引入路由,引入前面安装的东西;

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import VueResource from 'vue-resource'
import Mint from 'mint-ui'
import 'mint-ui/lib/style.css'
import '../static/style.css'
Vue.use(Mint)
Vue.use(VueResource)
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

src目录下创建router目录,新建index.js路由文件,

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)


const router = new VueRouter({
  routes:[{
    path:'/index',component:require('../views/index')
  },{
    path:'/rank',component:require('../views/rank')
  },{
    path:'/ringtone',component:require('../views/ringtone')
  },{
    path:'/plist',component:require('../views/plist')
  },{
    path:'/singer',component:require('../views/singer')
  },{
    path:'/search',component:require('../views/search')
  },{
    path:'*',redirect:'/index'
  }]
})

src目录下新建views目录,创建各个路由的模板文件;

src目录下新建store目录,创建index.js状态管理文件;

import Vue from 'vue'
import Vuex from 'vuex'
import VueResource from 'vue-resource'
Vue.use(Vuex);
Vue.use(VueResource)

const store = new Vuex.Store({
  state: {
    audio: {
      songUrl: '',
      imgUrl: 'http://m.kugou.com/v3/static/images/index/logo_kugou.png',
      title: '',
      singer: '',
      currentLength: 0,
      songLength: 0,
      currentFlag: false
    },
    head: {
      toggle: false,
      title: '',
      style: {'background': 'rgba(43,162,251,0)'}
    },
    headNav: 'head-nav1',
    audioLoadding: false,
    detailPlayerFlag: false,
    showPlayer: false,
    listenCount: 0,
    isPlay: true
  },
  getters: {
    audio: state=>state.audio,
    head: state=>state.head,
    audioLoadding: state=>state.audioLoadding,
    detailPlayerFlag: state=>state.detailPlayerFlag,
    showPlayer: state=>state.showPlayer,
    isPlay: state=>state.isPlay
  },
  mutations: {
    setAudio(state, audio){
      if (!state.listenCount) {
        state.showPlayer = true;  //首次进入应用时不可打开播放详情
      }
      state.listenCount++;
      state.audio = {...(state.audio), ...audio};
    },
    setAudioTime(state, time){
      state.audio.currentLength = time;
    },
    setCurrent(state, flag){
      state.audio.currentFlag = flag;
    },
    showHead(state, flag){
      state.head.toggle = flag;
    },
    setHeadTitle(state, title){
      state.head.title = title;
    },
    setHeadStyle(state, style){
      state.head.style = style;
    },
    resetHeadStyle: state=> {
      state.head.style = {'background': 'rgba(43,162,251,0)'};
    },
    toggleAudioLoadding: (state, flag)=> {
      state.audioLoadding = flag;
    },
    setHeadNav: (state, index)=> {
      state.headNav = 'head-nav' + index;
    },
    showDetailPlayer: (state, flag)=> {
      state.detailPlayerFlag = flag;
    },
    showPlayer: (state, flag)=> {
      state.showPlayer = flag;
    },
    isPlay: (state, flag)=> {
      state.isPlay = flag;
    },
    setLrc: (state, lrc)=> {
      state.audio = {...(state.audio), lrc}
    }
  },
  actions: {
    getSong({commit,state}, hash){
      commit('toggleAudioLoadding', true);
      Vue.http.get('http://lavyun.applinzi.com/apis/getKugouSong.php?hash=' + hash).then(res=> {
        var json_obj = JSON.parse(res.data);
        var songUrl = json_obj.url,
          imgUrl = json_obj.imgUrl.split('{size}').join('100'),
          title = json_obj.songName,
          singer = json_obj.choricSinger,
          songLength = json_obj.timeLength,
          currentLength = 0,
          audio = {songUrl, imgUrl, title, singer, songLength, currentLength};
        commit('setAudio', audio);
        commit('toggleAudioLoadding', false);
      });
    },
    getLrc({commit,state}, hash){
      Vue.http.get('http://lavyun.applinzi.com/apis/getLrc.php?hash=' + hash).then(res=> {
        commit('setLrc', res.data);
      })
    }
  }
});

export default store

src目录下新建jsons目录, 创建数据listindex.js和listplist文件;

demo参考一个酷狗app写的,主要是熟悉使用构建工具搭建Vue应用,

练习使用Vue-router、Vuex、以及Mint UI,

demo地址:

come on!