Vue笔记(有点乱)

# Vue学习笔记(2019.7.31)

vue

基本指令用法

v-cloak v-text v-html

  • v-cloak解决插值表达式闪烁问题
[v-cloak] {
	display: none; 
}
  • v-text会覆盖元素中原本的内容,但是插值表达式只会替换自己的这个占位符,不会把整个元素的内容清空。
<div v-text="msg2"></div> 
  • v-html,带有html标签的字符串
<div v-html="msg2">1212112</div>

v-bind v-on

  • v-bind指令可以被简写为 :要绑定的属性,v-bind 中,可以写合法的JS表达式。
<input type="button" value="按钮" :title="mytitle + '123'">
  • v-on

跑马灯

         lang() {
          if (this.intervalId != null) return;
          this.intervalId = setInterval(() => {
            var start = this.msg.substring(0, 1)
            var end = this.msg.substring(1)
            this.msg = end + start
          }, 400)

          // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去;【好处:程序员只需要关心数据,不需要考虑如何重新渲染DOM页面】
        },
        stop() { // 停止定时器
          clearInterval(this.intervalId)
          // 每当清除了定时器之后,需要重新把 intervalId 置为 null
          this.intervalId = null;
        }

v-on

  • .stop 阻止冒泡
  • .prevent 阻止默认事件
  • .capture 添加事件侦听器时使用事件捕获模式
  • .self 只当事件在该元素本身(比如不是子元素)触发时触发回调 (只会阻止自己身上冒泡行为的触发,并不会真正阻止 冒泡的行为)
  • .once 事件只触发一次

v-model

  • v-bind 只能实现数据的单向绑定,从 M 自动绑定到 V, 无法实现数据的双向绑定

  • 使用 v-model 指令,可以实现 表单元素和 Model 中数据的双向数据绑定

  • v-model 只能运用在 表单元素中

  <div id="app">
    <h4>{{ msg }}</h4>
    <!-- <input type="text" v-bind:value="msg" > -->
    <!-- input(radio, text, address, email....)   select    checkbox   textarea   -->
  </div>

class

  1. 数组
:class="['red', 'thin']"
  1. 数组中使用三元表达式
:class="['red', 'thin', isactive?'active':'']"
  1. 数组中嵌套对象
:class="['red', 'thin', {'active': isactive}]"

  1. 直接使用对象
:class="{'red':true,'thin':true,'active':isactive}"

内联样式:

直接在元素上通过 :style 的形式,书写样式对象

:style="{color: 'red', 'font-size': '40px'}"

将样式对象,定义到 data 中,并直接引用到 :style

v-for key

  1. 数组
v-for="(item, i) in list

  1. 迭代对象中的属性
v-for="(val, key, i) in userInfo

  1. 数字
v-for="i in 10"

当在组件中使用** v-for 时,key 现在是必须的。

v-if v-show

  • v-if 的特点:每次都会重新删除或创建元素

  • v-show 的特点: 每次不会重新进行DOM的删除和创建操作,只是切换了元素的 display:none 样式

如果元素涉及到频繁的切换,最好不要使用 v-if, 而是推荐使用 v-show,如果元素可能永远也不会被显示出来被用户看到,则推荐使用 v-if

filter,filters

  1. 私有过滤器
<td>{{item.ctime | dataFormat('yyyy-mm-dd')}}</td>

定义方式:

filters: { 
	dataFormat(input, pattern = ""){
		var dt = new Date(input);
      	// 获取年月日
     	var y = dt.getFullYear();
     	var m = (dt.getMonth() + 1).toString().padStart(2, '0');
      	var d = dt.getDate().toString().padStart(2, '0');

	...
	}
}

使用ES6中的字符串新方法 String.prototype.padStart(maxLength, fillString='') 或 String.prototype.padEnd(maxLength, fillString='')来填充字符串;

  1. 全局过滤器
