Vue 入门指南

图片来源:pixiv 63737968

学习 vue 过程中的笔记,未完更新中 ... 完整【示例代码】请去我的 GitHub 仓库 pero-vue 查看

更新于 2017.12.24
首发于夏味的博客: xiaweiss.com

1. 环境配置

注意本笔记使用的版本为当时的最新稳定版

  • Vue 2.x
  • webpack 2
  • node 8.9.0
  • npm 5.6.0

1.1 使用到的技术文档

1.2 需要安装的相关依赖,未来不一定正确,以官方文档为准

首先需要安装 node, 然后使用命令 npm install 依赖名称 来安装

  • babel-core
  • babel-loader
  • babel-preset-env
  • babel-preset-stage-2 (使用 import() 时才需要)
  • css-loader
  • html-webpack-plugin
  • style-loader
  • vue
  • vue-loader
  • vue-template-compiler
  • webpack
  • webpack-dev-server
  • vue-router
  • axios
  • vuex(选用)

1.3 webpack 配置项简介

项目根目录下,创建 webpack.config.js 配置文件

const path = require('path'); //node 内置模块
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 用于在 html 页面里自动插入资源引用的标签
const webpack = require('webpack'); //使用 webpack 内置的插件时,需要引入 webpack

module.exports = {
  entry: {
    index: './src/index.js' // 入口文件
    // bbb: './src/bbb.js' // 可以多个入口文件
  },
  output: {
    path: path.resolve('./dist'), // 输出路径,必须是绝对路径.path.resolve是nodeJS模块方法,把相对路径转为绝对路径
    // 或者使用语法__dirname +'/dist' 或  path.join(__dirname,'dist')
    // __dirname 表示当前模块的目录的绝对路径(并非全局变量,等价于path.dirname(__filename))
    // path.join用于处理连接路径时,统一不同系统路径符\和/问题。
    // publicPath: '/assets/', // 发布路径,填写此项后,打包后文件路径不再是相对路径,而是基于服务器根目录的路径,
    filename: 'js/[name].js', // [name] 表示块的名称,输出文件名,可以包含路径
    chunkFilename: 'js/[name].js' //webpack 分割后的文件,[id] 表示块的编号。[name] 表示块的名称,没有名称时会自动使用编号
  },
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js' // 默认是运行时构建,这里使用了template,必须用运行+编译时构建
    }
  },
  module: {
    rules: [
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, //使用 babel 对 js 转译
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        loader: 'babel-loader!vue-loader'
      },
      // 先使用 vue-loader对 vue 文件转译
      { test: /\.css$/, loader: 'style-loader!css-loader' }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html', // 输出文件名,可以包含路径
      template: 'src/index.html', // 模板文件位置
      inject: 'body', //插入位置,也可以写 head
      hash: true, // 在文件名后面加 hash 值,默认false
      chunks: ['index'] // 表示插入的块,对应 webpack 入口文件中的 index,不写会插入所有的入口文件
    }),
    new webpack.HotModuleReplacementPlugin() //如果 devServer 的配置项里 hot:true ,则需要配置此项
  ],
  externals: {
    vue: 'Vue' //打包时排除 vue,vue内容不会被写入js。
    //注意左边的 vue 是模块名,右边的 Vue 是不使用构建工具时的标准变量名,必须是Vue,与import的变量名无关
  },
  devServer: {
    contentBase: './dist/', //表示静态资源(非webpack编译产生)文件的目录位置,
    //这个目录的资源会被放到同样当成服务器根目录去
    //遇到同名文件,webpack编译后产生的文件优先级更高
    compress: true, //是否压缩
    port: 9000, //端口号
    host: '0.0.0.0', //默认是localhost,如果想被外部访问,这样设置
    historyApiFallback: true, //当使用 history 模式路由时,设置为true,404页面会被重定向到主页,
    hot: true // 热替换,可以在不刷新页面的情况下更新修改后数据,也可以配置在package.json 的 scripts 里,加 --hot参数
  }
};
复制代码

项目根目录下,创建 .babelrc 配置文件

babel-preset-env 相当于 es2015 ,es2016 ,es2017 及最新版本

