Vue2 总结(开发)

1. Vue CLI 脚手架

1.1 安装

npm install -g @vue/cli
# OR
yarn global add @vue/cli

1.2 升级

npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli

1.3 创建一个项目

vue create hello-world

在这里插入图片描述
会被提示选取一个 preset。你可以选默认的包含了基本的 Babel + ESLint 设置的 preset,也可以选“手动选择特性”来选取需要的特性
在这里插入图片描述
创建完成
在这里插入图片描述

1.4 目录结构

blog.csdnimg.cn/1b0c283392d54c208c31d993b11f00b4.png)

1.5 运行

vue-cli2.0

"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run dev",
  "build": "node build/build.js"
}

vue-cli3.0

"scripts": {
  "serve": "vue-cli-service serve", // 运行项目
  "build": "vue-cli-service build", // build
  "lint": "vue-cli-service lint" // 运行语法检查
},

在这里插入图片描述
启动运行项目

npm run serve

npm run xxx 中的 xxx 可以理解为键值对的 key,实际上 run 的是在 package.json 里面 scripts 配置的 value。比如,npm run serve 实际运行的是 vue-cli-service serve,而放在 3.0 以前 npm run dev 运行的则是 node build/dev-server.js 文件

2. Demo 案例

2.1 components

定义两个组件
在这里插入图片描述

<template>
  <div class="demo">
    <h2>学校名称:{{schoolName}}</h2>
    <h2>学校地址:{{schoolAddress}}</h2>
    <button @click="showName">点我提示学校名</button>
  </div>
</template>

<script>
export default ({
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'School',
  data() {
    return {
      schoolName: 'Vue',
      schoolAddress: '湖南'
    }
  },
  methods: {
    showName() {
      alert(this.schoolName)
    },
    demo() { }
  },
});
</script>

<style>
.demo {
  background-color: orange;
}
</style>
<template>
  <div>
    <h2>学生姓名:{{name}} </h2>
    <h2>学生年龄:{{age}} </h2>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Student',
  data() {
    return {
      name: '张三',
      age: 18
    }
  },
}
</script>

2.2 App.vue

管理所有的其他的组件

<template>
  <div id="app">
    <img alt="Vue logo"
         src="./assets/logo.png">
    <School></School>
    <Student></Student>
  </div>
</template>

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

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

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

2.3 main.js

// 项目入口文件
// 引入 Vue
import Vue from 'vue'
// 引入 App 组件
import App from './App.vue'

// 关闭 Vue 的生产提示
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

2.4 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">
    <!-- 配置网页标题 -->
    <title><%= htmlWebpackPlugin.options.title %>Fan</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>

3. render 函数

默认引入的 Vue 是一个不完整的 Vue(运行版的 vue),缺少模板解析器,此时使用 components 来注册组件会报错
在这里插入图片描述
可以看到有两个解决方案,第一种引入完整的 vue.js,第二种使用 render 函数
在这里插入图片描述

3.1 引入完整版的 vue.js

完整版的 Vue 在 vue/dist 下的 vue.js
在这里插入图片描述
引入完整版的 vue.js

// 引入 Vue
import Vue from 'vue/dist/vue'
// 引入 App 组件
import App from './App.vue'

// 关闭 Vue 的生产提示
Vue.config.productionTip = false

new Vue({
  template: `
    <div>
      <App></App>
    </div>
  `,
  components: {
    App
  }
}).$mount('#app')

此时运行成功
在这里插入图片描述

3.2 使用 render 函数

由于运行版的 vue 没有模板解析器,不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容

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

// 关闭 Vue 的生产提示
Vue.config.productionTip = false

new Vue({
  // render(createElement) {
  //   return createElement(App);
  // },
  // render:h => h(App)
}).$mount('#app')

4. 配置文件

  1. 使用 vue inspect > output.js 可以查看到 Vue 脚手架的默认配置
  2. 使用 vue.config.js 可以对脚手架进行个性化定制,官网配置地址 https://cli.vuejs.org/zh/config/#vue-config-js
    在这里插入图片描述
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false,
  pages: {
    index: {
      // page 的入口,默认 main.js
      entry: 'src/aa.js'
    }
  }
})

5. ref 、props 与 mixin

5.1 ref

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....\</h1> 或 \<School ref="xxx">\</School>
    2. 获取:this.$refs.xxx
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <h1 v-text="msg" ref="title"></h1>
    <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
    <School ref="sch" />
    <Student></Student>
  </div>
</template>

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

export default {
  name: 'App',
  data() {
    return {
      msg: 'MSG'
    }
  },
  methods: {
    showDOM() {
      console.log(this.$refs.title) // 真实DOM元素
      console.log(this.$refs.btn) // 真实DOM元素
      console.log(this.$refs.sch) // School组件的实例对象(vc)
    }
  },
  components: {
    School,
    Student
  }
}
</script>

在这里插入图片描述

5.2 props

5.2.1 概念

让组件接收外部传过来的数据

  1. 传递数据:<Demo name="xxx"/>
  2. 接收数据:
    1. 第一种方式(只接收):props:['name']
    2. 第二种方式(限制类型):props:{name:String}
    3. 第三种方式(限制类型、限制必要性、指定默认值):
      props:{
        	name:{
            	type: String, // 类型
            	required: true, // 必要性
            	default: '老王' // 默认值
        	}
      }
      
  3. props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么需要复制 props 的内容到 data 中一份,然后去修改 data 中的数据

5.2.2 使用

App.vue,使用组件 School.vue 时传入数据

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <!-- age 为数字类型,但传入的是一个字符串,此时需要用 v-vind 数据绑定,将 "" 里的看成一个表达式然后传入 -->
    <School schoolName="Vue" schoolAddress="长沙" :age="18" />
    <Student />
  </div>
</template>

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

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

School.vue,使用 props 接收传进来的数据

<template>
  <!-- 组件的结构 -->
  <div class="demo">
    <h2>{{info}}</h2>
    <h2>学校名称:{{schoolName}}</h2>
    <h2>学校地址:{{schoolAddress}}</h2>
    <h2> {{age + 1}} </h2>
    <button @click="showName">点我提示学校名</button>
  </div>
</template>

<script>
export default ({
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'School',
  data() {
    return {
      info: '原生信息'
    }
  },
  // 接收数据
  // props: ['schoolName', 'schoolAddress', 'age'],
  props: {
    schoolName: {
      type: String, // 类型
      required: true, // 必要性
      default: 'Vue' // 默认值
    },
    schoolAddress: String,
    age: Number
  }
});
</script>

展示将传进来的数据
在这里插入图片描述

5.3 mixin(混入/合)

5.3.1 概念

可以把多个组件共用的配置提取成一个混入对象

  1. 定义混入:
    {
       data(){....},
       methods:{....}
       ....
    }
    
  2. 使用混入:
    全局混入:Vue.mixin(xxx),会给所有都加上混入
    局部混入:mixins:['xxx']

5.3.2 使用

定义混入,mixin.js

export const mixin1 = {
	methods: {
		showName(){
			alert(this.name)
		}
	},
	mounted() {
		console.log('你好啊!')
	},
}
export const mixin2 = {
	data() {
		return {
			x:100,
			y:200
		}
	},
}

使用局部混入,School.vue

<script>
import { mixin1, mixin2 } from '../mixin.js';
export default ({
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'School',
  data() {
    return {
      info: '原生信息'
    }
  },
  // 使用局部混入
  mixins: [mixin1, mixin2],
  props: ['schoolName', 'schoolAddress', 'age'],
});
</script>

在这里插入图片描述
在这里插入图片描述
使用全局混入,main.js

// 引入 Vue
import Vue from 'vue'
// 引入 App 组件
import App from './App.vue'
import { mixin1, mixin2 } from './mixin'

// 关闭 Vue 的生产提示
Vue.config.productionTip = false
// 全局混入
Vue.mixin(mixin1)
Vue.mixin(mixin2)

new Vue({
  render:h => h(App)
}).$mount('#app')

在这里插入图片描述

6. 插件

6.1 概念