Vue.filter(
		'dataFormat', 
		function (input, pattern = '') {
		...
		}

注意:当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用!

键盘修饰符

定义:

Vue.config.keyCodes.f2 = 113;

使用自定义的按键修饰符:

<input type="text" v-model="name" @keyup.f2="add">

directive

  1. 全局自定义指令:
Vue.directive(
		'focus', {
      	inserted: function (el) { // inserted 表示被绑定元素插入父节点时调用
        el.focus();
      }
      });

  1. 自定义局部指令:
 directives: {
        color: { 
          bind(el, binding) {
            el.style.color = binding.value;
          }
        },
        'font-weight': function (el, binding2) { 
        // 自定义指令的简写形式,等同于定义了 bind 和 	update 两个钩子函数
          el.style.fontWeight = binding2.value;
        }
      }

使用方式:

<input type="text" v-color="'red'" v-font-weight="900">

vue的生命周期

  • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性

  • created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板

  • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中

  • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示

  • 运行期间的生命周期函数:

  • beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点

  • updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!

  • 销毁期间的生命周期函数:

  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。

  • destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

vue-resource

  1. 发送get请求:
this.$http.get('http://127.0.0.1:8899/api/getlunbo').then(res => {
    console.log(res.body);
  })

  1. 发送post请求:
this.$http.post(url,
				{ name: 'zs' },
                { emulateJSON: true}).then(res => {
    	console.log(res.body);
  });

全局配置接口和emulateJSON 选项:

注意:发起请求的前面不要加/了 this.$http.get('api/getprodlist').then

  • Vue.http.options.emulateJSON = true;

Vue中的动画

  1. 使用过渡类名
  • v-enter 【这是一个时间点】 是进入之前,元素的起始状态,此时还没有开始进入

  • v-leave-to 【这是一个时间点】 是动画离开之后,离开的终止状态,此时,元素 动画已经结束了

  • v-enter-active 【入场动画的时间段】

  • v-leave-active 【离场动画的时间段】

    .v-enter,
    .v-leave-to {
      opacity: 0;
      transform: translateX(150px);
    }
    
    .v-enter-active,
    .v-leave-active{
      transition: all 0.8s ease;
    }

<input type="button" value="toggle" @click="flag=!flag">
    <transition>
      <h3 v-if="flag">这是一个H3</h3>
    </transition>

transition后可加name属性 修改动画的v-前缀

  1. 使用第三方 CSS 动画库

先导入动画类库:

<link rel="stylesheet" type="text/css" href="./lib/animate.css">

再定义 transition 及属性:

<transition
	enter-active-class="fadeInRight"
    leave-active-class="fadeOutRight"
    :duration="{ enter: 500, leave: 800 }">
  	<div class="animated" v-show="isshow">动画哦</div>
</transition>

  • enter-active-class

  • leave-active-classs

  • :duration=''{enter: , leave: }'

  • animated

  1. 使用动画钩子函数
<transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter">
      <div v-if="isshow" class="show">OK</div>
    </transition>

定义三个 methods 钩子方法:

methods: {
        beforeEnter(el) { // 动画进入之前的回调
          el.style.transform = 'translateX(500px)';
        },
        enter(el, done) { // 动画进入完成时候的回调
          el.offsetWidth;
          el.style.transform = 'translateX(0px)';
          done();
        },
        afterEnter(el) { // 动画进入完成之后的回调
          this.isshow = !this.isshow;
        }
      }

  1. v-for 的列表过渡

transition-group 组件把v-for循环的列表包裹起来:

    <transition-group appear tag="ul" name="list">
      <li v-for="(item, i) in list" :key="i">{{item}}</li>
    </transition-group>

  • transition-group 添加 appear 属性,实现页面刚展示出来时候,入场时候的效果

  • 通过为 transition-group 元素,设置 tag 属性,指定 transition-group 渲染为指定的元素

下面的 .v-move 和 .v-leave-active 配合使用,能够实现列表后续的元素,渐渐地漂上来的效果

.v-move{
  transition: all 0.8s ease;
}
.v-leave-active{
  position: absolute;
}

vue组件

  1. 使用 Vue.extend 配合 Vue.component 方法:
var login = Vue.extend({
      template: '<h1>登录</h1>'
    });
    Vue.component('login', login);

  1. 直接使用 Vue.component 方法:
Vue.component('register', {
      template: '<h1>注册</h1>'
    });

  1. 字面量方式
var login= {
	template:#apl
	data(){
		return {}
	}
}
<template id="apl">
	<div></div>
</template>

vue.component('login', login)
//或者挂在到vm的components上

组件切换

v-if v-else
 <input type="button" value="toggle" @click="flag=!flag">
    <my-com1 v-if="flag"></my-com1>
    <my-com2 v-else="flag"></my-com2>

component:is

使用component标签,来引用组件,并通过:is属性来指定要加载的组件:

注意:组件名称是字符串

 <div id="app">
    <a href="#" @click.prevent="comName='login'">登录</a>
    <a href="#" @click.prevent="comName='register'">注册</a>
    <hr>
    <transition mode="out-in">
      <component :is="comName"></component>
    </transition>
  </div>

父子组件传值

  1. 父向子传值

    • 父组件定义子组件标签时bind简写绑定过去:
    < son :finfo="msg"></ son>
    
    

    再在子组件中定义props属性(注意是数组)

    props: ['finfo']
    
    
  2. 子向父传值(借用父向字传递方法)

    • 原理:父组件将方法的引用,传递到子组件内部,子组件在内部调用父组件传递过来的方法,同时把要发送给父组件的数据,在调用方法的时候当作参数传递进去;
    < son @func="getMsg"></ son>
    
    
    • 子组件内部通过this.$emit('方法名', 要传递的数据)方式,来调用父组件中的方法,同时把数据传递给父组件使用
    this.$emit('func', data)
    
    

兄弟之间的传递

  • 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
    • 提供事件中心 var hub = new Vue()
  • 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
  • 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
  • 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
 <div id="app">
    <div>父组件</div>
    <div>
      <button @click='handle'>销毁事件</button>
    </div>
    <test-tom></test-tom>
    <test-jerry></test-jerry>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      兄弟组件之间数据传递
    */
    //1、 提供事件中心
    var hub = new Vue();

    Vue.component('test-tom', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>TOM:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
          hub.$emit('jerry-event', 2);
        }
      },
      mounted: function() {
       // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on(方法名
        hub.$on('tom-event', (val) => {
          this.num += val;
        });
      }
    });
    Vue.component('test-jerry', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>JERRY:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
          hub.$emit('tom-event', 1);
        }
      },
      mounted: function() {
        // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on()方法名
        hub.$on('jerry-event', (val) => {
          this.num += val;
        });
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      },
      methods: {
        handle: function(){
          //4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据  
          hub.$off('tom-event');
          hub.$off('jerry-event');
        }
      }
    });
  </script>