{
  "presets": ["env"]
}
复制代码

1.4 运行

package.json 文件里添加配置

{
  // ...其他参数
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server"
  }
}
复制代码

然后使用 npm run dev 来启动 server 使用 npm run build 来打包输出

这里的 build 是自己起的。写为 "build": "webpack -p", 打包后时压缩代码

1.5 webpack-dev-server 热替换

热替换指,在不刷新页面的状态下,把修改后的结果更新到页面上

有两种配置方式

  • webpack CLI 方式:

package.json 文件里添加配置

{
  "scripts": {
    "dev": "webpack-dev-server --hot"
  }
}
复制代码
  • webpack 配置方式:

webpack.config.js 文件里添加配置

const webpack = require('webpack');
module.exports = {
  // 其他配置...
  plugins: [
    // 其他配置...
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    // 其他配置...
    hot: true
  }
};
复制代码

package.json 文件里依然是 "dev": "webpack-dev-server"

2. vue 语法

2.1 基本用法

<div id="app">
  {{ name }}
</div>
复制代码
import Vue from 'vue';

let param = {
  el: '#app',
  data: {
    name: 'hello vue'
  }
  //注意这里的 data 也可以使用这种语法
  //data() {
  //   return {name:'hello vue'}
  //}
};
new Vue(param);
复制代码

2.2 基本的组件

<div class="container">
  <my-name></my-name>
</div>
复制代码
import Vue from 'vue';
// 这是一个组件
let meName = {
  template: '<div>{{name}}</div>', // 组件的模板,渲染后会替换掉 <my-name></my-name>
  data() {
    return {
      name: 'xiawei'
    }; // 组件中 data 必须是函数,数据 return 出去,不可以写为 data:{name:'xiawei'}
  }
};

new Vue({
  el: '.container',
  components: {
    'my-name': meName
  }
});
复制代码

2.3 vue 文件形式的组件

为了方便,可以使用 vue 文件 来封装组件,可以认为一个 vue 文件是一个组件,子组件继续使用其他 vue 文件 引入。

index.js

import Vue from 'vue';
import myname from './components/myname.vue';

new Vue({
  el: '.container',
  components: {
    'my-name': myname
  }
});
复制代码

myname.vue

<template>
  <div>
    {{name}}
  </div>
</template>
<script>
export default {
  data() {
    return {
      name: 'xiawei'
    };
  }
};
</script>
<style lang="css" scoped>
</style>
复制代码

template 里,必须用一个 div 或者一个其他标签,包裹住所有的 html 标签

默认 lang="css" 可以省略,需要使用 sass 时,可以写 lang="scss" 等

scoped 是 vue 提供的属性 表示这里的样式只能在本组件内生效

2.4 组件通讯

2.4.1 使用 props 给组件传参
<myname value="xiawei"></myname>
复制代码
<template>
  <div>
    {{value}}
  </div>
</template>
<script>
export default {
  props:['value']
};
</script>
复制代码
2.4.2 访问其他组件,获取参数

可以通过 $parent 访问父组件,$children 访问子组件

user-login 有三个子组件,部分代码如下

<template>
  <div id="user">
        <h2>User Login</h2>
        <form>
            <user-name></user-name>
            <user-pass></user-pass>
            <user-submit></user-submit>
        </form>
    </div>
</template>
复制代码

这时在 user-submit 组件

<template>
    <div>
      <button v-on:click="test">submit</button>
      <!-- v-on:click="test" 表示点击事件时,触发 test 函数 -->
    </div>
</template>
<script>
export default {
  methods: {
    test() {
      this.$parent.$children[0] //访问 user-name 组件
      this.$parent.$children[1] //访问 user-pass 组件

      //获取 user-name 组件 data 中 username 的值
      this.$parent.$children[0].username
    }
  }
};
</script>
复制代码

要区分子组件是第几个,并不方便,可以使用 ref 来解决这个问题

相关代码修改为以下即可

<user-name ref="uname"></user-name>
<user-pass ref="upass"></user-pass>
复制代码
//获取 user-name 组件 data 中 username 的值
this.$parent.$refs.uname.username;
复制代码
2.4.3 父子组件自定义事件通讯

