首页 > 其他分享 >Vue第二季

Vue第二季

时间:2023-08-03 20:55:31浏览次数:43  
标签:Vue name 组件 state vue 第二季 import 路由

目录

016-Vue CLI 本地存储 自定义事件

3.8. WebStorage(js 本地存储)

存储内容大小一般支持 5MB 左右(不同浏览器可能还不一样)
浏览器端通过Window.sessionStorageWindow.localStorage属性来实现本地存储机制
相关API
xxxStorage.setItem('key', 'value')该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxStorage.getItem('key')该方法接受一个键名作为参数,返回键名对应的值
xxxStorage.removeItem('key')该方法接受一个键名作为参数,并把该键名从存储中删除
xxxStorage.clear()该方法会清空存储中的所有数据
备注
○ SessionStorage存储的内容会随着浏览器窗口关闭而消失
○ LocalStorage存储的内容,需要手动清除才会消失
○ xxxStorage.getItem(xxx)如果 xxx 对应的 value 获取不到,那么getItem()的返回值是null
○ JSON.parse(null)的结果依然是null

---localStorage.html
<!DOCTYPE html>
<html>

<head>
    <meta charset='utf-8'>
    <title>localStorage</title>

</head>

<body>
    <!-- 注意原html下方法是一定要加()的,否则不生效 -->
    <button onclick="saveData()">点我保存一个数据</button>
    <button onclick="readData()">点我读取一个数据</button>
    <button onclick="deleteData()">点我删除一个数据</button>
    <button onclick="clearData()">点我清空数据</button>

    <script type="text/javascript">
        let p = { name: '张三', age: 15 }
        // saveDate = function(){}
        function saveData() {

            // console.log(p.toString());
            window.localStorage.setItem('msg', 'hello!!!')
            window.localStorage.setItem('msg1', 123) //注意页面存储的数据key和value都是字符串,会自动转换
            //如果直接输出p的话,系统就会自动帮我们调用toString方法, 一旦调用这个方法数据就会变成Object
            // window.localStorage.setItem('msg1',p) //不能这样写,这样写不显示
            window.localStorage.setItem('msg2', JSON.stringify(p)) //所以这里我们转成json展示.
        }
        function readData() {

            // window.localStorage.setItem('msg', 'hello!!!')
            //window可以省略不写.
            console.log(localStorage.getItem('msg'))
            console.log(localStorage.getItem('msg1'))
            console.log(localStorage.getItem('msg2'))
           
        }
        function deleteData() {
            localStorage.removeItem("msg")
        }
        function clearData() {
            localStorage.clear()
        }

    </script>
</body>

</html>

增加数据

image-20230624181058753

image-20230624181120276

获取数据

image-20230624181240586

当我们关闭浏览器重新打开的,数据依然存在.

删除数据

image-20230624181345380

清空数据

image-20230624181404396

再来学习一下sessionStorage.会话级别的,一旦把浏览器关了,保存的数据就被清空了.自己测试一下.

其他测试用到的方法都是和localStorage一样的.

---sessionStorage.html
<!DOCTYPE html>
<html>

<head>
    <meta charset='utf-8'>
    <title>sessionStorage</title>

</head>

<body>
    <!-- 注意原html下方法是一定要加()的,否则不生效 -->
    <button onclick="saveData()">点我保存一个数据</button>
    <button onclick="readData()">点我读取一个数据</button>
    <button onclick="deleteData()">点我删除一个数据</button>
    <button onclick="clearData()">点我清空数据</button>

    <script type="text/javascript">
        let p = { name: '张三', age: 15 }
        // saveDate = function(){}
        function saveData() {

            // console.log(p.toString());
            window.sessionStorage.setItem('msg', 'hello!!!')
            window.sessionStorage.setItem('msg1', 123)
            //如果直接输出p的话,系统就会自动帮我们调用toString方法, 一旦调用这个方法数据就会变成Object
            // window.sessionStorage.setItem('msg1',p) //不能这样写,这样写不显示
            window.sessionStorage.setItem('msg2', JSON.stringify(p)) //所以这里我们转成json展示.
        }
        function readData() {

            // window.sessionStorage.setItem('msg', 'hello!!!')
            //window可以省略不写.
            console.log(sessionStorage.getItem('msg'))
            console.log(sessionStorage.getItem('msg1'))
            console.log(sessionStorage.getItem('msg2'))
           
        }
        function deleteData() {
            sessionStorage.removeItem("msg")
        }
        function clearData() {
            sessionStorage.clear()
        }

    </script>
</body>

</html>

image-20230624182038482

使用本地存储优化Todo-List

第一次使用的时候待办事项肯定是空的,在代码里面就是todos数组是空的,之后添加的话是要保存到数组里面,同时将数组存储到本地浏览器里面,这样下次打开浏览器的时候数组中的数据还是存在的.对数组的增删都是实时的.

这里我们使用watch监视属性,去监视todos数组的变化.

image-20230624220213171

报错信息

[Vue warn]: Error in render: "TypeError: Cannot read properties of null (reading 'length')"
found in

---> <MyFooter> at src/components/MyFooter.vue
       <App> at src/App.vue
         <Root>

image-20230624215810894

因为初始化的时候是没有数据的,我们写初始化的todos数组的地方导致的.

image-20230624220506619

image-20230624220705727

当我们去页面操作的时候会发现还是有bug,就是当我们勾选几个已完成的,然后重新刷新页面,勾选的数据就没有了,这是因为我们现在写的watch监视todos数组是简写形式,这种监视是浅层次的监视,不是深度监视,就是监视这个数组内部属性值的变化,所以监视watch还不能写成简写形式,因为我们需要设置深度监视

image-20230625213215646

---App.vue
<template>
    <div class="todo-container">
    <div class="todo-wrap">
      <MyHeader :receive="receive"></MyHeader>
      <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>
      <MyFooter :todos="todos"  :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></MyFooter>
    </div>
  </div>
</template>
<script>    
//引入组件
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import MyList from './components/MyList.vue' //MyItem组件是List组件的子组件
export default {
    name: 'App',
    components:{MyHeader,MyFooter,MyList},
    data(){
      return {
        //这个是简单声明三个待办事项,表示从后台传输过来的数据,然后页面展示的话用v-for循环遍历展示就可以了.动态的.
        
        todos:
          // [
            // {id:'001',title:'抽烟',done:true},
            // {id:'002',title:'喝酒',done:false},
            // {id:'003',title:'吹泡泡',done:true},
            //这里换成从本地浏览器中读取数据,用来初始化页面的时候使用
          // ]
          
          JSON.parse(localStorage.getItem("todos")) || [] //这里为什么要加后面的||[]  
          //是因为如果这里不写一个空数组的话,在初始化的时候会报错,加上后面的代码当前面为false的时候就会使用后面的空数组
          //空数组调用length是不会报错的.length是空
        
      }
    },
    methods:{
      //在父组件App中定义一个函数,用来子组件向父组件传递数据使用,在子组件中调用这个函数实际上就是调用父组件中的receive,
      //这样子组件在调用的时候就可以将数据传到父组件中定义的这个函数里面,这样在父组件中就能使用传递过来的数据了.
      receive(x,b){
        console.log("我是App组件,我收到了数据",x,b);
        this.todos.unshift(x)//将新增的数据放到数组第一行位置.
        console.log(this.todos);
      },
      //单选框勾选or取消勾选 一个todo
      checkTodo(id){
        //通过遍历的方式去对比id是那个,然后更新对应的done属性的值。
        this.todos.forEach((todo)=>{
          //如果遍历的id正好和传进来的id相同,那就表示是同一个.
          if(todo.id === id){
            todo.done =!todo.done
          }
        })
      },
      //删除一个todo
      deleteTodo(id){
        // 这里使用过滤器的方式达到删除的效果,注意使用过滤器后应该是会返回一个新的数组,
        // 所以需要将新的数组返回给原来的todos.否则不会引起原有数据todos的变化
         this.todos  = this.todos.filter((todo) =>
            {
              return  todo.id !== id
            }
          )

      },
      //这个函数的功能是: 点击最下面的勾选框 :全选或者全不选
      checkAllTodo(done){
        //通过遍历数据让数组中所有done属性的值变成全选框的值.
        this.todos.forEach((todo) =>{
          todo.done = done
        });
      },
      //过滤数组中done属性为true 的数据,表示删除已完成的事项.
      clearAllTodo(){
        this.todos = this.todos.filter(todo => {
          return !todo.done
        })
      }

    },
    watch:{
      //这个是监视todos发生变化的时候将数组存储进去.存储到本地浏览器中,我们页面在初始化的时候也需要去本地浏览器存储里面读取数据才能展示到页面.
      todos:{
        deep:true,
        handler(value){
          localStorage.setItem("todos", JSON.stringify(value))
        }
      }
    }
}
</script>

<style>


/*base*/ 
body {
  background: #fff;
}

.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}

.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}

.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}


</style>

3.9. 组件的自定义事件

1 一种组件间通信的方式,适用于:子组件 ===> 父组件

2 使用场景:子组件想给父组件传数据,那么就要在父组件中给子组件绑定自定义事件(事件的回调在A中)

3 绑定自定义事件

​ a 第一种方式,在父组件中<Demo @事件名="方法"/>或<Demo v-on:事件名="方法"/>

​ b 第二种方式,在父组件中this.$refs.demo.$on('事件名',方法)

<Demo ref="demo"/>

......

mounted(){

   this.$refs.demo.$on('atguigu',this.test)

}

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

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

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

6 组件上也可以绑定原生DOM事件,需要使用native修饰符 @click.native="show"

上面绑定自定义事件,即使绑定的是原生事件也会被认为是自定义的,需要加native,加了后就将此事件给组件的根元素

7 注意:通过this.$refs.xxx.$on('事件名',回调函数)绑定自定义事件时,回调函数要么配置在methods中,要么用箭头函数,否则 this 指向会出问题

代码练习

自定义事件实现子传父数据

image-20230626215103380

一种是使用之前学过的方式传递数据,另外一种使用自定义事件,比较一下.开始写代码

全部代码展示:

---App.vue
<template>
  <div class="title">
    <!-- <Student></Student> -->
    <!--  -->
    <h1 >{{name}}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"></School>


    <!-- 下面是自定义事件的几种写法 start -->

    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
    <!-- <Student v-on:atguigu="getStudentName" ></Student> -->
    <!-- 这样是简写的形式 -->
    <!-- <Student @atguigu="getStudentName" ></Student>   -->

    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) 搭配下面的mounted挂载方法使用-->
    <Student ref="student" ></Student>
    <!-- 这样是简写的形式 -->
    <!-- <Student ref="student" @atguigu="getStudentName" ></Student> -->

    <!-- 下面是自定义事件的几种写法 end -->


  </div>
</template>

<script>    
//引入School组件
import Student from './components/Student' 
import School from './components/School' 
export default {
    name:'App',
    components:{Student,School},
    data(){
     return{
        name:'杨洋',
     } 
    },
    methods:{
      getSchoolName(name){
        console.log('被调用了'+name)
      },
      // getStudentName(name){
      //   console.log('被调用了'+name)
      //   this.name = name
      // }
      getStudentName(name,...params){ //...params这样写是多参数写法,params是数组
				console.log('App收到了学生名:',name,params) //这种写法是接受多参数的,params是一个数组
				this.studentName = name
			},
    },
    mounted(){
      // this.$refs.Student  这一步是获取student组件实例对象vc
      // $on 当什么事件被触发的时候,  第一个参数就是事件名称,第二个参数是触发事件要执行的回调函数
      // 这里在复习一遍,在template模板中写的不需要添加this.但是在js里面写的话必须要写this

      this.$refs.student.$on('atguigu',this.getStudentName,666,888,999) //这样写的适用场景是什么?就是给组件添加绑定事件的时候有时候不想让它在加载解析模板的时候
      //就立即绑定,而是添加定时器去执行绑定事件,第一种自定义绑定事件就实现不了,因为在解析到第一种方式绑定事件的代码哪一行时,就会立即进行绑定.
      //需要使用定时任务去绑定事件的写法如下:
      // setTimeout(() => {
      //   this.$refs.Student.$on('atguigu',this.getStudentName) 
      // }, 3000); //这样写就是表示3秒后才有触发事件,因为三秒后才会去绑定

      // this.$refs.Student.$once('atguigu',this.getStudentName)   //使用once表示仅触发一次事件,只能触发一次.一次性的.
    }
    
}
</script>

<style scoped>
  .title{
    background-color: coral;
  }
</style>


---Student.vue
<template>
  <div class="student">
    <h2>姓名:{{ name }}</h2>
    <h2>性别:{{ sex  }}</h2>
    <button @click="sendStudentName">点我把学生名给App组件</button>

  </div>
</template>
<script>
export default {
    name:'Student',
    data(){
        return{
            name:'地儿',
            sex:'男'
        }
    },
    methods:{
      sendStudentName(){
        
        //emit触发事件 ,第一个参数是要绑定的事件,后面的话参数可以写多个你要传输的参数
        this.$emit('atguigu',this.name) //触发Student组件实例身上的atguigu事件
      }
    }
  
}
</script>

<style scoped>
  .student {
    background-color: aquamarine;
    padding: 5px;
    margin-top: 10px;
  }
</style>

---School.vue
<template>
  <div class="school">
    <h2>名称:{{ name}}</h2>
    <h2>地址:{{ address  }}</h2>
    <button @click="sendSchoolName">点我把学校名给App组件</button>
  </div>
</template>

<script>
export default {
    name:'School',
    props:['getSchoolName'],
    data(){
        return{
            name:'尚硅谷atguigu',
            address:'北京',
        }
    },
    methods:{
      sendSchoolName(){
        //这里记得加this.
        this.getSchoolName(this.name)
      }
    }

    
    
    
    
}
</script>

<style scoped>
  .school{
    background-color: wheat;
    padding: 5px;
    /* margin-top: 10px; */
  }
</style>

上面的代码是绑定事件,下面来学习一下解绑事件

---App.vue
<template>
	<div class="app">
		<h1>{{msg}},学生姓名是:{{studentName}}</h1>

		<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
		<School :getSchoolName="getSchoolName"/>

		<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
		<!-- <Student @atguigu="getStudentName" @demo="m1"/> -->

		<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
		<Student ref="student" @click.native="show"/>
	</div>
</template>

<script>
	import Student from './components/Student'
	import School from './components/School'

	export default {
		name:'App',
		components:{School,Student},
		data() {
			return {
				msg:'你好啊!',
				studentName:''
			}
		},
		methods: {
			getSchoolName(name){
				console.log('App收到了学校名:',name)
			},
			getStudentName(name,...params){
				console.log('App收到了学生名:',name,params)
				this.studentName = name
			},
			m1(){
				console.log('demo事件被触发了!')
			},
			show(){
				alert(123)
			}
		},
		mounted() {
			this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
			// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)
		},
	}
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