用于增强 Vue,包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据

  1. 定义插件:
    对象.install = function (Vue, options) {
    	// 1. 添加全局过滤器
    	Vue.filter(....)
    	// 2. 添加全局指令
    	Vue.directive(....)
    	// 3. 配置全局混入(合)
    	Vue.mixin(....)
    	// 4. 添加实例方法
    	Vue.prototype.$myMethod = function () {...}
    	// 5. 给 Vue 原型上添加一个方法(vm 和 vc 都能用)
    	Vue.prototype.$myProperty = xxxx
    }
    
  2. 使用插件:Vue.use()

6.2 使用

定义一个插件 plugins.js

export default {
	install(Vue, x, y, z){

		console.log(Vue, x, y, z)
		//全局过滤器
		Vue.filter('mySlice',function(value){
			return value.slice(0,  4)
		})

		//定义全局指令
		Vue.directive('fbind',{
			//指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value
			},
			//指令所在元素被插入页面时
			inserted(element){
				element.focus()
			},
			//指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value
			}
		})

		//定义混入
		Vue.mixin({
			data() {
				return {
					x:100,
					y:200
				}
			},
		})

		// 给 Vue原型上添加一个方法(vm 和 vc 都能用)
		Vue.prototype.hello = ()=>{alert('你好啊')}
	}
}

使用插件,main.js

// 引入 Vue
import Vue from 'vue'
// 引入 App 组件
import App from './App.vue'
import plugins from './plugins';

// 关闭 Vue 的生产提示
Vue.config.productionTip = false
// 使用插件,传入三个值
Vue.use(plugins, 1, 2, 3)

new Vue({
  render:h => h(App)
}).$mount('#app')

在这里插入图片描述

7. scoped 样式

让样式在局部生效,防止冲突。写法:<style scoped>

组件中所写的样式最后都是会汇总到一起的,假如存在重名的情况,则会产生样式冲突,后引入的组件的样式会覆盖前面引入的组件中的重名样式。使用 scoped 可以让样式只在该组件作用域内生效

<template></template>
<script></script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

8. 组件化案例

  1. 组件化编码流程:
    1. 拆分静态组件:组件要按照功能点拆分,命名不要与 html 元素冲突
    2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
      1. 一个组件在用:放在组件自身即可
      2. 一些组件在用:放在他们共同的父组件上(状态提升
    3. 实现交互:从绑定事件开始
  2. props 适用于:
    1. 父组件 ===> 子组件 通信
    2. 子组件 ===> 父组件 通信(要求父先给子一个函数)
  3. 使用 v-model 时:v-model 绑定的值不能是 props 传过来的值,因为 props 是不可以修改的
  4. props 传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,但不推荐这样做

在这里插入图片描述

8.1 定义 components

MyHeader.vue

<template>
  <div class="todo-header">
    <input type="text"
           placeholder="请输入你的任务名称,按回车键确认"
           v-model="name"
           @keyup.enter="add" />
  </div>
</template>

<script>
import { nanoid } from 'nanoid'

export default {
  name: 'MyHeader',
  data() {
    return {
      name: ''
    }
  },
  methods: {
    add() {
      // 校验数据,输入不能为空
      if (!this.name.trim()) return alert('输入不能为空');
      // 将用户的输入包装成一个 todo 对象
      const todoObj = { id: nanoid(), name: this.name, done: false };
      // 调用 App 加到 vc 上的方法,添加一个 todo 对象
      this.addTodo(todoObj);
      // 清空输入
      this.name = '';
    }
  },
  props: ['addTodo'],
}
</script>

<style scoped></style>

MyList.vue

<template>
  <ul class="todo-main">
    <!-- 展示 App 传过来的数据, MyItem 组件,传入数据和方法 -->
    <MyItem v-for="todoObj in todoList"
            :key="todoObj.id"
            :todo="todoObj"
            :checkTodo="checkTodo"
            :deleteTodo="deleteTodo" />
  </ul>
</template>

<script>
import MyItem from './MyItem.vue';

export default {
  name: 'MyList',
  // 接收 App 传过来的数据,根据 :xxx='' 的 xxx 名称来接收
  props: ['todoList', 'checkTodo', 'deleteTodo'],
  components: {
    MyItem
  }
}
</script>

<style scoped></style>

MyItem.vue

<template>
  <li>
    <label>
      <input type="checkbox"
             :checked='todo.done'
             @change="handleCheck(todo.id)" />
      <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,修改了props -->
      <!-- <input type="checkbox" v-model="todo.done"/> -->
      <span>{{todo.name}}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template>

<script scoped>
export default {
  name: 'MyItem',
  methods: {
    handleCheck(id) {
      // 调用 App 组件的方法,将 todo.done 值取反
      this.checkTodo(id);
    },
    // 调用 App 组件的方法,删除一个 todo
    handleDelete(id) {
      this.deleteTodo(id);
    }
  },
  props: ['todo', 'checkTodo', 'deleteTodo'],
}
</script>

<style scoped></style>

MyFooter.vue

<template>
  <div class="todo-footer"
       v-show="total">
    <label>
      <!-- <input type="checkbox" :checked="isAll" @change="checkAll" /> -->
      <input type="checkbox" v-model="isAll">
    </label>
    <span>
      <span>已完成{{doneTotal}} </span> / 全部{{total}}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: 'MyFooter',
  computed: {
    total() {
      return this.todos.length
    },
    doneTotal() {
      // return this.todos.reduce((pre, current) => {
      //   return pre + (current.done === true ? 1 : 0);
      // }, 0)
      return this.todos.reduce((pre, current) => pre + (current.done === true ? 1 : 0), 0)
    },
    isAll: {
      get() {
        return this.doneTotal === this.total && this.total > 0
      },
      set(value) {
        this.checkAllTodo(value);
      }
    }
  },
  methods: {
    clearAll() {
      this.clearAllTodo();
    }
  },
  props: ['todos', 'checkAllTodo', 'clearAllTodo']
}
</script>

<style scoped></style>

8.2 App.vue

<template>
  <div id="app">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader :addTodo="addTodo" />
        <!-- 传递 todos 给 MyList,名称为 todoList,要用数据绑定,使 '' 里的内容为表达式 -->
        <MyList :todoList="todos"
                :checkTodo="checkTodo"
                :deleteTodo="deleteTodo" />
        <MyFooter :todos="todos"
                  :checkAllTodo="checkAllTodo"
                  :clearAllTodo="clearAllTodo" />
      </div>
    </div>
  </div>
</template>

<script>
import MyHeader from './components/MyHeader.vue';
import MyFooter from './components/MyFooter.vue';
import MyList from './components/MyList.vue';

export default {
  name: 'App',
  data() {
    return {
      todos: [
        { id: '1001', name: '吃饭', done: true },
        { id: '1002', name: '睡觉', done: false },
        { id: '1003', name: '学习', done: true },
      ]
    }
  },
  methods: {
    // 添加一个 todo
    addTodo(todoObj) {
      this.todos.unshift(todoObj)
    },
    // 勾选或取消勾选一个 todo
    checkTodo(id) {
      this.todos.forEach(todo => {
        if (todo.id === id) todo.done = !todo.done;
      })
    },
    // 删除一个 todo
    deleteTodo(id) {
      if (confirm('确定删除吗?')) {
        this.todos = this.todos.filter(todo => {
          return todo.id !== id;
        })
      }
    },
    // 全选或全不选
    checkAllTodo(done) {
      this.todos.forEach(todo => {
        todo.done = done;
      })
    },
    // 清除所有已经完成的 todo
    clearAllTodo() {
      this.todos = this.todos.filter(todo => !todo.done)
    }
  },
  components: {
    MyHeader,
    MyFooter,
    MyList
  }
}
</script>
<style></style>

8.3 reduce 方法

pre 初始为 0 ,之后为上一次这个函数的返回值,current 为当前对象

this.todos.reduce((pre, current) => {
	console.log('@', pre);
	console.log('@', current);
	return pre + 1;
}, 0)

在这里插入图片描述

9. webStorage 浏览器本地存储

9.1 概念

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