地存储获取时需注意:

JSON.parse(localStorage.getItem(' cmts') || '[]')

localStorage.setItem('cmts', JSON.stringify(list))

组件插槽

  • 组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力
匿名插槽
  <div id="app">
    <!-- 这里的所有组件标签中嵌套的内容会替换掉slot  如果不传值 则使用 slot 中的默认值  --> 
    <alert-box>有bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
    <alert-box></alert-box>
  </div>

  <script type="text/javascript">
    /*
      组件插槽:父组件向子组件传递内容
    */
    Vue.component('alert-box', {
      template: `
        <div>
          <strong>ERROR:</strong>
		# 当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。
		# 插槽内可以包含任何模板代码,包括 HTML
          <slot>默认内容</slot>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>
</body>
</html>

具名插槽
  • 具有名字的插槽
  • 使用 < slot >中的 "name" 属性绑定元素
 <div id="app">
    <base-layout>
       <!-- 2、 通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上
				如果没有匹配到 则放到匿名的插槽中   --> 
      <p slot='header'>标题信息</p>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <p slot='footer'>底部信息信息</p>
    </base-layout>

    <base-layout>
      <!-- 注意点:template临时的包裹标签最终不会渲染到页面上     -->  
      <template slot='header'>
        <p>标题信息1</p>
        <p>标题信息2</p>
      </template>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <template slot='footer'>
        <p>底部信息信息1</p>
        <p>底部信息信息2</p>
      </template>
    </base-layout>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      具名插槽
    */
    Vue.component('base-layout', {
      template: `
        <div>
          <header>
			###	1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字
            <slot name='header'></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
			###  注意点: 
			###  具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序
            <slot name='footer'></slot>
          </footer>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>
</body>
</html>

作用域插槽
  • 父组件对子组件加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致
<div id="app">
    <!-- 
		1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件,
		但样式希望不一样 这个时候我们需要使用作用域插槽 
		
	-->  
    <fruit-list :list='list'>
       <!-- 2、 父组件中使用了<template>元素,而且包含scope="slotProps",
			slotProps在这里只是临时变量   
		---> 	
      <template slot-scope='slotProps'>
        <strong v-if='slotProps.info.id==3' class="current">
            {{slotProps.info.name}}		         
         </strong>
        <span v-else>{{slotProps.info.name}}</span>
      </template>
    </fruit-list>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      作用域插槽
    */
    Vue.component('fruit-list', {
      props: ['list'],
      template: `
        <div>
          <li :key='item.id' v-for='item in list'>
			###  3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg="xxx",
			###   插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。
					如果父组件为这个插槽提供了内容,则默认的内容会被替换掉
            <slot :info='item'>{{item.name}}</slot>
          </li>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        list: [{
          id: 1,
          name: 'apple'
        },{
          id: 2,
          name: 'orange'
        },{
          id: 3,
          name: 'banana'
        }]
      }
    });
  </script>
</body>
</html>

购物车案例

this.$refs

定义:ref="mycom"

获取:this.$refs.mycom.innerText

vue-router

  1. 使用 router-link 组件来导航

    < router-link to="/login">登录< /router-link>

  2. 使用 router-view 组件来显示匹配到的组件

    < router-view>< /router-view>

  3. 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则

 var router = new VueRouter({
      routes: [
      	{ path: '/', redirect: '/login' },
        { path: '/login', component: login },
        { path: '/register', component: register }
      ]
    });

  1. 再挂载到vm实例上

路由传参

  1. restful风格:
const router = new VueRouter({
    routes: [
       { path: '/register/:id', component: register } 
    ]
})

通过this.$route.params获取

  1. url地址传参
 <router-link to="/login?id=10&name=zs">登录</router-link>

通过this.$route.query.id获取

  1. props属性
const router = new VueRouter({
    routes: [
      { path: '/register/:id', component: register, props: true}
    ]
})

const register = {
    props: ['id']
    template: `<div> {id} </div>`
}

路由嵌套

    cost router = new VueRouter({
      routes: [
        {
          path: '/account',·
          component: account,
          children: [
            { path: 'login', component: login },
            { path: 'register', component: register }
          ]
        }
      ]
    })

使用 children 属性,实现子路由,同时,子路由的 path 前面,不要带 / ,否则永远以根路径开始请求,这样不方便我们用户去理解URL地址

命名路由

const router = new VueRouter({
    routes: [
      { path: '/register/:id',
       	component: register, 
       	name: register
      }
    ]
})

使用:
<router-link :to="{name: 'register', params: {id: 123}}">
router.push({name: register, params: {id: 123}})

编程式导航

router.push('/home')
router.push({path: '/home'})
router.push({name: register, params: {id: 123}})
router.push({path: '/register', query: {uname: 'lisi'}})

命名视图

var router = new VueRouter({
      routes: [
        {
          path: '/', components: {
            'default': header,
            'left': leftBox,
            'main': mainBox
          }
        }
      ]
    })

watch computed

  1. watch:
watch: {
        'firstName': function (newVal, oldVal) { // 第一个参数是新数据,第二个参数是旧数据
          this.fullName = newVal + ' - ' + this.lastName;
        },
        '$route': function (newVal, oldVal) {
          if (newVal.path === '/login') {
            console.log('这是登录组件');
          }
        }

  1. computed:

    computed: { 
        // 计算属性; 特点:当计算属性中所以来的任何一个 data 属性改变之后,都会重新触发本计算	属性 的重新计算,从而更新 fullName 的值fullName() {
        return this.firstName + ' - ' + this.lastName;
      }
    
    

注意: 只要 计算属性,这个 function 内部,所用到的 任何 data 中的数据发送了变化,就会立即重新计算 这个 计算属性的值

计算属性缓存 vs 方法

<p>Reversed message: "{{ reversedMessage() }}"</p>

// 在组件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
  now: function () {
    return Date.now()
  }
}


webpack

安装

方式一:

  1. 运行npm i webpack -g全局安装webpack,这样就能在全局使用webpack的命令
  2. 在项目根目录中运行npm i webpack --save-dev安装到项目依赖中

方式二:

安装webpack:npm install webpack webpack-cli -D

执行命令:

1. webpack src/js/main.js dist/bundle.js

2. webpack 

单独使用webpack命令,webpack默认打包src目录里的index.js到dist目录的main.js

配置

  1. 在项目根目录中创建webpack.config.js

    var path = require('path');
    module.exports = {
        entry: path.join(__dirname, 'src/js/main.js'),
        output: { 
            path: path.join(__dirname, 'dist'), 
            filename: 'bundle.js' 
        }
    }
    
    
  2. 修改项目中的package.json文件添加运行脚本dev

    "scripts":{
            "dev":"webpack"
     }
    
    
  3. 运行dev命令进行项目打包:npm run dev

  4. 在页面中引入项目打包之后在dist文件夹中生成的js文件

    <script src="../dist/bundle.js"></script>
    
    

webpack-dev-server

作用:webpack自动打包

  • npm i webpack-dev-server -D

  • package.json文件中的指令,来进行运行webpack-dev-server命令,在scripts节点下新增:

"scripts":{
	"dev":"webpack-dev-server"
}

此时需要修改index.html中script的src属性为:

<script src="/bundle.js"></script>

  • 为了能在访问http://localhost:8080/的时候直接访问到index首页,可以使用--contentBase src指令来修改dev指令,指定启动的根目录:
 "dev": "webpack-dev-server --contentBase src"

html-webpack-plugin

由于使用--contentBase指令的过程比较繁琐,需要指定启动的目录,同时还需要修改index.html中script标签的src属性,所以推荐大家使用html-webpack-plugin插件配置启动页面.

  1. 运行npm i html-webpack-plugin -D安装到开发依赖
  2. 修改webpack.config.js配置文件如下:
	var path = require('path');
    var htmlWebpackPlugin = require('html-webpack-plugin');

    module.exports = {
        entry: path.resolve(__dirname, 'src/js/main.js'), 
        output: { 
            path: path.resolve(__dirname, 'dist'), 
            filename: 'bundle.js' 
        },
        plugins:[ 
            new htmlWebpackPlugin({
                template:path.join(__dirname, 'src/index.html'),
                filename:'index.html'
            })
        ]
    }

  1. 修改package.jsonscript节点中的dev指令如下:
"dev": "webpack-dev-server --open --host 127.0.0.1 --port 8888"

注意:html-webpack-plugin插件会自动把bundle.js注入到index.html页面中,所以可以不用添加引入bundle.js的script标签

webpack-dev-server配置项

方式1:

"dev": "webpack-dev-server --hot --port 4321 --open"

方式2:

  1. 修改webpack.config.js文件,新增devServer节点如下:
devServer:{
        hot:true,
        open:true,
        port:4321
    }

  1. 在头部引入webpack模块:
var webpack = require('webpack');

  1. plugins节点下新增:
new webpack.HotModuleReplacementPlugin()

打包其他类型文件

  1. 安装相关打包需要工具

打包css、less、sass:

npm i style-loader css-loader -D
npm i less-loader less -D
npm i sass-loader node-sass -D

配置webpack.config.js的module中的rules数组:

module.exports = {
        ......
        module : {
            rules:[
                {   test:/\.css$/, use:['style-loader','css-loader']  },
                {   test:/\.less$/,use:['style-loader','css-loader','less-loader']  },
                {   test:/\.scss$/,use:['style-loader','css-loader','sass-loader']   }
            ]
        }
    }

自动添加浏览器前缀postcss

npm i postcss-loader autoprefixer -D

创建并配置postcss.config.js文件:

const autoprefixer = require("autoprefixer");
module.exports = {
    plugins:[ autoprefixer ]
}

配置更改webpack.config.js的module中的rules数组:

module.exports = {
    ......
    plugins:[ htmlPlugin ],
    module : {
        rules:[
            ..........
            {  test:/\.css$/, use:['style-loader','css-loader','postcss-loader']  }
        ]
    }
}


处理css中的路径:

npm i url-loader file-loader -D

配置webpack.config.js的module中的rules数组:

module.exports = {
        ......
        module : {
            rules:[
                        .......
	{  test:/\.jpg|png|gif|bmp|ttf|eot|svg|woff|woff2$/,
                           use:"url-loader?limit=16940"  }
            ]
        }
    }

处理高级JS语法:

//转换器相关包
npm i babel-loader @babel/core @babel/runtime -D

//语法插件包
npm i @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-proposal-class-properties -D

在项目根目录创建并配置babel.config.js文件:

module.exports = {
        presets:["@babel/preset-env"],
        plugins:[ "@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties" ]
    }

配置webpack.config.js的module中的rules数组:

rules:[
	{  test:/\.js$/,  use:"babel-loader",  exclude:/node_modules/  }
]

处理vue:

npm i vue-loader vue-template-compiler -D

配置规则:更改webpack.config.js的module中的rules数组:

    const VueLoaderPlugin = require("vue-loader/lib/plugin");
   	 	module.exports = {
        plugins:[ htmlPlugin, new VueLoaderPlugin()],
        module : {
            rules:[
                ......,
                	{ test:/\.vue$/,loader:"vue-loader",   
                }
            ]
        }
    }

构建Vue项目

npm install vue

  1. webpack.config.js中添加resolve属性:
resolve: {
    alias: {
      'vue$': 'vue/dist/vue.js' //可以不添加
    }
  }

  1. 不修改,用 render()
//import vue from '../node_modules/vue/dist/vue.js'
import Vue from 'vue'
// 导入 App组件
import App from './components/App.vue'
// 创建一个 Vue 实例,使用 render 函数,渲染指定的组件
var vm = new Vue({
  el: '#app',
  render: c => c(App)
});

webpack完整配置:

var path = require('path')
var htmlWebpackPlugin = require('html-webpack-plugin')
var webpack = require('webpack')

module.exports = {
  entry: path.join(__dirname, './src/main.js'), // 入口文件
  output: { // 指定输出选项
    path: path.join(__dirname, './dist'), // 输出路径
    filename: 'bundle.js' // 指定输出文件的名称
  },
  plugins: [ // 所有webpack  插件的配置节点
    new htmlWebpackPlugin({
      template: path.join(__dirname, './src/index.html'), // 指定模板文件路径
      filename: 'index.html' // 设置生成的内存页面的名称
    }),
    new VueLoaderPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  module: { // 配置所有第三方loader 模块的
    rules: [ // 第三方模块的匹配规则
      { test: /\.css$/, use: ['style-loader', 'css-loader'] }, 
      { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, 
      { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
      { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader?limit=7631&name=[hash:8]-[name].[ext]' }, 
      // limit 给定的值,是图片的大小,单位是 byte, 如果我们引用的 图片,大于或等于给定的 limit值,则不会被转为base64格式的字符串, 如果 图片小于给定的 limit 值,则会被转为 base64的字符串
      { test: /\.(ttf|eot|svg|woff|woff2)$/, use: 'url-loader' }, // 处理 字体文件的 loader 
      { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }, // 配置 Babel 来转换高级的ES语法
      { test: /\.vue$/, use: 'vue-loader' } 
    ]
  },
  resolve: {
    alias: { // 修改 Vue 被导入时候的包的路径
      // "vue$": "vue/dist/vue.js"
    }
  }
}

webpack中vue-router

  1. 下载
npm i vue-router

  1. main.js导入并引用
import VueRouter from 'vue-router'
Vue.use(VueRouter);

  1. 抽离router.js 导入需要展示的组件:
import login from './components/account/login.vue'

  1. 创建路由对象:
var router = new VueRouter({
  routes: [
{ path: '/', redirect: '/login' },

{ path: '/login', component: login },

{ path: '/register', component: register }
  ]

});

  1. 挂载到vue实例上

使用第三方组件

使用MintUI

首先下载:

npm i mint-ui

  1. 完整引入:
import MintUI from 'mint-ui'  //导入所以组件
import 'mint-ui/lib/style.css' //可省略node_modules
Vue.use(MintUI)

//css组件可全局使用
<mt-button type="primary" size="large">primary</mt-button>


//组件中导入组件js组件要按需导入
import { Toast } from 'mint-ui'
//js中使用
this.toastInstance = Toast({
    //配置项
})

//销毁Toast:
this.toastInstace.close()

  1. 按需导入MintUI
    • 先下载:
npm install babel-plugin-component -D

然后将 .babelrc 修改为:

{
  "presets": [
    ["es2015", { "modules": false }]
  ],
  "plugins": [["component", [
    {
      "libraryName": "mint-ui",
      "style": true
    }
  ]]]
}

再按需导入:

import { Button, Cell } from 'mint-ui'

Vue.component(Button.name, Button)
Vue.component(Cell.name, Cell)

使用MUI

需手动下载,文档新建lib放入其中

import '../lib/mui/css/mui.min.css'

使用boostrap

首先:

npm install jquery  -D
npm install bootstrap  -D

webpack.config.js添加:

plugins: [
        new webpack.ProvidePlugin({
          $: "jquery",
          jQuery: "jquery",
          "windows.jQuery": "jquery"    

 })

最后导入使用:

import $ from ‘jquery’
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap/dist/css/bootstrap.min.js'

中间组件的切换动画

v-enter和v-leave-to要拆分来写 还需加绝对定位:

.v-enter {
  opacity: 0;
  transform: translateX(100%);
}

.v-leave-to {
  opacity: 0;
  transform: translateX(-100%);
  position: absolute;
}

.v-enter-active,
.v-leave-active {
  transition: all 0.5s ease;
}


vue中的vue-resource

import VueResource from 'vue-resource'

Vue.use(VueResource);

mui的tab-top-webview-main分类滑动栏问题

  1. App.vue 中的 router-link 身上的类名 mui-tab-item 存在兼容性问题,导致tab栏失效,可以把mui-tab-item改名为mui-tab-item1,并复制相关的类样式,来解决这个问题;

  2. tab-top-webview-main`组件第一次显示到页面中的时候,无法被滑动的解决方案:

 import mui from '../../../lib/mui/js/mui.min.js'


  • 导入的 mui.js ,但是,控制台报错: Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode

  • 原因是 mui.js 中用到了 'caller', 'callee', and 'arguments' 东西,但是, webpack 打包好的 bundle.js 中,默认是启用严格模式的,所以,这两者冲突了;

  • 解决方案: 1. 把 mui.js 中的 非严格 模式的代码改掉;但是不现实; 2. 把 webpack 打包时候的严格模式禁用掉;需要在webpack.config.js的plugings节点添加如下:

{ "plugins":["transform-remove-strict-mode"]
}

在 组件的 mounted 事件钩子中,注册 mui 的滚动事件:

mounted() {
        mui('.mui-scroll-wrapper').scroll({
          deceleration: 0.0005 //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
        });
  	}

  1. 滑动的时候报警告:Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

解决方法,可以加上 { touch-action: none; } 这句样式去掉。*

lazyload,vue-preview

1 mint-ui的lazyload按需导入有问题,只能全部导入所以组件

<ul>
  <li v-for="item in list">
    <img v-lazy="item">
  </li>
</ul>

image[lazy=loading] {
  width: 40px;
  height: 300px;
  margin: auto;
}

  1. vue-preview的使用:
 <img class="preview-img" v-for="(item, index) in list" :src="item.src" height="100" @click="$preview.open(index, list)" :key="item.src">

 getThumbs() {
      // 获取缩略图
      this.$http.get("api/getthumimages/" + this.id).then(result => {
        if (result.body.status === 0) {
          // 循环每个图片数据,补全图片的宽和高
          result.body.message.forEach(item => {
            item.w = 600;
            item.h = 400;
          });
          // 把完整的数据保存到 list 中
          this.list = result.body.message;
        }
      });
    }
  }

真机调试

  1. 要保证自己的手机可以正常运行;

  2. 要保证 手机 和 开发项目的电脑 处于同一个 WIFI 环境中,也就是说 手机 可以 访问到 电脑的 IP

  3. 打开自己的 项目中 package.json 文件,在 dev 脚本中,添加一个 --host 指令, 把 当前 电脑的 WIFI IP地址, 设置为 --host 的指令值;

  • 如何查看自己电脑所处 WIFI 的IP呢, 在 cmd 终端中运行 ipconfig , 查看 无线网的 ip 地址

vue-router编程式导航

router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

抽离轮播图,小球动画,传值问题

抽离轮播图组件

<template>
  <div>
    <mt-swipe :auto="4000">
      <!-- 在组件中,使用v-for循环的话,一定要使用key-->
      <!-- 将来,谁使用此 轮播图组件,谁为我们传递 lunbotuList -->
      <!-- 此时,lunbotuList 应该是 父组件向子组件传值来设置 -->
      <mt-swipe-item v-for="item in lunbotuList" :key="item.url">
        <img :src="item.img" alt="" :class="{'full': isfull}">
      </mt-swipe-item>
    </mt-swipe>
  </div>
  
  <!-- 1. 首页中的图片,它的宽和高,都是使用了100% 的宽度 -->
  <!-- 2. 在商品详情页面中,轮播图的 图片,如果也使用 宽高为100%的话,页面不好看 -->
  <!-- 3. 商品详情页面中的轮播图,期望 高度是 100%,但是 宽度为 自适应 -->
  <!-- 5. 我们可以定义一个 属性,让使用轮播图的调用者手动指定是否为100%的宽度 -->

</template>

<script>
export default {
  props: ["lunbotuList", "isfull"]
};
</script>

<style lang="scss" scoped>
.mint-swipe {
  height: 200px;

  .mint-swipe-item {
    text-align: center;

    img {
      // width: 100%;
      height: 100%;
    }
  }
}

.full {
  width: 100%;
}
</style>


小球动画

beforeEnter(el) {
      el.style.transform = "translate(0, 0)";
    },
    enter(el, done) {
      el.offsetWidth;

      // 小球动画优化思路:
      // 1. 先分析导致 动画 不准确的 本质原因: 我们把 小球 最终 位移到的 位置,已经局限在了某一分辨率下的 滚动条未滚动的情况下;
      // 2. 只要分辨率和 测试的时候不一样,或者 滚动条有一定的滚动距离之后, 问题就出现了;
      // 3. 因此,我们经过分析,得到结论: 不能把 位置的 横纵坐标 直接写死了,而是应该 根据不同情况,动态计算这个坐标值;
      // 4. 经过分析,得出解题思路: 先得到 徽标的 横纵 坐标,再得到 小球的 横纵坐标,然后 让 y 值 求差, x 值也求 差,得到 的结果,就是横纵坐标要位移的距离
      // 5. 如何 获取 徽标和小球的 位置???   domObject.getBoundingClientRect()

      // 获取小球的 在页面中的位置
      const ballPosition = this.$refs.ball.-();
      // 获取 徽标 在页面中的位置
      const badgePosition = document
        .getElementById("badge")
        .getBoundingClientRect();

      const xDist = badgePosition.left - ballPosition.left;
      const yDist = badgePosition.top - ballPosition.top;

      el.style.transform = `translate(${xDist}px, ${yDist}px)`;
      el.style.transition = "all 0.5s cubic-bezier(.4,-0.3,1,.68)";
      done();
    },
    afterEnter(el) {
      this.ballFlag = !this.ballFlag;
    }

numberbox子组件传值给父组件

//父组件:
getSelectedCount(count) {
	// 当子组件把 选中的数量传递给父组件的时候,把选中的值保存到 data 上
	this.selectedCount = count;
	console.log("父组件拿到的数量值为: " + this.selectedCount);
}

//子组件:
countChanged() {
      // 每当 文本框的数据被修改的时候,立即把 最新的数据,通过事件调用,传递给父组件
      // console.log(this.$refs.numbox.value);
      this.$emit("getcount", parseInt(this.$refs.numbox.value));
    }

当父组件要传递给子组件的值是异步请求的值时需要子组件用watch去监听传递过来的值

父组件要传递给子组件的值是异步请求的值
watch: {
    // 属性监听
    max: function(newVal, oldVal) {
      // 使用 JS API 设置 numbox 的最大值
      mui(".mui-numbox")
        .numbox()
        .setOption("max", newVal);
    }
  }

vuex 优化购物车

加入购物车按钮:

    addToShopCar() {
      // 添加到购物车
      this.ballFlag = !this.ballFlag;
      // { id:商品的id, count: 要购买的数量, price: 商品的单价,selected: false  }
      // 拼接出一个,要保存到 store 中 car 数组里的 商品信息对象
      var goodsinfo = {
        id: this.id,
        count: this.selectedCount,
        price: this.goodsinfo.sell_price,
        selected: true
      };
      // 调用 store 中的 mutations 来将商品加入购物车
      this.$store.commit("addToCar", goodsinfo);
    }

购物车vuex里的store:

var car = JSON.parse(localStorage.getItem('car') || '[]')

var store = new Vuex.Store({
  state: { // this.$store.state.***
    car: car // 将 购物车中的商品的数据,用一个数组存储起来,在 car 数组中,存储一些商品的对象, 咱们可以暂时将这个商品对象,设计成这个样子   
    // { id:商品的id, count: 要购买的数量, price: 商品的单价,selected: false  }
  },
  mutations: { // this.$store.commit('方法的名称', '按需传递唯一的参数')
    addToCar(state, goodsinfo) {
      // 点击加入购物车,把商品信息,保存到 store 中的 car 上
      // 分析:
      // 1. 如果购物车中,之前就已经有这个对应的商品了,那么,只需要更新数量
      // 2. 如果没有,则直接把 商品数据,push 到 car 中即可

      // 假设 在购物车中,没有找到对应的商品
      var flag = false

      state.car.some(item => {
        if (item.id == goodsinfo.id) {
          item.count += parseInt(goodsinfo.count)
          flag = true
          return true
        }
      })

      // 如果最终,循环完毕,得到的 flag 还是 false,则把商品数据直接 push 到 购物车中
      if (!flag) {
        state.car.push(goodsinfo)
      }

      // 当 更新 car 之后,把 car 数组,存储到 本地的 localStorage 中
      localStorage.setItem('car', JSON.stringify(state.car))
    },
    updateGoodsInfo(state, goodsinfo) {
      // 修改购物车中商品的数量值
      // 分析: 
      state.car.some(item => {
        if (item.id == goodsinfo.id) {
          item.count = parseInt(goodsinfo.count)
          return true
        }
      })
      // 当修改完商品的数量,把最新的购物车数据,保存到 本地存储中
      localStorage.setItem('car', JSON.stringify(state.car))
    },
    removeFormCar(state, id) {
      // 根据Id,从store 中的购物车中删除对应的那条商品数据
      state.car.some((item, i) => {
        if (item.id == id) {
          state.car.splice(i, 1)
          return true;
        }
      })
      // 将删除完毕后的,最新的购物车数据,同步到 本地存储中
      localStorage.setItem('car', JSON.stringify(state.car))
    },
    updateGoodsSelected(state, info) {
      state.car.some(item => {
        if (item.id == info.id) {
          item.selected = info.selected
        }
      })
      // 把最新的 所有购物车商品的状态保存到 store 中去
      localStorage.setItem('car', JSON.stringify(state.car))
    }
  },
  getters: { // this.$store.getters.***
    // 相当于 计算属性,也相当于 filters
    getAllCount(state) {
      var c = 0;
      state.car.forEach(item => {
        c += item.count
      })
      return c
    },
    getGoodsCount(state) {
      var o = {}
      state.car.forEach(item => {
        o[item.id] = item.count
      })
      return o
    },
    getGoodsSelected(state) {
      var o = {}
      state.car.forEach(item => {
        o[item.id] = item.selected
      })
      return o
    },
    getGoodsCountAndAmount(state) {
      var o = {
        count: 0, // 勾选的数量
        amount: 0 // 勾选的总价
      }
      state.car.forEach(item => {
        if (item.selected) {
          o.count += item.count
          o.amount += item.price * item.count
        }
      })
      return o
    }
  }
})

vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。公用数据存储处理模块

  1. 使用步骤1:导包
import Vuex from 'vuex'
// 3. 注册vuex到vue中
Vue.use(Vuex)

  1. 新建store实例
var store = new Vuex.Store({
  state: {
    // 大家可以把 state 想象成 组件中的 data ,专门用来存储数据的
    // 如果在 组件中,想要访问,store 中的数据,只能通过 this.$store.state.*** 来访问
    count: 0
  },
  mutations: {
    // 注意: 如果要操作 store 中的 state 值,只能通过 调用 mutations 提供的方法,才能操作对应的数据,不推荐直接操作 state 中的数据,因为 万一导致了数据的紊乱,不能快速定位到错误的原因,因为,每个组件都可能有操作数据的方法;
    increment(state) {
      state.count++
    },
    // 注意: 如果组件想要调用 mutations 中的方法,只能使用 this.$store.commit('方法名')
    // 这种 调用 mutations 方法的格式,和 this.$emit('父组件中方法名')
    subtract(state, obj) {
      // 注意: mutations 的 函数参数列表中,最多支持两个参数,其中,参数1: 是 state 状态; 参数2: 通过 commit 提交过来的参数;
      console.log(obj)
      state.count -= (obj.c + obj.d)
    }
  },
  getters: {
    // 注意:这里的 getters, 只负责 对外提供数据,不负责 修改数据,如果想要修改 state 中的数据,请 去找 mutations
    optCount: function (state) {
      return '当前最新的count值是:' + state.count
    }
    // 经过咱们回顾对比,发现 getters 中的方法, 和组件中的过滤器比较类似,因为 过滤器和 getters 都没有修改原数据, 都是把原数据做了一层包装,提供给了 调用者;
    // 其次, getters 也和 computed 比较像, 只要 state 中的数据发生变化了,那么,如果 getters 正好也引用了这个数据,那么 就会立即触发 getters 的重新求值;
  }
})

state

  • *mapState 辅助函数
  1. 某个组件想要用到store里state里的数据时,
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

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

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

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

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

computed: mapState(['count'])
// 映射 this.count 为 store.state.count

  1. 当与局部计算属性混合使用:
computed: {
  localComputed () { /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })
}

getters

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

通过属性访问:

store.getters.doneTodos 

Getter 也可以接受其他 getter 作为第二个参数:

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}

store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

mapGetters辅助函数

return方法的gettersb不能用mapGetters映射

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ]),
    getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }   
  }
}

