Vuex实现组件通信-Vue学习笔记(2).md

2017.4.17-4.21

前言

本周继续上周的登录界面,使用less与vuex进行完善。

笔记导航

  • Vue套件使用
  • Vuex状态管理学习( state & getters & mutations & actions
  • 通过Vuex实现多个组件间的通信
  • 使用less编辑页面样式

详细笔记

1. Vue组件嵌套使用

新建父组件,在script段落内引入子组件,并且在父组件中插入已定义的子组件

<!-- 组件嵌套实例 -->
<!-- 父组件Entry.Vue  -->
<!-- 子组件Login.Vue & Register.Vue -->

<template>
  <div class="entry-box">
    <h1>entry</h1>
    <login></login>
    <register></register>
  </div>
</template>

<script>
// 声明引入子组件变量
import login from './Login'
import register from './Register'
export default {
  name: 'entry',
  // ******* 在components参数中赋值,添加引入的子组件  *******
  components: {
    login,
    register
  }
  // ******* 在components参数中赋值,添加引入的子组件  *******
}
</script>
Vue语法缩写:

@ === v-on 事件绑定监听

<!-- 完整语法 -->
<a v-on:click="doSomething"></a>
<!-- 缩写 -->
<a @click="doSomething"></a>   

: === v-bind 元素绑定

<!-- 完整语法 -->
<a v-bind:href="url"></a>
<!-- 缩写 -->
<a :href="url"></a>

2. 使用Vuex

Vuex的用处:

通过内部变量(store实例中的state)来管理整个系统的状态,提供多种系统的接口(getters、mutations、actions),使Vue组件可以通过store实例的接口获取store实例中的state变量。

Vuex的好处:

使用state: 实现Vue组件中的通信
使用getters与mutations: 不需要在每一个Vue组件中重写相同的处理函数(比如用于获取state中的参数)
使用actions: 把Vue之间的同步调用转变成异步调用,提高响应效率

Vuex学习资源:
Vuex入门视频(共5个)
Vuex视频对应的练习源码

上面推荐的入门视频中对于Vuex的一个模型图示,看完视频再看会比较清晰
2.1 关于Store & State

Step1: 安装Vuex

npm install --save vuex

Step2: 声明store实例,并引用到application中
// 新建store.js文件  
// store.js 配置Vuex实例
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
export const store = new Vuex.Store({
  state: { 
    //填充用于管理状态的共享变量
  }
})


//在main.js中引入Store实例
// main.js
// 使用{}引入store实例,因为store是一个const变量
import { store } from './store'

new Vue({
  router,
  store,
  // 其他属性 ...
}).$mount('#app')
Step3: 传递Store实例中的state参数中的共享变量

在使用store之前,需要在App.vue中将公共状态传递给子组件(通过props获取)。
相当于子组件中传入的数据只是数据,而非数据地址,不可修改数据本身,因此自身操作不会影响全局,需要抛出事件给父组件。
而使用js操作变量时,子组件需要通过emit-on机制向App.vue发起事件冒泡,由app.vue执行操作并改变内部的data,进而更新子组件中的参数变化。

// App.vue
<template>
  <div id="app">
    // 通过绑定:users="unregisteredUsers" 向app-registration模块中传递props值
    <app-registration @userRegistered="userRegistered" :users="unregisteredUsers"></app-registration>
    <app-registrations @userUnregistered="userUnregistered" :registrations="registrations"></app-registrations>
  </div>
</template>

<script>
  import Registration from './components/Registration.vue';
  import Registrations from './components/Registrations.vue';

export default {
    data() {        // data中存储被传到两个组件中的变量
      return {
        registrations: [],
        users: [
            {id: 1, name: 'Max', registered: false},
            {id: 2, name: 'Anna', registered: false},
            {id: 3, name: 'Chris', registered: false},
            {id: 4, name: 'Sven', registered: false}
        ]
      }
    }
    computed: {
        unregisteredUsers() {
            return this.users.filter((user) => {
                return !user.registered;
            });
        }
    },
    // 声明引用的子组件
    components: {
        appRegistration: Registration,
        appRegistrations: Registrations
    }
}
</script>

在使用store之后,不需要再在子组件中通过props获取传入参数,可以直接通过this.$store.state.[变量名称]直接对store实例中的state参数进行有效编辑。
原本在app.vue中进行的操作,可以在子组件中直接执行。

// Registration.vue
<template>
    <div id="registration">
        <h3>Register here</h3>
        <hr>
        <div class="row" v-for="user in users">
            <h4>{{ user.name }}</h4>
            <button @click="registerUser(user)">Register</button>
        </div>
    </div>
</template>

<script>
    export default {
      computed: {
        // 通过user() 获取store中的公共共享参数
        users() {
         return this.$store.state.users.filter(user => {     
            return !user.registered;    // filter:条件过滤,只返回user.registered===false的元素
          })
        }
      },
        methods: {
            registerUser(user) {
              user.registered = true;
              const date = new Date;
              this.$store.state.registrations.push({userId: user.id, name: user.name, date: date.getMonth() + '/' + date.getDay()})
            }
        }
    }
</script>
2.2 关于getters与mutations

概念:这一对Vuex实例属性,在一般情况下被理解作getter与setteraccessor和mutator

getters [new store property]

getters用于简化子组件从store.js中获取公共变量的代码

使用getters优化前:
// store.js 
export const store = new Vuex.Store({
  state: {
    registrations: [],
    users: [
        {id: 1, name: 'Max', registered: false},
        {id: 2, name: 'Anna', registered: false},
        {id: 3, name: 'Chris', registered: false},
        {id: 4, name: 'Sven', registered: false}
    ]
  }
})

// 子组件 Registration.vue
export default {
      computed: {
        users() {
          return this.$store.state.users.filter(user => {
            return !user.registered;
          })
        }
      }
}

使用getters优化后:
// store.js
export const store = new Vuex.Store({
  state: {
    registrations: [],
    users: [
        {id: 1, name: 'Max', registered: false},
        {id: 2, name: 'Anna', registered: false},
        {id: 3, name: 'Chris', registered: false},
        {id: 4, name: 'Sven', registered: false}
    ]
  },
  getters: {
    unregisteredUsers(state) {
       return state.users.filter(user => {
         return !user.registered;
       })
    },
    registerUser(state) {
      return state.registrations;
    }
    totalRegistration(state) {
      return state.registrations.length;
    }
  }
})

// 子组件 Registration.vue
export default {
      computed: {
        users() {
          // 以属性的形式调用getters中的方法,不需要传入参数
          return this.$store.getters.unregisteredUsers;
          }
      }
}

使用mapGetters的二度简化

安装mapGetters的编译工具
npm install --save-dev babel-preset-stage-2
配置mapGetters的编译参数

// .babelrc 文件
{
  "presets": [
    ["es2015", { "modules": false }],
    ["stage-2"]    //新增参数
  ]
}
// 子组件 Registrations.vue
<script>
// 引入mapGetters
import { mapGetters } from 'vuex'
    export default {
      computed: mapGetters({
        // getters映射的语法规则:
        // 'customized name' : 'name of getters'
        registrations: 'registeredUser',
        total: 'totalRegistrations'
      }),
        methods: {
            unregister(registration) {
              const user = this.$store.state.users.find(user => {
                  return user.id == registration.userId;
              });
              user.registered = false;
              this.$store.state.registrations.splice(this.$store.state.registrations.indexOf(registration), 1);
            }
        }
    }
</script>

mutations [new store property]

getters用于管理可复用的'获取state参数的操作';相应地,mutations用于管理可复用的'修改state参数的js操作'。

// mutations 的定义与声明
// store.js
export const store = new Vuex.Store({
  state: { ··· },
  getters: { ··· },
  mutations: {
    register(state, userId) {
      const user = state.users.find(user => {
          return user.id == userId;
      });
      user.registered = true;
      const date = new Date;
      const registration = {
        userId: userId,
        name: user.name,
        date: date.getMonth() + '/' + date.getDay()
      }
      state.registrations.push(registration);
    },
    unregister(state, userId) {
      const user = state.users.find(user => {
          return user.id == userId;
      });
      user.registered = false;
      // 使用findIndex定位目标删除元素
      const registrationIndex = state.registrations.findIndex(registration => {
        return registration.userId == userId;
      })
      state.registrations.splice(registrationIndex, 1);

      // 使用find函数定位目标删除元素
      // const registration = state.registrations.find(registration => {
      //   return registration.userId == userId;
      // })
      // state.registrations.splice(state.registrations.indexOf(registration), 1);
    }
  }
})

// mutations在组件中的调用
// Registration.vue
<script>
    export default {
      computed: {
        users() {
          return this.$store.getters.unregisteredUsers;
          }
      },
      methods: {
            registerUser(user) {
              // 与getters不同,在子组件中使用commit调用mutations中的函数
              this.$store.commit('register', user.id);
        }
      }
    }
</script>

2.3 关于actions

在nodejs中,我们认识到最深刻的一点就是异步调用。但是在Vuex中,mutations属性中包含了大量函数接口,并且具有同步执行的特点。因此在必须在执行完mutations中某个被调用的函数之后,才能继续调用下一个,效率大大降低。
这个问题,就是actions属性所要解决的问题 ==> 异步调用(Async)

// store.js 
// 声明actions, 可以在其中的函数中加入异步代码
export const store = new Vuex.Store({
  state: { ··· },
  getters: { ··· },
  mutations: {
    register(state, userId) {
      ······
    },
    unregister(state, userId) {
      ······
    }
  },
  actions: {
    // actions中的函数名可自定义,此处为了方便练习与mutations中函数同名
    // 写法一:
    register(context, userId) {
      context.commit('register', userId);
    },
    // 写法二:
    unregister( { commit } , userId) {
      commit('unregister', userId);
    }
  }
})


// 在子组件中使用dispatch,调用可异步执行的actions 
// Registrations.vue
<script>
import { mapGetters } from 'vuex'
    export default {
      computed: mapGetters({
        registrations: 'registeredUser',
        total: 'totalRegistrations'
      }),
      methods: {
            unregister(registration) {
              // 调用不可异步的mutations的写法,传入的第一个参数是actions中的函数名
              // this.$store.commit('unregister', registration.userId);

              // 调用可异步的actions的写法
              this.$store.dispatch('unregister', registration.userId);
            }
        }
    }
</script>

2.3 关于store.js的合理分装

store.js中包含了getters、mutations以及actions,随着application的功能扩展,函数将会越来越多,因此把这三个属性分装出去,是十分有必要的。
在src目录下新增store文件夹,更新后当前目录如下:

.
├── build/                      
├── config/
├── node_modules
├── src/
│   ├── main.js                 
│   ├── App.vue       
│   ├── store/  
│   │   ├── store.js
│   │   ├── getters.js  
│   │   ├── mutations.js
│   │   └── actions.js
│   ├── components/           
│   │   └── ...
│   └── assets/                 
│       └── ...
├── .babelrc                   
├── .postcssrc.js               
├── .eslintrc.js               
├── .editorconfig           
├── index.html                 
└── package.json            

分装方法如下:

// store.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'


Vue.use(Vuex)

export const store = new Vuex.Store({
  state: {
    registrations: [],
    users: [
        {id: 1, name: 'Max', registered: false},
        {id: 2, name: 'Anna', registered: false},
        {id: 3, name: 'Chris', registered: false},
        {id: 4, name: 'Sven', registered: false}
    ]
  },
  getters,
  mutations,
  actions
})


// getters.js
export default {
  unregisteredUsers(state) {
     return state.users.filter(user => {
       return !user.registered;
     })
  },
  registeredUser(state) {
    return state.registrations;
  },
  totalRegistrations(state) {
    return state.registrations.length;
  }
}

// mutations.js 与 actions.js同getters的做法

3.使用less修改Vue界面

首先:安装less依赖:
npm install less less-loader --save
然后:修改webpack.base.conf.js文件,配置loader加载依赖,让其支持外部的less。

在modules.rules属性中添加一个新的对象

{
  test: /\.less$/,
  loader: "style-loader!css-loader!less-loader",
}
最后:在Vue组件中使用的时候在style标签里加上lang="less"
less语法

美出天际的登录界面模板

4. Vue组件布局与路由

1. App.vue是整个application的主容器,根据不同的路由放入不同的组件。

即使当前Vue中没有html标签,也同样可以设置html、body标签的属性(width:100%)。

URL中的#号作用——定位页面元素

2. css样式笔记

提高样式优先级
在样式的后面添加"!important"

width: 40% !important;

input标签与button标签在同一行内顶部对齐: vertical-align: baseline;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容