9.2 纯 HTML 使用

<body>
  <div id="root">
    <h2>浏览器本地存储</h2>
    <button onclick="saveData()">点击存储数据</button>
    <button onclick="getData()">点击读取数据</button>
    <button onclick="deleteData()">点击删除数据</button>
    <button onclick="clearData()">点击清空数据</button>
  </div>
  <script type="text/javascript">
    let person = {name: '张三', age: 18};
    function saveData(){
        localStorage.setItem('msg', 'hello');
        localStorage.setItem('key', 'value');
        localStorage.setItem('person', JSON.stringify(person))
    }
    function getData(){
		console.log(localStorage.getItem('msg'))
		console.log(localStorage.getItem('key'))

		const result = localStorage.getItem('person')
		console.log(JSON.parse(result))
	}
	function deleteData(){
		localStorage.removeItem('key')
	}
	function clearData(){
		localStorage.clear()
	}
  </script>
</body>

存储数据
在这里插入图片描述
读取数据
在这里插入图片描述
删除数据
在这里插入图片描述
清空数据
在这里插入图片描述

9.3 Vue 中使用

将组件化案例中写死的 todos 改为从浏览器本地存储中读取,读取不到时就为空数组,然后使用侦听器对 todos 进行监视,每当 todos 发生变化,就把 todos 重新存储到浏览器本地存储

<script>
export default {
  name: 'App',
  data() {
    return {
      // 从浏览器本地存储中读取
      todos: JSON.parse(localStorage.getItem('todos')) || []
    }
  },
  watch: {
  	// 需要开启深度监视,监视里面每一个对象的变化
    todos: {
      deep: true,
      handler(value) {
        localStorage.setItem('todos', JSON.stringify(value));
      }
    }
  },
}
</script>

10. 组件自定义事件

10.1 概念

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件
  2. 使用场景:Fu 是父组件,Zi 是子组件,Zi 想给 Fu 传数据,那么就要在 Fu 中给 Zi 绑定自定义事件(事件的回调也在 Fu 中)
  3. 绑定自定义事件:
    1. 第一种方式,在父组件中:<Demo @fan="test"/><Demo v-on:fan="test"/>fan 表示自定义事件名,test 表示回调函数
    2. 第二种方式,在父组件中:
      <Demo ref="de"/>
      ......
      mounted(){
        // ‘fan’ 表示自定义事件名,this.test 表示回调函数
        this.$refs.de.$on('fan',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用 once 修饰符,或 $once 方法
  4. 触发自定义事件:this.$emit('fan',数据)
  5. 解绑自定义事件 :this.$off('fan')
  6. 组件上也可以绑定原生 DOM 事件,需要使用 native 修饰符
  7. 注意:通过 this.$refs.xxx.$on('fan', 回调函数)绑定自定义事件时,回调函数要么配置在 methods 中,要么用箭头函数,否则 this 指向会出问题(这时 this 代表触发自定义事件的组件 Zi,而并非自定义事件和回调函数所在的组件 Fu)
  8. 销毁当前 Student 组件的实例,销毁后所有 Student 实例的自定义事件全都不奏效,但原生 DOM 事件依然有效

10.2 绑定

App.vue,自定义事件

<template>
  <div id="app">
    <h2>接收到的学生名字:{{studentName}} </h2>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <!-- <School :getSchoolName="getSchoolName" /> -->
    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
    <!-- <Student @fan.once="getStudentName" /> -->

    <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref),要配合 mounted 绑定自定义事件  -->
    <Student ref="stu" />
  </div>
</template>

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

export default {
  name: 'App',
  data() {
    return {
      studentName: ''
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('App收到了学校名:', name)
    },
    getStudentName(name, ...params) {
      console.log('App收到了学生名:', name, params, this)
      this.studentName = name;
    },
  },
  components: { Student },
  mounted() {
    this.$refs.stu.$on('fan', this.getStudentName) // 绑定自定义事件,配合 ref
    // this.$refs.stu.$once('fan', this.getStudentName) // 绑定自定义事件(一次性)
  },
}
</script>
<style></style>

Student.vue,触发自定义事件

<template>
  <div class="student">
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <button @click="sendStudentlName">把学生名给App</button>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Student',
  data() {
    return {
      name: '张三',
      sex: '男',
      number: 0
    }
  },
  methods: {
    sendStudentlName() {
      // 触发 Student组件实例身上的自定义事件 fan
      this.$emit('fan', this.name, 11, 22, 33)
    }
  },
}
</script>

<style scoped>

在这里插入图片描述
点击将子组件的学生名字传给父组件,同时查看回调函数写在 methods 里的 this
在这里插入图片描述
查看回调函数直接写在 this.$refs.xxx.$on(‘组件名’, 回调函数) 里的 this
在这里插入图片描述

10.3 解绑和销毁

销毁当前 Student 组件的实例,销毁后所有 Student 实例的自定义事件全都不奏效,但原生 DOM 事件依然有效

<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 {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Student',
  data() {
    return {
      name: '张三',
      sex: '男',
      number: 0
    }
  },
  methods: {
    sendStudentlName() {
      // 触发 Student组件实例身上的自定义事件 fan
      this.$emit('fan', this.name, 11, 22, 33)
    },
    unbind() {
      this.$off('fan');
      // this.$off(['fan','demo']) // 解绑多个自定义事件
      // this.$off() // 解绑所有的自定义事件
    },
    death() {
      // 销毁了当前Student组件的实例,销毁后所有 Student 实例的自定义事件全都不奏效
      this.$destroy();
    },
    add() {
      console.log('add回调被调用了')
      this.number++
    },
  },
}
</script>

<style scoped>

在这里插入图片描述
使用自定义事件
在这里插入图片描述
解绑自定义事件,此时该自定义事件不奏效
在这里插入图片描述
销毁实例,此时自定义事件全都不奏效,但原生 DOM 事件仍奏效
在这里插入图片描述
在这里插入图片描述

11. 全局事件总线(GlobalEventBus)

11.1 概念

一种组件间通信的方式,适用于任意组件间通信

  1. 安装全局事件总线:
    new Vue({
    	......
    	beforeCreate() {
    		// 安装全局事件总线,$bus就是当前应用的vm
    		Vue.prototype.$bus = this
    	},
    	......
    }) 
    
  2. 使用事件总线:
    1. 接收数据:A组件想接收数据,则在A组件中给 $bus 绑定自定义事件,事件的回调留在A组件自身
      methods(){
          demo(data){......}
      }
      ......
      mounted() {
          this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx', 数据)
    3. 最好在 beforeDestroy 钩子中,用 $off 去解绑当前组件所用到的事件

11.2 使用

Student.vue,向 School.vue 传递数据,使用 this.$bus.$emit(‘xxx’, data) 触发自定义事件

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

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Student',
  data() {
    return {
      name: '张三',
      sex: '男',
    }
  },
  methods: {
    sendStudentName() {
      this.$bus.$emit('hello', this.name);
    }
  },
}
</script>

<style scoped>

School.vue,接收从 Student.vue 传过来的数据,使用 this.$bus.$on(‘xxx’, data => { })) 监听自定义事件进行接收

<template>
  <div class="school">
    <h2>学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
    <h2>收到的学生姓名:{{studentName}} </h2>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'School',
  data() {
    return {
      name: 'Vue',
      address: '北京',
      studentName: ''
    }
  },
  methods: {
    // getName(data) {
    //   console.log('School 组件收到了数据:', data);
    //   this.studentName = data;
    // }
  },
  mounted() {
    // this.$bus.$on('hello', this.getName)
    this.$bus.$on('hello', data => {
      console.log('School 组件收到了数据:', data);
      this.studentName = data;
    });
  },
  // 解绑事件
  beforeDestroy() {
    this.$bus.$off('hello');
  },
}
</script>

<style scoped>

在这里插入图片描述
点击传递数据
在这里插入图片描述

12. 消息订阅与发布(pubsub)

12.1 概念

