Vue详解(四)

一、浏览器本地存储webStorage

存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

1. 相关API

(1) xxxxxStorage.setItem('key', 'value')该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
(2) xxxxxStorage.getItem('person');该方法接受一个键名作为参数,返回键名对应的值。
(3) xxxxxStorage.removeItem('key');该方法接受一个键名作为参数,并把该键名从存储中删除。
(4)xxxxxStorage.clear()该方法会清空存储中的所有数据。

2. 注意点:

    (1). SessionStorage存储的内容会随着浏览器窗口关闭而消失。

    (2). LocalStorage存储的内容,需要手动清除才会消失。

    (3). ```xxxxxStorage.getItem(xxx)```如果xxx对应的value获取不到,那么getItem的返回值是null。

    (4). ```JSON.parse(null)```的结果依然是null。

二、组件的自定义事件

组件的自定义事件

1. 一种组件间通信的方式,适用于:<strong style="color:red">子组件 ===> 父组件</strong>

2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(<span style="color:red">事件的回调在A中</span>)。

3. 绑定自定义事件:

    1. 第一种方式,在父组件中

:```<Demo @atguigu="test"/>```  或 ```<Demo v-on:atguigu="test"/>```

    2. 第二种方式,在父组件中:

         <Demo ref="demo"/>
             ......
          mounted(){

           this.$refs.xxx.$on('atguigu',this.test)
        }

        ```

3. 若想让自定义事件只能触发一次,可以使用```once```修饰符,或```$once```方法。

4. 触发自定义事件:```this.$emit('atguigu',数据)```    

5. 解绑自定义事件```this.$off('atguigu')```

6. 组件上也可以绑定原生DOM事件,需要使用```native```修饰符。

7. 注意:通过```this.$refs.xxx.$on('atguigu',回调)```绑定自定义事件时,回调<span style="color:red">要么配置在methods中</span>,<span style="color:red">要么用箭头函数</span>,否则this指向会出问题!

父组件给子组件传递数据,用 props属性

子组件给父组件传递数据的方法如下:

1.props法

父组件先给子组件传递一个函数

<!-- 通过父组件给子组件传递函数类型的props实现:子给父传数据 -->
<School :getSchoolName="getSchoolName" />
getSchoolName(name) {
    console.log('App收到了学校名:', name);
},

子组件用props接收到函数后进行调用传值

<button @click="sendSchoolName(name)">点击把name交给App</button>
 props: ['getSchoolName'],
    methods: {
        sendSchoolName(name) {
            this.getSchoolName(name);
        }
    },
2.自定义事件(v-on)

父组件给子组件绑定zzz事件,事件触发就执行getStudentName函数

<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传数据(第一种写法,使用v-on或@) -->
<Student v-on:zzz="getStudentName"></Student>
getStudentName(name, age) {
            console.log('App收到了学生名:', name, age);
        }

在子组件中用$emit去触发zzz事件,第一个参数是事件名称,后面可以传参数

<button @click="sendStudentName">点我把学生名给App</button>
sendStudentName() {
            //触发Student组件实例身上的zzz事件,并传数据过去
            this.$emit('zzz', this.name, this.age);
        }
3.自定义事件(ref)
<Student ref="student" />

用ref把标签标记一下,再获取进行绑定自定义事件

methods: {
    getSchoolName(name) {
        console.log('App收到了学校名:', name);
    },
    getStudentName(name, age) {
        console.log('App收到了学生名:', name, age);
    }
},
//挂载完成时,手动把zzz事件绑定到student的vc上
mounted() {
    // this.$refs.student.name???
    setTimeout(() => {
        //挂载完成后,隔3秒再给Student绑定事件
        this.$refs.student.$on('zzz', this.getStudentName);
    }, 3000);

这种方法可以实现异步

4.解绑自定义事件
//解绑一个自定义事件
this.$off('zzz');
//解绑多个自定义事件
this.$off(['zzz', 'zc']);
//解绑所有自定义事件
this.$off();
5.注意点

(1)组件标签上写的v-on都会被当成自定义事件,即便是写@click也会当成自定义事件,想要用原生DOM事件的话,需要加native,比如@click.native = "demo"

(2)通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中然后通过this.getStudentName传过来。

this.$refs.student.$on('zzz', this.getStudentName);
mounted() {
        this.$refs.student.$on('zzz',  (name, ...arr) => {
            console.log(this);  //this指向App组件实例
            this.studentName = name;  //成功,因为this指向的是App组件实例对象
        });
    }

如果不写箭头函数,this会出现问题,即指向的是student组件

三、全局事件总线(GlobalEventBus)

一种组件间通信的方式,适用于任意组件间通信。是想实现这个功能,必须在所有组件外设置一个中转站,所有组件都能访问到它,并且他还有$on,$off、$emit等方法。显然,只能Vue.prototype上添加一个属性,因为vc和vm都能访问到它。

1.安装全局事件总线

用vm安装(一般用这个)

   new Vue({
   	......
   	beforeCreate() {
   		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
   	},
       ......
   }) 

用vc安装(了解)

const Demo = Vue.extend({});
const d = new Demo();
Vue.prototype.$bus = d;
2.使用全局事件总线

接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的<回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }

提供数据的组件B去触发这个自定义事件这样就实现了组件B把数据传给组件A

this.$bus.$emit('xxxx',数据)
3.解绑全局事件总线

在组件被销毁前解绑它所绑定的事件总线

beforeDestory(){
    this.$bus.$off('xxxx')
}

当遇到孙传爷数据,之前都是传函数给儿子再给孙子比较麻烦,现在就可以用事件总线了。

四、消息订阅与发布(pubsub)

和全局事件总线一样适用于任意组件间通信,但它要安装第三方库,而全局事件总线是在Vue上操作,所以pubsub用的比较少。

1.安装

安装pubsub:npm i pubsub-js
引入:import pubsub from 'pubsub-js

2.使用

接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

methods(){
  demo(msgName,data){......}
}
......
mounted() {
  this.pubsubId = pubsub.subscribe('xxx',this.demo) //订阅消息
},   
beforeDestroy() {
   pubsub.unsubscribe(this.pubsubId); //销毁时取消订阅
},

提供数据:

methods: {
    sendStudentName() {
        pubsub.publish('xxx', this.name); //发布消息并传数据
    }
},

五、$nextTick

1. 语法:this.$nextTick(回调函数)

2. 作用:在下一次 DOM 更新结束后执行其指定的回调。

3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

4.写一个定时器(延迟为0)也和$nextTick有同样的效果

为什么没写$nextTick初始获取不了焦点,因为input框开始v-show是false是隐藏的,调用handleEdit时虽然先把isEdit改为了true,但模版还没解析(函数执行完才解析,不是改一个数据就解析一次,效率问题),所以input还是隐藏的,this.$refs.inputTitle.focus()找不到元素。

			<input 
				type="text" 
				v-show="todo.isEdit" 
				:value="todo.title" 
				@blur="handleBlur(todo,$event)"
				ref="inputTitle"
			>
			handleEdit(todo){
				if(todo.hasOwnProperty('isEdit')){
					todo.isEdit = true
				}else{
					// console.log('@')
					this.$set(todo,'isEdit',true)
				}
                 //无法获取焦点
				
				// this.$refs.inputTitle.focus()
                //能够获取焦点

				this.$nextTick(function(){
					this.$refs.inputTitle.focus()
				})
			},

六、Vue封装的过度与动画

1.动画效果

实现-实现come和go的样式切换,就是换clss的名字 用transtion交给vue管理,下面的类名也必须写成.v-enter-active,.v-leave-active,transtion可以设置name,但下面的类名的v也要改成name的值。appear就是让页面一点开就有v-enter-active的效果。最后渲染完会去掉transtion标签

<template>
  <div>
      <button @click="isShow=!isShow">显示与隐藏</button>
      <!--实现come和go的样式切换,就是换clss的名字 用transtion交给vue管理 -->
      <transition appear>
         <h1 v-show="isShow" class="come">你好啊</h1>
      </transition>

  </div>

</template>

<script>
export default {
  // 名字加引号
  name:'Test1',
  data(){
    return{
      isShow:true
    } 
  }
}
</script>

<style scoped>
h1{
  background-color: orange;
}
/* .come{
  animation: myCss 1s linear;
}  */
.v-enter-active{
  animation: myCss 1s linear;
}
/* .go{
  animation: myCss 1s reverse;
} */
.v-leave-active{
  animation: myCss 1s reverse;
}
@keyframes myCss {
  from{
    transform: translateX(-100%);
  }
  to{
    transform: translateX(0);
  }
}
</style>
2.过渡效果

写法:

   1. 准备好样式:

      元素进入的样式:

        1. v-enter:进入的起点

        2. v-enter-active:进入过程中

        3. v-enter-to:进入的终点

      元素离开的样式:

        1. v-leave:离开的起点

        2. v-leave-active:离开过程中

        3. v-leave-to:离开的终点

   2. 使用```<transition>```包裹要过度的元素,并配置name属性:

      <transition name="hello">

         <h1 v-show="isShow">你好啊!</h1>

      </transition>

   3. 备注:若有多个元素需要过度,则需要使用:```<transition-group>```,且每个元素都要指定```key```值。

		<transition-group name="hello" appear>
			<h1 v-show="!isShow" key="1">你好啊!</h1>
			<h1 v-show="isShow" key="2">尚硅谷!</h1>
		</transition-group>
<template>
  <div>
      <button @click="isShow=!isShow">显示与隐藏</button>
      <transition name="hello" appear>
         <h1 v-show="isShow" class="come">你好啊</h1>
      </transition>

  </div>

</template>

<script>
export default {
  // 名字加引号
  name:'Test1',
  data(){
    return{
      isShow:true
    } 
  }
}
</script>

<style scoped>
h1{
  background-color: orange;
}

  /*  进入的起点*/
.hello-enter,.hello-leave-to{
  transform: translateX(-100%);
}
  /*  进入的终点*/
.hello-enter-to,.hello-leave{
  transform: translateX(0);
}
.hello-enter-active,.hello-leave-active{
  transition: 1s linear;
}



</style>
3.集成第三方动画库

npm里的animate.css

/在js里引入
	import 'animate.css'
//在template配置 完全不用写css
		<transition-group 
			appear
			name="animate__animated animate__bounce" 
			enter-active-class="animate__swing"
			leave-active-class="animate__backOutUp"
		>
			<h1 v-show="!isShow" key="1">你好啊!</h1>
			<h1 v-show="isShow" key="2">尚硅谷!</h1>
		</transition-group>

七、脚手架配置代理

1.方法一

在脚手架文件目录下npm i axios安装一下子axios,用axios发送请求

问题:当出现跨域问题(违反同源策略,同源策略就是协议、域名、端口号三个必须完全一样)怎么办?可以用代理服务器解决,就是在vue脚手架中再开启一台代理服务端,端口也是8080,浏览器发送8080请求去找资源,会去代理服务器找,而代理服务器经过配置(若配置5000端口)会转发给5000端口那台服务器找。代理服务器和5000这台服务器它们是两台服务器之间交换数据,不是浏览器和服务器所以不需要遵循同源策略,很容易就能得到数据。

//在vue.config.js中添加如下配置
devServer:{
  proxy:"http://localhost:5000"
}
<template>
<body>
  <button @click="getStudents">获取学生信息</button>
</body>
</template>

<script>
import axios from 'axios';
export default {
  name: 'App',
  methods:{
    getStudents(){
      axios.get('http://localhost:8080/students').then(
        response=>{
          console.log('请求成功了',response.data);
        },
        error=>{
          console.log('请检查错误',error)
        }
      )
    }
  }
}
</script>

说明:

1. 优点:配置简单,请求资源时直接发给前端(8080)即可。

2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。

3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)如果要找的资源前端有同名的就不会转发到服务器去找了,如前端public有个students文件,就不会转发了。

2.方式二

在vue.config.js中添加如下配置

 devServer: {
    proxy: {
      // zcj写在路径前面,加上这个才会进行代理转发,即使前端有同名资源
      '/zcj': {
        //指明ajax请求要转发给谁 
        target: 'http://localhost:5000',
        //pathRewrite把请求转发给5000时去掉zcj路径,正则表达式
        pathRewrite: { '^/zcj': '' },
        ws: true,  //用于支持websocket
        //changeOrigin用于控制请求头中的host值
        // true就是把请求源改成和目标服务器一致(5000),false就是8080
        changeOrigin: true  //写true比较合适,默认也是true(React里默认false)
      },
      '/cj': {
        //指明ajax请求要转发给谁 
        target: 'http://localhost:5001',
        //pathRewrite把请求转发给5000时去掉zcj路径
        pathRewrite: { '^/cj': '' },
        ws: true,  //用于支持websocket
        //changeOrigin用于控制请求头中的host值
        // true就是把请求源改成和目标服务器一致(5000),false就是8080
        changeOrigin: true  //写true比较合适,默认也是true(React里默认false)
      },
    }

  }
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/
  methods:{
    getStudents(){
      axios.get('http://localhost:8080/zcj/students').then(
        response=>{
          console.log('请求成功了',response.data);
        },
        error=>{
          console.log('请检查错误',error)
        }
      )
    },
    getCars(){
      axios.get('http://localhost:8080/cj/cars').then(
        response=>{
          console.log('请求成功了',response.data);
        },
        error=>{
          console.log('请检查错误',error)
        }
      )
    },
  }

1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。加了前缀,即使前端有同名资源代理服务器也会转发。

2. 缺点:配置略微繁琐,请求资源时必须加前缀。

3.pathRewrite,把路径重写去掉这个路径前缀,要不然最后的资源服务器找不到这个前缀路径下的资源

八、Github案例

1.步骤

(1)首先把现成的html和css进行拆分,注意bootstrap样式需要在public下新建css文件夹粘贴bootstrap.css,然后去index.html引入,直接import会有问题。

(2)在search输入框中绑定keyWord,获得用户输入的值。在按钮里绑定click事件的函数,函数去发axios请求给`https://api.github.com/search/users?q=${this.keyWord}`得到返回的数据