---Student.vue
<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<h2>当前求和为:{{number}}</h2>
		<button @click="add">点我number++</button>
		<button @click="sendStudentlName">把学生名给App</button>
		<button @click="unbind">解绑atguigu事件</button>
		<button @click="death">销毁当前Student组件的实例(vc)</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男',
				number:0
			}
		},
		methods: {
			add(){
				console.log('add回调被调用了')
				this.number++
			},
			sendStudentlName(){
				//触发Student组件实例身上的atguigu事件
				this.$emit('atguigu',this.name,666,888,900)
				// this.$emit('demo')
				// this.$emit('click')
			},
			unbind(){
				this.$off('atguigu') //解绑一个自定义事件
				// this.$off(['atguigu','demo']) //解绑多个自定义事件
				// this.$off() //解绑所有的自定义事件
			},
			death(){
				this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

---School.vue
<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
		<button @click="sendSchoolName">把学校名给App</button>
	</div>
</template>

<script>
	export default {
		name:'School',
		props:['getSchoolName'],
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		methods: {
			sendSchoolName(){
				this.getSchoolName(this.name)
			}
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>
---main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
    //设置定时任务去销毁vm实例,注意这里是vm实例,而不是组件实例
	/* mounted() {
		setTimeout(()=>{
			this.$destroy()
		},3000)
	}, */
})

vue.js2.7之前的版本是当销毁组件实例对象时,原生dom方法不受影响,仍然可以调用,但是在之后的版本就不可以了.

image-20230701164922288

注意事项:当使用ref属性是尽心自定义绑定事件的时候,需要注意以下地方:

image-20230701171419469

当我们使用ref进行自定义事件绑定时,ref和mounted挂载一起搭配使用.下面这两张截图中的代码是一起的.

image-20230701172313937

image-20230701171903438

我们可以看到去添加atguigu事件的时候,触发getStudentName事件,如果此时我们在on方法里面直接写触发的自定义事件,

image-20230701172447845

调用的时候会发现页面并没有将数据传递给App组件的name属性,就是this.name=name这一行并没有执行成功,我们看一下此时的this是谁

image-20230701174604135

这里Vue有定义:触发了atguigu事件,当中的this就是谁.

image-20230701175304414

这里还有一种解决方式:如果写成了箭头函数,那么也能实现,因为箭头函数没有自己的this.此时就会在代码中向外查找,mounted函数的外面就是App组件的,所以此时的this就是App组件的实力对象.

image-20230701175517017

使用自定义事件注意二:除了自定义事件外,组价还可以使用原生事件,比如click事件

image-20230701181106805

image-20230701181717030

image-20230701181643877

但是只是这样写的话是不可以的,因为vue会把click事件当做自定义事件.

如何实现呢?

先将在Student中写的绑定click事件删除掉(这一步不算实现步骤里面的),在绑定事件的名称后面.native就可以了.

image-20230701181833100

注意此时Student中是没有绑定click事件的.

image-20230701182001952

这样Student组件就能使用原生事件了.

image-20230701182025151

使用自定义事件优化Todo-List

odo-List案例中的子传父部分全都改成自定义事件,方式的话按照上面上面刚才学过的.

备注:

image-20230702175834977

017-Vue CLI 全局事件总线 消息的订阅与发布

3.10. 全局事件总线(GlobalEventBus)

一种可以在任意组件间通信的方式,本质上就是一个对象,它必须满足以下条件
1 所有的组件对象都必须能看见他
2 这个对象必须能够使用$on$emit$off方法去绑定、触发和解绑事件

image-20230702112658979

使用步骤

1 定义全局事件总线

new Vue({

   	...

   	beforeCreate() {

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

   	},

   ...

})

2 使用事件总线

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

export default {

    methods(){

        demo(data){...}

    }

    ...

    mounted() {

        this.$bus.$on('xxx',this.demo)

    }

}

b 提供数据:this.$bus.$emit('xxx',data)

3 最好在beforeDestroy钩子中,用$off()去解绑当前组件所用到的事件

代码练习

---main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线 这是要做的第一步,因为vm能够被所有组件看到,所以定义在这里
	},
})
---App.vue
<template>
	<div class="app">
		<h1>{{msg}}</h1>
		<School/>
		<Student/>
	</div>
</template>

<script>
	import Student from './components/Student'
	import School from './components/School'

	export default {
		name:'App',
		components:{School,Student},
		data() {
			return {
				msg:'你好啊!',
			}
		}
	}
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

---Student.vue
<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男',
			}
		},
		mounted() {
			// console.log('Student',this.x)
		},
		methods: {
			sendStudentName(){
                //提供数据:this.$bus.$emit('xxx',data) 负责将数据传递过去
				this.$bus.$emit('hello',this.name)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

---School.vue
<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		mounted() {
			// console.log('School',this)
            //接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身 
			this.$bus.$on('hello',(data)=>{
				console.log('我是School组件,收到了数据',data)
			})
		},
		beforeDestroy()  //这个方法的作用就是在School组件实例对象被销毁之前先解绑 绑定的事件.
			this.$bus.$off('hello')
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

image-20230702172004511

3.11. 消息的订阅与发布(基本不用)

消息订阅与发布(pubsub)消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信

使用步骤

1 安装pubsub:npm i pubsub-js

2 引入:import pubsub from 'pubsub-js'

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

export default {
    methods: {
       demo(msgName, data) {...}
    }
    ...
    mounted() {
			this.pid = pubsub.subscribe('xxx',this.demo)
    }
}

4 提供数据:pubsub.publish('xxx',data)

5 最好在beforeDestroy钩子中,使用pubsub.unsubscribe(pid)取消订阅

代码演示:

---main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
})
---App.vue
<template>
	<div class="app">
		<h1>{{msg}}</h1>
		<School/>
		<Student/>
	</div>
</template>

<script>
	import Student from './components/Student'
	import School from './components/School'

	export default {
		name:'App',
		components:{School,Student},
		data() {
			return {
				msg:'你好啊!',
			}
		}
	}
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

---Student.vue
<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男',
			}
		},
		mounted() {
			// console.log('Student',this.x)
		},
		methods: {
			sendStudentName(){
				// this.$bus.$emit('hello',this.name)
				pubsub.publish('hello',666) //这里是发布
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

---School.vue
<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		mounted() {
			// console.log('School',this)
			/* this.$bus.$on('hello',(data)=>{
				console.log('我是School组件,收到了数据',data)
			}) */
            //这里是订阅了hello消息
			this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
				console.log(this)
				// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
			})
		},
		beforeDestroy() {
			// this.$bus.$off('hello')
			pubsub.unsubscribe(this.pubId) //这里是取消订阅
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

image-20230702172122110

018-Vue CLI $nextTick 过渡与动画

给TodoList案例增加一个编辑事件

具体需求位置:

image-20230702194541039

我们要实现这个功能需要实现的几个步骤:

1.点击编辑(编辑按钮背景颜色换一个,以便于区别于删除按钮)按钮后触发事件,前面的文字变成一个输入框,可以修改内容.同时编辑按钮隐藏不显示.

2.输入完毕后鼠标失去焦点后保存数据.输入内容修改不可为空.为空修改失败.

开发详细步骤:

增加编辑按钮

image-20230702195339298

修改编辑按钮背景颜色 btn-danger表示的是红色,换个颜色展示

image-20230702195619409

之所以这里的按钮样式要写在App中,是因为要控制按钮的整体性.就整个项目而言.不能换个页面按钮样式就变一个

image-20230702195719441

image-20230702212341082

解释一下为什么代码里面删除按钮会写在编辑按钮的前面:

image-20230702210054892

页面能展示以后,开始写编辑按钮点击事件的方法,就是点击编辑的时候,前面待办事项文字变成在输入框里面,之后就能修改了

image-20230702212213152

image-20230702212553258

点编辑以后才出现的

image-20230702212657904

image-20230702212800211

此时页面最明显的问题就是点编辑后改完值不会变会原来的(这是接下来要做的事).就是输入框变成原来的只显示文字

image-20230702221423489

有个注意点就是:

image-20230702222411327

失去焦点的时候就需要保存数据,这里的数据我们是保存到本地浏览器的.这里就是绑定自定义事件去传值修改todos数组中的值. 因为这里涉及到子传父数据,我们使用全局事件总线

image-20230702223340204

还有就是要实现编辑的时候,编辑按钮隐藏显示.

代码实现很简单,就是单机编辑的时候使用v-show来控制

image-20230705210740948

image-20230705210653615

还有就是编辑的时候不能为空.需要增加校验.

image-20230705213211964

这里还有个问题点击编辑input就应该自动获取焦点,而不是还要在输入框点击一下才能获取焦点,这个下面学习新的方法请看3.12.不然的话问题很严重,就是点了编辑,变成输入框了,但是我又不想改了,这个时候就触发不了失去焦点事件,因为失去焦点之前必须先获取焦点,以下截图是问题bug显示,就是不想改了不点击输入框,页面就一直长这样了.

image-20230705211150160

3.12. $nextTick

这是一个生命周期钩子
this.$nextTick(回调函数)在下一次DOM更新结束后执行其指定的回调
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
使用 $nextTick 优化 Todo-List

先来看看原来的做法,这个我们应该可以想到

image-20230705215731778

但是点击页面结果却是并没有获取焦点,原因跟代码的执行过程有关,程序会在执行完this.$refs.inputTitle.focus()后才回去解析模板(等回调方法代码都执行完以后才去解析模板),todo.isEdit=true 代码并没有被解析,也就是说此时的input输入框还没有被渲染到页面,虽然执行了this.$refs.inputTitle.focus()代码,此时不会获取到焦点.

下图现在还是没有获取焦点,因为上面的代码没有成功生效

image-20230706213029032

有一种解决方式就是使用定时器,把获取焦点事件的代码写到定时任务中去

image-20230706213220546

换成vue官方推荐的

image-20230706221403738

都能实现点击编辑立即获取焦点.

image-20230706221530670

3.13. 过渡与动画

Vue封装的过度与动画:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名

image-20230708172113945

写法
1 准备好样式

  • 元素进入的样式
    ⅰv-enter 进入的起点
    ⅱv-enter-active 进入过程中
    ⅲv-enter-to 进入的终点
  • 元素离开的样式
    ⅰv-leave 离开的起点
    ⅱv-leave-active 离开过程中
    ⅲv-leave-to 离开的终点

2 使用<transition>包裹要过度的元素,并配置name属性,此时需要将上面样式名的v换为name
3 要让页面一开始就显示动画,需要添加appear

---动画效果
<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<transition name="hello" appear>
			<h1 v-show="isShow">你好啊!</h1>
		</transition>
	</div>
</template>

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

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

	.hello-enter-active{
		animation: atguigu 0.5s linear;
	}

	.hello-leave-active{
		animation: atguigu 0.5s linear reverse;
	}

	@keyframes atguigu {
		from{
			transform: translateX(-100%);
		}
		to{
			transform: translateX(0px);
		}
	}
</style>

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

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

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}
	/* 进入的起点、离开的终点 */
	.hello-enter,.hello-leave-to{
		transform: translateX(-100%);
	}
	.hello-enter-active,.hello-leave-active{
		transition: 0.5s linear;
	}
	/* 进入的终点、离开的起点 */
	.hello-enter-to,.hello-leave{
		transform: translateX(0);
	}

</style>

5 第三方动画库Animate.css

<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<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>
	</div>
</template>

<script>
	import 'animate.css'
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

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

</style>

image-20230708173522446

总结:

image-20230708173644751

image-20230708173702378

image-20230708173909361

019-Vue中的Ajax 配置代理 slot插槽

4.1. Vue脚手架配置代理

本案例需要下载 axios库 npm install axios

配置参考文档 Vue-Cli devServer.proxy

vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写

方法一

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

module.exports = {

  devServer:{

    proxy:"http://localhost:5000"

  }

}

方法二

编写vue.config.js配置具体代理规则