父组件 user-login.vue 里,给子组件 user-name 设置自定义事件 updateUserName

这个事件是绑定在 user-name 组件上的,在 组件对象 .$listeners里可以查看到,可以用 组件对象 .$emit 来触发

$emit 触发时,参数 1 是事件名,后几个参数可以传给事件对象(类似 jQuery 的trigger 方法)

<template>
  <user-name ref="uname" v-on:updateUserName="setUserName"></user-name>
</template>
<script>
import username from './user/user-name.vue';

export default {
  data() {
    return {
      username: ''
    };
  },
  components: {
    'user-name': username
  },
  methods: {
    setUserName(uname) {
      this.username = uname;
    }
  }
};
</script>
复制代码

子组件 user-name.vue,当输入框内容改变,触发 change 事件

然后执行了 $emit 来触发 updateUserName事件,this.username 作为参数传给了updateUserName 事件

<template>
  <input type="text" v-model="username" v-on:change="userNameChange">
</template>
<script>
export default {
  data() {
    return {
      username: ''
    };
  },
  methods: {
    userNameChange() {
      this.$emit('updateUserName', this.username);
    }
  }
};
</script>
复制代码

2.5 v-if,路由原理

v-if 主要用于渲染模板,下面代码

当变量 isadmin 为 true 时,只显示 Admin Login

反之,只显示User Login

注意,程序依据 isadmin == true 的结果来判断

<template>
  <h2 v-if="isadmin">Admin Login</h2>
  <h2 v-else>User Login</h2>
</template>
复制代码

在 index.js 添加下面代码