(3)再用全局事件总线把数据传给List,List把数据给data,再用v-for去渲染这个页面。

(4)进行完善,在初始页面添加欢迎词,页面加载时提示正在加载中,有错误显示错误信息,注意代码中扩展运算符的运用,

2.代码

App.vue

<template>
  <div class="container">
<search></search>
<List></List>
  </div>
</template>

<script>
import axios from 'axios';
import List from './components/List.vue';
import Search from './components/Search.vue';
export default {
  name: 'App',
  components:{
    Search,
    List
  },
}
</script>
<style>

</style>

List.vue

<template>
    <div class="row">
      <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
        <a :href="user.html_url" target="_blank">
          <img :src="user.avatar_url" style='width: 100px'/>
        </a>
        <p class="card-text">{{ user.login }}</p>
      </div>
      <h1 v-show="info.isFirst">欢迎您</h1>
      <h1 v-show="info.isLoading">正在 加载......</h1>
      <h1 v-show="info.eorMsg">{{ info.eorMsg }}</h1>
    </div>
</template>

<script >
export default {
  name:'List',
  data(){
    return {
      info:{
        isFirst:true,
        isLoading:false,
        eorMsg:'',
        users:[]
      }
     
    }
  },
  mounted(){
    this.$bus.$on('UpdateInfo',(infoObj)=>{
      // 这里很厉害,infoObj里的属性替换合并info对象里的属性
      this.info={...this.info,...infoObj}
    })
  }
}