一种组件间通信的方式,适用于任意组件间通信

  1. 安装 :pubsub:npm install pubsub-j
  2. 引入 :import pubsub from 'pubsub-js'
  3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
    methods(){
    	demo(MsgName, data){......}
    }
    ......
    mounted() {
    	this.pid = pubsub.subscribe('xxx',this.demo) // 订阅消息
    }
    
  4. 提供数据:pubsub.publish('xxx',数据)
  5. 最好在 beforeDestroy 钩子中,用 pubSub.unsubscribe(pid) 去取消订阅

12.2 使用

Student.vue,提供数据(发布),使用 pubsub.publish('xxx', data)

<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 {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Student',
  data() {
    return {
      name: '张三',
      sex: '男',
    }
  },
  methods: {
    sendStudentName() {
      // this.$bus.$emit('hello', this.name);
      pubsub.publish('hello', this.name)
    }
  },
}
</script>

<style scoped>

School.vue,接收数据(订阅),使用 pubsub.subscribe('xxx', (msgName, data) => { })

<template>
  <div class="school">
    <h2>学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
    <h2>收到的学生姓名:{{studentName}} </h2>
  </div>
</template>

<script>
import pubsub from 'pubsub-js';

export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'School',
  data() {
    return {
      name: 'Vue',
      address: '北京',
      studentName: ''
    }
  },
  mounted() {
    // this.$bus.$on('hello', data => {
    //   console.log('School 组件收到了数据:', data);
    //   this.studentName = data;
    // });
    this.pubId = pubsub.subscribe('hello', (msgName, data) => {
      console.log('有人发布了 hello 消息,hello 消息的回调函数执行了', msgName, data);
      this.studentName = data;
    })
  },
  beforeDestroy() {
    // this.$bus.$off('hello');
    pubsub.unsubscribe(this.pubId)
  },
}
</script>

<style scoped>

在这里插入图片描述点击传递数据
在这里插入图片描述

13. nextTick

在下一次 DOM 更新结束后执行其指定的回调,当改变数据后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick 所指定的回调函数中执行,语法:this.$nextTick(回调函数)

修改组件化案例,添加一个编辑的功能,MyItem.vue 添加编辑功能和焦点事件,省略其他代码

<template>
  <li>
    <label>
      <span v-show="!todo.isEdit">{{todo.name}}</span>
      <input v-show="todo.isEdit"
             type="text"
             :value="todo.name"
             @blur="handleBlur(todo, $event)"
             ref="inputName">
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    <button class="btn btn-editor" @click="handleEdit(todo)">编辑</button>
  </li>
</template>

<script scoped>
export default {
  name: 'MyItem',
  methods: {
    // 编辑
    handleEdit(todo) {
      // if(todo.hasOwnProperty('isEdit')) {
      // 点击编辑,假如有 isEdit 属性,则设置为 true,让其显示
      if (Object.prototype.hasOwnProperty.call(todo, 'isEdit')) {
        todo.isEdit = true;
      } else {
        // 否则给其添加一个 isedit 属性,设置为 true
        this.$set(todo, 'isEdit', true);
      }
      // 在显示输入框之后,获取输入框焦点,配合 blur 使用
      this.$nextTick(function () {
        this.$refs.inputName.focus();
      })
    },
    // 失去焦点回调(真正执行修改逻辑)
    handleBlur(todo, event) {
      // 失去焦点,将 isEdit 设置为 false,隐藏输入框,显示<span>
      todo.isEdit = false;
      // 进行空值判断
      if (!event.target.value.trim()) return alert('输入不能为空');
      // 传递修改的值给 App
      this.$bus.$emit('updateTodo', todo.id, event.target.value)
    }
  },
  props: ['todo'],
}
</script>
<style scoped>

App.vue,省略其他代码

<template></template>

<script>
export default {
  name: 'App',
  mounted() {
    // 更新,接收传过来的值
    this.$bus.$on('updateTodo', (id, name) => {
      this.todos.forEach((todo) => {
        if (todo.id === id) todo.name = name;
      })
    })
  },
}
</script>
<style></style>

14. Vue 封装的过度与动画 transition

14.1 概念

在插入、更新或移除 DOM 元素时,在合适的时候给元素添加样式类名
在这里插入图片描述

  1. 准备好样式:
    1. 元素进入的样式(未设置 name 属性,默认为 v-xxx):
      1. v-enter:进入的起点,定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除
      2. v-enter-active:进入过程中,定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数
      3. v-enter-to:进入的终点,定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除
    2. 元素离开的样式:
      1. v-leave:离开的起点,定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除
      2. v-leave-active:离开过程中,定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数
      3. v-leave-to:离开的终点,定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除
  2. 使用 <transition> 包裹要过度的元素,并配置 name 属性:
    <transition name="hello">
       	<h1 v-show="isShow">你好啊!</h1>
    </transition>
    
  3. 若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定 key 值
  4. 自定义过渡的类名
    在这里插入图片描述

14.2 动画效果

先创建动画,然后使用 <transition> ... </transition>,将需要动画过度的 ... 包裹起来,用 .xxx-enter-active 表示进来的时候的效果,.xxx-leave-active 表示离开的时候的效果,xxx 可以在 <transition> 里用 name 属性指定,同时还可以用 apper 设置动画一进来就加载

<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!-- name 表示设置动画样式的名称,appper 表示一进来就加载动画 -->
    <transition name="hello" appear>
      <h1 v-show="isShow">你好啊!</h1>
    </transition>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Test',
  data() {
    return {
      isShow: true
    }
  },
}
</script>

<style scoped>
/* 创建动画 */
@keyframes fan {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0px);
  }
} 
/* 来的时候的效果 */
.hello-enter-active {
  animation: fan 0.5s linear;
}
/* 离开的时候的效果 */
.hello-leave-active {
  animation: fan 0.5s linear reverse;
}

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

14.3 过度效果

<transition></transition> 只能使用一个元素,多个元素使用 <transition-group>

<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">Fan!</h1>
    </transition-group>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  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>

14.4 集成第三方库 Animate.css

  1. 安装:npm install animate.css --save
  2. 引入:import 'animate.css';
  3. name 设置为 animate__animated 前缀加名字,然后使用自定义类名配合使用
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition appear
                name="animate__animated animate__bounce"
                enter-active-class="animate__swing"
                leave-active-class="animate__backOutUp">
      <h1 v-show="isShow">你好啊!</h1>
    </transition>
  </div>
</template>

<script>
import 'animate.css'
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Test',
  data() {
    return {
      isShow: true
    }
  },
}
</script>

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

15. Vue 中的 ajax

15.1 请求方式

发 ajax 请求的方式:

  1. xhr( new XMLHttpRequest() ):xhr.open(); xhr.send();
  2. JQuery:封装(很多 DOM 操作),$.get,$.post
  3. Axios:封装, 体积小
  4. fetch:与 xhr 同级,会包装两层,同时兼容性差
  5. vue-resource :Vue 里的插件库

15.2 解决 ajax 请求跨域问题

  1. cors(Cross-origin resource sharing):跨域资源共享,需要后端加上HTTP 头
  2. jsonp:只能解决 get 请求
  3. 代理服务器

15.2.1 代理方式一

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

devServer:{
	proxy:"http://localhost:5000"
}
  1. 优点:配置简单,请求资源时直接发给前端(8080)即可,会代理到 5000
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时(没有匹配到静态文件的请求),那么该请求会转发给服务器 (优先匹配前端资源)

发送请求

<template>
  <div> <button @click="getStudents">获取学生信息</button> </div>
</template>
<script>
import axios from 'axios' // 引入 axios
export default {
  name: 'App',
  methods: {
    getStudents() {
      axios.get('http://localhost:8080/students').then(
        response => {
          console.log('请求成功了', response.data)
        },
        error => {
          console.log('请求失败了', error.message)
        }
      )
    },
  },
}
</script>