module.exports = {
	devServer: {
      proxy: {
      '/api1': {							// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',	// 代理目标的基础路径
        pathRewrite: {'^/api1':''},				// 代理往后端服务器的请求去掉 /api1 前缀
        ws: true,								// WebSocket
        changeOrigin: true,
        
      },
      '/api2': {
        target: 'http://localhost:5001',
        pathRewrite: {'^/api2': ''},
        changeOrigin: true
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明
1 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
2 缺点:配置略微繁琐,请求资源时必须加前缀

练习代码:

这里有两个服务器,运行以后可以发送请求访问他们,当做模拟访问的服务器.

image-20230708181541900

进入到文件目录下使用命令窗口启动运行

image-20230708181712020

image-20230708181829994

接着安装axios 在vscode终端使用命令npm install axios,安装完成后在App.vue中引入该库使用并访问上面的服务器地址

---App.vue
<template>
  <div>
    <button @click="getStudents">获取学生信息</button>
  </div>
</template>

<script>    


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


运行程序访问:

image-20230708183026338

涉及到跨域问题,也就是违背了同源策略,同源策略要求三个东西必须一致.协议名,主机名,端口号三个必须一致.导致请求发送出去了,但是服务器没有返回数据给客户端

image-20230708185013338

解决方式思路:找一个代理服务器.服务器的端口号和客户端保持一致,这样就不违背同源策略了,同时服务器之间传输不存在跨域问题.这样问题就解决了.

image-20230708185359220

代理服务器比如后端的nginx. 这里我们不讲,这里我们学习vue相关的,vue中可以在vue.config.js中配置代理服务器.

image-20230708191427427

image-20230708191526345

修改完配置以后记得一定要重启一下程序

image-20230708191802592

重启以后:

image-20230708192504368

但是这种方式是有缺点的,比如:

image-20230708192711628

image-20230708192747258

出现的问题就是当public下有该资源的时候,会优先访问public路径下的.不会再转发了

image-20230708192902337

还有另外一种方式:

<\i\m\g\ src="Vue第一季.assets/image-20230708195851348.png" alt="image-20230708195851348" style="zoom:150%;" />

---vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false,
  //开启代理服务器(方式一)
	// devServer: {
  //   proxy: 'http://localhost:5000'
  // }, 
  //开启代理服务器(方式二)
	devServer: {
    proxy: {
      //'/atguigu' 这个代表在请求路径最前面增加前缀为atguigu,这样在请求的时候就能达到区别的作用了.
      '/atguigu': {
        target: 'http://localhost:5000',
				pathRewrite:{'^/atguigu':''},   //路径重写:因为请求中是带有atguigu的,但是实际访问服务器的时候
        // 是没有的。所以我们需要使用正则表达式,把atguigu字符串给去掉转换成空才可以正确访问到服务器资源
        // ws: true, //用于支持websocket
        // changeOrigin: true //用于控制请求头中的host值
      },
      '/demo': {
        target: 'http://localhost:5001',
				pathRewrite:{'^/demo':''},
        // ws: true, //用于支持websocket
        // changeOrigin: true //用于控制请求头中的host值
      }
    }
  }
})

---App.vue
<template>
  <div>
    <button @click="getStudents">获取学生信息</button>
    <button @click="getCars">获取汽车信息</button>
  </div>
</template>

<script>    


import axios from 'axios'
export default {
    name:'App',
    methods:{
      getStudents(){
        axios.get('http://localhost:8080/atguigu/students').then(
          response => {
            console.log('请求成功了',response.data);
          },
          error => {
            console.log('请求成功了',error.message);
          }
        )
      },
      getCars(){
        axios.get('http://localhost:8080/demo/cars').then(
          response => {
            console.log('请求成功了',response.data);
          },
          error => {
            console.log('请求成功了',error.message);
          }
        )
      }

    }
}
</script>




image-20230708212044724

4.2. GitHub用户搜索案例

需求案例:

页面搜索框实现模糊查询

image-20230708212208176

image-20230708213131575

模拟的是这个,因为涉及到组件,所以这里分为两个组件,一个是搜索的组件,另外一个是下面图片的组件.

引入bootstrap样式是在index.html中

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
		<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
		<!-- 开启移动端的理想视口 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
		<!-- 配置页签图标 -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
		<!-- 引入第三方样式 注意引入的格式要和vue中推荐的写法保持一致 -->
		<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
		<!-- 配置网页标题 -->
    <title>硅谷系统</title>
  </head>
  <body>
		<!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
		<!-- 容器 -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

---App.vue
<template>
    <div class="container">
      <Search></Search>
      <List></List>
    </div>
</template>
  
<script>    

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


---Search.vue
<template>
    <section class="jumbotron">
        <h3 class="jumbotron-heading">Search Github Users</h3>
        <div>
          <input type="text" placeholder="enter the name you search"/>&nbsp;<button>Search</button>
        </div>
    </section>
</template>

<script>
export default {

}
</script >

---List.vue
<template>
  <div class="row">
    <div class="card">
      <a href="https://github.com/xxxxxx" target="_blank">
        <\i\m\g\ src="https://certification.vuejs.org/images/vue-badge.svg" style='width: 100px'/>
      </a>
      <p class="card-text">xxxxxx</p>
    </div>
    <div class="card">
      <a href="https://github.com/xxxxxx" target="_blank">
        <\i\m\g\ src="https://certification.vuejs.org/images/vue-badge.svg" style='width: 100px'/>
      </a>
      <p class="card-text">xxxxxx</p>
    </div>
    <div class="card">
      <a href="https://github.com/xxxxxx" target="_blank">
        <\i\m\g\ src="https://certification.vuejs.org/images/vue-badge.svg" style='width: 100px'/>
      </a>
      <p class="card-text">xxxxxx</p>
    </div>
    <div class="card">
      <a href="https://github.com/xxxxxx" target="_blank">
        <\i\m\g\ src="https://certification.vuejs.org/images/vue-badge.svg" style='width: 100px'/>
      </a>
      <p class="card-text">xxxxxx</p>
    </div>
    <div class="card">
      <a href="https://github.com/xxxxxx" target="_blank">
        <\i\m\g\ src="https://certification.vuejs.org/images/vue-badge.svg" style='width: 100px'/>
      </a>
      <p class="card-text">xxxxxx</p>
    </div>
  </div>
</template>

<script>
export default {

}
</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 > \i\m\g {
		margin-bottom: .75rem;
		border-radius: 100px;
	}

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

image-20230708223053255

目前是改造完成一版(静态展示).

接下来做的就是输入框输入内容,点击搜索按钮想制定的服务器发送请求获取数据,然后传给List组件遍历循环展示

---Search.vue
<template>
    <section class="jumbotron">
        <h3 class="jumbotron-heading">Search Github Users</h3>
        <div>
            <!-- v-model绑定事件 获取输入框的值 -->
          <input type="text" placeholder="enter the name you search" v-model="keyWords"/>&nbsp;
          <!-- 添加点击 搜索按钮事件 -->
          <button @click="searchUsers">Search</button>
        </div>
    </section>
</template>

<script>
// 引入axios
import axios from 'axios'
export default {
    name:'Search',
    data(){
        return {
            keyWords:'',
        }
    },
    methods:{
        searchUsers(){
            //如果请求地址中的参数是动态的,需要按照以下的写法(es6的模板字符串)
            axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
                response => {
                    console.log('请求成功了',response.data.items)
                    
                },
                error=>{
                    console.log('请求成功了',error.message)
                }
            )
        }
    }
}
</script >

image-20230709111304792

接下来就是将获取的数据传递给List组件,这里采用之前学习过的全局事件总线

首先是在main.js中定义全局事件总线

image-20230709112227459

使用事件总线-接受数据List组件中

image-20230709113035789

使用事件总线-提供数据Search组件

image-20230709113257015

image-20230709164619481

测试请求成功了

image-20230709164708122

这里再对界面优化一下,就是初始化进来页面的时候,下面图片区域显示welcome欢迎字样.点击搜索的时候显示英文加载中.如果请求失败则显示报错信息

----Search.vue
<template>
    <section class="jumbotron">
        <h3 class="jumbotron-heading">Search Github Users</h3>
        <div>
            <!-- v-model绑定事件 获取输入框的值 -->
          <input type="text" placeholder="enter the name you search" v-model="keyWords"/>&nbsp;
          <!-- 添加点击 搜索按钮事件 -->
          <button @click="searchUsers">Search</button>
        </div>
    </section>
</template>

<script>
// 引入axios
import axios from 'axios'
export default {
    name:'Search',
    data(){
        return {
            keyWords:'',
        }
    },
    methods:{
        searchUsers(){
           //请求前更新List的数据
				this.$bus.$emit('updateListData',{isLoading:true,errmsg:'',users:[],isFirst:false})
				axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
					response => {
						console.log('请求成功了')
						//请求成功后更新List的数据
						this.$bus.$emit('updateListData',{isLoading:false,errmsg:'',users:response.data.items})
					},
					error => {
						//请求后更新List的数据
						this.$bus.$emit('updateListData',{isLoading:false,errmsg:error.message,users:[]})
					}
			)
        }
    }
}
</script >

---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">
        <\i\m\g\ :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.errmsg">{{ info.errmsg }}</h1>
  </div>
</template>

<script>
export default {
    name:'List',
    data(){
       return {
            info:{
                isFirst:true,//是不是第一次加载
                isLoading:false,//是否处于加载中
                errmsg:'',//错误信息
                users:[]
            }
       } 
    },
    mounted(){
        this.$bus.$on('updateListData',(dataObj) => {
            console.log('我是List组件,我接受到了数据:'+dataObj.users)
            // this.users = users;
            this.info = {...this.info,...dataObj} //这种写法表示info和dataObj对比,如果dataObj中的存在值则用dataObj中的,没有的话就还用原来info中的值.
        })
    }
}
</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 > \i\m\g {
		margin-bottom: .75rem;
		border-radius: 100px;
	}

	.card-text {
		font-size: 85%;
	}
</style>
---main.js
//引入Vue
import Vue from 'vue'
//引入组件App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip=false
//创建vm
new Vue({
    el:'#app',
    render:h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this
    },
})

显示效果:

image-20230709180326233

image-20230709180342716

image-20230709180352342

image-20230709182516713

4.3. vue-resource

vue项目常用的两个Ajax库
1 axios:通用的Ajax请求库,官方推荐,效率高
2 vue-resource:vue插件库,vue 1.x使用广泛,官方已不维护
下载 vue-resource 库 npm i vue-resource

使用方式:

在main.s中引入插件

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import vueResource from 'vue-resource'
//关闭Vue的生产提示
Vue.config.productionTip = false
//使用插件
Vue.use(vueResource)

//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this
	},
})

直接使用:this.$http.get ,跟axios基本没有区别

image-20230709215501336

<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="searchUsers">Search</button>
		</div>
	</section>
</template>

<script>
	export default {
		name:'Search',
		data() {
			return {
				keyWord:''
			}
		},
		methods: {
			searchUsers(){
				//请求前更新List的数据
				this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})
				this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
					response => {
						console.log('请求成功了')
						//请求成功后更新List的数据
						this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})
					},
					error => {
						//请求后更新List的数据
						this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})
					}
				)
			}
		},
	}
</script>

4.4. slot 插槽

<slot>插槽:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,
适用于 父组件 ===> 子组件
1 分类:默认插槽、具名插槽、作用域插槽

2 使用方式
a 默认插槽

 父组件中:
        <Category>
           <div>html结构1</div>
        </Category>
子组件中:Category
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot>插槽默认内容...</slot>
            </div>
        </template>

b 具名插槽
父组件指明放入子组件的哪个插槽slot="footer",如果是template可以写成v-slot:footer

父组件中:
        <Category>
            <template slot="center">
              <div>html结构1</div>
            </template>

            <template v-slot:footer>
               <div>html结构2</div>
            </template>
        </Category>
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot name="center">插槽默认内容...</slot>
               <slot name="footer">插槽默认内容...</slot>
            </div>
        </template>

c 作用域插槽
scope用于父组件往子组件插槽放的html结构接收子组件的数据
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

父组件中:
        <Category>
            <template scope="scopeData">
                <!-- 生成的是ul列表 -->
                <ul>
                  <li v-for="g in scopeData.games" :key="g">{{g}}</li>
                </ul>
            </template>
        </Category>

        <Category>
            <template slot-scope="scopeData">
                <!-- 生成的是h4标题 -->
                <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
            </template>
        </Category>
子组件中:
        <template>
            <div>
                <slot :games="games"></slot>
            </div>
        </template>
		
        <script>
            export default {
                name:'Category',
                props:['title'],
                //数据在子组件自身
                data() {
                    return {
                        games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                    }
                },
            }
        </script>

需求案例-不适用插槽:

image-20230709222028224

不适用插槽的写法:

根据这个页面,首先就是写一个组件,然后这个组件是可以复用的,就是数据有所不同,再就是添加样式.

开始写代码:首先是编写组件,里面的数据先固定写,首先写的就是静态的页面展示部分,

image-20230711215029908

然后将组件放到App组件中去使用

image-20230711215212913

但是页面显示的话是长下面这样子,所以布局上需要使用css代码实现控制.

image-20230711214921659

可以让上面的图片变成下面的样式

image-20230711214828421

image-20230711214758522

标题的背景颜色是黄色

image-20230711215858171

image-20230711215911225

静态页面展示就完了,现在需要准备数据,将数据显示到页面展示,三个部分传输的数据是不一样的,这里需要思考

准备的数据,我们写的数据是在App.vue中的,然后通过props传输给Category组件

image-20230712204135863

Category组件接受数据,并使用数据

image-20230712205223008

页面效果

image-20230712205240798

需求案例-默认插槽

效果实现图二:

image-20230712205301691

如何实现传输数据的不同,比如有的是图片,有的是数据,有的是视频,

image-20230712205804791

这时候就需要学习插槽了,先来学一下什么是默认插槽

首先是定义一个插槽,标记一个位置

---Category.vue
<template>
  <div class="category">
    <h3>{{title}}分类</h3>
        <!-- <li>xxx</li>
        <li>xxx</li>
        <li>xxx</li> -->
        <!-- 通过for循环去遍历listData数据 -->
        <!-- <li v-for="(item,index) in listData" :key="index">{{item}}</li> -->
        <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
        <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

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

<style>
    .category{
        background-color: skyblue;
        width:200px;
        height:300px;
    }
    h3{
      text-align: center;
      background-color: orange;
    }
    video{
		  width: 100%;
	  }
	\i\m\g{
		  width: 100%;
	}
</style>

然后是在组件标签中写入需要的标签内容和数据

---App.vue
<template>
  <div class="container">
    <!-- 需要三个就复制三个出来 
     
      看一下为什么不这样写,这样写的话Category组件就需要接受三个参数,但是我们统一写成listData那就只需要接受一次,只是传过去的数据是不一样的.
    <category :foods="foods"></category> 
    <category :games="games"></category>
    <category :films="films"></category>


    -->
    <!-- <category title="美食" :listData="foods"></category> 
    <category title="游戏" :listData="games"></category>
    <category title="电影" :listData="films"></category> -->
    <!-- 学习一个默认插槽
     :listData="foods" 数据也不需要传输了,因为我们是在这里直接使用
    -->
    <category title="美食" >
      <\i\m\g\ 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>
     <!-- controls是控制视频播放的 如果只写路径没有效果-->
    <category title="电影" >
      <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
    </category>

  </div>
</template>

<script>    
//引入category组件
import category from './components/Category' 
export default {
    name:'App',
    components:{category},
    data(){
      return {
        foods:['火锅','烧烤','小龙虾','牛排'],
				games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
				films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
      }
    }
}
</script>

<style scoped>
  .container{
    /* 布局方式变为弹性盒模型 */
    display:flex;
    /*  justify-content 属性用来设置项目在主轴方向上的对齐方式 */
    justify-content: space-around;
  }
</style>


页面显示:

image-20230712212606481

需求案例-具名插槽

案例效果三:

image-20230712214132859

当需要使用多个插槽的时候

错误的写法:

image-20230712214939005

image-20230712214859346

image-20230712214916847

这个时候就需要指定插槽了,也就是标明路径,做出标记

---App.vue           
<template>
  <div class="container">
    <!-- 需要三个就复制三个出来 
     
      看一下为什么不这样写,这样写的话Category组件就需要接受三个参数,但是我们统一写成listData那就只需要接受一次,只是传过去的数据是不一样的.
    <category :foods="foods"></category> 
    <category :games="games"></category>
    <category :films="films"></category>


    -->
    <!-- <category title="美食" :listData="foods"></category> 
    <category title="游戏" :listData="games"></category>
    <category title="电影" :listData="films"></category> -->
    <!-- 学习一个默认插槽
     :listData="foods" 数据也不需要传输了,因为我们是在这里直接使用
    -->
    <!-- 具名插槽,就是插槽要制定名称才可以.适用于需要使用多个插槽的时候当存在多个插槽 -->
    <category title="美食" >
        <!-- slot="center" 声明一下插槽的名称,数据就会显示在对应的插槽位置 -->
      <\i\m\g\ slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
      <a slot="footer" href="https://www.bilibili.com/">更多美食</a>
    </category> 
    <category title="游戏" >
      <!--  -->
      <ul slot="footer">
        <li v-for="(item,index) in games" :key="index">{{ item }}</li>
      </ul>
      <div class="foot" slot="footer">
        <a href="https://www.bilibili.com/">单机游戏推荐</a>
        <a href="https://www.bilibili.com/">网络游戏推荐</a>
      </div>
    </category>
    <category title="电影" >
      <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
      <!-- template 这个标签不会显示到前段页面,div的话就会显示 这是template的优势-->
        <!--  v-slot:footer 这种写法必须写到template标签里面,其他地方使用的话就会报错,注意
			写法 footer不用加引号
		-->
      <template v-slot:footer>
				<div class="foot">
					<a href="http://www.atguigu.com">经典</a>
					<a href="http://www.atguigu.com">热门</a>
					<a href="http://www.atguigu.com">推荐</a>
				</div>
				<h4>欢迎前来观影</h4>
			</template>
    </category>

  </div>
</template>

<script>    
//引入category组件
import category from './components/Category' 
export default {
    name:'App',
    components:{category},
    data(){
      return {
        foods:['火锅','烧烤','小龙虾','牛排'],
				games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
				films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
      }
    }
}
</script>