</script >

<style scoped>
.album {
  min-height: 50rem; /* Can be removed; just added for demo purposes */
  padding-top: 3rem;
  padding-bottom: 3rem;
  background-color: #f7f7f7;
}

.card {
  float: left;
  width: 33.333%;
  padding: .75rem;
  margin-bottom: 2rem;
  border: 1px solid #efefef;
  text-align: center;
}

.card > img {
  margin-bottom: .75rem;
  border-radius: 100px;
}

.card-text {
  font-size: 85%;
}
</style>


search.vue

<template>
    <section class="jumbotron">
      <h3 class="jumbotron-heading">Search Github Users</h3>
      <div>
        <input type="text" placeholder="enter the name you search" v-model="keyWord"/>&nbsp;
        <button @click="getUsers">Search</button>
      </div>
    </section>
</template>

<script>
import axios from 'axios';

export default {
  name:'Search',
  data(){
    return {
      keyWord:''
    }
  },
  methods:{
    getUsers(){
      this.$bus.$emit('UpdateInfo',{isFirst:false,isLoading:true,eorMsg:'',users:[]})
      axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
        
        response=>{
          console.log('请求成功了')
          // idFirst后面都是false可以不用传,因为在List中用了...运算符进行合并属性
          this.$bus.$emit('UpdateInfo',{isLoading:false,eorMsg:'',users:response.data.items})
        },
        error=>{
          console.log('出现了错误')
          this.$bus.$emit('UpdateInfo',{isLoading:false,eorMsg:error.message,users:[]})
        }
      )    
    }
  }
}
</script>

