文章目录
安装vue-loader后,有两个必须:
- 必须安装vue-template-compiler
- 必须在webpack配置文件的plugins字段配置
VueLoaderPlugin
const {VueLoaderPlugin} = require("vue-loader");
module.exports = {
plugins:[
new VueLoaderPlugin()
]
}
不使用vuex
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
<button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
props:{
"title":{
type:String,
default:"计数器"
},
"value":{
type:Number,
default:0
}
},
data:function(){
return {
count:this.value
}
},
methods:{
handleIncrement:function(){
this.count++;
},
handleDecrement:function(){
this.count--;
}
}
}
</script>
不传参,使用props默认值
const vm = new Vue({
render:function(createElement){
return createElement(Counter);
}
}).$mount("#root");
自己传参
const vm = new Vue({
render:function(createElement){
return createElement(
Counter,
{
attrs:{
title:"one",
value:10
}
}
)
}
}).$mount("#root");
使用vuex
每个应用仅包含一个store实例,从应用根组件注入store实例,整个应用中的组件都可以访问store中的状态。
import store from "./store.js";
const vm = new Vue({
el:"#root",
store:store,
render:h => h(Counter)
})
Vue组件从store中获取状态,一旦store中的状态发生变化,相应的Vue组件会同步更新。
不能直接修改store中的状态,只能通过commit mutations来实现。
- store
- state
- getters
- mutations
- actions
- commit()
- dispatch()
store中的state
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state:{
count:0,
title:"计数器",
},
mutations:{
increment:function(state){
state.count++;
},
decrement:function(state){
state.count--;
}
}
});
export default store;
访问store中的state
第一种方式:this.$store.state
data选项
data选项从store中获取状态,点击"+“或”-",this.$store.state.count
值变化,但没有驱动页面渲染
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
<button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
</div>
</template>
<script>
export default {
data:function(){
return {
title:this.$store.state.title,
count:this.$store.state.count
}
},
methods:{
handleIncrement:function(){
this.$store.commit("increment");
console.log(this.$store.state.count);
},
handleDecrement:function(){
this.$store.commit("decrement");
console.log(this.$store.state.count);
}
}
}
</script>
计算属性
使用计算属性从store中获取状态,点击"+“或”-",this.$store.state.count
值变化,且可以驱动页面渲染
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
<button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
</div>
</template>
<script>
export default {
computed:{
title:function(){
return this.$store.state.title
},
count:function(){
return this.$store.state.count
}
},
methods:{
handleIncrement:function(){
this.$store.commit("increment");
console.log(this.$store.state.count);
},
handleDecrement:function(){
this.$store.commit("decrement");
console.log(this.$store.state.count);
}
}
}
</script>
第二种方式:辅助函数mapState
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
<button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
computed:mapState({
count:state => state.count,
title:state => state.title
}),
methods:{
handleIncrement:function(){
this.$store.commit("increment");
},
handleDecrement:function(){
this.$store.commit("decrement");
}
}
}
</script>
当映射的计算属性与state的子节点名称相同时,可以给mapState传递一个字符串数组,就像下面这样:
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
<button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
computed:mapState(["count","title"]),
methods:{
handleIncrement:function(){
this.$store.commit("increment");
},
handleDecrement:function(){
this.$store.commit("decrement");
}
}
}
</script>
使用对象扩展运算符...
computed:{
...mapState({
title:state => state.title,
count:state => state.count
})
}
或者
computed:{
...mapState(["title","count"])
}
store中的getters
getters接受state作为第一个参数,其返回值是根据store中的state推导计算而来,且只有它的依赖项发生变化才会被重新计算,类似于计算属性。
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
list:[
{content:"吃饭",done:false},
{content:"睡觉",done:true},
{content:"打豆豆",done:false}
]
}
const getters = {
list_done:function(state){
return state.list.filter(item => item.done);
},
list_undo:function(state){
return state.list.filter(item => !item.done);
}
}
export default new Vuex.Store({
state,
getters
});
访问store中的getters
使用this.$store.getters
<template>
<ul>
<li v-for="(item,idx) in list"
v-bind:key="idx">
{{item.content}}
</li>
</ul>
</template>
<script>
export default {
computed:{
list:function(){
return this.$store.getters.list_done
}
}
}
</script>
使用辅助函数mapGetters
<template>
<ul>
<li v-for="(item,idx) in list"
v-bind:key="idx">
{{item.content}}
</li>
</ul>
</template>
<script>
import {mapGetters} from "vuex";
export default {
computed:{
...mapGetters({
list:"list_undo"
})
}
}
</script>
当映射的计算属性与getters中的节点名称相同时,可以向mapGetters传递一个字符串数组。
<template>
<ul>
<li v-for="(item,idx) in list_undo"
v-bind:key="idx">
{{item.content}}
</li>
</ul>
</template>
<script>
import {mapGetters} from "vuex";
export default {
computed:{
...mapGetters(["list_undo"])
}
}
</script>
store中的mutations
mutations非常类似于事件,
mutations:{
type:fn
}
type
事件类型,是一个字符串fn
事件处理程序,接受state
作为第一个参数,也可以接受第二个参数。- 第二个参数是数值、字符串等简单类型
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const state = { count:0, title:"计数器" } const mutations = { increment:function(state,n){ state.count = state.count+n; }, decrement:function(state){ state.count--; } } export default new Vuex.Store({ state, mutations });
<template> <div> <h4 v-bind:class='["title"]'>{{title}}</h4> <span v-bind:class='["count"]'>{{count}}</span> <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button> <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button> </div> </template> <script> import {mapState} from "vuex"; export default { computed:{ ...mapState(["title","count"]) }, methods:{ handleIncrement:function(){ this.$store.commit("increment",10); }, handleDecrement:function(){ this.$store.commit("decrement"); } } } </script>
- 第二个参数是一个对象
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const state = { count:0, title:"计数器" } const mutations = { increment:function(state,obj){ state.count = state.count+obj.step; }, decrement:function(state){ state.count--; } } export default new Vuex.Store({ state, mutations, });
当第二个参数是一个对象时,还可以像下面这样commit mutations:<template> <div> <h4 v-bind:class='["title"]'>{{title}}</h4> <span v-bind:class='["count"]'>{{count}}</span> <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button> <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button> </div> </template> <script> import {mapState} from "vuex"; export default { computed:{ ...mapState(["title","count"]) }, methods:{ handleIncrement:function(){ this.$store.commit("increment",{ step:20 }); }, handleDecrement:function(){ this.$store.commit("decrement"); } } } </script>
<template> <div> <h4 v-bind:class='["title"]'>{{title}}</h4> <span v-bind:class='["count"]'>{{count}}</span> <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button> <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button> </div> </template> <script> import {mapState} from "vuex"; export default { computed:{ ...mapState(["title","count"]) }, methods:{ handleIncrement:function(){ this.$store.commit({ type:"increment", step:10 }) }, handleDecrement:function(){ this.$store.commit("decrement"); } } } </script>
辅助函数mapMutations
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
count:0,
title:"计数器"
}
const mutations = {
increment:function(state,n){
if(typeof n==="number"){
state.count+=n;
}else{
state.count++;
}
},
decrement:function(state){
state.count--;
}
}
export default new Vuex.Store({
state,
mutations
});
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
<button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
</div>
</template>
<script>
import {mapState,mapMutations} from "vuex";
export default {
computed:{
...mapState(["title","count"])
},
methods:{
...mapMutations({
handleIncrement:"increment",
handleDecrement:"decrement"
}),
},
mounted:function(){
setTimeout(() => {
this.handleIncrement(10);
},2000)
}
}
</script>
此时this.handleIncrement
将映射为this.$store.commit(increment)
;
this.handleIncrement(10)
将映射为this.$store.commit(increment,10)
;
mutation必须是同步函数,那如果想要异步操作呢?
store中的actions
store中的actions和mutations类似,不同的是,actions里可以包含任意异步操作。
每个action接受context
对象作为第一个参数,context对象具有与store一样的属性和方法,也可以接受第二个参数。
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
count:0,
title:"计数器"
}
const mutations = {
increment:function(state){
state.count++;
},
decrement:function(state){
state.count--;
}
}
const actions = {
asyncIncrement:function(context){
setTimeout(() => {
context.commit("increment");
},2000);
},
asyncDecrement:function(context){
setTimeout(() => {
context.commit("decrement");
},1000);
}
}
export default new Vuex.Store({
state,
mutations,
actions
});
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
<button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
computed:{
...mapState(["title","count"])
},
methods:{
onIncrement:function(){
this.$store.dispatch("asyncIncrement");
},
onDecrement:function(){
this.$store.dispatch("asyncDecrement");
}
}
}
</script>
store.dispatch分发action,等异步操作完成,通过commit mutation来更新store中的state。
action的第二个参数可以是一个数值、字符串等基本类型,也可以是一个对象。
- 第二个参数是一个数值
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
count:0,
title:"计数器"
}
const mutations = {
increment:function(state,n){
state.count+=n;
},
decrement:function(state){
state.count--;
}
}
const actions = {
asyncIncrement:function(context){
setTimeout(() => {
context.commit("increment",10);
},2000);
},
asyncDecrement:function(context){
setTimeout(() => {
context.commit("decrement");
},1000);
}
}
export default new Vuex.Store({
state,
mutations,
actions
});
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
<button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
computed:{
...mapState(["title","count"])
},
methods:{
onIncrement:function(){
this.$store.dispatch("asyncIncrement",10);
},
onDecrement:function(){
this.$store.dispatch("asyncDecrement");
}
}
}
</script>
- 第二个参数是一个对象
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
count:0,
title:"计数器"
}
const mutations = {
increment:function(state,obj){
state.count+=obj.step;
},
decrement:function(state){
state.count--;
}
}
const actions = {
asyncIncrement:function(context,obj){
setTimeout(() => {
context.commit("increment",obj);
},2000);
},
asyncDecrement:function(context){
setTimeout(() => {
context.commit("decrement");
},1000);
}
}
export default new Vuex.Store({
state,
mutations,
actions
});
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
<button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
computed:{
...mapState(["title","count"])
},
methods:{
onIncrement:function(){
this.$store.dispatch("asyncIncrement",{
step:10
});
},
onDecrement:function(){
this.$store.dispatch("asyncDecrement");
}
}
}
</script>
如果第二个参数是一个对象,也可以按下面这样dispatch action:
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
<button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
computed:{
...mapState(["title","count"])
},
methods:{
onIncrement:function(){
this.$store.dispatch({
type:"asyncIncrement",
step:10
})
},
onDecrement:function(){
this.$store.dispatch("asyncDecrement");
}
}
}
</script>
使用辅助函数mapActions
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
count:0,
title:"计数器"
}
const mutations = {
increment:function(state){
state.count++
},
decrement:function(state){
state.count--;
}
}
const actions = {
asyncIncrement:function(context){
setTimeout(() => {
context.commit("increment");
},2000);
},
asyncDecrement:function(context){
setTimeout(() => {
context.commit("decrement");
},1000);
}
}
export default new Vuex.Store({
state,
mutations,
actions
});
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
<button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
</div>
</template>
<script>
import {mapState,mapActions} from "vuex";
export default {
computed:{
...mapState(["title","count"])
},
methods:{
...mapActions({
onIncrement:"asyncIncrement",
onDecrement:"asyncDecrement"
})
}
}
</script>
异步在action中的使用
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
<button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
computed:{
...mapState(["title","count"])
},
methods:{
onIncrement:function(){
this.$store.dispatch("asyncIncrement").then(function(){
console.log("add success");
})
},
onDecrement:function(){
this.$store.dispatch("asyncDecrement");
}
}
}
</script>
new Promise()
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
count:0,
title:"计数器"
}
const mutations = {
increment:function(state){
state.count++
},
decrement:function(state){
state.count--;
}
}
const actions = {
asyncIncrement:function(context){
return new Promise(resolve => {
setTimeout(() => {
context.commit("increment");
resolve();
},2000);
})
},
asyncDecrement:function({commit}){
const timeout = ms => new Promise((resolve) => setTimeout(resolve,ms));
timeout(1000).then(function(){
commit("decrement");
})
console.log("sub success")
}
}
export default new Vuex.Store({
state,
mutations,
actions
});
async/await
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
count:0,
title:"计数器"
}
const mutations = {
increment:function(state){
state.count++
},
decrement:function(state){
state.count--;
}
}
const actions = {
asyncIncrement:function(context){
return new Promise(resolve => {
setTimeout(() => {
context.commit("increment");
resolve();
},2000);
})
},
asyncDecrement:async function({commit}){
await setTimeout(function(){
commit("decrement");
},1000);
console.log("sub success")
}
}
export default new Vuex.Store({
state,
mutations,
actions
});
@babel/preset-env
可以转译ES2017、ES2016、ES2015的最新语法,但不能转译全局对象及API,所以需要安装@babel/plugin-transform-plugin
来转译async/await
。
但是,随后仍遇到问题:
Uncaught TypeError: Cannot assign to read only property ‘exports’ of object
这是ES Module 和 CommonJS混用导致的。
解决方法是,
//webpack.config.js
use:{
loader:"babel-loader",
options:{
presets:["@babel/preset-env"],
plugins:["@babel/plugin-transform-runtime"],
sourceType: 'unambiguous'
}
}
sourceType: 'unambiguous'
让babel严格区分 ES Module文件和CommonJS文件。
store中的module
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const counter = {
state:() => ({
count:0,
title:"计数器"
}),
getters:{
another_count:function(state){
return state.count+1;
}
},
mutations:{
increment:function(state){
state.count++
},
decrement:function(state){
state.count--;
}
},
actions:{
"asyncIncrement":function(context){
setTimeout(() => {
context.commit("increment");
},1000);
}
}
}
const todolist = {
state:() => ({
list:[
{content:"吃饭",done:false},
{content:"睡觉",done:true},
{content:"打豆豆",done:false}
]
}),
getters:{
list_undo:function(state){
return state.list.filter(item => !item.done);
},
list_done:function(state){
return state.list.filter(item => item.done);
}
},
mutations:{
click:function(state){
if(state.list.length>0){
state.list.splice(0,1);
}
}
},
actions:{
asyncClick:function(context){
setTimeout(()=>{
context.commit("click");
},1000);
}
}
}
export default new Vuex.Store({
modules:{
counter,
todolist
}
})
<template>
<div>
<h4 v-bind:class='["title"]'>{{title}}</h4>
<span v-bind:class='["count"]'>{{count}}</span>
<button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
<button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
<button v-bind:class='["btn"]' v-on:click="handleAsyncIncrement">异步-</button>
</div>
</template>
<script>
export default {
computed:{
title:function(){
console.log(Object.keys(this.$store.state));
return this.$store.state.counter.title
},
count:function(){
return this.$store.state.counter.count
}
},
methods:{
handleIncrement:function(){
this.$store.commit("increment");
},
handleDecrement:function(){
this.$store.commit("decrement");
},
handleAsyncIncrement:function(){
this.$store.dispatch("asyncIncrement")
}
}
}
</script>
<template>
<div>
<ul>
<li v-for="(item,idx) in list_undo"
v-bind:key="idx">
{{item.content}}
</li>
</ul>
<button v-on:click="handleClick">click me</button>
<button v-on:click="handleAsyncClick">async click me</button>
</div>
</template>
<script>
import {mapGetters} from "vuex";
export default {
computed:{
list_undo:function(){
console.log(Object.keys(this.$store.getters));
return this.$store.getters.list_undo
}
},
methods:{
handleClick:function(){
this.$store.commit("click");
},
handleAsyncClick:function(){
this.$store.dispatch("asyncClick");
}
}
}
</script>
Object.keys(this.$store.state)
输出["counter", "todolist"]
;
Object.keys(this.$store.getters)
输出 ["another_count", "list_undo", "list_done"]