15.2.2 代理方式二

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

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  devServer: {
    proxy: {
      '/fan': { // 匹配所有以 '/fan'开头的请求路径
        target: 'http://localhost:5000', // 代理目标的基础路径
        pathRewriter: {
          // 请求默认会加上拦截的前缀,设置转发请求时去掉前缀 '/fan'
          '^ /fan': ''
        },
        ws: true, // 用于支持 WebSocket
        // 默认值为 true,服务器收到的请求头中的 host 为:localhost:5000,代理目标的地址
        // 设置为 false 时,服务器收到的请求头中的 host 为:localhost:8080,代理服务器的地址
        changeOrigin: true // 用于控制请求头中的 host 值
      },
      '/foo': {
        target: '<other_url>'
      }
    }
  }
})
  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
  2. 缺点:配置略微繁琐,请求资源时必须加前缀

发送请求,url 加上前缀

<template>
  <div> <button @click="getStudents">获取学生信息</button> </div>
</template>
<script>
import axios from 'axios' // 引入 axios
export default {
  name: 'App',
  methods: {
    getStudents() {
      // 加上前缀 /fan,才会被匹配到走代理
      axios.get('/fan/students').then(
        response => {
          console.log('请求成功了', response.data)
        },
        error => {
          console.log('请求失败了', error.message)
        }
      )
    },
  },
}
</script>

15.3 使用

在这里插入图片描述
在这里插入图片描述
安装 Axios:npm install axios --save
单组件引入:import axios from 'axios',直接使用 axios
main.js 全局引入:Vue.prototype.$axios = axios,使用 this.$axios 来使用 Axios

Search.vue,搜索框,将 ajax 请求到的数据传给 List.vue

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

<script>
import axios from 'axios'
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Search',
  data() {
    return {
      keyWord: ''
    }
  },
  methods: {
    searchUsers() {
      // 请求前更新 List 的数据
      // 欢迎词为 true(只有第一次需要欢迎词,后面都不需要),加载中为 false,错误信息为 false,数据为空数组
      this.$bus.$emit('updateListData', { isFirst: true, isLoading: false, errMsg: '', users: [] })
      // 向 GitHub 发送 ajax 请求
      axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
        response => {
          console.log('请求成功了')
          // 请求成功后更新 List 的数据,传递给 Lsit组件,不需要欢迎词,
          this.$bus.$emit('updateListData', { isLoading: false, errMsg: '', users: response.data.items })
        },
        error => {
          // 请求失败后更新 List 的数据
          console.log('请求失败', error.message);
          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">
        <img :src="user.avatar_url" style='width: 100px' />
      </a>
      <p class="card-text">{{user.login}}</p>
    </div>
    <!-- 展示欢迎词 -->
    <h1 v-show="info.isFirst">欢迎使用!</h1>
    <!-- 展示加载中 -->
    <h1 v-show="info.isLoading">加载中....</h1>
    <!-- 展示错误信息 -->
    <h1 v-show="info.errMsg">{{info.errMsg}}</h1>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'List',
  data() {
    return {
      info: {
        isFirst: true,
        isLoading: false,
        errMsg: '',
        users: []
      }
    }
  },
  mounted() {
    this.$bus.$on('updateListData', dataObj => {
      console.log('List 组件收到了数据');
      // 设置后面存在的对象属性会覆盖原来的属性,不存在的则不会覆盖,保留原来的属性
      this.info = { ...this.info, ...dataObj }
    })
  },
}
</script>
<style scoped></style>

15.4 请求携带参数

15.4.1 Get 请求

在这里插入图片描述
三种方式:

onSubmit() {
  // axios.get('http://localhost:8080/hrms/recruitment/test', { params: { recrTitle: '开发' } }).then(
  // axios.get('http://localhost:8080/hrms/recruitment/test?recrTitle=开发').then(
  axios({
	method: 'get',
	url: 'http://localhost:8080/hrms/recruitment/test',
	params: {
	  recrTitle: '开发'
	},
	headers: { token: ''}
  }).then(
    response => {
      this.menuList = response.data
    }
  )
}

参数会带在 URL 后面
在这里插入图片描述

15.4.2 Post 请求

在这里插入图片描述

onSubmit() {
  // axios.post('http://localhost:8080/hrms/recruitment/test', { recrTitle: '开发' }).then(
  // axios.post('http://localhost:8080/hrms/recruitment/test', this.formData}).then(
  axios({
	method: 'post',
	url: 'http://localhost:8080/hrms/recruitment/test',
	data: {
	  recrTitle: '开发'
	},
  }).then(
    response => {
      this.menuList = response.data
    }
  )
}

在这里插入图片描述
在这里插入图片描述

15.4.3 Put 请求

与 Post 请求同

15.4.4 Delete 请求

onSubmit() {
  // axios.delete('http://localhost:8080/hrms/recruitment/test', { data: { recrTitle: '开发' } }).then(
  axios({
	method: 'post',
	url: 'http://localhost:8080/hrms/recruitment/test',
	data: {
	  recrTitle: '开发'
	},
  }).then(
    response => {
      this.menuList = response.data
    }
  )
}

15.5 vue-resource(插件库)

  1. 安装:npm install vue-resource
  2. 引入:import vueResource from 'vue-resource'
  3. 使用:Vue.use(vueResource)
  4. 发送请求时,把 axios 换成 this.$http,其他与axios 同
this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
	response => {
		console.log('请求成功了')
	}
	error => {
		console.log('请求失败了')
	}
)

16. 插槽

让父组件可以向子组件指定位置插入 html 结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件

16.1 默认插槽

Category.vue,定义插槽,子组件

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Category',
  props: ['title']
}
</script>
<style scoped></style>

App.vue,父组件,填入 html 结构到子组件 Category.vue

<template>
  <div class="container">
    <Category title="美食">
      <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg"
           alt="">
    </Category>

    <Category title="游戏">
      <ul>
        <li v-for="(g,index) in games"
            :key="index">{{g}}</li>
      </ul>
    </Category>

    <Category title="电影">
      <video controls
             src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
    </Category>
  </div>
</template>

<script>
import Category from './components/Category'
export default {
  name: 'App',
  components: { Category },
  data() {
    return {
      games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
    }
  },
}
</script>
<style scoped></style>

16.2 具名插槽

Category.vue,定义插槽,使用 slot 给插槽定义名字,假如存在多个插槽时必须定义名字,只有一个插槽可忽略

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    <slot name="center">Center 我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
    <slot name="footer">Footer 我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Category',
  props: ['title']
}
</script>
<style scoped></style>

App.vue,父组件,填入 html 结构到子组件 Category.vue,使用 slot 表示填入对应的插槽

<template>
  <div class="container">
    <Category title="美食">
      <!-- 填入插槽 center -->
      <img slot="center"
           src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg"
           alt="">
      <!-- 插槽 footer 未填入,则显示默认值 -->
    </Category>

    <Category title="游戏">
      <!-- 填入插槽 center -->
      <ul slot="center">
        <li v-for="(g,index) in games"
            :key="index">{{g}}</li>
      </ul>
      <!-- 填入插槽 footer -->
      <div class="foot"
           slot="footer">
        <a href="https://blog.fan223.cn">单机游戏</a>
        <a href="https://blog.fan223.cn">热门游戏</a>
      </div>
    </Category>

    <Category title="电影">
      <video slot="center"
             controls
             src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
      <!-- <template v-slot:footer> -->
      <template slot="footer">
        <div class="foot">
          <a href="https://blog.fan223.cn">经典</a>
          <a href="https://blog.fan223.cn">热门</a>
          <a href="https://blog.fan223.cn">推荐</a>
        </div>
        <h4 align="center"> 欢迎观看!</h4>
      </template>
    </Category>
  </div>
</template>

<script>
import Category from './components/Category'
export default {
  name: 'App',
  components: { Category },
  data() {
    return {
      games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
    }
  },
}
</script>
<style scoped></style>

在这里插入图片描述

16.3 作用域插槽

数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games 数据在 Category 组件中,但使用数据所遍历出来的结构由 App 组件决定)

Category.vue,数据在里面,通过数据绑定,让 App.vue 可以获取到数据,:name=‘xxx’,name 表示绑定的名字,xxx 为表达式

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
    <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    <slot :games="games"
          :msg="hello">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Category',
  props: ['title'],
  data() {
    return {
      games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
      hello: 'hello'
    }
  },
}
</script>
<style scoped></style>