<style>

</style>
3.vue-resource

vue-resource是用来发送请求的一个插件。在vue1.0版本经常用,现在都用axios,了解一下就行。发送的方法就是把axios.get换成this.$http.get,其它都一样。

安装vue-resource :npm i vue-resource
引入插件:import vueResource from 'vue-resource'
使用插件:Vue.use(vueResource)

   this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
        
        response=>{
          console.log('请求成功了')
          // idFirst后面都是false可以不用传,因为在List中用了...运算符进行合并属性
          this.$bus.$emit('UpdateInfo',{isLoading:false,eorMsg:'',users:response.data.items})
        },
        error=>{
          console.log('出现了错误')
          this.$bus.$emit('UpdateInfo',{isLoading:false,eorMsg:error.message,users:[]})
        }
      )    

九、插槽

1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 <strong style="color:red">父组件 ===> 子组件</strong> 。

2. 分类:默认插槽、具名插槽、作用域插槽

1.默认插槽

这三个用同一个组件,如何更换一下里面的内容?

在组件标签写要显示的内容,但是要插在组件模板的哪个位置要用插槽来指明,插槽起到一个定位的效果。相当于slot标签在子组件挖个坑,然后在父组件的组件标签里面写东西往里边填。

子组件

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <slot>我是一些默认值,没传数据显示我</slot>
  </div>