<style scoped>
  .container, .foot{
    /* 布局方式变为弹性盒模型 */
    display:flex;
    /*  justify-content 属性用来设置项目在主轴方向上的对齐方式 */
    justify-content: space-around;
  }
  h4{
		text-align: center;
	}
</style>



---Category.vue
<template>
  <div class="category">
    <h3>{{title}}分类</h3>
        <!-- <li>xxx</li>
        <li>xxx</li>
        <li>xxx</li> -->
        <!-- 通过for循环去遍历listData数据 -->
        <!-- <li v-for="(item,index) in listData" :key="index">{{item}}</li> -->
        <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充)
          name="center"  表示给插槽起一个名称,使用的时候需要指定名称才能放到对应的位置
        -->
        <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
        <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

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

<style>
    .category{
        background-color: skyblue;
        width:200px;
        height:300px;
    }
    h3{
      text-align: center;
      background-color: orange;
    }
    video{
		  width: 100%;
	  }
	  \i\m\g{
		  width: 100%;
	  }
</style>

需要注意的地方:

image-20230712221408533

image-20230712221414619

显示效果:因为没有使用到center插槽,所以center插槽会显示默认的效果.

image-20230712221429331

需求案例-作用于插槽

写三个游戏分类,数据是同一个,但是样式不同

image-20230713212854516

现在是数据在Category组件中,如果将数据传输到App.vue组件中去使用

image-20230713220232860

首先是在定义插槽的时候将数据绑定到插槽上

image-20230713222810699

image-20230713222932460

image-20230713223017664

全部代码:

---App.vue
<template>
  <div class="container">
    
    <category title="游戏" >
      <!-- 
        第一步是将在定义插槽的地方将数据传递过来
        第二步就是必须使用template标签将结构包括起来
        第三步就是使用 scope="xiaom" 声明一下,里面的值可以随便写,不用和定义插槽的位置保持一致
        传递过来的是一个对象,使用的使用需要注意以下,不能直接使用.
       -->
      <template scope="xiaom">
        {{ xiaom }}
        <ul>
          <li v-for="(item,index) in xiaom.games" :key="index">{{ item }}</li>
        </ul>
      </template>
    </category> 
    <category title="游戏" >
      <template scope="{games}">
				<ol>
					<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
				</ol>
			</template>
    </category>
    <category title="游戏" >
      <!-- 这种是新版本的写法  slot-scope="{games}" 
        旧版本的写法 scope="{games}"
      -->
      <template slot-scope="{games}">
				<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
			</template>
    </category>

  </div>
</template>

<script>    
//引入category组件
import category from './components/Category' 
export default {
    name:'App',
    components:{category},
    
}
</script>

<style scoped>
  .container, .foot{
    /* 布局方式变为弹性盒模型 */
    display:flex;
    /*  justify-content 属性用来设置项目在主轴方向上的对齐方式 */
    justify-content: space-around;
  }
  h4{
		text-align: center;
	}
</style>


---Category.vue
<template>
  <div class="category">
    <h3>{{title}}分类</h3>
        <!-- 学习使用作用于插槽  
          在定义插槽的时候将数据传递过去,也就是谁使用这个插槽,谁就可以获得games数据并且可以使用该数据
        -->
        <slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

<script>
export default {
    name:'Category',
    props:['title'],
    data(){
      return {
				games:['红色警戒','穿越火线','劲舞团','超级玛丽']
      }
    }
}
</script>

<style>
    .category{
        background-color: skyblue;
        width:200px;
        height:300px;
    }
    h3{
      text-align: center;
      background-color: orange;
    }
    video{
		  width: 100%;
	  }
	  \i\m\g{
		  width: 100%;
	  }
</style>

image-20230715113206110

020-Vuex

5.1. 理解 Vuex

5.1.1. Vuex 是什么

1概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信

2Vuex Github地址

下面图片演示了在A组件中有一个x属性,B\C\D组件都想使用该组件,不仅能读取该数据,还能修改该数据.

fokdofodk

都想操作x属性,那么就把x属性提取出来

3243434fdslfkad

5.1.2. 什么时候使用 Vuex

1 多个组件依赖于同一状态

2 来自不同组件的行为需要变更同一状态

5.1.3. Vuex 工作原理图

image.png

2 对vuex的简单理解 backend-后端;后台;后段;编译器后端
每一个 Vuex 应用的核心就是 store,里面又包括:
(1)state(数据):用来存放数据源,就是公共状态;
(2)getters(数据加工):有的时候需要对数据源进行加工,返回需要的数据;
(3)actions(事件):要执行的操作,可以进行同步或者异步事件
(4)mutations(执行):操作结束之后,actions通过commit更新state数据源
(5)modules:使用单一状态树,致使应用的全部状态集中到一个很大的对象,所以把每个模块的局部状态分装使每一个模块拥有本身的 state、mutation、action、getters、甚至是嵌套子模块;

3 vuex的工作流程就是:
(1)通过dispatch去提交一个actions,
(2) actions接收到这个事件之后,在actions中可以执行一些异步|同 步操作,根据不同的情况去分发给不同的mutations,
(3)actions通过commit去触发mutations,
(4)mutations去更新state数据,state更新之后,就会通知vue进行渲染

image-20230723130638140

5.2. 求和案例

5.2.1. 使用纯 vue 编写

image.png

---Count.vue
<template>
  <div>
    <h1>当前求和为:{{ sum }}</h1>
    <!-- 
        <select v-model.number="n">   这样写就等于是将n强制转换为number类型
    -->
    <select v-model="n">
        <!-- 
            :value="1"  表示会将1转换为数字1 ,否则页面显示就是字符串1,就会导致显示的时候是字符串拼接而不是加减运算
         -->
        <option :value="1">1</option>
        <option :value="2">2</option>
        <option :value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd">当前求和为奇数再加</button>
    <button @click="incrementwait">等一等再加</button>
  </div>
</template>

<script>
export default {
    name:'Count',
    data(){
        return  {
            // n和sum都要写成数字的,不能是字符串
            n:1, //用户选择的数字
            sum:0 //当前的和   
        }
    },
    methods:{
        increment(){
            this.sum += this.n;
        },
        decrement(){
            this.sum -= this.n;
        },
        incrementOdd(){
            // sum % 2 表示 取余要么是1要么是0 转换过来就是1代表true,0代表false,如果取余是1表示是奇数,取余为0表示偶数
            if( this.sum % 2  ){
                this.sum += this.n;
            }
        },
        incrementwait(){
            setTimeout(()=>{
                this.sum += this.n;
            },400);
        }
    }
}
</script>

<style>
    button{
        margin-left: 5px;
    }
</style>
---App.vue
<template>
  <div>
		<Count/>
	</div>
</template>

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

---main.js
//引入Vue
import Vue from 'vue'
//引入组件App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip=false
//创建vm
new Vue({
    el:'#app',
    render:h => h(App)
})

5.2.2.搭建Vuex环境

使用vuex需要下载插件,关于插件版本需要特别注意

image-20230715173541774

这个是必须要对应的,高版本不能兼容低版本.

步骤1.下载安装vuex npm i vuex@3 指定安装的版本为vuex3版本

image-20230715223808173

步骤二:

image-20230715224412503

image-20230715224314783

但是此时的store的值是我们自己随便写的,并不是我们学习的vuex中的属性,所以需要进一步写

就是在src文件夹下面创建一个目录store,然后在这个目录下创建一个index.js文件,文件的内容就是关于store的

//该文件用于创建Vuex中最为核心的store

//引入Vuex
import Vuex from 'vuex'

//准备actions——用于响应组件中的动作
const actions = {	

}
//准备mutations——用于操作数据(state)
const mutations = {
	
}
//准备state——用于存储数据
const state = {
	
}

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

创建Vuex中最为核心的store以后,就可以在App中去使用了,当我们引入store文件并且使用它的时候,

image-20230715233046081

这样写会发现报错

Uncaught Error: [vuex] must call Vue.use(Vuex) before creating a store instance.(未捕获错误:[vuex]必须在创建存储实例之前调用Vue.use(vuex)。)

image-20230715233615635

这样写是没有效果的

image-20230715233908711

解决报错的方案就是:

image-20230715234513248

此时程序不报错了.到这里vuex环境就搭建好了

5.2.3 使用Vuex编写求和案例

Vuex的基本使用
1 初始化数据state,配置actions、mutations,操作文件store.js
2 组件中读取vuex中的数据$store.state.数据
3 组件中修改vuex中的数据$store.dispatch('action中的方法名',数据) 或$store.commit('mutations中的方法名',数据)
若没有网络请求或其他业务逻辑,组件中也可越过actions,即不写dispatch,直接编写commit

练习步骤

步骤一:首先是将sum交给vuex管理

image-20230716102207841

步骤二:使用dispatch去分配任务

先来写点击页面+号触发的函数,根据vuex流程图,要使用vuex中的dispatch去分发这个任务,

image-20230716105001880

先点击页面+号看一下页面报错信息

[vuex] unknown action type: jia

image-20230716105150274

提示我们vuex中的action是没有jia的

步骤三:编写action,这样action中就有jia函数了

来到index.js当中进行编写

image-20230716112440042

image-20230716111427097

步骤四:在mutations修改数据

image-20230716112908648

image-20230716112944870

记得用到sum的地方使用插值语法写一下.

image-20230716114614928

到这里+号的功能已经写完了,按照同样的方式再来写一下其他功能

image-20230716114804675

image-20230716115128582

image-20230716115200182

剩下的页面功能

image-20230716140622301

image-20230716141104159

页面测试一下看一下效果是不是正常的,

image-20230716141229059

经过测试是能够正常使用的.但是目前代码层面来说是还有改进的空间的,

image-20230716142243658

对代码进行调整优化

image-20230716143623177

image-20230716144014531

页面是正常使用的.image-20230716143421172

vuex开发者工具界面

image-20230716151036201

再来回顾一下在actions节点的context里面为什么有好多的属性.为什么不直接有一个commit就完事了,我们来分析一下

image-20230716151332490

当业务逻辑非常复杂的时候,actions中一个函数可能处理不完,需要调用其他actions中的函数时候就需要用到context中的属性去实现了,看下面截图可以体会一下

image-20230716151558132

同时我们可以看到在context中的属性直接就有一个state,那能不能直接操作state属性呢?答案是能实现功能,但是不经过mutations的话,vuex开发者工具是不生效的,我们可以仔细观察一下vuex流程图中的mutations右侧是有一个devtools的,所以最好不要这样去做.还有就是业务逻辑写在actions中的优点就是这里的代码是可以做到共享的,所以人都是可以操作的.这段代码是可以复用的

5.3. getters 配置项

1 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工,相当于全局计算属性
2 在store.js中追加getters配置

3 组件中读取数据 $store.getters.bigSum 这里的bigSum是在store.js中追加getters配置项的函数名称,看下面的代码案例演示

案例演示使用

现在的需求又改了,页面要求展示一条求和放大10倍的数据

image-20230716152756151

需要在store.js(也就是index.js)中去写增加getters配置项

image-20230716153619884

image-20230716153714681

输出vc组件看一下getters在哪,getters配置项也是属于store中的

image-20230716154132445

注意:写getters的时候

image-20230716154543447

虽然上面第二个参数名称起的是state,但是实际并不是我们想要的state,一个a实际代表的才是state.因为state的属性是sum

image-20230716154555697

5.4. 四个 map 方法的使用

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

先来看下案例:

image-20230716175541020

重点是代码部分:我们自己先来实现一下,先是在vuex中的state里面添加两个属性以及对应的数据,

image-20230716180120567

然后回到Count.vue组件中进行页面渲染展示

image-20230716180327060

但是会发现有个问题,就是这些有些是重复的,或者说这里的代码能不能在精简一下.这里我们目前能想到的解决办法就是在下面的代码写一个computed,然后将上面这部分代码包装到函数里面.如下图

image-20230716181028668

image-20230716181527576

vuex中已经存在相应的方法帮助我们实现了.先来介绍一下mapstate,先在需要使用的地方引入一下.

image-20230716190903916

可以看到页面也是能够正常展示的

image-20230716191209207

mapstate是有两种写法的:一种是对象写法,另外一种是数组写法

image-20230716212241140

学习完mapstate后,我们可以再学习一下mapGetters,

image-20230716215244662

再来学习一下mapMutations,这个方法是用用于帮助生成与mutations对话的方法,即包含$store.commit(xxx)的函数

image-20230716220927303

所以我们需要优化的就是这里的代码,以此来练习一下mapMutations()方法,首先是从vuex中引入一下mapMutations

image-20230718212035300

写法如下,前面的...代表的是遍历这个对象中的元素,这样写就代替了注释掉的加和减两个方法

image-20230718212022245

image-20230718212620399

但是这样写完还不行,因为并没有传输数据给到mutations,之前的写法是有将加的值传递过去的,注意这种写法...mapMutations({increment:'JIA',decrement:'JIAN'}),等同于下面的写法,但是我们知道如果不传输值的话,

image-20230718212750503

这里的参数value是鼠标事件.

image-20230718213215814

所以传值的话我们可以在这里进行传值.将n传递过去

image-20230718213420062

image-20230718213406692

这里还有mapActions的用法,跟上面的用法是一样的,但是功能是有区别的.

image-20230718215349058

image-20230718215427001

汇总一下代码:

---Count.vue
<template>
  <div>
    <!-- $store.state.sum   在模板中可以直接使用vc身上的属性,最前面不用加this  然后也是从组件身上一层一层往下找 -->
    <!-- <h1>当前求和为:{{ $store.state.sum }}</h1>
    <h1>当前求和为:{{ $store.getters.bigSum }}</h1>
    <h1>我在{{ $store.state.school }},学习-{{ $store.state.msg }}</h1> -->
    <h1>当前求和为:{{ sum }}</h1>
    <h1>当前求和为:{{ bigSum }}</h1>
    <h1>我在{{ school }},学习-{{ msg }}</h1>
    <!-- 
        <select v-model.number="n">   这样写就等于是将n强制转换为number类型
    -->
    <select v-model="n">
        <!-- 
            :value="1"  表示会将1转换为数字1 ,否则页面显示就是字符串1,就会导致显示的时候是字符串拼接而不是加减运算
         -->
        <option :value="1">1</option>
        <option :value="2">2</option>
        <option :value="3">3</option>
    </select>
    <button @click="decrement(n)">-</button>
    <button @click="increment(n)">+</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementwait(n)">等一等再加</button>
  </div>
</template>