App.vue,使用 scope=‘xxx’ 或 slot-scope=‘xxx’ 来获取数据对象,然后用该数据对象 xxx.name 获取 Category 组件的数据,或者直接 scope={name} 或 slot-scope={name} 直接获取传过来的数据

<template>
  <div class="container">

    <Category title="游戏">
      <!-- <template scope="fan"> -->
      <!-- 先获取数据对象,名字自定义,然后用该数据对象.name 获取数据,name 为数据绑定时的名字 -->
      <template slot-scope="fan">
        <ul>
          <li v-for="(g,index) in fan.games" :key="index">{{g}}</li>
        </ul>
        <h4> {{fan.msg}} </h4>
      </template>
    </Category>

    <Category title="游戏">
      <!-- 直接通过 {name} 获取数据 -->
      <template slot-scope="{games, msg}">
        <ol>
          <li v-for="(g,index) in games" :key="index">{{g}}</li>
        </ol>
        <h4> {{msg}} </h4>
      </template>
    </Category>

    <Category title="游戏">
      <template slot-scope="fan">
        <h4 v-for="(g,index) in fan.games" :key="index">{{g}}</h4>
      </template>
    </Category>
  </div>
</template>

<script>
import Category from './components/Category'
export default {
  name: 'App',
  components: { Category },
}
</script>
<style scoped></style>

在这里插入图片描述

17. Vuex

17.1 概念

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

  1. 多个组件依赖于同一状态
  2. 来自不同组件的行为需要变更同一状态

在这里插入图片描述
在这里插入图片描述

17.2 Vuex 核心概念和 API

17.2.1 state

  1. Vuex 管理的状态对象

  2. 它应该是唯一的

    const state = {
    	xxx: initValue
    }
    

    在这里插入图片描述

17.2.2 actions

  1. 值为一个对象,包含多个响应用户动作的回调函数

  2. 在组件中使用:$store.dispatch('对应的 action 回调名', 参数) 触发 actions 中的回调

  3. 通过 commit() 来触发 mutation 中函数的调用, 间接更新 state

  4. 可以包含异步代码(定时器, ajax 等等)

    const actions = {
    	yyy(context, value){
    	    if (context.state.xxx % 2) {
    	      console.log(context, value);
    	      context.commit('YYY', value)
    	    }
    	},
    }
    
  5. context: 上下文,包含多个可能会用到的方法,如 dispatch,可以分发 Action
    在这里插入图片描述

    const actions = {
    	yyy(context, value){
    		context.dispatch('yyyy', value);
    	},
    	yyyy(context, value){
    		......
    		context.commit('YYY', value)
    	},
    }
    

17.2.3 mutations

  1. 值是一个对象,包含多个直接更新 state 的方法
  2. 在 action 中使用:commit('对应的 mutations 方法名', 参数) 触发 mutations 中的回调
  3. 不能写异步代码、只能单纯的操作 state
    const mutations = {
    	YYY(state, value){
    	    state.xxx += value;
    	},
    }
    

17.2.4 getters

当 state 中的数据需要经过加工后再使用时,使用 getters 加工

  1. 值为一个对象,包含多个用于返回数据的函数
  2. 如何使用?—— $store.getters.xxx
    const getters = {
    	zzz(state){
    	    return state.xxx * 10
    	},
    }
    

17.2.5 modules

  1. 包含多个 module,用于模块化
  2. 一个 module 是一个 store 的配置对象
  3. 与一个组件(包含有共享数据)对应

17.3 搭建 Vuex 环境

17.3.1 安装

vue2 中要使用 vuex3 版本,vuex4 只能在 vue3 中使用

npm install vuex@3

17.3.2 准备 store

在 src 目录下创建 store/index.js
在这里插入图片描述

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

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

// 准备 actions——用于响应组件中的动作
const actions = {}
// 准备 mutations——用于操作数据(state)
const mutations = {}
// 准备state——用于存储数据
const state = {}
// 准备getters——用于将state中的数据进行加工
const getters = {}

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

17.3.3 在 main.js 引入 store

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

// 关闭 Vue 的生产提示
Vue.config.productionTip = false

// 引入 store,假如 store 文件夹下为 index.js,则默认会去找,可以不写
// import store from './store/index'
import store from './store'

new Vue({
  render:h => h(App),
  store, // 使用
  beforeCreate() {
    Vue.prototype.$bus = this;
  },
}).$mount('#app')

使用时用 this.$store 来使用 store

17.4 工作流程/原理

在这里插入图片描述

  1. 按照流程,数据在 state 中,sum = 0
  2. 首先在 Count.vue 中用 this.$store.dispatch('addOdd', this.num) 传递函数名和参数给 store 的 actions
  3. actions 接收到后进行处理(逻辑判断等)后再用 context.commit('ADD', value) 传递建议大写的函数名和参数给 mutations
  4. 在 mutations 中对 state 中的数据进行操作
  5. 假如不用在 actions 进行处理(逻辑判断等),可以直接在 Count.vue 中用 this.$store.commit('REDUCE', this.num) 传递给 mutations

17.5 使用

Count.vue,用于计算的组件,组件中读取 Vuex 中的数据(state):$store.state.sum,读取加工后的数据(getter):$store.getters.bigSum

<template>
  <div>
    <h1>当前求和为:{{$store.state.sum}}</h1>
    <h1>放大 10 倍后的值:{{$store.getters.bigSum}}</h1>
    <select v-model.number="num">
      <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 {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Count',
  data() {
    return {
      num: 1, // 用户选择的数字
    }
  },
  methods: {
    increment() {
      // this.$store.dispatch('add', this.num)
      this.$store.commit('ADD', this.num) // 直接 commit 给 mutations
    },
    decrement() {
      // this.$store.dispatch('reduce', this.num)
      this.$store.commit('REDUCE', this.num) // 直接 commit 给 mutations
    },
    incrementOdd() {
      // 先 dispatch给 actions 进行处理,然后再 commit 给 mutations
      this.$store.dispatch('addOdd', this.num)
    },
    incrementWait() {
      this.$store.dispatch('addWait', this.num)
    },
  },
}
</script>
<style lang="css"></style>

store,即 src/store/index.js

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

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

// 准备 actions——用于响应组件中的动作
const actions = {
  // add(context, value) {
  //   context.commit('ADD', value);
  // },
  // reduce(context, value){
  //   context.commit('REDUCE', value)
  // },
  addOdd(context, value){
    if (context.state.sum % 2) {
      context.commit('ADD', value)
    }
  },
  addWait(context, value){
    setTimeout(() => {
      context.commit('ADD', value)
    }, 500);
  }
}
// 准备 mutations——用于操作数据(state)
const mutations = {
  ADD(state, value){
    state.sum += value;
  },
  REDUCE(state, value){
    state.sum -= value;
  },
}
// 准备state——用于存储数据
const state = {
  sum: 0 // 和
}
// 准备getters——用于将state中的数据进行加工
const getters = {
	bigSum(state){
		return state.sum*10
	}
}

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

在这里插入图片描述
在这里插入图片描述

17.6 map 方法

  1. mapState方法:用于帮助我们映射 state 中的数据为计算属性
    computed: {
    	// ...Obj,将 Obj 里的每一组 key:value 展开放到该位置
    	// 借助 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:'addOdd',incrementWait:'addWait'})
    
    	// 靠 mapActions 生成:incrementOdd、incrementWait(数组形式),名字相同可简写
    	...mapActions(['addOdd','addWait'])
    }
    
  4. mapMutations方法:用于帮助我们生成与 mutations 对话的方法,即:包含 $store.commit(xxx)的函数
    methods:{
    	// 靠 mapActions 生成:increment、decrement(对象形式)
    	...mapMutations({increment:'ADD',decrement:'REDUCE'}),
    
    	// 靠 mapMutations 生成:JIA、JIAN(对象形式),名字相同可简写
    	...mapMutations(['ADD','REDUCE']),
    }
    

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