当浏览器路径 hash 部分(#号及其后面的部分)变化时,会触发 hashchange 事件

判断 hash 的值,各种值走自己的业务逻辑,就可以切换页面、改变数据,这就是路由原理

window.onhashchange = function() {
  if (window.location.hash === '#admin') {
    myvue.$children[0].$data.isadmin = true;
  } else {
    myvue.$children[0].$data.isadmin = false;
  }
};
复制代码

相关需要掌握的还有 v-for,参见官方文档

2.6 计算属性 computed

计算属性和 data 里的普通属性调用时相同的,但定义时不同

计算属性使用函数定义,return 的值,就是计算属性的值

当计算属性内的其他变量的值发生变化时,函数就会执行,运算得到新的值

所以计算属性的值是依赖其他变量的,它没有初始值,不可以在 data 里声明

下面的例子,通过计算属性比对输入的值来筛选 fav.class2

filter 数组方法 返回通过筛选条件的新数组,当 return true 时符合条件被选入。

indexOf 字符串方法 返回符合条件的字符串序号,如果找不到时,会返回数字 -1,可以用来匹配字符串类似的方法,还有 indexOf 数组方法

<template>
  <input type="text" v-model="inputText" class="form-control">
  <table v-if="isShow()" class="table">
    <thead>
      <tr>
        <th>
          type 1
        </th>
        <th>
          type 2
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="fav in getFavs">
        <td>{{ fav.class1 }}</td>
        <td>{{ fav.class2 }}</td>
      </tr>
    </tbody>
  </table>
</template>
<script>
export default {
  data() {
    return {
      favs: [
        { class1: 'web', class2: 'js' },
        { class1: 'pro', class2: 'java' }
      ],
      inputText: ''
    };
  },
  methods: {
    isShow() {
      return !(this.inputText == '');
    }
  },
  computed: {
    getFavs() {
      return this.favs.filter(abc => {
        return abc.class2.indexOf(this.inputText) >= 0;
      });
    }
  }
};
</script>
复制代码
2.6.1 计算属性配合过滤方法

vue 2.x 的过滤器方法,与 vue 1.x 语法不同,并不适合和 v-for 配合使用,计算属性配合过滤方法来实现。

上节的例子,更复杂一点,数组的情况 ( 和上面重复的部分没写出来,完整代码请查看github)

getFavs 决定展示第几条数据,filterClass2 负责对展示出来的数据筛选

<template>
  <tr v-for="fav in getFavs">
    <td>{{ fav.class1 }}</td>
    <td><a v-for="code in filterClass2(fav.class2)">{{ code }} </a></td>
  </tr>
</template>
<script>
export default {
  data() {
    return {
      favs: [
        { class1: 'web', class2: ['js', 'html', 'css', 'jssdk'] },
        { class1: 'pro', class2: ['java'] }
      ]
    };
  },
  methods: {
    filterClass2(class2) {
      return class2.filter(v => {
        return v.indexOf(this.inputText) >= 0;
      });
    }
  },
  computed: {
    getFavs() {
      return this.favs.filter(abc => {
        return abc.class2.filter(code => {
          return code.indexOf(this.inputText) >= 0;
        }).length > 0;
      });
    }
  }
};
</script>
复制代码

2.7 路由

2.7.1 路由的基本使用

首先 npm 安装依赖官方的路由插件 vue-router

index.html

  <div class="container">
    <page-nav></page-nav>
    <router-view></router-view>
  </div>
复制代码

index.js 文件

import Vue from 'vue';
import VueRouter from 'vue-router'; // 引入插件
Vue.use(VueRouter); // 使用插件

import pagenav from './components/page-nav.vue';
import newslist from './components/news-list.vue';
import userlogin from './components/user-login.vue';

const routerConfig = new VueRouter({
  routes: [
    { path: '/news', component: newslist },
    { path: '/login', component: userlogin }
  ]
});

// 全局注册公共组件(也可以像原先注册子组件的方式来做)
Vue.component('page-nav', pagenav);

let myvue = new Vue({
  el: '.container',
  router: routerConfig
  // 路由中引入过子组件了,所以此处不需要再引入子组件
  // components: {
  //   'page-nav': pagenav,
  //   'user-login': userlogin
  // }
});
复制代码

page-nav.vue 的部分代码

推荐使用 router-link 语法作为切换按钮,它默认会渲染成 a 标签也可以使用 a 标签来做

当某个 router-link 被点击选中时,vue 会给它的 html 标签添加上 class router-link-active

可以通过给 .router-link-active写 css 样式, 来给选中的 router-link 添加样式

<template>
  <ul class="nav navbar-nav">
    <li><router-link to="/login">login</router-link></li>
    <li><a href="#/news">News</a></li>
  </ul>
</template>
复制代码
2.7.2 axios 的基本使用

引入

import axios from 'axios';
复制代码

如需全局引入,可以再加上下面这句,组件内调用时使用 this.$axios 即可

Vue.prototype.$axios = axios;
复制代码

get 请求

axios
  .get('http://localhost:8000/test.php', {
    params: {
      ID: 12345
    }
  })
  .then(response => {
    alert(response.data);
  });
复制代码

post 请求参数 axios 默认转为 json 格式

axios
  .post('http://localhost:8000/test.php', { name: 'xiawei', age: 20 })
  .then(response => {
    alert(response.data);
  });
复制代码

键值对方式(php 用 $_POST 可以取到值)

axios.post('http://localhost:8000/test.php', 'name=xiawei&age=20');
复制代码

也可以使用 node 内置模块来转换格式

import querystring from 'querystring';
axios.post(
  'http://localhost:8000/test.php',
  querystring.stringifyname({ name: 'xiawei', age: 20 })
);
复制代码

这部分的 php 代码,是放置在项目根目录的 test.php 文件

<?php
  //指定允许其他域名访问
  header('Access-Control-Allow-Origin:*');

  //响应类型
  header('Access-Control-Allow-Methods:GET,POST,PUT');
  header('Access-Control-Allow-Headers:x-requested-with,content-type');

  echo file_get_contents('php://input');//显示接收到的原始数据
  var_export($_POST);
  echo 'hello php';
复制代码

Mac 内置了 php,直接启动 php 内置服务:到项目根目录下,Terminal 里执行下面命令即可

windows 下载 php 后,把 php 目录添加到系统环境变量 PATH 里后,同样执行下面命令

php -S 0.0.0.0:8000
复制代码
2.7.3 动态加载新闻详细页

在新闻列表页,点击标题跳转到新闻详细页,动态加载新闻内容

index.js 部分代码

import axios from 'axios';
Vue.prototype.$axios = axios;

const routerConfig = new VueRouter({
  routes: [
    { path: '/', component: newslist },// 设置首页
    { path: '/news', component: newslist, name: 'newslist' },// 可以给路由设置别名 name
    { path: '/news/:newsid', component: newsdetail, name: 'newsdetail' },// 如果需要参数,使用冒号的来做占位符
    { path: '/login', component: userlogin, name: 'userlogin' }
  ]
});
复制代码

new-list.vue 部分代码

<template>
  <div class="page-header" v-for="news in newslist">
    <h2><router-link :to="{ name: 'newsdetail', params: {newsid: news.newsid} }">{{news.title}}</router-link> <small>{{news.pubtime}}</small></h2>
    <!-- 这里的 newsdetail 以及 params 里左边的 newsid 是和路由定义时的相关参数对应 -->
    <p>{{news.desc}}</p>
  </div>
</template>
复制代码

news-detail.vue 部分代码

<template>
  <div>
    <h2> {{ newstTitle }} <small>{{ newsDate }}</small></h2>
    <p>{{ newsContent }}</p>
  </div>
</template>
<style>

</style>
<script>
export default {
  // 生命周期,组件被创建时执行
  created() {
    this.$axios
      .get('http://localhost:8000/news.php?newsid='+ this.$route.params.newsid)
      // 可以通过全局变量 $route.params 来访问路由里的变量获取到新闻编号101
      .then(response => {
        this.newstTitle = response.data.title;
        this.newsDate = response.data.pubtime;
        this.newsContent = response.data.desc;
      });
  }
};
</script>
复制代码

通过全局变量 $route 来访问路由里的各种数据

例如 $route.params.newsid 可以获得路由占位符 :newsid 处的新闻编号值 101

2.8 异步加载和 webpack 代码分割

当项目比较大的时候,可以使用异步加载组件的方式来按需加载,而不是一次性加载全部组件。

还可以配合 webpack 代码分割功能,把打包后的 js,分割成多个 js 文件,做到按需引用。

之前的引入组件的方式是

import userlogin from './components/user-login.vue';
复制代码

使用 vue 异步加载的方式引入

var userlogin = function(resolve) {
  resolve(require('./components/user-login.vue'));
};
复制代码

使用 ES2015 语法,并且简化参数名,可以写为

const userlogin = r => {
  r(require('./components/user-login.vue'));
};
复制代码

结合 webpack 代码分割功能后

const userlogin = r => {
  require.ensure([], () => {
    r(require('./components/user-login.vue'));
  });
};
复制代码

如果需要把某几个组件打包为一组,给它们的 require.ensure() (文档1文档2)添加最后一个参数(例如'aaa'),且值相同

  require.ensure([], () => {
    r(require('./components/user-login.vue'));
  },'aaa');
复制代码

也可以使用 webpack + ES2015 语法来进行代码分割

import() (文档) 是 ES2015 草案的语法,所以使用时需要 babel 转译

babel 配置里需要添加草案语法的转译 presets stage-2 ,npm 安装依赖 babel-preset-stage-2

.babel 文件,注意配置的数组里,presets 解析的顺序是从右到左的,先执行 stage-2

{
  "presets": ["env", "stage-2"]
}
复制代码
const userlogin = () => import('./components/user-login.vue');
// 也就是 function() { return import('./components/user-login.vue')};
复制代码

把某几个文件打包为一组时,使用这个语法

const userlogin = () => import(/* webpackChunkName: "aaa" */'./components/user-login.vue');
复制代码

最后分割后的文件名,可以在 webpack 配置里 output 配置项里添加 chunkFilename 配置项来控制

output: {
  filename: 'js/[name].js', // [name] 表示块的名称,输出文件名,可以包含路径
  chunkFilename: 'js/[name].js'
  //webpack 分割后的文件,[id] 表示块的编号。[name] 表示块的名称,没有名称时会自动使用编号
},
复制代码

2.9 开发插件

有时现有的插件并不能满足自己的业务需求,这时需要自己开发插件

2.9.1 自定义指令

在 src 文件夹下新建一个 js 文件,比如命名为 plugin.js

export default {
  install(Vue) {
    // 添加实例方法
    Vue.prototype.$name = 'xiawei';// 可以在组件内使用 this.$name 取到值 'xiawei'

    // 这里添加时方法来检测用户名是否合法,6~20位合法,否则显示提示
    Vue.prototype.checkUserName = value => {
      if (value == '') return true;
      return /\w{6,20}/.test(value);
    };
    // 可以在组件内使用 this.checkUserName(’‘’)

    // 添加全局自定义指令 v-uname
    Vue.directive('uname', {
      bind() {
        console.log('begin');
      },
      update(el, binding, vnode) {
        vnode.context[binding.expression] = !/\w{6,20}/.test(el.value);
      }
    });
  }
};
复制代码

directive (文档) 里的生命周期里的三个参数:

  • el 参数表示指令所绑定的元素,可以用来直接操作 dom
  • binding 参数表示绑定对象,binding.expression 取到传入的表达式,binding.value 可以取到表达式的值 这里的表达式也可以是函数名,取到的值是函数体,binding.oldValue
  • vnode 参数表示 Vue 编译生成的虚拟节点

关于官方文档里,添加全局方法或属性 Vue.myGlobalMethod 和添加实例方法和属性 Vue.prototype.$myMethod 二者区别

全局方法或属性使用 Vue.名称 来调用,而实例方法和属性使用 (实例化后的 Vue 对象).名称 来调用,也就是组件内的常见 this.名称 来调用,即使看起来名称一样的Vue.aaaVue.prototype.aaa也是两个不同的变量

具体可以参见这篇文章:js里面的实例方法和静态方法

index.js 内加载插件

import plugin from './plugin.js';
Vue.use(plugin);
复制代码

user-name.vue 添加 v-uname 和 label 元素

<input type="text" v-model="username" v-uname="showErrorLabel" v-on:change="userNameChange" class="form-control" :placeholder="placeholder">
<label v-if="showErrorLabel" class="label label-danger">Please check your username and try again</label>
复制代码
2.9.2 手动挂载子组件

上面只是控制变量,并不是很方便,可以通过插件动态插入移除提示框

export default {
  install(Vue) {
    // 创建变量,定义初始值
    Vue.errorLabel = null;
    Vue.hasErrorLabel = false;
    // 这个全局变量来标记是否插入了 label,给初始值时必须放在 update 外面

    // 添加全局自定义指令 v-uname
    Vue.directive('uname', {
      bind(el) {
        let error = Vue.extend({
          template:
            '<label class="label label-danger">Please check your username and try again</label>'
        });

        Vue.errorLabel = (new error()).$mount().$el;
        // $mount() 方法不填参数时,表示把 vm 实例对象变成一个可以挂载的状态,这时就可以访问到 $el 获取到元素了
      },
      update(el, binding, vnode) {
        // 这里每次 update 是从组建原始的状态 update 的,所以不会重复插入多个
        if (/\w{6,20}/.test(el.value)) {
          if (Vue.hasErrorLabel) {
            el.parentNode.removeChild(Vue.errorLabel);
            Vue.hasErrorLabel = !Vue.hasErrorLabel;
          }
        } else {
          if (!Vue.hasErrorLabel) {
            el.parentNode.appendChild(Vue.errorLabel);
            Vue.hasErrorLabel = !Vue.hasErrorLabel;
          }
        }
      }
    });
  }
};
复制代码

user-name.vue 组件里,这时不需要写 label 元素,只需要写入 v-uname 即可

<input type="text" v-model="username" v-uname v-on:change="userNameChange" class="form-control" :placeholder="placeholder">
复制代码
2.9.3 插件里包含子组件

上一小节的代码,当有多个 input 元素时,就会出现其他元素显示不正常的情况,原因是多个标签共用了同一个 Vue.hasErrorLabel

所以当插件不仅仅处理数据时,还需要独立的处理 dom 元素时,使用子组件的方式更加合理,它们是互相独立的

export default {
  install(Vue) {
    Vue.component('p-username', {
      template: `<div>
          <input class="form-control" type="text" v-model="textValue" />
          <label class="label label-danger" v-if="showErrorLabel">Please check your username and try again</label>
        </div>`,
        // 这里使用了 ES2015 的模板字符串语法
      data() {
        return {
          textValue: ''
        };
      },
      computed: {
        showErrorLabel() {
          return !(/\w{6,20}/.test(this.textValue) || this.textValue == '');
        }
      }
    });
  }
};
复制代码

其中,为了方便 template 里使用了 ES2015 的模板字符串语法(参考文档

user-name.vue 文件(不需要写 input 元素)

<p-username></p-username>
复制代码

2.10 全局状态管理 vuex

应遵循以下规则

  • 应用级的状态集中放在 store 中
  • 计算属性使用 getters
  • 改变状态的方式是提交 mutations,这是个同步的事务
  • 异步逻辑应该封装在 action 中

也即是与组件的概念相对应的 store -> data getters -> computed mutations/actions -> methods

2.10.1 vuex 基本使用

npm 安装依赖 vuex

index.js

import Vuex from 'vuex';
Vue.use(Vuex);

const vuex_store = new Vuex.Store({
  state: {
    user_name: ''
  },
  mutations: {
    showUserName(state) {
      alert(state.user_name);
    }
  }
});
复制代码

赋值:user-name.vue 组件中使用

<div class="page-header" v-for="news in $store.state.newslist">
复制代码
this.$store.state.user_name = this.username;
复制代码

触发:user-submit.vue 组件中使用

this.$store.commit('showUserName');
复制代码

即可完成简单的输入用户名,点提交按钮后 alert 出用户名

2.10.2 vuex 计算属性

vuex 里的计算属性使用的是 getters,用法和 组件里的计算属性 computed 类似,只是被触发的时机不同

从数据里展示没有删除的新闻展示

index.js

const vuex_store = new Vuex.Store({
  state: {
    user_name: '',
    newslist: []
  },
  mutations: {
    showUserName(state) {
      alert(state.user_name);
    }
  },
  getters: {
    getNews(state) {
      return state.newslist.filter(news => !news.isdeleted);
    }
  }
});
复制代码

news-list.vue

<div class="page-header" v-for="news in $store.getters.getNews">
复制代码
export default {
  created() {
    if (this.$store.state.newslist.length == 0) {
      this.$axios.get('http://localhost:8000/news.php').then(response => {
        this.$store.state.newslist = response.data;
      });
    }
  }
};
复制代码
2.10.3 actions

mutations 是同步执行的,里面不能放异步执行的东西 actions 里放异步执行的,异步执行完后,去手动触发 mutations

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context,param) {
      // 异步业务 param -> param2
      context.commit('increment',param2);
    }
  }
})
复制代码