<script>
// 引入一下mapState
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
    
    name:'Count',
    data(){
        return  {
            // n和sum都要写成数字的,不能是字符串
            n:1, //用户选择的数字
            // sum:0 //当前的和   sum这里已经交给vuex中的state管理了,所以注释掉了这里不用写
        }
    },
    computed:{
        // sum(){
        //     return this.$store.state.sum
        // },
        
        // school(){
        //     return this.$store.state.school
        // },
        // msg(){ 
        //     return this.$store.state.msg
        // },
        
        // mapState(), 里面传输一个对象 {} 对象是{} 这样包裹起来的
        //借助mapState生成计算属性,从state中读取数据。(对象写法)
        // ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}),  前面key不带引号其实是可以的.
        ...mapState({'sum':'sum', 'msg':'msg', 'school':'school'}),  //这种是最基本的写法  ...是es6中最新的写法,表示将mapstate对象中的元素都遍历展示到这里
		//借助mapState生成计算属性,从state中读取数据。(数组写法)
	    // ...mapState(['sum','school','msg']), //数组这种写法需要注意的是函数sum和state中的属性名称要保持一致.数组中的sum在这里有两个身份


        /* ******************************************************************** */


        // bigSum() {
        //     return this.$store.state.bigSum
        // },
        //借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
			// ...mapGetters({bigSum:'bigSum'})
        //借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
        ...mapGetters(['bigSum'])  
    },
    methods:{
        // increment(){
        //     // 这里我们要使用dispatch去分发任务.这个任务是在store中,而store又在vc实例身上
        //     // 所以写的时候必须从this身上开始找,一层一层往下找
        //     //dispatch('jia',this.n)  这里面的参数第一个就是要找的函数名,第二个参数就是要传的值是多少,这个n也是在n身上
        //     // this.$store.dispatch('jia',this.n)  //没有业务逻辑可以直接跳过actions节点
        //     // this.$store.commit('JIA',this.n)   // 注意commit中的第一个参数JIA是mutations中的函数
        //     // console.log(this)
        // },
        // decrement(){
        //     // this.$store.dispatch('jian',this.n) //没有业务逻辑可以直接跳过actions节点
        //     this.$store.commit('JIAN',this.n)
        // },

        //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),

        //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
        // ...mapMutations(['JIA','JIAN']), //注意这种写法需要名称保持一致

        // incrementOdd(){
        //     this.$store.dispatch('jiaOdd',this.n)
        // },
        // incrementwait(){
        //     // setTimeout(()=>{
        //         this.$store.dispatch('jiawait',this.n)
        //     // },1000);
        // }

        //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
		...mapActions({incrementOdd:'jiaOdd',incrementwait:'jiawait'})

        //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
        // ...mapActions(['jiaOdd','jiaWait'])


    }
}
</script>

<style>
    button{
        margin-left: 5px;
    }
</style>
---index.vue
//该文件用于创建Vuex中最为核心的store
// 引入vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex'
//使用vuex
Vue.use(Vuex)

//准备actions——用于响应组件中的动作
const actions = {	
    // 在action当中需要使用commit() 传递给mutations 
    // jia(context,value) 第一个参数是简化版的ministore, 第二个参数value是传递过来的参数
    
    //start --------------------------------
    // jia(context,value){
    //     console.log('actions中的jia被调用了',context,value)
    //     //将函数名和传递的参数提交给下一流程-mutations中
    //     // context.commit('JIA',value) 第一个参数是函数名,第二个参数是传递的值
    //     context.commit('JIA',value)  //这里写成 JIA 是为了做个区分,知道这个大写的JIA是在mutations中的
    // },
    // jian(context,value){
    //     console.log('actions中的jian被调用了',context,value)
    //     context.commit('JIAN',value)
    // },
    //end ---------------------------------  start和end 部分的代码因为不涉及到业务逻辑,所以在vuex中可以跳过actions,直接commit走到mutataions
    jiaOdd(context,value){
        console.log('actions中的jiaOdd被调用了',context,value)
        if(context.state.sum%2){
            context.commit('JIA',value)//这里可以复用mutations中的JIA函数
        }
    },
    jiawait(context,value){
        console.log('actions中的jiawait被调用了',context,value)
        setTimeout(()=>{
            context.commit('JIA',value) //这里可以复用mutations中的JIA函数
        },1000);
    }
}
//准备mutations——用于操作数据(state)
const mutations = {
    // JIA(state,value) 第一个参数是state,第二个参数是传递的值
	JIA(state,value){
        console.log('mutations中的JIA被调用了',state,value);
        state.sum += value
    },
    JIAN(state,value){
        console.log('mutations中的JIAN被调用了',state,value);
        state.sum -= value
    },
    // JIAODD(state,value){
    //     console.log('mutations中的JIAODD被调用了',state,value);
    //     if(state.sum%2){
    //         state.sum += value
    //     }
    // },
    // JIAWAIT(state,value){
    //     console.log('mutations中的JIAWAIT被调用了',state,value);
    //     state.sum += value
    // }

}
//准备state——用于存储数据
const state = {
	sum:0,    //将sum放到vuex中的state
    school:'尚硅谷',
    msg:'后端学前端',
}
//准备getters——用于将state中的数据进行加工
const getters = {
    bigSum(a,state){ //注意这里的参数第一位代表的就是state,不要整错了,第二个参数好像是getter自己了
        //需要写return 将数据返回出去
        console.log(a,state)
        return a.sum*10
    }
}
//创建并暴露store
export default new Vuex.Store({

    //在index.js文件中写的函数都需要放到这里才能暴漏出去,供外界使用
    //这些都算是配置项的名称,必须写到这里,外界引入的时候才能使用
	actions,
	mutations,
	state,
    getters
})
---Main.js
//引入Vue
import Vue from 'vue'
//引入组件App
import App from './App.vue'

//引入vuex
import  Vuex from 'vuex'
//引入store
import store from './store'



//关闭Vue的生产提示
Vue.config.productionTip=false
//创建vm
new Vue({
    el:'#app',
    store,
    render:h => h(App)
})

---App.vue
<template>
  <div>
		<Count/>
	</div>
</template>

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

页面展示

image-20230718215618364

5.5. 多组件共享数据案例

写两个组件模拟一下组件之间共享数据-下图是最终效果图

image-20230722095613490

先来添加一个Person组件来展示下面的是人员列表

image-20230722122725715

image-20230722100101739

红框内的是先写的,刚新建页面上来就是先按照需求将页面静态画出来,然后再去实现功能,比如输入名字需要使用v-model双向绑定,定义的name需要在data中声明定义,点击添加按钮的时候触发点击事件,methods中就需要写一个对应的方法.写到这里我们再来看页面,功能就是点击添加按钮将数据添加动态实时展示,所以我们需要在vuex的index中再去定义一个变量,以便这个变量能够被所有组件共享

image-20230722122028985

定义完变量以后,回到person组件当中使用这个组件

image-20230722122335868

在vuex中的mutations中定义方法

image-20230722122601966

通过unshift就可以向personList数组中添加数据了

image-20230722122647188

接着就是在count组件中

image-20230722123008194

同时我们也可以在Person组件中使用count组件中sum求和的值

image-20230722123333116

image-20230722123423249

完整的代码

---App.vue
<template>
  <div>
		<Count/>
    <hr>
    <!-- 步骤三:使用组件 -->
    <Person></Person>
	</div>
</template>

<script>
//步骤一:引入组件
import Count from './components/Count';
import Person from './components/Person';
export default {
  name:'App',
  //步骤二:注册组件
  components:{Count,Person},
}
</script>

---Main.js
//引入Vue
import Vue from 'vue'
//引入组件App
import App from './App.vue'

//引入vuex
import  Vuex from 'vuex'
//引入store
import store from './store'



//关闭Vue的生产提示
Vue.config.productionTip=false
//创建vm
new Vue({
    el:'#app',
    store,
    render:h => h(App)
})
---index.js
//该文件用于创建Vuex中最为核心的store
// 引入vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex'
//使用vuex
Vue.use(Vuex)

//准备actions——用于响应组件中的动作
const actions = {	
    // 在action当中需要使用commit() 传递给mutations 
    // jia(context,value) 第一个参数是简化版的ministore, 第二个参数value是传递过来的参数
    
    //start --------------------------------
    // jia(context,value){
    //     console.log('actions中的jia被调用了',context,value)
    //     //将函数名和传递的参数提交给下一流程-mutations中
    //     // context.commit('JIA',value) 第一个参数是函数名,第二个参数是传递的值
    //     context.commit('JIA',value)  //这里写成 JIA 是为了做个区分,知道这个大写的JIA是在mutations中的
    // },
    // jian(context,value){
    //     console.log('actions中的jian被调用了',context,value)
    //     context.commit('JIAN',value)
    // },
    //end ---------------------------------  start和end 部分的代码因为不涉及到业务逻辑,所以在vuex中可以跳过actions,直接commit走到mutataions
    jiaOdd(context,value){
        console.log('actions中的jiaOdd被调用了',context,value)
        if(context.state.sum%2){
            context.commit('JIA',value)//这里可以复用mutations中的JIA函数
        }
    },
    jiawait(context,value){
        console.log('actions中的jiawait被调用了',context,value)
        setTimeout(()=>{
            context.commit('JIA',value) //这里可以复用mutations中的JIA函数
        },1000);
    }
}
//准备mutations——用于操作数据(state)
const mutations = {
    // JIA(state,value) 第一个参数是state,第二个参数是传递的值
	JIA(state,value){
        console.log('mutations中的JIA被调用了',state,value);
        state.sum += value
    },
    JIAN(state,value){
        console.log('mutations中的JIAN被调用了',state,value);
        state.sum -= value
    },
    // JIAODD(state,value){
    //     console.log('mutations中的JIAODD被调用了',state,value);
    //     if(state.sum%2){
    //         state.sum += value
    //     }
    // },
    // JIAWAIT(state,value){
    //     console.log('mutations中的JIAWAIT被调用了',state,value);
    //     state.sum += value
    // }
    ADD_PERSON(state,value){
        console.log('mutations中的ADD_PERSON被调用了',state,value);
        state.personList.unshift(value)
    }

}
//准备state——用于存储数据
const state = {
	sum:0,    //将sum放到vuex中的state
    school:'尚硅谷',
    msg:'后端学前端',
    personList:[  //数组里面,每个元素都是对象
        {id:'001',name:'张三'}
    ]
}
//准备getters——用于将state中的数据进行加工
const getters = {
    bigSum(a,state){ //注意这里的参数第一位代表的就是state,不要整错了,第二个参数好像是getter自己了
        //需要写return 将数据返回出去
        console.log(a,state)
        return a.sum*10
    }
}
//创建并暴露store
export default new Vuex.Store({

    //在index.js文件中写的函数都需要放到这里才能暴漏出去,供外界使用
    //这些都算是配置项的名称,必须写到这里,外界引入的时候才能使用
	actions,
	mutations,
	state,
    getters
})
---Count.vue
<template>
  <div>
    <!-- $store.state.sum   在模板中可以直接使用vc身上的属性,最前面不用加this  然后也是从组件身上一层一层往下找 -->
    <!-- <h1>当前求和为:{{ $store.state.sum }}</h1>
    <h1>当前求和为:{{ $store.getters.bigSum }}</h1>
    <h1>我在{{ $store.state.school }},学习-{{ $store.state.msg }}</h1> -->
    <h1>当前求和为:{{ sum }}</h1>
    <h1>当前求和为:{{ bigSum }}</h1>
    <h1>我在{{ school }},学习-{{ msg }}</h1>
    <h3 style="color:red">Person组件中人数为:{{ personList.length }}</h3>
    <!-- 
        <select v-model.number="n">   这样写就等于是将n强制转换为number类型
    -->
    <select v-model="n">
        <!-- 
            :value="1"  表示会将1转换为数字1 ,否则页面显示就是字符串1,就会导致显示的时候是字符串拼接而不是加减运算
         -->
        <option :value="1">1</option>
        <option :value="2">2</option>
        <option :value="3">3</option>
    </select>
    <button @click="decrement(n)">-</button>
    <button @click="increment(n)">+</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementwait(n)">等一等再加</button>
  </div>
</template>

<script>
// 引入一下mapState
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
    
    name:'Count',
    data(){
        return  {
            // n和sum都要写成数字的,不能是字符串
            n:1, //用户选择的数字
            // sum:0 //当前的和   sum这里已经交给vuex中的state管理了,所以注释掉了这里不用写
        }
    },
    computed:{
        // sum(){
        //     return this.$store.state.sum
        // },
        
        // school(){
        //     return this.$store.state.school
        // },
        // msg(){ 
        //     return this.$store.state.msg
        // },
        
        // mapState(), 里面传输一个对象 {} 对象是{} 这样包裹起来的
        //借助mapState生成计算属性,从state中读取数据。(对象写法)
        // ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}),  前面key不带引号其实是可以的.
        ...mapState({'sum':'sum', 'msg':'msg', 'school':'school','personList':'personList'}),  //这种是最基本的写法  ...是es6中最新的写法,表示将mapstate对象中的元素都遍历展示到这里
		//借助mapState生成计算属性,从state中读取数据。(数组写法)
	    // ...mapState(['sum','school','msg']), //数组这种写法需要注意的是函数sum和state中的属性名称要保持一致.数组中的sum在这里有两个身份


        /* ******************************************************************** */


        // bigSum() {
        //     return this.$store.state.bigSum
        // },
        //借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
			// ...mapGetters({bigSum:'bigSum'})
        //借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
        ...mapGetters(['bigSum'])  
    },
    methods:{
        // increment(){
        //     // 这里我们要使用dispatch去分发任务.这个任务是在store中,而store又在vc实例身上
        //     // 所以写的时候必须从this身上开始找,一层一层往下找
        //     //dispatch('jia',this.n)  这里面的参数第一个就是要找的函数名,第二个参数就是要传的值是多少,这个n也是在n身上
        //     // this.$store.dispatch('jia',this.n)  //没有业务逻辑可以直接跳过actions节点
        //     // this.$store.commit('JIA',this.n)   // 注意commit中的第一个参数JIA是mutations中的函数
        //     // console.log(this)
        // },
        // decrement(){
        //     // this.$store.dispatch('jian',this.n) //没有业务逻辑可以直接跳过actions节点
        //     this.$store.commit('JIAN',this.n)
        // },

        //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),

        //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
        // ...mapMutations(['JIA','JIAN']), //注意这种写法需要名称保持一致

        // incrementOdd(){
        //     this.$store.dispatch('jiaOdd',this.n)
        // },
        // incrementwait(){
        //     // setTimeout(()=>{
        //         this.$store.dispatch('jiawait',this.n)
        //     // },1000);
        // }

        //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
		...mapActions({incrementOdd:'jiaOdd',incrementwait:'jiawait'})

        //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
        // ...mapActions(['jiaOdd','jiaWait'])


    }
}
</script>

<style>
    button{
        margin-left: 5px;
    }
</style>
---Person.vue
<template>
	<div>
		<h1>人员列表</h1>
		<h3 style="color:red">Count组件求和为:{{sum}}</h3>
		<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>
	</div>
</template>

<script>
	import {nanoid} from 'nanoid'
	import { mapState } from 'vuex'
	export default {
		name:'Person',
		data() {
			return {
				name:''
			}
		},
		computed:{
			personList(){
				return this.$store.state.personList
			},
			sum(){
				return this.$store.state.sum   //返回state中定义的sum 的值
			}
			// ...mapState(['sum','school','subject','personList']),  //使用这种方式也是可以的.但是需要上面引入(import { mapState } from 'vuex')一下
		},
		methods: {
			add(){
				const personObj = {id:nanoid(),name:this.name}
				this.$store.commit('ADD_PERSON',personObj)
				this.name = '' //这里是将输入框中的值清空
			}
		},
	}