<template>
  <div>
    <h1>当前求和为:{{sum}}</h1>
    <h1>放大 10 倍后的值:{{$store.getters.bigSum}}</h1>
    <h1>学校:{{school}}</h1>
    <h1>学科:{{subject}}</h1>
    <select v-model.number="num">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <!-- 需要传参 -->
    <button @click="increment(num)">+</button>
    <button @click="decrement(num)">-</button>
    <button @click="incrementOdd(num)">当前求和为奇数再加</button>
    <button @click="incrementWait(num)">等一等再加</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'

export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Count',
  data() {
    return {
      num: 1, // 用户选择的数字
    }
  },
  methods: {
    ...mapMutations({ increment: 'ADD', decrement: 'REDUCE' }),

    ...mapActions({ incrementOdd: 'addOdd', incrementWait: 'addWait' })
  },
  computed: {
    ...mapState(['sum', 'school', 'subject']),

    ...mapGetters(['bigSum'])
  }
}
</script>
<style lang="css"></style>

在这里插入图片描述

17.7 模块化+命名空间

修改 store

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
  }
})
  1. 开启命名空间后,组件中读取 state 数据
    // 方式一:自己直接读取
    this.$store.state.personAbout.list
    // 方式二:借助 mapState读取,在前面加上模块的名字
    ...mapState('countAbout', ['sum','school','subject']),
    
  2. 开启命名空间后,组件中读取 getters 数据
    // 方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    // 方式二:借助 mapGetters 读取
    ...mapGetters('countAbout', ['bigSum'])
    
  3. 开启命名空间后,组件中调用 dispatch
    // 方式一:自己直接 dispatch
    this.$store.dispatch('personAbout/addPersonWang', person)
    // 方式二:借助 mapActions
    ...mapActions('countAbout', {incrementOdd:'addOdd',incrementWait:'addWait'})
    
  4. 开启命名空间后,组件中调用commit
     // 方式一:自己直接 commit
    this.$store.commit('personAbout/ADD_PERSON', person)
    // 方式二:借助 mapMutations
    ...mapMutations('countAbout', {increment:'ADD',decrement:'REDUCE'}),
    

18. vue-router 路由

一个路由(route)就是一组映射关系(key - value),key 为路径, value 可能是 function 或 componen,多个路由需要路由器(router)进行管理。vue-router 是 vue 的一个插件库,专门用来实现 SPA 应用

18.1 SPA

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

18.2 路由分类

  1. 后端路由:
    1. 理解:value 是 function, 用于处理客户端提交的请求
    2. 工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数来处理请求, 返回响应数据
  2. 前端路由:
    1. 理解:value 是 component,用于展示页面内容
    2. 工作过程:当浏览器的路径改变时, 对应的组件就会显示

18.3 基础使用

  1. 安装:npm install vue-router@3,当前 vue-router 默认版本为 vue-router4,用在 Vue3 中,Vue2 只能用 vue-router3,安装时需要指定版本
  2. 创建 router 的组件,路由组件通常存放在 src/pages 文件夹,一般组件通常存放在 src/components 文件夹
    在这里插入图片描述
  3. 创建路由器,在 src 目录下创建 router/index.js
    在这里插入图片描述
    // 该文件专门用于创建整个应用的路由器
    // 引入VueRouter
    import VueRouter from 'vue-router'
    
    // 引入路由组件
    import Home from '../pages/Home.vue'
    import About from '../pages/About.vue'
    
    
    // 创建 router 实例对象(路由器),去管理一组一组的路由规则,并暴露出去
    export default new VueRouter({
      // 路由配置
      routes: [
        {
          path: '/about',
          component: About
        },
        {
          path: '/home',
          component: Home
        },
      ]
    })
    
  4. main.js,引入 vue-router,使用该插件
    // 引入 Vue
    import Vue from 'vue'
    // 引入 App 组件
    import App from './App.vue'
    // 引入 VueRouter
    import VueRouter from 'vue-router'
    // 引入路由器
    import router from './router/index.js'
    
    // 关闭 Vue 的生产提示
    Vue.config.productionTip = false
    
    // 应用插件
    Vue.use(VueRouter)
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	router:router
    })
    
  5. App.vue,使用 <router-link to="/xxx"> 标签实现路由的切换,to 表示路由路径,使用 <router-view> 标签指定组件的呈现位置
    <template>
      <div>
    	<h2>Vue Router Demo</h2>
        <div>
    		<!-- 原始 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 to="/about">About</router-link>
    		<router-link to="/home">Home</router-link>
    	</div>
    	<div>
    		<!-- 指定组件的呈现位置 -->
    		<router-view></router-view>
    	</div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
    }
    </script>
    

点击页面中的导航链接不会刷新页面,只会做页面的局部更新
在这里插入图片描述
在这里插入图片描述

  1. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
  2. 每个组件都有自己的 $route 属性,里面存储着自己的路由信息
  3. 整个应用只有一个 router,可以通过组件的 $router 属性获取到

18.4 多级(嵌套)路由

  1. 再准备两个路由组件 Message.vue 和 News.vue
    在这里插入图片描述

  2. 在路由器里配置路由规则,src/router/index.js,使用 children 属性来配置多级路由

    // 该文件专门用于创建整个应用的路由器
    // 引入VueRouter
    import VueRouter from 'vue-router'
    
    // 引入路由组件
    import Home from '../pages/Home.vue'
    import About from '../pages/About.vue'
    import News from '../pages/News.vue'
    import Message from '../pages/Message.vue'
    
    
    // 创建一个路由器
    export default new VueRouter({
      // 路由配置
      routes: [
        {
          path: '/about',
          component: About
        },
        {
          path: '/home', // 默认为一级路由
          component: Home,
          children: [ // children 配置二级路由
            {
              path: 'news', // 此处前面不要加 /,如一定不要写:/news
              component: News,
            },
            {
              path: 'message',
              component: Message },
          ]
        },
      ]
    })
    
  3. 在 Home.vue 路由组件添加子路由

    <template>
      <div>
        <h2>Home组件内容</h2>
        <div> <ul class="nav nav-tabs">
            <li>
              <!-- 跳转(要写完整路径)-->
              <router-link to="/home/news">News</router-link>
            </li>
            <li>
              <router-link to="/home/message">Message</router-link>
            </li> </ul>
          <router-view></router-view>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      // eslint-disable-next-line vue/multi-word-component-names
      name: 'Home'
    }
    </script>
    

在这里插入图片描述
在这里插入图片描述

18.5 路由传参

18.5.1 路由的query参数

  1. 传递参数
    <template>
      <div>
        <ul>
          <li v-for="message in messageList" :key="message.id">
            <!-- 跳转路由并携带 query 参数,to 的字符串写法 -->
            <!-- <router-link :to="`/home/message/detail?id=${message.id}&title=${message.title}`"> {{ message.title }} </router-link> -->
    
            <!-- 跳转路由并携带 query 参数,to 的对象写法 -->
            <router-link :to="{
                path:'/home/message/detail',
                query:{
                  id: message.id,
                  title: message.title
                }
              }"> {{message.title}} </router-link>
          </li>
        </ul>
        <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      // eslint-disable-next-line vue/multi-word-component-names
      name: 'Message',
      data() {
        return {
          messageList: [
            { id: '001', title: 'message001' },
            { id: '002', title: 'message002' },
            { id: '003', title: 'message003' },
          ]
        }
      },
    }
    </script>
    
  2. 接收参数
    <template>
      <ul>
      	<!-- 接收参数 -->
        <li>消息编号:{{$route.query.id}}</li>
        <li>消息标题:{{$route.query.title}} </li>
      </ul>
    </template>
    
    <script>
    export default {
      // eslint-disable-next-line vue/multi-word-component-names
      name: 'Detail',
    }
    </script>
    

18.5.2 命名路由

在路由器里(即 src/router/index.js)使用 name 属性给路由命名

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

// 创建一个路由器
export default new VueRouter({
  // 路由配置
  routes: [
    { path: '/about', component: About },
    { path: '/home', component: Home, children: [
        { path: 'news', component: News, },
        { path: 'message', component: Message,
          children: [
            {
              name: 'detail', // 给路由命名
              path: 'detail',
              component: Detail,
            }
          ]
        },
      ]
    },
  ]
})