</template>

<script>
export default {
  name:'Category',
  props:['title']
}
</script>

父组件

<template>
  <div class="container">
   <Category  title="美食">
    <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
   </Category> 
   <Category  title="游戏">
    <ul>
      <li v-for="(item,index) in games" :key='index'>{{ item }}</li>
    </ul>
   </Category> 
   <Category  title="电影">
    <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
   </Category> 
  </div>
  
</template>

<script>
import Category from './components/Category1.vue';
export default {
  name: 'App',
  components:{
    Category
  },
  data(){
    return{
      foods:['小米','大米','西瓜','苹果'],
      games:['植物大战僵尸','qq飞车','王者荣耀','恋与深空'],
      films:['唐探1','唐探2','唐探3','唐探4']
    }
  }
}
</script>
2、具名插槽

给子组件的slot取个名字,在父组件指明slot='xxx'内容放在哪个插槽里。

子组件

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <slot name="center">我是一些默认值,没传数据显示我1</slot>
    <slot name="footer">我是一些默认值,没传数据显示我1</slot>
  </div>
</template>

父组件  当想把多个结构放在同一标签,可以用template标签和v-slot:footer配合使用

<template>
  <div class="container">
   <Category  title="美食">
    <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="" slot="center">
    <a href="http://www.baidu.com" slot="footer">点我跳到百度</a>
   </Category> 
   <Category  title="游戏">
    <ul>
      <li v-for="(item,index) in games" :key='index'>{{ item }}</li>
    </ul>
    <!-- 当想把多个结构放在同一标签,可以用template标签和v-slot:footer配合使用 -->
    <template v-slot:footer>
      <h4>试一下v-slot</h4>
      <a href="http://www.baidu.com" >点我跳到百度</a>
    </template>
   </Category> 
   <Category  title="电影">
    <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
   </Category> 
  </div>
  
</template>
3.作用域插槽

现在三个categroy内容全不变,但是要让样式变而且数据在category组件里怎么办?

在category组件里往App组件传数据,App组件一定要写<template scope=’xxx‘>去接收,或者是slot-scope。

子组件

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <slot name="center" :youxis="games">我是一些默认值,没传数据显示我1</slot>
    <!-- <slot name="footer" :youxis="games">我是一些默认值,没传数据显示我2</slot> -->
  </div>
</template>

<script>
export default {
  name:'Category',
  props:['title'],
  data(){
    return{
      games:['植物大战僵尸','qq飞车','王者荣耀','恋与深空'],
    }
  }
}
</script>