</script>

5.6. 模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    // 定义一个countAbout模块 里面包含完整的state,mutations,actions,getters
    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  6. 开启命名空间后,组件中调用commit

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

代码演示案例:

针对vuex的store.js文件中存在的问题,就是在mutations中实现的功能大批量增加的时候,里面每个函数容易形成错综复杂的结果,这个时候我们就需要针对代码进行一些处理,比如说做一些区分,属于哪个业务模块的函数就集中放到一个空间当中,

image-20230722135205719

image-20230722135922701

针对以上图片中的多个函数可以进行整理分类

image-20230722140447862

这里分类以后看一下有什么不同的地方,分类以后就变成了a模块里面有actions,mutations等配置,b也有一套相同的配置项

image-20230722175302899

//该文件用于创建Vuex中最为核心的store
// 引入vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex'
//使用vuex
Vue.use(Vuex)
// 求和相关的配置
const countOptions = {
    actions:{
        jiaOdd(context,value){
            console.log('actions中的jiaOdd被调用了',context,value)
            if(context.state.sum%2){
                context.commit('JIA',value)//这里可以复用mutations中的JIA函数
            }
        },
        jiawait(context,value){
            console.log('actions中的jiawait被调用了',context,value)
            setTimeout(()=>{
                context.commit('JIA',value) //这里可以复用mutations中的JIA函数
            },1000);
        }   
    },
    mutations:{
        // JIA(state,value) 第一个参数是state,第二个参数是传递的值
        JIA(state,value){
            console.log('mutations中的JIA被调用了',state,value);
            state.sum += value
        },
        JIAN(state,value){
            console.log('mutations中的JIAN被调用了',state,value);
            state.sum -= value
        },
    },
    state:{
        sum:0,    //将sum放到vuex中的state
        school:'尚硅谷',
        msg:'后端学前端',
    },
    getters:{
        bigSum(a,state){ //注意这里的参数第一位代表的就是state,不要整错了,第二个参数好像是getter自己了
            //需要写return 将数据返回出去
            console.log(a,state)
            return a.sum*10
        }
    }
}
// 人员相关的配置
const personOptions = {
    actions:{

    },
    mutations:{
        ADD_PERSON(state,value){
            console.log('mutations中的ADD_PERSON被调用了',state,value);
            state.personList.unshift(value)
        }
    },
    state:{
        personList:[  //数组里面,每个元素都是对象
            {id:'001',name:'张三'}
        ]
    },
    getters:{
        
    }
}

//创建并暴露store
export default new Vuex.Store({

    //在index.js文件中写的函数都需要放到这里才能暴漏出去,供外界使用
    //这些都算是配置项的名称,必须写到这里,外界引入的时候才能使用
	actions,
	mutations,
	state,
    getters
})

但是代码还没有写完,按照上面的写法这里写的代码就有问题了,因为无法直接找到actions等配置项,并且store.js中目前actions有两个配置,countOptions和personOptions里面都有,

image-20230722174517551

正确的写法是:

image-20230722174831989

countOptions和personOptions都算是模块,修改完store.js文件以后.看一下使用情况

首先启动一下项目:这里如果属性是undefined那么页面只是不显示,不会报错,但是undefined再去调用别的方法就会报错,这个错误我们可以找到在哪

image-20230722180056807

image-20230722180241970

看一下state中现在有什么?输出一下

image-20230722180532578

image-20230722180511756

也就是在store.js中暴漏出去的两个模块名称

image-20230722180608292

所以这里如果想要使用personList,需要调用一下personAbout

image-20230722181449813

如果不想每次使用时候前面都得加countAbout或者personList.还有另外一种写法,使用mapState的时候指定命名空间

关于vuex模块化后的mapState的使用方式:

image-20230722182404882

image-20230722182408339

image-20230722182545231

关于vuex模块化后的mapMutations的使用方式和vuex模块化后的mapActions的使用方式:

image-20230722195324737

这样改完以后功能还是正常的.

image-20230722195401071

接下来修改一下Person组件,在这里同样要遵循使用vuex模块化后的使用规则,不能够直接使用了,需要我们指定具体的模块名称然后再去使用该模块下的属性或者是方法.

image-20230723095359467

输出看一下在state中一级属性是什么

image-20230723095545552

image-20230723095524165

关于vuex模块化后的commit的使用方式:注意写法就是这样写的

image-20230723095736028

关于vuex模块化后的getters的使用方式:

image-20230723102343251

image-20230723102315326

页面显示效果:

image-20230723102403427

image-20230723100534356

关于vuex模块化后的dispatch的使用方式:

image-20230723104730668

复习一下,actions中是一般写业务逻辑

image-20230723104755630

这部分用法就写完了.关于模块化还可以这样写

image-20230723123454275

只需要在这里引入一下就可以

image-20230723123521686

还需要将person.js暴漏出去

image-20230723123557305

image-20230723123627091

21-Vue Router 相关理解 基本路由 多级路由

6.1 相关理解

6.1.1 vue-router 的理解

vue的一个插件库,专门用来实现SPA应用

6.1.2 对SPA应用的理解

单页Web应用(single page web application,SPA)
整个应用只有一个完整的页面
点击页面中的导航链接不会刷新页面,只会做页面的局部更新
数据需要通过ajax请求获取

image-20230723130858653

image-20230723131719212

image-20230723131733226

6.1.3 路由的理解

什么是路由?
一个路由就是一组映射关系(key - value)
key为路径,value可能是function或componen
路由分类
后端路由
理解:value是function,用于处理客户端提交的请求
工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
前端路由
理解:value是component,用于展示页面内容
工作过程:当浏览器的路径改变时,对应的组件就会显示

image-20230723131158325

6.2 基本路由

使用步骤:

1.安装vue-router,命令npm i vue-router

2.应用插件 Vue.use(VueRouter)

3.编写router配置项

import VueRouter from 'vue-router'			// 引入VueRouter
import About from '../components/About'	// 路由组件
import Home from '../components/Home'		// 路由组件

// 创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
	routes:[
		{
			path:'/about',
			component:About
		},
		{
			path:'/home',
			component:Home
		}
	]
})

//暴露router
export default router

4.实现切换(active-class可配置高亮样式)

<router-link></router-link>浏览器会被替换为a标签

<router-link active-class="active" to="/about">About</router-link>

5.指定展示位置

<router-view></router-view>

练习代码演示:

步骤一:先安装一下vue-router,后面的@3表示vue2版本只能安装vue-router的3版本,vue3版本只能安装vue-router4版本,如果只写npm i vue-router表示安装最新的版本,对应版本不兼容,只能匹配对应安装

image-20230723153855549

步骤二: 导入并且应用插件

image-20230723155542866

步骤三:编写router配置项

image-20230723160430325

新建完路由器配置文件以后,需要在main.js中引入并且使用一下.

image-20230723182903324

步骤四:在代码中使用路由 步骤五:指定展示位

image-20230723183141463

image-20230723160939548

image-20230723160955288

实现效果:

image-20230723182531884

全部代码:

---main.js
//引入Vue
import Vue from 'vue'
//引入组件App
import App from './App.vue'
//引入VueRouter
import VueRouter from 'vue-router'

//引入路由器
import router from './router'

//关闭Vue的生产提示
Vue.config.productionTip=false
//应用插件
Vue.use(VueRouter)
//创建vm
new Vue({
    el:'#app',
    render:h => h(App),
    router:router
})

---index.js
// 该文件专门用于创建整个应用的路由器

import VueRouter from 'vue-router'
//引入组件
import About from '../components/About'
import Home from '../components/Home'

//创建并暴露一个路由器
export default new VueRouter({
	routes:[
		{
			path:'/about',
			component:About
		},
		{
			path:'/home',
			component:Home
		}
	]
})

---App.vue
<template>
  <div>
    <div class="row">
      <div class="col-xs-offset-2 col-xs-8">
        <div class="page-header"><h2>Vue Router Demo</h2></div>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-2 col-xs-offset-2">
        <div class="list-group">
	        <!-- 原始html中我们使用a标签实现页面的跳转 -->
          <!-- <a class="list-group-item active" href="./about.html">About</a>
          <a class="list-group-item" href="./home.html">Home</a> -->

          <!-- Vue中借助router-link标签实现路由的切换 --> 
          <!-- router-link 底层实际上就是a标签 -->
                                          <!-- active-class可配置高亮样式 -->
                                                                    <!-- to="/about" 表示的就是要跳转的路径 -->
          <router-link class="list-group-item" active-class="active" to="/about">About</router-link>
          <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>

          
        </div>
      </div>
      <div class="col-xs-6">
        <div class="panel">
          <div class="panel-body">
            <!-- 指定组件的呈现位置 -->
            <!-- 这里什么也不用写 -->
            <router-view></router-view>
          </div>
        </div>
      </div>
    </div>
  </div>


</template>

<script>   
export default {
    name:'App',
    
}
</script>


---About.vue
<template>
  <h3>我是About组件</h3>
</template>

<script>
export default {
    name:'About'
}
</script>

<style>

</style>
---Home.vue
<template>
  <h3>我是Home组件</h3>
</template>

<script>
export default {
    name:'Home'
}
</script>

<style>

</style>

6.3.几个注意事项

1.路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
比如上一节的案例就可以修改为
src/pages/Home.vue
src/pages/About.vue
src/router/index.js
src/components/Banner.vue
src/App.vue
2.通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
3.每个组件都有自己的$route属性,里面存储着自己的路由信息
4.整个应用只有一个router,可以通过组件的$router属性获取到

代码练习:

演示:1.路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹

image-20230723190534098

之前使用到这两个路由组件的地方都需要修改一下

image-20230723190610135

演示验证:2.通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载

在About组件中输出日志

---About.vue
<template>
  <h3>我是About组件</h3>
</template>

<script>
export default {
    name:'About',
    beforeDestroy() {
			console.log('About组件即将被销毁了')
		},
		 mounted() {
			console.log('About组件挂载完毕了',this)
		},
}
</script>

<style>

</style>
---Home.vue
<template>
  <h3>我是Home组件</h3>
</template>

<script>
export default {
    name:'Home',
    beforeDestroy() {
			console.log('Home组件即将被销毁了')
		},
		 mounted() {
			console.log('Home组件挂载完毕了',this)
		},  
}
</script>

<style>

</style>

image-20230723191136060

关于route和router

image-20230723191626740

image-20230723192033359

image-20230723192046134

image-20230723191844697

通过上面的截图中验证出:route是单个组件的路由,每个组件都是不一样的,router是整个路由器,只有一个.

6.4. 多级路由

使用方式步骤一 配置路由规则,使用children配置项

routes:[
	{
		path:'/about',
		component:About,
	},
	{
		path:'/home',
		component:Home,
		children:[ 					// 通过children配置子级路由
			{
				path:'news', 		// 此处一定不要带斜杠,写成 /news
				component:News
			},
			{
				path:'message',	     // 此处一定不要写成 /message
				component:Message
			}
		]
	}
]

使用方式步骤二跳转(要写完整路径)

<router-link to="/home/news">News</router-link>

代码练习:

需求是给Home组件下新增两个子路由组件

开发步骤一:首先是配置路由文件

image-20230723201017079

在需要使用的地方使用标明并且使用

image-20230723210459812

上面的路径就会先去路由配置文件中查找,然后根据路径找到指定的组件进行展示.

image-20230723210600436

image-20230723210611710

展示效果显示

image-20230723210649288

本次代码:

---index.js
// 该文件专门用于创建整个应用的路由器

import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'

import New from '../pages/New'
import Message from '../pages/Message'


//创建并暴露一个路由器
export default new VueRouter({
	routes:[
		{
			path:'/about',
			component:About
		},
		{

			path:'/home',
			component:Home,
            children:[
                {
                    path: 'news',
                    component:New,
                },
                {
                    path: 'message',
                    component:Message,
                }
            ]
		}
	]
})

---Home.vue
<template>
  <div>
    <h3>我是Home组件</h3>
    <div>
			<ul class="nav nav-tabs">
				<li>
                                                                    <!-- to="/home/news" 路径必须要写全的。 -->
					<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
				</li>
				<li>
					<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
				</li>
			</ul>
      <!--  指定组件的呈现位置 -->
        <!-- 这里什么也不用写 --> 
			<router-view></router-view>
		</div>
  </div>
  
  
</template>

<script>
export default {
    name:'Home',
    // beforeDestroy() {
		// 	console.log('Home组件即将被销毁了')
		// },
		//  mounted() {
		// 	console.log('Home组件挂载完毕了',this)
		// 	window.aboutRoute = this.$route
		// 	window.aboutRouter = this.$router
		// },  
}
</script>

<style>

</style>
---Message
<template>
	<div>
		<ul>
			<li>
				<a href="/message1">message001</a>&nbsp;&nbsp;
			</li>
			<li>
				<a href="/message2">message002</a>&nbsp;&nbsp;
			</li>
			<li>
				<a href="/message/3">message003</a>&nbsp;&nbsp;
			</li>
		</ul>
	</div>
</template>

<script>
	export default {
		name:'Message'
	}
</script>
---New.vue
<template>
	<ul>
		<li>news001</li>
		<li>news002</li>
		<li>news003</li>
	</ul>
</template>

<script>
	export default {
		name:'News'
	}
</script>

其他代码没有变.

22-Vue Router query 命名路由 params props

6.5. 路由的 query 参数

使用步骤一:传递参数

传递参数有以下两种写法
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link>
				
<!-- 跳转并携带query参数,to的对象写法(推荐) -->
<router-link 
	:to="{
		path:'/home/message/detail',
		query:{
		   id: m.id,
       title: m.title
		}
	}"
>跳转</router-link>

使用步骤二:接收参数

<!-- 接受参数的写法 因为传递参数是通过query传递的 -->
<li>消息id:{{ $route.query.id }}</li>
<li>消息标题:{{ $route.query.title }}</li>

代码演示练习:

需求小案例:点击消息的时候展示详细信息

image-20230723211913864

点击消息的时候对应进行展示,这时候需要再写一个组件Detail.vue组件来进行详细信息的展示,每次点击的时候都把需要展示的数据传递过去.先进行路由的配置.

image-20230723213419441

路由配置完以后,Detail组件是没有的,新建一个

image-20230723213917473

找到message组件,在这里进行数据的传递,

image-20230723220925645

image-20230723220848103

然后再回过头去写接受参数的代码

image-20230723221009977

页面展示信息:点击对应的消息就会显示详细内容

image-20230723221030829

本次演示全部代码:

---index.js
// 该文件专门用于创建整个应用的路由器

import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'

import New from '../pages/New'
import Message from '../pages/Message'

import Detail from '../pages/Detail'