简化路由的跳转

<template>
  <div>
    <ul>
      <li v-for="message in messageList" :key="message.id">
        <!-- 跳转路由并携带query参数,to的对象写法 -->
        <router-link :to="{
            name: 'detail',
            <!-- path:'/home/message/detail', -->
            query:{
              id: message.id,
              title: message.title
            }
          }"> {{message.title}} </router-link>
      </li>
    </ul>
    <router-view></router-view>
  </div>
</template>

18.5.2 路由的 params 参数

路由携带 params 参数时,若使用 to 的对象写法,则不能使用 path 配置项,必须使用命名路由 name 配置

  1. 配置路由,声明接收 params 参数
    // 该文件专门用于创建整个应用的路由器
    import VueRouter from 'vue-router'
    // 引入组件
    ......
    
    // 创建一个路由器
    export default new VueRouter({
      // 路由配置
      routes: [
        { path: '/about', component: About },
        { path: '/home', component: Home, children: [
            { path: 'news', component: News, },
            { path: 'message', component: Message,
              children: [
                {
                  name: 'detail', // 给路由命名
                  path:'detail/:id/:title', // 使用占位符声明接收 params 参数
                  component: Detail,
                }
              ]
            },
          ]
        },
      ]
    })
    
  2. 传递参数
    <template>
      <div>
        <ul>
          <li v-for="message in messageList" :key="message.id">
            <!-- 跳转并携带 params 参数,to 的字符串写法 -->
    		<!-- <router-link :to="/home/message/detail/${message.id}/${message.title}">{{message.title}}</router-link> -->
    
            <!-- 跳转路由并携带 params 参数,to 的对象写法 -->
            <router-link :to="{
                name: 'detail',
                params:{
                  id: message.id,
                  title: message.title
                }
              }"> {{message.title}} </router-link>
          </li>
        </ul>
        <router-view></router-view>
      </div>
    </template>
    
  3. 接收参数
    <template>
      <ul>
        <li>消息编号:{{$route.params.id}}</li>
        <li>消息标题:{{$route.params.title}} </li>
      </ul>
    </template>
    

在这里插入图片描述
在这里插入图片描述

18.6 路由的 props 配置

在路由器里(即 src/router/index.js)使用 name 属性给路由命名

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

// 创建一个路由器
export default new VueRouter({
  // 路由配置
  routes: [
    { path: '/about', component: About },
    { path: '/home', component: Home, children: [
        { path: 'news', component: News, },
        { path: 'message', component: Message,
          children: [
            {
              name: 'detail', // 给路由命名
              path: 'detail/:id/:title',
              component: Detail,
              // 第一种写法:props 值为对象,该对象中所有的 key-value 的组合最终都会通过 props 传给 Detail 组件
              // props:{a:900}

              // 第二种写法:props 值为布尔值,布尔值为 true,则把路由收到的所有 params 参数通过 props 传给 Detail 组件
              // props:true

              //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
              props($route){ // 或者写为 route、{route:{id, title}}
                return {
                  id: $route.params.id,
                  title: $route.params.title
                }
              }
            }
          ]
        },
      ]
    },
  ]
})

让路由组件更方便的收到参数

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

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Detail',
  props: ['id', 'title'],
}
</script>

18.7 <router-link>的 replace 属性

控制路由跳转时操作浏览器历史记录的模式

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

18.8 编程式路由导航

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

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

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Message',
  methods: {
    pushShow(message) {
      this.$router.push({
        name: 'detail',
        params: {
          id: message.id,
          title: message.title
        }
      })
    }
  }
}
</script>

使用 push 和 replace 方法来进行跳转操作,可用于 button 按钮
在这里插入图片描述
控制前进和后退

this.$router.forward() // 前进
this.$router.back() // 后退
this.$router.go() // 可前进也可后退,传入参数为前进或后退的步数

18.9 缓存路由组件

让不展示的路由组件保持挂载,不被销毁,在组件的呈现位置包上 <keep-alive> 标签,include 属性表示组件名

<template>
  <div>
    <h2>Home组件内容</h2>
    <div>
      <ul class="nav nav-tabs">
        <li>
          <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>
      <!-- 缓存多个路由组件 -->
	  <!-- <keep-alive :include="['News','Message']"> -->
      <!-- 缓存一个路由组件 -->
	  <keep-alive include="News"> 
		<router-view></router-view>
	  </keep-alive>
    </div>
  </div>
</template>

切换其他组件再回来,内容还在
在这里插入图片描述

18.10 路由组件的生命周期钩子

路由组件所独有的两个钩子,用于捕获路由组件的激活状态

  1. activated :路由组件被激活时触发
  2. deactivated :路由组件非激活时触发
<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>

18.11 路由(导航)守卫

对路由进行权限控制,分为全局守卫、独享守卫、组件内守卫

18.11.1 meta 路由元信息

将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的 meta 属性来实现,并且它可以在路由地址和导航守卫上都被访问到:

const router =  new VueRouter({
	routes:[
		{
			name:'guanyu',
			path:'/about',
			component:About,
			meta:{isAuth:true, title:'关于'}
		},
	]
})

18.11.2 全局守卫

在路由器 src/router/index.js 定义全局守卫,分为全局前置守卫和后置守卫,可以通过路由元信息存储的内容来进行验证

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

// 创建并暴露一个路由器
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:true
						}
					]
				}
			]
		}
	]
})

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

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

export default router

参数 To 和 From
在这里插入图片描述
点击 News,进行权限判断
在这里插入图片描述

18.11.3 独享守卫

只有前置守卫,没有后置

const router =  new VueRouter({
	routes:[
		{
			name:'zhuye', path:'/home', component:Home, meta:{title:'主页'},
			children:[
				{
					name:'xinwen',
					path:'news',
					component:News,
					meta:{isAuth:true,title:'新闻'},
					beforeEnter: (to, from, next) => {
						if(to.meta.isAuth){ // 判断是否需要鉴权
							if(localStorage.getItem('school')==='vue'){
								next()
							}else{
								alert('学校名不对,无权限查看!')
							}
						}else{
							next()
						}
					}
				},
			]
		}
	]
})
// 全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
	document.title = to.meta.title || '系统'
})
export default router

18.11.4 组件内守卫

<script>
	export default {
		name:'About',
		// 通过路由规则,进入该组件时被调用
		beforeRouteEnter (to, from, next) {
			if(to.meta.isAuth){ //判断是否需要鉴权
				if(localStorage.getItem('school')==='vue'){
					next()
				}else{
					alert('学校名不对,无权限查看!')
				}
			}else{
				next()
			}
		},
		// 通过路由规则,离开该组件时被调用
		beforeRouteLeave (to, from, next) {
			next()
		}
	}
</script>

18.12 路由器的两种工作模式

// 创建并暴露一个路由器
export default new VueRouter({
  // mode: 'history'
  mode: 'hash',
	routes:[
		{
			name:'guanyu',
			path:'/about',
			component:About,
			meta:{title:'关于'}
		},
	]
})
  1. hash 模式:
    1. # 及其后面的内容就是 hash 值,但地址中永远带着 # 号,不美观
    2. hash 值不会包含在 HTTP 请求中,即:hash 值不会带给服务器,兼容性较好
    3. 若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法
  2. history 模式:
    1. 地址干净,美观
    2. 兼容性和 hash 模式相比略差
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题

19. Vue UI 组件库

19.1 移动端常用 UI 组件库

  1. Vanthttps://youzan.github.io/vant
  2. Cube UIhttps://didi.github.io/cube-ui
  3. Mint UIhttp://mint-ui.github.io

19.2 PC 端常用 UI 组件库

  1. Element UIhttps://element.eleme.cn
  2. IView UIhttps://www.iviewui.com

19.3 Element UI

详见:https://blog.csdn.net/ACE_U_005A/article/details/124464590

posted @ 2022-12-21 13:55  凡223  阅读(29)  评论(0编辑  收藏  举报