父组件

<template>
  <div class="container"> 
   <Category  title="游戏">
    <template slot="center" scope="zcj">
      <ul>
      <li v-for="(item,index) in zcj.youxis" :key='index'>{{ item }}</li>
    </ul>
    </template>
  
   </Category> 
   <Category  title="游戏">
    <!-- template标签必须写 -->
    <!-- 这个scope随便取名,会把插槽的数据自动付给它,它是一个对象,因为可能很多数据传过来 -->
 <template slot="center" scope="zcj">
  <ol>
      <li v-for="(item,index) in zcj.youxis" :key='index'>{{ item }}</li>
    </ol>
 </template>
   </Category> 
   <Category  title="游戏">
  <template slot="center" scope="zcj">
    <h4 v-for="(item,index) in zcj.youxis" :key='index'>{{ item }}</h4>
  </template>
   </Category> 
  </div>
  
</template>

<script>
import Category from './components/Category1.vue';
export default {
  name: 'App',
  components:{
    Category
  },

}
</script>

十、Vuex

1.Vuex的基本概念及环境

        在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。 多个组件需要共享数据时使用。

2.环境搭建

  (1)安装vuex,输入命令npm i vuex@3。vue2安装vuex3,vue3安装vuex4。在package.json文件里看下vue的版本。

  (2)创建文件:src/store/index.js

        为什么不能在main文件下写Vue.use(Vuex),因为我们在main文件下要import ./store/index.js(vue会自动找index,所以可以省略index) 而在import的时候会执行index里的代码,而创建store必须先执行Vue.use(Vuex),所以把这行代码写在index中。

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}

//创建并暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state
})

(3)在main中引入和配置store

js在执行时会先扫描import把import都提到最前面先执行了。如果在main.js里 import store from './store' 无论放到哪里都会比Vue.use(Vuex)先执行,要想把 Vue.use(Vuex) 要放到实例化之前只有放进index.js。

import Vue from 'vue'
import App from './App.vue'
import store from './store'    //引入store

Vue.config.productionTip = false
// import vueResource from 'vue-resource'
// Vue.use(vueResource)
new Vue({
  el: '#app',
  // render: h => h(App),
  render: creatElement => creatElement(App),
  store,    //配置store
  beforeCreate() {
    Vue.prototype.$bus = this
  },
})
2.Vuex的工作原理

        vc里写函数dispatch给action(服务员),action里面写数据业务逻辑,commit给mutations(厨师)做最后的处理。

3.vuex的使用

以求和为例

(1)把要让公共访问的sum放到index.js的state里,在count.vue的模板里用{{ $store.state.sum }}去访问sum。

const state = {
  sum: 0,
}

(2)组件的回调函数之间写 this.$store.dispatch('函数名',参数)给action进行业务处理,如果不需要业务处理,可以之间this.$store.commit('JIA',this.n)给mutation。

  methods:{
    increment(){
      this.$store.commit('JIA',this.n)
    },

    incrementOdd(){
      this.$store.dispatch('jiaOdd',this.n)
    },

  },

(3)在action里用jiaOdd来接收,jiaOdd能够收到俩个参数,第一个是简化版的store称之为context进行业务处理,第二个就是传过来的参数value,处理完再commit给mutation。

const actions = {
  jiaOdd(context, value) {
    console.log('jian的action执行了');
    if (context.state.sum % 2) {
      context.commit('JIA', value)
    }
  },

}

(4)在mutation中用JIA来接收,,一般都写成actions对应函数的大写。也是接两个参数,第一个参数是state对象,第二个参数是传过来的数据。

// 用于操作数据
const mutations = {
  JIA(state, value) {
    // console.log(state, value)
    state.sum += value
  },

}

(5)mutation主要用来处理数据来实现响应式页面

框架:

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)

const actions = {
    //响应组件中加的动作
	jia(context,value){
		// console.log('actions中的jia被调用了',miniStore,value)
		context.commit('JIA',value)
	},
}

const mutations = {
    //执行加
	JIA(state,value){
		// console.log('mutations中的JIA被调用了',state,value)
		state.sum += value
	}
}

//初始化数据
const state = {
   sum:0
}

//创建并暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state,
})