//创建并暴露一个路由器
export default new VueRouter({
	routes:[
		{
			path:'/about',
			component:About
		},
		{

			path:'/home',
			component:Home,
            children:[  // 通过children配置子级路由
                {
                    path: 'news',// 此处一定不要带斜杠,写成 /news
                    component:New,
                },
                {
                    path: 'message',// 此处一定不要带斜杠,写成 /message
                    component:Message,
                    children:[ // 通过children配置子级路由
                        {
                            path:'detail', // 此处一定不要带斜杠,写成 /detail
                            component:Detail,
                        }
                    ]
                }
            ]
		}
	]
})

---Message.vue
<template>
	<div>
		<ul>
			<!-- <li>
				<a href="/message1">message001</a>&nbsp;&nbsp;
			</li> -->
			<!-- <li>
				<a href="/message2">message002</a>&nbsp;&nbsp;
			</li>
			<li>
				<a href="/message/3">message003</a>&nbsp;&nbsp;
			</li> -->
			<li v-for="(item) in messageList" :key="item.id">
				<!-- 点击title的时候实现 跳转,这里也不是跳转,就是打开一个a标签 -->
				<!-- 跳转路由并携带query参数,to的字符串写法 
					关于to后面字符串里面的写法解释一下:
					因为to=""  这里面是字符串,所以如果需要携带变量的话,肯定是不能直接写的, 
				-->
				<router-link :to="`/home/message/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>&nbsp;&nbsp;
				
				<!-- 跳转路由并携带query参数,to的对象写法 -->
				<!-- <router-link :to="{
					path:'/home/message/detail',
					query:{
						id:item.id,
						title:item.title
					}	
				}"
				>{{ item.title }}
			</router-link> -->
			</li>
		</ul>
		<!-- 在这里标记 -->
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name:'Message',
		data(){
			return {
				messageList:[
					{id:'001',title:'消息001'},
					{id:'002',title:'消息002'},
					{id:'003',title:'消息003'}
				]
			}
		}
	}
</script>
---Detail.vue
<template>
  <div>
    <ul>
        <!-- 接受参数的写法 因为传递参数是通过query传递的 -->
        <li>消息id:{{ $route.query.id }}</li>
        <li>消息标题:{{ $route.query.title }}</li>
    </ul>
  </div>
</template>

<script>
export default {
    name:'Detail',
    mounted(){
        console.log(this.$route) //输出$route可在页面看一下route里有什么
    }
}
</script>

<style>

</style>

其他部分的代码保持不变

6.6. 命名路由

命名路由
作用:可以简化路由的跳转

如何使用

​ 使用步骤一:给路由命名

{
	path:'/demo',
	component:Demo,
	children:[
		{
			path:'test',
			component:Test,
			children:[
				{
          name:'hello' // 给路由命名
					path:'welcome',
					component:Hello,
				}
			]
		}
	]
}

​ 使用步骤二:简化跳转

<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>

<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>

<!--简化写法配合传递参数 -->
<router-link 
	:to="{
		name:'hello',
		query:{
		    id:666,
        title:'你好'
		}
	}"
>跳转</router-link>

代码练习:

image-20230724221631362

---src/router/index.js
// 该文件专门用于创建整个应用的路由器

import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'

import New from '../pages/New'
import Message from '../pages/Message'

import Detail from '../pages/Detail'

//创建并暴露一个路由器
export default new VueRouter({
	routes:[
		{
            name:'guanyu',
			path:'/about',
			component:About
		},
		{

			path:'/home',
			component:Home,
            children:[  // 通过children配置子级路由
                {
                    path: 'news',// 此处一定不要带斜杠,写成 /news
                    component:New,
                },
                {
                    path: 'message',// 此处一定不要带斜杠,写成 /message
                    component:Message,
                    children:[ // 通过children配置子级路由
                        {
                            name:'xiangqing',
                            path:'detail', // 此处一定不要带斜杠,写成 /detail
                            component:Detail,
                        }
                    ]
                }
            ]
		}
	]
})

---Message.vue
<template>
	<div>
		<ul>
			<!-- <li>
				<a href="/message1">message001</a>&nbsp;&nbsp;
			</li> -->
			<!-- <li>
				<a href="/message2">message002</a>&nbsp;&nbsp;
			</li>
			<li>
				<a href="/message/3">message003</a>&nbsp;&nbsp;
			</li> -->
			<li v-for="(item) in messageList" :key="item.id">
				<!-- 点击title的时候实现 跳转,这里也不是跳转,就是打开一个a标签 -->
				<!-- 跳转路由并携带query参数,to的字符串写法 
					关于to后面字符串里面的写法解释一下:
					因为to=""  这里面是字符串,所以如果需要携带变量的话,肯定是不能直接写的, 
				-->
				<!-- <router-link :to="`/home/message/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>&nbsp;&nbsp; -->
				
				<!-- 跳转路由并携带query参数,to的对象写法 -->
				<router-link :to="{
					// path:'/home/message/detail',
					name:'xiangqing',
					query:{
						id:item.id,
						title:item.title
					}	
				}"
				>{{ item.title }}
			</router-link>
			</li>
		</ul>
		<!-- 在这里标记 -->
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name:'Message',
		data(){
			return {
				messageList:[
					{id:'001',title:'消息001'},
					{id:'002',title:'消息002'},
					{id:'003',title:'消息003'}
				]
			}
		}
	}
</script>

页面依然是能够正常运行的

image-20230724221733201

6.7. 路由的 params 参数

使用步骤

使用步骤一:配置路由,声明接收params参数

{
	path:'/home',
	component:Home,
	children:[
		{
			path:'news',
			component:News
		},
		{
			component:Message,
			children:[
				{
					name:'xiangqing',
					path:'detail/:id/:title', //使用占位符声明接收params参数
					component:Detail
				}
			]
		}
	]
}

使用步骤二:传递参数
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
				
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link 
	:to="{
		name:'xiangqing',
		params:{
		   id:666,
            title:'你好'
		}
	}"
>跳转</router-link>

使用步骤三:接收参数

$route.params.id
$route.params.title

代码练习

image-20230725213013385

按照上面使用步骤来写:

首先是需要在路由中配置路径的地方使用占位符声明接收params参数,

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'

//创建并暴露一个路由器
export default new VueRouter({
	routes:[
		{
			name:'guanyu',
			path:'/about',
			component:About
		},
		{
			path:'/home',
			component:Home,
			children:[
				{
					path:'news',
					component:News,
				},
				{
					path:'message',
					component:Message,
					children:[
						{
							name:'xiangqing',
							path:'detail/:id/:title',  //使用占位符声明接收params参数
							component:Detail,
						}
					]
				}
			]
		}
	]
})

其次在转换路径的地方使用param传参,这里to有两种写法

<template>
	<div>
		<ul>
			<li v-for="m in messageList" :key="m.id">
				<!-- 跳转路由并携带params参数,to的字符串写法 -->
				<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->

				<!-- 跳转路由并携带params参数,to的对象写法 -->
				<router-link :to="{
					name:'xiangqing',
					params:{
						id:m.id,
						title:m.title
					}
				}">
					{{m.title}}
				</router-link>
			
			</li>
		</ul>
		<hr>
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name:'Message',
		data() {
			return {
				messageList:[
					{id:'001',title:'消息001'},
					{id:'002',title:'消息002'},
					{id:'003',title:'消息003'}
				]
			}
		},
	}
</script>

最后就是使用参数:在需要使用传递参数的地方使用参数

<template>
	<ul>
		<li>消息编号:{{$route.params.id}}</li>
		<li>消息标题:{{$route.params.title}}</li>
	</ul>
</template>

<script>
	export default {
		name:'Detail',
		mounted() {
			// console.log(this.$route)
		},
	}
</script>

其他代码保持不变

6.8. 路由的 props 配置

props作用:让路由组件更方便的收到参数

{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	// props:{a:900}

	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props(route){
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
}

代码练习:

第一种写法-值为对象:

用法和作用:

//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。

// props:{a:1,b:'nanshou'} //此处关于props的位置为什么写在这里? 因为我们要给detail组件传递参数.给谁传递参数就写在那个组件相关的位置

image-20230725222856300

image-20230725222940627

image-20230725222953370

第二种写法-值为布尔值:

用法和作用:需要知道和理解下面两句话:

//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件(这个组件就是上一级的组件)收到的所有params参数,以props的形式传给Detail组件。

//只有是params参数才可以, query方式的参数没有效果

image-20230725224613957

为什么要用params传递参数呢? 因为我们现在用的props的第二种写法就是这么规定的

image-20230725224736438

image-20230725224812333

image-20230725224819036

第三种写法-值为函数:

用法和作用:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件

在index使用函数的写法:

image-20230726221842684

根据props传递参数的用法,下面这里参数的时候需要写成query

image-20230726222014816

image-20230726222046074

23-Vue Router replace 编程式导航 缓存路由组件

6.9<router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 开启replace模式
    <router-link :replace="true" ...>News</router-link>
    简写 <router-link replace ...>News</router-link>

总结:浏览记录本质是一个栈,默认push,点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,改为replace就是不追加,而将栈顶地址替换

代码练习

当我们点击页面的时候,初始化进来,页面是不能够选择后退前进的

image-20230729085603719

点击about组件,然后点击home组件,再点击new组件,每次访问的地址都会发生变化,

image-20230729085745011

浏览器地址是变化的.浏览器的历史记录有两种写入方式,下面这种方式是默认的方式push.点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,

image-20230729085502992

还有另外一种模式就是replace模式,每一次都会将前一次的地址给替换掉,前一次的地址就不存在了,回退的时候也不可能回退到前一次,而是前一次的前一次.

image-20230729094644727

比如我们使用的路由是replace模式,在点击About和Home就会将之前的地址个替换掉,

image-20230729095230276

当我们点击页面的时候,可以看到是没有后退路径的,因为上一次的路径已经被替换了

image-20230729095500920

点击news和push

image-20230729095706220

当我们将About和Home换位默认的push模式,news和message换成replace模式后

image-20230729101945824

image-20230729101958854

依次点击about和home,news和message,看一下地址是怎么变化的.

image-20230729100014988

会发现点完message以后,点击左上角后退以后直接就回退到了about这里

image-20230729102057256

image-20230729102104192

作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

this.$router.push({}) 内传的对象与<router-link>中的to相同
this.$router.replace({})
this.$router.forward() 前进
this.$router.back() 后退
this.$router.go(n) 可前进也可后退,n为正数前进n,为负数后退

具体编码:

//$router的两个API
this.$router.push({
	name:'xiangqing',
		params:{
			id:xxx,
			title:xxx
		}
})

this.$router.replace({
	name:'xiangqing',
		params:{
			id:xxx,
			title:xxx
		}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退

代码演示

之前路由跳转的时候是使用的router-link标签,除了router-link之外,我们还可以点击按钮的时候实现路由跳转,

写一个button按钮点击事件

image-20230729104134114

image-20230729104155218

其他的replace\back\forward\go的使用方式类似,比较简单,在代码里面看一下怎么使用的就可以了.

全部代码

---Banner.vue
<template>
	<div class="col-xs-offset-2 col-xs-8">
		<div class="page-header">
			<h2>Vue Router Demo</h2>
			<button @click="back">后退</button>
			<button @click="forward">前进</button>
			<button @click="test">测试一下go</button>
		</div>
	</div>
</template>

<script>
	export default {
		name:'Banner',
		methods: {
			back(){
				this.$router.back()
				// console.log(this.$router)
			},
			forward(){
				this.$router.forward()
			},
			test(){
				this.$router.go(3)
			}
		},
	}
</script>
---Message.vue
<template>
	<div>
		<ul>
			<li v-for="m in messageList" :key="m.id">
				<!-- 跳转路由并携带params参数,to的字符串写法 -->
				<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->

				<!-- 跳转路由并携带params参数,to的对象写法 -->
				<router-link :to="{
					name:'xiangqing',
					query:{
						id:m.id,
						title:m.title
					}
				}">
					{{m.title}}
				</router-link>
				<button @click="pushShow(m)">push查看</button>
				<button @click="replaceShow(m)">replace查看</button>
			</li>
		</ul>
		<hr>
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		name:'Message',
		data() {
			return {
				messageList:[
					{id:'001',title:'消息001'},
					{id:'002',title:'消息002'},
					{id:'003',title:'消息003'}
				]
			}
		},
		methods: {
			pushShow(m){
				this.$router.push({
					name:'xiangqing',
					query:{
						id:m.id,
						title:m.title
					}
				})
			},
			replaceShow(m){
				this.$router.replace({
					name:'xiangqing',
					query:{
						id:m.id,
						title:m.title
					}
				})
			}
		},
	}
</script>

image-20230729131228241

6.11. 缓存路由组件

作用:让不展示的路由组件保持挂载,不被销毁

具体编码:<keep-alive include="News"><router-view></router-view></keep-alive>
<keep-alive :include="['News', 'Message']"><router-view></router-view></keep-alive>

// 缓存一个路由组件
<keep-alive include="News"> // include中写想要缓存的组件名,不写表示全部缓存
    <router-view></router-view>
</keep-alive>

// 缓存多个路由组件
<keep-alive :include="['News','Message']"> 
    <router-view></router-view>
</keep-alive>

代码演示:

当我们在news中输入了内容,切换到Message的时候再切换回来,会发现刚才输入的内容没有了.因为切换的时候news被销毁了

image-20230729132754823

image-20230729132921884

image-20230729132928036

如何才能使得切换的时候组件不被销毁呢?

image-20230729133139181

使用<keep-alive include="New"><router-view></router-view></keep-alive> 标签包裹起来就可以了,News组件就不会被销毁了 在切换的时候. include="New" 这里面的参数写的是news组件中的name出的名字

image-20230729133956493

如果不写include就表示这个组件下的所有子组件都使用了缓存.这里需要注意:如果Home组件被销毁了,那么缓存也就失效了,这么说是keep-alive写在Home组件当中,Home组件本身并没有使用缓存,当切换到Home组件之外的组件,比如跟他平级的,Home组件就销毁了.

使用了缓存以后-这里是news组件使用了缓存

image-20230729134510691

然后切换到message

image-20230729134529135

再切回到news组件,数据还在

image-20230729134544124

假设此时我切换到About组件, Home组件及其下的所有组件就都被销毁了

image-20230729134625322

全部代码:

---News.vue
<template>
	<ul>
		<li>news001</li><input type="text" placeholder="请输入内容">
		<li>news002</li><input type="text" placeholder="请输入内容">
		<li>news003</li><input type="text" placeholder="请输入内容">
	</ul>
</template>

<script>
	export default {
		name:'Newssss',
		beforeDestroy() {
			console.log('Newssss组件即将被销毁了')
		},
	}
</script>
---Message
<template>
  <div>
    <h3>我是Home组件</h3>
    <div>
			<ul class="nav nav-tabs">
				<li>
                                                                    <!-- to="/home/news" 路径必须要写全的。 -->
					<router-link replace  class="list-group-item" active-class="active" to="/home/news">News</router-link>
				</li>
				<li>
					<router-link replace class="list-group-item" active-class="active" to="/home/message">Message</router-link>
				</li>
			</ul>
      <!--  指定组件的呈现位置 -->
        <!-- 这里什么也不用写 --> 
		<keep-alive include="Newssss">
			<router-view></router-view>
		</keep-alive>
		</div>
  </div>
  
  
</template>

<script>
export default {
    name:'Home',
    beforeDestroy() {
			console.log('Home组件即将被销毁了')
	},
		//  mounted() {
		// 	console.log('Home组件挂载完毕了',this)
		// 	window.aboutRoute = this.$route
		// 	window.aboutRouter = this.$router
		// },  
	
}
</script>

<style>

</style>

24-Vue Router activated deactivated 路由守卫

6.12.两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
  2. 具体名字:
    1. activated路由组件被激活时触发。
    2. deactivated路由组件失活时触发。

用处就是当既想用缓存路由的时候,切换回来组件中的某些任务开始执行,如果切换出去就不执行

代码用法:

<template>
    <ul>
        <li :style="{opacity}">欢迎学习vue</li>
        <li>news001 <input type="text"></li>
        <li>news002 <input type="text"></li>
        <li>news003 <input type="text"></li>
    </ul>
</template>

<script>
    export default {
        name:'News',
        data(){
            return{
                opacity:1
            }
        },
        activated(){
            console.log('News组件被激活了')
            this.timer = setInterval(() => {
                this.opacity -= 0.01
                if(this.opacity <= 0) this.opacity = 1
            },16)
        },
        deactivated(){
            console.log('News组件失活了')
            clearInterval(this.timer)
        }
    }
</script>

6.13.路由守卫

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
    	console.log('beforeEach',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
    			next() //放行
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next() //放行
    	}
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
    	console.log('afterEach',to,from)
    	if(to.meta.title){ 
    		document.title = to.meta.title //修改网页的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    
  4. 独享守卫:

    beforeEnter(to,from,next){
    	console.log('beforeEnter',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){
    			next()
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next()
    	}
    }
    
  5. 组件内守卫:

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }
    

代码练习

全局前置守卫

首先是来练习一下什么事全局前置守卫,它的作用是初始化时执行、每次路由切换前执行 ,是一个函数,全局路由守卫可以做什么呢? 比如说权限验证,满足设置的条件以后才可以继续,或者说权限级别够了才能看那个页面,

比如根据不同用户权限只能查看对应的页面,有的页面如果用户没有权限是看不了的,我们回去判断一下当前浏览器中存着的用户是不是符合条件,如果符合条件就可以看指定的页面,

比如此时我们在页面添加一个

image-20230729170903334

image-20230729171234711

image-20230729172253088

可以看到代码里面我是故意写的molibai1 ,这样验证是过不去的,所以点击页面任何内容都不会查看,都会弹窗

image-20230729171357834

改为正确的value以后所有的内容就能正常显示了,会遇到需求就是需要在访问某个页面的时候去做一个权限验证,比如这里我们对news组件和message组件进行限制,查看这两个组件内容需要权限,此时我们可以利用to.path或者to.name(如果使用name的话需要在index路由配置文件中进行name的配置),判断一下是不是new和message 的路径,如果是话就进行权限验证

image-20230729172424433

当然如果权限验证的文件很多的时候,这里进行再写就想对麻烦一点,换种方式也可以在路由文件中对应的地方做一个权限验证的标记,这样我们只需要判断这个权限验证的标记是不是为真,如果为真就权限验证,如果不为真,就直接放过表示这个路由地址是不需要权限验证的.这里我们就需要注意到vue中针对to里面的参数有一个属性,这个属性

image-20230729172831953

我们能在这个属性里面进行自定义配置,比如配置一个标记权限的属性,

image-20230729173045386

对添加了这个标记的属性的值进行判断就可以了

image-20230729173834704

image-20230729173512034

image-20230729173452391

全局后置路由守卫

再来学习一下后置路由守卫-初始化的时候被调用、每次路由切换之后被调用

首先我们需要了解后置守卫是在什么时候被调用的,这个是至关重要的,当路由切换之后被调用,如果这个时候还需要进行一些其他配置就需要将代码写在后置守卫里面,比如需求是点击那个组件的时候这个浏览器窗口的title就展示对应信息

在index中进行了配置,meta中

image-20230729180246029

image-20230729180331260

image-20230729180203596

image-20230729180155107

但是点击页面还是能够发现一下问题的,看我document.title=to.meta.title这行代码的写的位置是否正确

image-20230729180457531

因为初始化进来的时候是么有组件的,我们也没有进行配置,这里我们先进行一下配置 加一个或者

image-20230729180556075

还有一个问题就是页面我们点了news组件,但是没有权限进去,此时是不显示news组件信息的,但是问题是窗口的title却显示的新闻

image-20230729180821187

image-20230729180826788

我们可能会将设置title的代码放到权限验证里面

image-20230729180954387

这里我们写了两边,但是再学习了后置守卫以后,我们就可以把代码放到后置守卫里面,因为走到后置守卫这里的话表示是一定会走当前的路由地址的,这个时候我们再进行相关的配置就一定不会出错了.

image-20230729181305977

全局路由守卫和后置守卫的代码配置

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'

//创建并暴露一个路由器
const router =  new VueRouter({
	routes:[
		{
			name:'guanyu',
			path:'/about',
			component:About,
			meta:{title:'关于'}
		},
		{
			name:'zhuye',
			path:'/home',
			component:Home,
			meta:{title:'主页'},
			children:[
				{
					name:'xinwen',
					path:'news',
					component:News,
					meta:{isAuth:true,title:'新闻'}
				},
				{
					name:'xiaoxi',
					path:'message',
					component:Message,
					meta:{isAuth:true,title:'消息'},
					children:[
						{
							name:'xiangqing',
							path:'detail',
							component:Detail,
							meta:{isAuth:true,title:'详情'},

							//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
							// props:{a:1,b:'hello'}

							//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
							// props:true

							//props的第三种写法,值为函数
							props($route){
								return {
									id:$route.query.id,
									title:$route.query.title,
									a:1,
									b:'hello'
								}
							}

						}
					]
				}
			]
		}
	]
})

//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
	console.log('前置路由守卫',to,from)
	if(to.meta.isAuth){ //判断是否需要鉴权
		if(localStorage.getItem('school')==='atguigu'){
			next()
		}else{
			alert('学校名不对,无权限查看!')
		}
	}else{
		next()
	}
})