组件内触发

this.$store.dispatch('increment',param);
复制代码
2.10.4 把业务按模块分类

之前写的 index.js 是这样

const vuex_store = new Vuex.Store({
  state: {
    user_name: '',
    newslist: []
  },
  mutations: {
    showUserName(state) {
      alert(state.user_name);
    }
  },
  getters: {
    getNews(state) {
      return state.newslist.filter(news => !news.isdeleted);
    }
  }
});
复制代码

按模块分离后

index.js

import news_module from './store/news.js';
import users_module from './store/users.js';

const vuex_store = new Vuex.Store({
  modules: {
    news: news_module,
    users: users_module
  }
});
复制代码

news.js

export default {
  state: {
    newslist: []
  },
  getters: {
    getNews(state) {
      return state.newslist.filter(news => !news.isdeleted);
    }
  }
}
复制代码

users.js

export default {
  state: {
    user_name: ''
  },
  mutations: {
    showUserName(state) {
      alert(state.user_name);
    }
  }
}
复制代码

分离后,注意相关模块里的 this.$store.state

按业务模块名分别改为 this.$store.state.newsthis.$store.state.users

注意不同业务模块里,getters 里函数重名了会报错, mutations 里函数重名了会两边都执行

推荐课程:VUE.JS+PHP 前后端分离实战视频电商网站

欢迎添加我个人微信,互相学习交流

转载于:https://juejin.im/post/5b46276bf265da0f72385e2c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值