注意点:

  1. 组件中读取vuex中的数据:$store.state.sum

  2. tions里面也可以操作数据,但是如果不在mutations里操作数据,而在actions里操作数据,vuex开发者工具会失效的

  3. 一般来说都会把网络请求或其他业务逻辑写到actions里面

  4. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

4.getter的使用

(1)概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

(2)使用:

在index.js中写getters配置项,需要有返回值这和计算属性很像。

const getters = {
  // 有参数state,这样才能拿到sum进行操作
  bigSum(state) {
    return state.sum * 10
  }
}
// 创建并暴露store
export default new Vuex.Store({
  actions,
  mutations,
  state,
  // 把getters配置到里面
  getters
})

(2)组件中读取数据:$store.getters.bigSum

    <h1>放大后的和为:{{ $store.getters.bigSum }}</h1>

 (3)其实state就类似于datagetters就类似computed

5.mapState与mapGetters
    <h1>当前求和为:{{ $store.state.sum }}</h1>
    <h1>放大后的和为:{{ $store.getters.bigSum }}</h1>
    <h1>我在{{$store.state.school}}学校,学习{{ $store.state.subject }}</h1>

每次用插值语法我定义的属性 ,都是写$store.state,很麻烦怎么办?可以用计算属性去解决。

  computed:{
    sum(){
      return this.$store.state.sum
    },
    school(){
      return this.$store.state.school
    },
    subject(){
      return this.$store.state.subject
    }
  },
    <h1>当前求和为:{{ sum }}</h1>
    <h1>放大后的和为:{{ $store.getters.bigSum }}</h1>
    <h1>我在{{school}}学校,学习{{ subject }}</h1>

可是写计算属性也很麻烦,怎么办?用mapState

先引入import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'

数组写法和对象写法:如果方法名和函数名不一样用对象写法,一样用数组写法(相当于是省略)

    // 数组写法
    // ...mapState(['sum','school','subject']),
    // 对象写法
    ...mapState({sum:'sum',school:'school',subject:'subject'})

...是扩展运算符,以下是例子:

let obj1 = {x:100, y:200};
let obj2 = {
    a:1,
    ...obj1,
    b:2
}
console.log(obj2);  //{a: 1, x: 100, y: 200, b: 2}
6.mapActions与mapMutations
  methods:{
    increment(){
      this.$store.commit('JIA',this.n)
    },
    decrement(){
      this.$store.commit('JIAN',this.n)
    },
    incrementOdd(){
      this.$store.dispatch('jiaOdd',this.n)
    },
    incrementWait(){
      this.$store.dispatch('jiaWait',this.n)
    }
  },

同样调用dispatch和commit也很麻烦,所以创建了mapActions与mapMutations。

mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数。mapMutation方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数。

也有对象写法和数组写法:

  methods:{

    // 对象写法
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    // 数组写法,就当作是函数名和即将处理的函数名相同的简写方式
    // ...mapMutations({JIA:'JIA',JIAN:'JIAN'}),
    // ...mapMutations(['JIA','JIAN'])
  },

当时好像没有指定传入的参数this.n,实际底层创建的是这样,这个value其实就是event,而我们想要的是自己指定参数,所以可以在事件函数里面就传如下:

 add(value) {
     this.$store.commit('JIA', value);
 },
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
7.多组件共享数据

放在vuex的state里的值就相当于全局变量,每一个组件都可以用,可以在插值语法里用

$store.state.personList之间调用,也有在computed里写上...mapState({personList:'personList'}),

然后之间在插值语法里写personList调用。

8.vuex模块化+namespaced

目的:一个index的mutation、action里包括了人员相关操作、订单相关操作等很乱。所以最后给它进行分类,每一类有自己的mutation、action等,让代码更好维护,让多种数据分类更加明确。

使用:首先要写命名空间namespaced=true,要不然mapState等不会认识我们的模块名,不开启命名空间只能要获取值要一层一层调,如perAbout.personList

(1)开启命名空间后,组件中读取state数据:

//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']),

(2)开启命名空间后,组件中读取getters数据:

//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])

(3)开启命名空间后,组件中调用dispatch:

//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

(4)开启命名空间后,组件中调用commit:

//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

index.js