//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
	console.log('后置路由守卫',to,from)
	document.title = to.meta.title || '硅谷系统'
})

export default router

独享守卫代码练习

这个守卫的功能就是针对某个组件下的路径进行拦截,就是写的位置不一样,还有就是函数名是beforeEnter,其他代码都是和全局变量前置守卫是一样的.

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'

//创建并暴露一个路由器
const router =  new VueRouter({
	routes:[
		{
			name:'guanyu',
			path:'/about',
			component:About,
			meta:{title:'关于'}
		},
		{
			name:'zhuye',
			path:'/home',
			component:Home,
			meta:{title:'主页'},
			children:[
				{
					name:'xinwen',
					path:'news',
					component:News,
					meta:{isAuth:true,title:'新闻'},
					beforeEnter: (to, from, next) => {
						console.log('独享路由守卫',to,from)
						if(to.meta.isAuth){ //判断是否需要鉴权
							if(localStorage.getItem('school')==='atguigu'){
								next()
							}else{
								alert('学校名不对,无权限查看!')
							}
						}else{
							next()
						}
					}
				},
				{
					name:'xiaoxi',
					path:'message',
					component:Message,
					meta:{isAuth:true,title:'消息'},
					children:[
						{
							name:'xiangqing',
							path:'detail',
							component:Detail,
							meta:{isAuth:true,title:'详情'},

							//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
							// props:{a:1,b:'hello'}

							//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
							// props:true

							//props的第三种写法,值为函数
							props($route){
								return {
									id:$route.query.id,
									title:$route.query.title,
									a:1,
									b:'hello'
								}
							}

						}
					]
				}
			]
		}
	]
})

//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
/* router.beforeEach((to,from,next)=>{
	console.log('前置路由守卫',to,from)
	if(to.meta.isAuth){ //判断是否需要鉴权
		if(localStorage.getItem('school')==='atguigu'){
			next()
		}else{
			alert('学校名不对,无权限查看!')
		}
	}else{
		next()
	}
}) */

//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
	console.log('后置路由守卫',to,from)
	document.title = to.meta.title || '硅谷系统'
})

export default router

组件内路由守卫代码练习

跟前置守卫和后置守卫是有区别的,

---About.vue
<template>
	<h2>我是About的内容</h2>
</template>

<script>
	export default {
		name:'About',
		/* beforeDestroy() {
			console.log('About组件即将被销毁了')
		},*/
		/* mounted() {
			console.log('About组件挂载完毕了',this)
			window.aboutRoute = this.$route
			window.aboutRouter = this.$router
		},  */
		mounted() {
			// console.log('%%%',this.$route)
		},

		//通过路由规则,进入该组件时被调用
		beforeRouteEnter (to, from, next) {
			console.log('About--beforeRouteEnter',to,from)
			if(to.meta.isAuth){ //判断是否需要鉴权
				if(localStorage.getItem('school')==='atguigu'){
					next()
				}else{
					alert('学校名不对,无权限查看!')
				}
			}else{
				next()
			}
		},

		//通过路由规则,离开该组件时被调用
		beforeRouteLeave (to, from, next) {
			console.log('About--beforeRouteLeave',to,from)
			next()
		}
	}
</script>

6.14.路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

image-20230729211942716

  1. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
  2. hash模式:
    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  3. history模式:
    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
const router =  new VueRouter({
	mode:'history',
	routes:[...]
})

export default router

如果mode设置为history了,浏览器请求路径里面就不会出现带有#号.

25- Vue UI 组件库

7.1 常用UI组件库

7.1.1 移动端常用UI组件库

Vant
Cube UI
Mint UI
https://nutui.jd.com/#/

7.1.2. PC端常用UI组件库

Element UI
IView UI

image-20230729220450889

7.2. element-ui基本使用

安装 element-ui:npm i element-ui -S
src/main.js

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';							// 引入ElementUI组件库
import 'element-ui/lib/theme-chalk/index.css';	// 引入ElementUI全部样式

Vue.config.productionTip = false

Vue.use(ElementUI)	// 使用ElementUI

new Vue({
    el:"#app",
    render: h => h(App),
})
---App.vue
<template>
	<div>
		<br>
		<el-row>
			<el-button icon="el-icon-search" circle></el-button>
			<el-button type="primary" icon="el-icon-edit" circle></el-button>
			<el-button type="success" icon="el-icon-check" circle></el-button>
			<el-button type="info" icon="el-icon-message" circle></el-button>
			<el-button type="warning" icon="el-icon-star-off" circle></el-button>
			<el-button type="danger" icon="el-icon-delete" circle></el-button>
		</el-row>
	</div>
</template>

<script>
	export default {
		name:'App',
	}
</script>

7.3. element-ui按需引入

image-20230729224150586

安装 babel-plugin-componentnpm i babel-plugin-component -D
修改 babel-config-js

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    ["@babel/preset-env", { "modules": false }]
  ],
  plugins: [
    [
      "component",
      {        
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
---main.js
import Vue from 'vue'
import App from './App.vue'
import { Button,Row } from 'element-ui'	// 按需引入  //样式的话就需要手动引入了,因为脚手架会帮助我们自动识别分析

Vue.config.productionTip = false

Vue.component(Button.name, Button);
Vue.component(Row.name, Row);
/* 或写为
 * Vue.use(Button)
 * Vue.use(Row)
 */

new Vue({
    el:"#app",
    render: h => h(App),
})

image-20230729224305498

标签:Vue,name,组件,state,vue,第二季,import,路由
From: https://www.cnblogs.com/javaxubo/p/17604429.html

相关文章

  • vue + canvas 实现涂鸦面板
    前言专栏分享:vue2源码专栏,vuerouter源码专栏,玩具项目专栏,硬核......
  • Vue 中如何进行权限控制?
    随着前端技术的不断发展,越来越多的前端框架被使用在Web应用程序中,其中尤为出色的一个就是Vue。Vue是一个易于理解并且使用方便的框架,它被广泛地应用于Web应用程序的开发中。在大多数Web应用程序中,权限控制是至关重要的一部分,如何在Vue中进行权限控制就成为了一个十分关......
  • 在vue中使用Electron开发C/S架构中的C(客户端界面)
    Electron简介:Electron是利用web前端技术进行桌面应用开发的一套框架。我是用的nodejs版本(16.18.1)和npm版本(8.19.2):创建vue-electron项目,鄙人测试了两种方式创建vue-electron项目,如下所示:1、vue-cli-plugin-electron-builder插件方式1.全局安装vue-cli:  npminstall-g@vue/......
  • vue 动态绑定style class
    绑定style<!--基本使用--><div:style="{color:activeColor,fontSize:fontSize+'px'}">基本使用</div><!--数组--><div:style="styleArr">123</div><div:style="[astyle,bStyle]"&g......
  • 前端项目时因chunk-vendors过大导致首屏加载太慢,Vue Build时chunk-vendors的优化方案
    1、compression-webpack-plugin插件打包.gz文件安装插件也可以指定版本 我这里下载的是1.1.2版本的,试过更高的版本会有ES6语法的报错,因为我node使用的是v12,如果node版本更高可以尝试更高版本npminstall--save-devcompression-webpack-pluginnpminstall--save-devc......
  • Vue学习笔记:setup语法糖
    在使用VCA方式编写Vue代码是,大部分逻辑都是被setup(){}包裹起来,且最后使用的时候需要return{}出去。比较繁琐,实际上Vue提供了setup语法糖,可以简化这些操作。1不再需要exportdefault与return不使用语法糖写法<template><div@click="handelClick">app-{{msg}}</div></te......
  • Vuejs+WebApi导出Excel
    前后端分离,前端Vuejs,后端.Net6WebApi后端代码1publicclassSalesReportController:BaseController2{3privateSerilog.ILogger_log=GetLogger<SalesReportController>();4privatereadonlyISqlSugarClient_db;5privateIHostEnvironme......
  • vue2项目中引入svg图标
    vue版本  vue:"^2.6.11"1.下载对应的svg依赖npminstallsvg-sprite-loader--save-dev2.创建svgIcon文件夹文件夹下对应3个文件svg文件夹:存放svg文件index.js文件:vue挂载svgindex.vue文件:封装的svg文件 3.index.js文件全局挂载到vue上1importVue......
  • 谈谈 Vuex 模块化的缺点
    Vue项目经常需要用到Vuex,Vuex利用响应式,在不同的组件之间传递数据已经相当方便了,但是在使用模块化的过程中还是有点复杂,主要集中在两点:访问state,action,mutation,getter时候,必须使用模块名作为前缀基本没有代码提示假设,store的目录结构是这样的:想要dispatch一个action......
  • vue中展示多张小图轮播每几秒走一张,支持左右点击
    html部分    <divclass="regional-services-list"style="overflow:hidden;"@mouseenter="stopCar()"@mouseleave="starCar()">     <divclass="preIcon"@click="preChange()"><......