mutation

  • 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

需要以相应的 type 调用 store.commit 方法:

store.commit('increment')

  • 提交载荷(Payload)

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}

store.commit('increment', 10)

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

// ...
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

  • 对象风格的提交方式
store.commit({
  type: 'increment',
  amount: 10
})

  • Mutation 必须是同步函数

  • mapMutations

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    incrementByMutation (state,playLoad) {
      state.count + = playload.count
    }
  },
  actions: {
    incrementByAction (context) {
      context.commit('incrementByMutation')
    }
  }
})

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):

actions: {
  incrementByAction ({ commit }) {
    commit('incrementByMutation')
  }
}

Action 通过 store.dispatch 方法触发:

store.dispatch('incrementByAction')

可以在 action 内部执行异步操作:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('incrementByMutation')
    }, 1000)
  }
}

Actions 支持同样的载荷方式和对象方式进行分发:

// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

actions: {
  incrementAsync ({ commit },count) {
    setTimeout(() => {
      commit('incrementByMutation',count)
    }, 1000)
  }
}


组合action:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

store.dispatch('actionA').then(() => {
  // ...
})

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

module

使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

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 的状态

  • 对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

  • 同样,对于模块内部的 action,局部状态通过 context.state暴露出来,根节点状态则为 context.rootState:
const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

  • 对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

无命名空间情况下,模块内部的 getter、mutation 和 getter 是注册在全局命名空间下的,所以:

  1. 访问a模块下count的state:
store.state.a.count

  1. 访问a模块下的mutation ,getter,action
store.commit('someMutationInA')

store.getters.someGetterINA

store.dispatch('someActionINA')

添加命名空间:

  • 访问a模块下的mutation ,getter,action:
store.commit('a/someMutationInA')

store.getters['a/someGetterINA']

store.dispatch('a/someActionINA')

  • 在带命名空间的模块内访问全局内容:
modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

  • 带命名空间的模块注册全局 action
{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,

      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}

  • 带命名空间的绑定函数
computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

简化:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

而且,你可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

posted @ 2020-01-31 22:25  garxirapper  阅读(165)  评论(0编辑  收藏  举报