// 该文件用于创建Vuex中最核心的store
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const countOptions = {
  namespaced: true,
  actions: {
    jiaOdd(context, value) {
      console.log('jian的action执行了');
      if (context.state.sum % 2) {
        context.commit('JIA', value)
      }
    },
    jiaWait(context, value) {
      console.log('jiaWait的action执行了');
      setTimeout(() => {
        context.commit('JIA', value)
      }, 500);
    }
  },
  mutations: {
    JIA(state, value) {
      // console.log(state, value)
      state.sum += value
    },
    JIAN(state, value) {
      // console.log(state, value)
      state.sum -= value
    },
  },
  state: {
    sum: 0,
    school: 'hzu',
    subject: '计算机',
  },
  getters: {
    bigSum(state) {
      return state.sum * 10
    }
  }

}
const personOptins = {
  namespaced: true,
  actions: {

  },
  mutations: {
    ADD_PERSON(state, value) {
      state.personList.unshift(value)
    }
  },
  state: {
    personList: [
      { id: '001', name: 'zhangsan' }
    ]
  },
  getters: {

  }
}

// 创建并暴露store
export default new Vuex.Store({
  modules: {
    countAbout: countOptions,
    personAbout: personOptins
  }
})

Count.vue

<template>
  <div>
    <h4>当前求和为:{{ sum }}</h4>
    <h4>放大后的和为:{{ bigSum }}</h4>
    <h4>我在{{school}}学校,学习{{ subject }}</h4>
    <h4>下方组件的总人数是:{{personList.length }}</h4>
    <!-- option的value是字符 在前面加冒号 -->
     <!-- 后者在v-model后加number 让收到的值转为数字型 -->
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
import { mapState,mapGetters,mapActions,mapMutations } from 'vuex';
export default {
  name:'Count',
  data(){
    return {
      n:1
    }
  },
  computed:{
    // 手动写法
    // sum(){
    //   return this.$store.state.sum
    // },
    // school(){
    //   return this.$store.state.school
    // },
    // subject(){
    //   return this.$store.state.subject
    // }
    // 数组写法
    // ...mapState(['sum','school','subject']),
    // 对象写法
    ...mapState('countAbout',{sum:'sum',school:'school',subject:'subject'}),
    ...mapState('personAbout',{personList:'personList'}),
    ...mapGetters('countAbout',['bigSum'])
  },
  methods:{
    // 程序员手动写
    // increment(){
    //   this.$store.commit('JIA',this.n)
    // },
    // decrement(){
    //   this.$store.commit('JIAN',this.n)
    // },
    // incrementOdd(){
    //   this.$store.dispatch('jiaOdd',this.n)
    // },
    // incrementWait(){
    //   this.$store.dispatch('jiaWait',this.n)
    // }
    // 对象写法
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    // 数组写法,就当作是函数名和即将处理的函数名相同的简写方式
    // ...mapMutations({JIA:'JIA',JIAN:'JIAN'}),
    // ...mapMutations(['JIA','JIAN'])
  },
  mounted(){
    console.log('Count',this);
    
  }
}
</script>

<style>
  button{
    margin-left: 5px;
  }
</style>

Person.vue

<template>
  <div>
    <h5>人员列表</h5>
    <input type="text" placeholder="请输入名字" v-model="name">
    <button @click="add">添加</button>
    <ul>
      <li v-for="p in personList" :key="p.id">{{ p.name }}</li>
    </ul>
    <h5>count里面的sum值是多少:{{ sum }}</h5>
  </div>
  
</template>

<script>
import { nanoid } from 'nanoid';
export default {
  name:'Person',
  data(){
    return {
      name:''
    }
  },
  computed:{
    personList(){
      return  this.$store.state.personAbout.personList
    },
    sum(){
      return  this.$store.state.countAbout.sum
    }
  },
  methods:{
    add(){
      const personObj={id:nanoid(),name:this.name}
      // console.log(personObj);
      this.$store.commit('personAbout/ADD_PERSON',personObj)
      
    }
  }
}
</script>

<style>

</style>

App.vue

<template>
  <div>
    <Count></Count>
    <Person></Person>
  </div>

</template>

<script>
import Count from './components/Count.vue';
import Person from './components/Person.vue';
export default {
  name: 'App',
  components:{
    Count,
    Person
  },
  mounted() {
    console.log(this);
  },
}
</script>
<style>

</style>

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值