vue组件化知识

vue组件化知识

1 组件

1.1 介绍

这是vue提出的一个独有的概念,组件化编程,组件是什么呢,就是一个包含了代码和资源的集合。
里面有html,css,js,mp3,ttf等文件
一个页面里面只会有一个vue实例,其它的都是用组件,比如header组件,body组件,footer组件,而header组件里可能还有nav组件,search组件,这样的话,你那里需要再用到这个结构,就直接使用这个组件即可。注意这不仅是其它页面可以用这个页面的组件,就是一个页面里面有多个相同结构的也可以直接使用,这就使得代码复用性提高

1.2 非单文件组件

1.2.1创建一个组件

const school = Vue.extend({
    template: `
        <div>
            <p>{{name}}</p>
            <p>{{address}}</p>
        </div>
    `,
    data(){
        return {
            name: "xxx",
            address: "中国"
        }
    }
});

这个template就是模板的意思,组件是一个集合,当然有最基本的html,这个html就是写在template里面,而且只能有一个祖宗元素,就比如把这个template的div去掉是不行的
Vue.extend就是一个创建组件的方法,里面的配置项和之前写的vue实例是基本一致的,但是不能写el,因为一个组件如果和一个元素绑定在一起了,那还怎么复用,在哪里使用是要看你在哪里调用
还有一个需要注意的是,data只能写成函数式,如果不写函数式会报错,而不让写函数式的原因就是如果写成对象式,那么你复用这个组件的时候,data里的数据是在同一片内存里的,一改全改,这显然不符合我们的需求,所以要用函数式,每用一次,就给你一个全新的对象,而不是别人在用的对象

1.2.2 注册一个组件

有两种注册方式,第一种局部

new Vue({
	components:{
		school,
	}
})

全局

Vue.component("student", student);

1.2.3 使用

这个称为组件标签

<div id = "root">
    <school></school>
    <student></student>
</div>

1.2.4 注意点

注册组件的名字在开发者工具里面是大驼峰写法,无论你写的是大写还是小写
多个字母的名字,vue推荐使用大驼峰(需要在脚手架环境下),或者用a-b的方式
使用组件的时候可以使用单标签,需要在脚手架环境下
创建脚手架的时候可以自定义开发者工具里显示的名字,用name配置项,而且这个创建可以简写
const a ={
name: “fdsj”,
template:``
}

1.3 组件的嵌套

在这里插入图片描述
要实现一个这样的结果
下面是html结构,一般的标准里会有一个组件是一人之下,万人之上,vm下面只有一个组件,就是app,而app控制着所有的组件

<div id = "root">
        <app></app>
</div>

vue实例里的组件配置只写了儿子辈,而没有更下一层的

new Vue({
    el: "#root",
    components:{
        app,
    }
});

app的下一层里面有testa和testb,所以我们要与vue实例一样直接使用components配置好,然后在template里面使用

const app = {
    template: `
        <div>
            <testa></testa>
            <testb></testb> 
        </div>
    `,
    components: {
        testa,
        testb
    }
}

以此类推,testa里面的components里写的就是testc

1.4 VueComponent

组件的本质其实就是一个构造函数,而我们在使用一个组件的时候,vue帮我实现了new的实例化操作
而我们在使用vue.extend的时候,这个函数就会返回一个构造函数,而这两个构造函数也不是同一个,因为在extend的内部,实现了一个先用let a = function, return a的结构,这就使得每一次获取的构造函数都不是一个函数
正因为是构造函数,所以用同一个组件多次,这些组件都互不干扰,因为他们都是一个个的vueComponent的实例对象
组件的实例对象和vm的属性是一样的
在控制台输出vm
在这里插入图片描述
组件以数组的形式存在 c h i l d r e n 这个属性里面,而组件的 children这个属性里面,而组件的 children这个属性里面,而组件的children则是这个组件底下的组件
组件里的this指向的是VueComponent实例对象(vc)

1.5 VC的原型链

VueComponent.prototype._proto_ === Vue.prototype
需要先了解原型对象知识才能学会,忘了先去复习一下 js遗漏知识的补充
通过原型链的知识,我们可以知道,vc的_proto_指向的就是VueComponent.prototype,vc的prototype的_proto_指向的应该是object,但是vue内部做了一个处理,就是将vc._proto_指向了vm._proto_就是说在vc上找一个东西,没有在vc上找到,就去VueComponent上找,再找不到就去vm_proto_上去找,而vm的原型对象上放了很多东西,data里的数据, m o u n t , mount, mountwatch都在这。
而这种设计也不会破坏原有的Object的方法,因为可以通过Vue.prototype._proto_去找到

1.6 单文件组件

前置知识是es6的模块化 es新特性学习笔记
首先创建一个文件,用大驼峰的命名方式,比如我这个文件就叫School.vue
然后就可以在里面放组件的东西了

<template>
  
</template>

<script>

</script>

<style>

</style>

结构就是这样的,对应的就是html js和css
加上内容,就是这样的

<template>
  <div id="demo">
    <p>学校名称:{{name}}</p>
    <p>学校地址:{{address}}</p>
    <button @click = "showMsg">点我提示信息</button>
  </div>
</template>

<script>
    const School = {
        name: "School",
        data() {
            return {
                name: "xuexiao",
                address: "dizhi"
            }
        },
        methods: {
            showMsg() {
                alert("快来加入我们吧");
            }
        },
    };
    export default School;
</script>

这个时候我们就用上了es模块化的内容,用暴露,然后我们才能够注册一个组件
我们还可以使用简写形式

export default {
    name: "School",
    data() {
   //...

我们写好了组件之后,就可以写一个Vue实例,这个文件叫main.js

import App from "./App.vue";
new Vue({
    el: "#root",
    template: `<App></App>`,
    components:{App},
})

然后再在html的最后引入这个main.js就可以使用了,但是现在还不能使用,需要在特定的环境下面运行这个文件。这也就是下面要学的脚手架

2 脚手架

2.1 创建脚手架

全名: Command Line Interface(CLI)命令行接口工具
脚手架版本:5,尽量用最新的。
全局配置脚手架: npm install -g @vue/cli有警告不管它
下载慢就使用淘宝镜像: npm config set registry https://registry.npmmirror.com
在cmd中输入vue没报错则成功
cmd中打开到对应的文件目录, 在此目录下创建项目 vue create NAME
在这里插入图片描述
这里是让你选择一个vue的版本,label是es6转es5的工具,eslint是检测语法的
启动项目npm run serve
在创建的时候,就会有一个预设好的项目,当我们启动项目的时候,vue会启动一个服务器,
这个页面就是访问的地址,第一个是本地的,第二个是别人可以访问的,打开后的页面就是下面这样
图
如需要停止这个服务,只需要使用ctrl+c,然后输入y即可

2.2 脚手架分析

安装完毕的结构是这样的
在这里插入图片描述
src下面的不用在意,是一些npm,git等一些配置文件
点开src和public
在这里插入图片描述
public里面是一个图标文件和index.html网页,而src里面则是组件和main.js这个入口文件,还有assets里面放的是用到的图片视频等资源
可以看到App.vue是在components的外面的,而一般的组件就放在components里,main.js
main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

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

第一行是引入vue
第二行引入App.vue
后面用到了一个render,暂时不用理会,这里使用mount将vm挂载在#app上
在index.html里就有一个这样的东西,那么是如何将App里面的内容放到容器里的呢,没有注册组件和使用template,在html也没有使用App标签。就是靠这个render

<div id="app"></div>

打开App.vue,基本结构是一样的,就是那三样,这里没有使用render,而是使用template
在这里插入图片描述
回到之前的问题,为什么使用render,而不是使用template的方式,在这里使用template是行不通的
注意到main.js里的import Vue from 'vue'
这个导入第三方库的时候,我们还没有指明文件类型,而vue也不是一个.vue的文件,在node_modules里找到vue的package.json,有这么一项,"module": "dist/vue.runtime.esm.js",
这个就是说我们在使用模块化导入的时候具体的地址是什么,所以我们在导入vue的时候,就相当于导入了vue.runtime.esm.js。
其实还有很多个版本的vue
在这里插入图片描述
这些都是基于vue.js的简化版本。而我们导入的那个vue.runtime.esm.js的意思,带有runtime就是没有模板解析器,就是不能使用template,所以我们在main.js 里面会报错,说我们的vue没有模板解析器。
而在.vue的文件里面,脚手架本身就有一个模板解析器,所以在.vue的文件里面我们可以使用template
用多个版本的原因,如果用的是完整版的vue,那么在我们完成项目的时候,这个模板解析器会依旧保存下来, 但是这个时候已经解析完了,不需要模板解析器了,所以会浪费空间,虽然也只是100多kb,但既然这个东西不应该出现在这里,那么就可以进行优化。

2.3 render

render可以理解成渲染,没有模板解析器,也就代表没有第三方帮助我们渲染,所以我们要使用这个内置的函数自己进行渲染。

render(createElement){
	return 
}

返回值是需要呈现在页面上的元素,而参数就是一个创建元素的函数,里面的参数可以是组件,也可以是标签和内容
比如

render(createElement){
	return createElement(App);
	//return createElement("h1", "hello world");//标签和内容
}

因为render一般也用不到this,所以我们就直接使用箭头函数进行精简,并且将这个比较长的参数名,换一个短的。这样就出现了默认的那种形式

render: h=>h(App);

2.4 修改默认配置

默认配置就比如脚手架默认将main.js作为入口文件,如果没有这个文件,我们就不可以成功运行项目,还有就是当我们写了一个没有使用的变量的时候,eslint会进行语法检查,然后报错,也不能运行成功,如果项目还在开发的过程中,这个东西就会很烦,所以我们也可以通过修改默认配置暂时关闭语法检查。
vue修改默认配置的方法比较巧妙,不允许开发人员动核心的配置,不会破坏整体的结构,需要写一个vue.config.js的文件,然后把你需要修改的东西写在里面,当vue发现你写了这个文件的时候,就会把同样的东西给替换,这样做就可以保留原本,如果出错删掉这个文件就可以恢复默认。
而且不是所有的配置项都是能够修改的,只有在vue的CLI官网列出来的那些才能够被修改。
比如之前说的修改代码检查

module.exports = defineConfig({
  lintOnSave: false
})

2.5 ref属性

下面是一个App.vue的样例,实现的功能很简单,就是点击按钮输出dom
在这里插入图片描述

<template>
  <div>
    <p id="test">test</p>
    <button @click = "showDom">点击获取上方DOM</button>
    <student/>
  </div>
</template>

<script>
    import Student from "./components/Student.vue";
    export default {
        name: "App",
        components: {
            Student,
        },
        methods: {
            showDom() {
                console.log(document.getElementById("test"));
            }
        }
    }
</script>

使用的是DOM操作,而在vue里提供了ref属性来定位元素

<p ref="test">test</p>
console.log(this.$refs.test);

组件里的this指向的是组件实例对象,带有ref属性的元素,都会被组件实例对象收录,存在this.$refs里面,通过值我们可以定位到这些元素
而ref还能做到一个dom比较难做到的事情,就是定位到子组件的实例对象
比如
``` ``
这两个写法,然后第一个用dom定位得到的是stundent里的dom元素,而得不到组件实例对象

2.6 props配置项

2.6.1 引入

prop的作用就是父组件向子组件传参,组件本身就是为了复用,有的时候只是数据有点不一样,所以我们可以通过传参的方式,把之前的组件拿来用
父组件使用的时候,就像是写属性一样用键值的方式传参

<student name = "iceylia" age = "50"/>

下面有多种形式的方法引入

2.6.2 简单引入

这个是最常见的方式,props是与data同级的配置项,这样写就相当于我们在data里面写了一个属性,props在这里的意思也就是属性

data() {
   return {
    }
},
props:["name", "age"],

引入之后的使用方法与之前无异

2.6.3 类型限制

因为我们在写父组件的时候,使用的是属性传入的方式,而属性的值只能是字符串,所以传入的也只能是字符串,这就不利于我们操作,当然,我们可以在子组件用强转的方式进行转换,但是最优解决方案应该是在传入的时候就是一个数字类型

<student name = "iceylia" :age = "50"/>

只需要像这样在age前加一个:,这是数据绑定,数据绑定会把里面的内容变为一个表达式,也就是说现在的age就是一个数字类型的数据了
类型限定也就是如果你传入的不是一个指定的类型,虽然依旧能够执行,但是会报错。
写法如下

data() {
   return {
    }
},
props:{
	name: String,
	age: Number,	
}

2.6.4 完整引入

data() {
   return {
    }
},
props:{
	name{
		type: String,
		required: true,
	}
	age{
		type: Number,
		required: false,
		default: 15
	}
}

type 类型
required 是否必须
default 默认值

2.6.5 修改props的技巧

props一般是不允许修改的,可能产生莫名其妙的错误,如果非要修改,就就用一个类似中间变量修改就可以了
props的优先级高于data,props会先被解析,也就是说data里的赋值语句可以看成是永远在props后。

data() {
    return {
        tempAge: this.age,
    }
},
props:["name", "age"],

修改中间变量,而不修改传进来的值

2.7 mixin混入

也可以叫混合,官网上叫混入
作用是如果你在多个组件里用到了同一个配置项,那么我们可以用另一个文件把这些配置项放在一起,起到复用的作用
下面是一个与入口文件同级的mixin.js的文件

export const mixin1 = {
    data() {
        return {
            x: 1,
            y: 2,
        }
    },
}
export const mixin2 = {
    mounted() {
        console.log("混入成功");
    }
}

用分别暴露的方式定义了两个对象,里面写的就是vc的配置项
使用的时候先引入再在配置项里写mixin

import {mixin1} from "../mixin.js";
export default {
	//data..
	//props//
	mixins:[mixin1],
}

如果想要在所有的vc和vm里都有这个配置项,可以直接在入口文件main.js里面写上全局混入

import {mixin2} from "./mixin.js";
Vue.mixin(mixin2);

2.8 plugins插件

插件的作用就是把之前写的全局过滤器,自定义指令,全局混入和定义的Vue原型对象的方法都放到插件里,然后在入口文件引入和引入就可以使用了
比如我在main.js 同级下写了一个plugins.js的文件,插件的本质是一个对象,有install函数的对象就是插件,参数是Vue构造函数

export default {
    install(Vue){
        Vue.prototype.hello = ()=>{console.log("hello")};
        Vue.minin({
			console.log("混入");
		});
    }
}

使用

import plugins from "./plugins.js";
Vue.use(plugins);

然后在组件里尝试使用hello函数

<button @click = "hello">hello</button>

2.9 scoped样式

组件里的style最终都会汇总,这就很可能命名冲突,所以我们需要一个局部的样式,

<style scoped>
  div{
    background-color: grey;
  }
</style>

当然在App.vue里面写的一般是全局的,不需要加scoped
scoped局部样式实现原理
在这里插入图片描述
在这里插入图片描述
自动生成了一个data-v的属性,然后在你的局部样式里面都加了一个属性选择器,只有拥有这个属性的元素才会被应用到这一个样式

3 todolist案例

3.1 准备

在这里插入图片描述
实现一个这样的功能,能够实现增删数据

3.1.1 组件的划分

TodoHeader
TodoList
TodoItem
TodoFooter
其中item是list的子组件

3.1.2 数据的使用

多个数据js中只有两种方式存储,一种是数组一种是对象,这里使用的是数组,然后数组里的元素是对象。里面有id,name和completed
可以暂时存放在list组件中

todos: [
  {id: "001", name: "艾希", completed: true},
  {id: "002", name: "去月球", completed: false},
  {id: "003", name: "王国", completed: true},
]
<MyItem v-for="todo in todos" :key="todo.id" :todo="todo"/>

v-for的指令也可以这样写,:todo是props,向子组件item传参
而在item里,单选框的默认值可以根据completed完成,由于checked是一个动态的属性,所以我们在写的时候需要使用v-bind,如果不写:,那这个todo.completed就是字符串,而不是表达式

<input type="checkbox" :checked="todo.completed"/> 

3.2 添加数据

实现输入框功能
想要添加数据,就需要做到接收,处理和传输
数据有id,name和completed,是一个对象,completed可以默认为false

3.2.1 生成id

数据有id,name和completed,id我们可以借助一个uuid库的简化版nanoid,uuid是可以生成全球唯一的id,但是体积太大,nanoid则十分轻量

npm i nanoid

nanoid使用的是分别暴露,nanoid是一个函数,直接使用就可以生成一个id,nanoid();

import {nanoid} from "nanoid";
console.log(nanoid());

3.2.2 获取输入内容

使用事件监听,keyup.enter

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

获取内容有两种方式,一种是使用dom操作

add(event) {
    console.log(event.target.value);
}

第二种是使用v-model
data中有title这个数据,v-model绑定input,然后读取title就可以读取input.value
这样就获取了全部的数据,组成一个对象即可

3.2.3 传递数据

兄弟组件直接如何传递数据还没有学,现在的只是做不到,只能使用最基础的去实现
就是将todos的数据放在app组件中,向app传递数据,app再向list和item传
app向list和item传可以使用props,父传子
但是header收集的数据如何传到app是一个问题
这里使用的是一个方法,在app定义一个方法,然后传到header中去使用,header中使用这个方法传的参数,可以在app中接收到
app

data() {
    return {
        todos: [
            {id: "001", name: "艾希", completed: true},
            {id: "002", name: "去月球", completed: false},
            {id: "003", name: "王国", completed: true},
        ]
    }
},
methods: {
    addTodo(todo){
        this.todos.unshift(todo);
    }
},
<MyHeader :addTodo = "addTodo"/>

header

add() {
   if(this.title.trim() === ""){
        alert("请输入正确的格式");
        return;
    }
    this.addTodo({id: nanoid(), name: this.title, completed: false})
    this.title = "";
}

3.4 勾选

实现勾选时改变数据

  1. 接收 change事件监听,获取this.todo.id
  2. 处理 forEach遍历数组,找到数据completed取反
  3. 传输 app定义函数,用props接收,将id作为函数参数传递

item中这样写也能实现效果,v-model绑定checked,双向数据绑定,勾选则改变todo,completed

<input type="checkbox" v-model="todo.completed"/>  

不推荐,改变了props值,之前修改props值会报错,这里不会,因为vue只能检测表层。修改的是对象中的数据,没有修改对象地址,算是漏洞吧。虽然简便,但不推荐使用。

3.5 删除

在这里插入图片描述

删除的实现方法差不多一样,先在app定义一个deteleTodo的方法
传给item,点击就调用deleteTodo,传id参数,删除参数
deleteTodo具体方法如下

this.todos = this.todos.filter(item => item.id !== id);

3.6 底部统计

在这里插入图片描述

把todos传到footer,用插值语法计算全部,用计算属性统计完成

<span>已完成{{completedSum}}</span> / 全部{{todos.length}}
computed: {
  completedSum(){
    return this.todos.reduce((pre, curr)=>pre + (curr.completed ? 1 : 0), 0);
  }
}

3.7 底部交互

3.7.1 需求

三个部分
在这里插入图片描述
当todos全部勾选的时候,左下角会被自动选上,少了一个就没了。
选上左下角的按钮的时候也能全选
在这里插入图片描述
当没有代办事项的时候底部消失
在这里插入图片描述
一键清除已完成

3.7.2 全选

判断已完成和全部的数量是否相等即可
上一节已经获取两个数据

<input type="checkbox" :checked="isAll"/>

isAll是计算属性,判断是否相等

想要全选就要在app组件加方法来影响数据

checkAllTodo(completedStatus){
  this.todos.forEach(item => {
    item.completed = completedStatus;
  })
}

传到footer,此时有两种解决方案
第一种用事件监听,监听这个按键,用event.target.checked获取checked属性
第二种巧妙一些,用v-model绑定isAll,当页面修改的时候,就会修改isAll,isAll是计算属性,需要写setter

<input type="checkbox" v-mode="isAll"/>
isAll: {
  get(){
    return this.totalNum === this.completedSum;
  },
  set(value){
    this.checkAllTodo(value);
  }
},

3.7.3 隐藏底部

在footer最外面的div加上v-show="todos.length“

3.7.4 清除已完成

app定义事件,footer事件监听,调用事件

clearCompletedTodos(){
  this.todos = this.todos.filter(item => {
    return !item.completed;
  })
}

3.8 本地存储

app里的todos要先本地里读取

todos: JSON.parse(localStorage.getItem("todos")) || [],

JSON.parse是因为对象在存储中都是json形式的字符串,所以我们存储的时候也要是json形式,如果是不用json直接存储的形式
在这里插入图片描述

[object Object]

|| []的原因,当初次使用这个网站,还没有todos这个数据的时候,直接读取的结果为null,之前用到的todos.length就会报错,所以在没有的时候就读取空数组
然后是数据的存储
在删除,勾选,添加的时候都需要修改数据,但没必要在每个情况下都写一个函数,只需要使用watch监视todos,在todos修改的时候重新读取即可

watch:{
  todos:{
    deep: true,
    handler(value){
      localStorage.setItem("todos", JSON.stringfy(value));
    }
  }
}

这里用到了深度监视,在勾选的时候,只修改了completed,所以需要深度监视来记录勾选的状态

4 自定义事件

自定义事件是一种组件间通信方式,适用子传父
父组件使用子组件当标签,那如果要实现子组件触发一个绑定事件的时候,调用的不是子组件自身的方法,而是父组件的方法
有一种方法当然是用props实现,把方法传过去。
第二种就是用自定义事件,在子组件标签写个自定义的事件如<clid @custom="function1"/>
cutom就是一个自定义的事件,那么如何触发这个自定义事件,就是在子组件触发指定的绑定事件的时候加一个触发某事件的api

4.1 绑定

定义了一个app组件下面有一个Student组件

<Student @click1="demo1"/>

student有一个click1的事件监听,demo1是定义在app中的事件
而在Student组件中

<button @click="function1">按键1</button>

有一个按键,绑定了内置事件,当这个按键被按下去的时候就能触发app的click1事件

function1(){
  	this.$emit("click1");
},

this. e m i t ( " c l i c k 1 " ) 的意思就是触发这个组件实例对象上的 c l i c k 1 , e m i t 本身是发射的意思,在这里可以理解成触发如果要向父组件的事件传递参数,只需要写在 e m i t 里面就可以了 ‘ ‘ t h i s . emit("click1")的意思就是触发这个组件实例对象上的click1,emit本身是发射的意思,在这里可以理解成触发 如果要向父组件的事件传递参数,只需要写在emit里面就可以了``this. emit("click1")的意思就是触发这个组件实例对象上的click1emit本身是发射的意思,在这里可以理解成触发如果要向父组件的事件传递参数,只需要写在emit里面就可以了‘‘this.emit(“click1”, this.name)``
还有一种绑定自定义事件的方法,第一种是直接在标签上用vue指令绑定自定义事件,第二种是通过拿到组件实例,在组件实例上添加自定义事件

<Student @click1="demo1" ref="student"/>
methods: {
   demo1(){
       console.log("success");
   },
   demo2(name){
       console.log(name);
   }
},
mounted() {
  	this.$refs.student.$on("click2", this.demo2);
},

主要的思路就是通过refs属性得到student的实例对象,然后在mounted生命周期函数中用$on添加事件,最后在student组件中用emit触发就可以
这种方法虽然麻烦,但是胜在灵活

4.2 解绑

自定义事件用不上了就需要解绑
和emit一样,放在子组件里面,放在emit下面一行也行,在某个你想要的时机触发解绑
解绑某一个

this.$off("click1");

解绑多个

this.$off(["click2", "click1"];

解绑所有

this.$off();

在组件被销毁的时候。所有的自定义事件,子组件都没了,当然,原生的事件还是会存在

4.3 注意点

  1. 自定义事件也能用修饰符
  2. 组件标签上要用原生事件,需要加修饰符.native,如@click.native="demo”
  3. 如果用绑定的第二种方法,想要实现.once的效果,this,$refs.student.$once()

5 全局事件总线

5.1 引入

实现任意组件间的通信
window.bus = data
window上的东西所有组件都能获取,直接window.bus就能得到这个数据,当然不推荐在window上放东西。这里的window指代一个任意组件都能访问到的地方,然后我添加了一个属性bus
如果这个bus还能使用$on,$off,$emit等和自定义事件相关的api,那么就能通过这个实现全局事件总线
比如有两个组件,A和B
B想给A传
A里面可以写

window.bus.$on("test1",  function1);
function1(data){
	console.log(data);
}

B里面可以写

window.bus.$emit("test2", testData);

就是A给window.bus绑定一个事件,B来触发,带参数的那种。而这个事件的回调本身是在A身上的,A就接收到了参数
如何找到合适的地方放这个bus
组件身上的emit方法实际上是vue的原型对象上的,而根据vue的重要关系,组件的原型对象能够访问到vue的原型对象的数据
所以可以放在vue的原型对象上
为什么不能直接在组件的原型对象上放,因为不同的组件有不同的原型对象
放在上面什么才能调用emit,当然可以放一个组件的实例对象,标准的写法是放vm,vm当然可以使用vue原型的东西,不过也就是简便了一些

5.2 实现

在这里插入图片描述
上面是组件a,下面是组件B,按B,A能够获取B发送来的姓名,显示在后面

5.2.1 main.js

组件实例对象的总线

const temp = Vue.extend({});
Vue.prototype.$bus = new temp();
new Vue()//...

标准做法,使用vm

new Vue({
    el: "#app",
    render: h=>h(App),
    beforeCreate(){
        Vue.prototype.$bus = this;
    }
})

this就是vm,相当于复制了一份vm到vue原型上作为总线使用

5.2.3 A

<template>
    <div>
        <p>姓名:{{userName}}</p>
    </div>
</template>
<script>
    export default {
        name: 'A',
        data() {
            return {
                userName:"",
            }
        },
        methods: {
            function1(value){
                this.userName = value;
            }
        },
        mounted() {
            this.$bus.$on("test", this.function1);
        },
        beforeDestroy() {
            this.$bus.$off("test");
        },
    }
</script>

思路是首先在data中定义到name,然后等到b传来了新数据就更新name
通过生命周期函数,在mounted的时候给总线绑定一个自定义事件,等到组件销毁的时候给总线解绑,否则总线上的自定义事件越来越多

5.2.4 B

B组件就是一个点击事件,给自定义事件激发并传数据

methods: {
    sendMsg(){
        this.$bus.$emit("test", this.userName);
    },
},

6 消息订阅

消息订阅是一个所有框架通用的一个概念,同样是用来实现全局的通信,所以消息订阅在vue中使用的少。
消息订阅需要借用第三方库,这里使用pubsub-js
安装

npm i pubsub-js

引入

import pubsub from "pubsub-js";

订阅与取消订阅

mounted(){
	this.pid = pubsub.subscribe(msgName, callback(msgName, data));
},
beforeDestroyed(){
	pubsub.unsubscribe(this.pid);//类似定时器,需要通过订阅api返回的id取消订阅
}

发送消息

pubsub.publish(msgName, "我是发送的数据data");

7 nextTick

this.$nextTick(callback);
在下次更新,以及循环结束之后,延时执行函数。如果在获取数据后立即使用,就会等到dom更新完了,才会执行函数。
这个循环,指的是v-for。有的时候,我们已经获取了数据,然后需要页面更新完毕进行下一步操作,但是v-for还没有更新完,就需要使用nextTick.
axios请求和操作更新后的dom的指令是在同一个函数里面,不会在axios得到数据后就更新,而是会在函数全执行完了才会更新。
如果你没有更新dom就想要操作是不会有结果的。
$nextTick()的作用就是将里面的函数等到页面更新再执行,就相当于写了一个异步任务,加了一个定时器
就比如你想要设计一个编辑模式,需要操作dom,获取焦点,这个时候,而输入框是在点击后出现的,这个时候你需要等到输入框出现后才能获取焦点
在使用swiper的时候,就需要使用nextTick的技巧

8 进入离开的过度与动画

8.1 介绍

进入离开的意思就是一个元素使用v-show或者v-if控制条件渲染,那么在我们

8.2 动画

下面是一段动画内容的css,与vue暂时无关,定义关键帧,然后有一个进入和离开的类名

.test-enter-active{
  animation: testAnimation 1s;
}
.test-leave-active{
  animation: testAnimation 1s reverse;
}
@keyframes testAnimation{
  from{
    transform: translateX(-100%);
  }
  to{
    transform: translateX(0);
  }
}

如果将类名轮换着给同一个元素,就能实现一个元素的出现和离开的动画
vue优化的就是这一段的js代码

<transition name="test">
  <h1 v-show="isShow">iceylia</h1>
</transition>

vue定义了一个新的标签,transition,在解析的时候不会解析这个标签
当标签内容出现,标签会加上test-enter-active,播放其中的动画。在标签内容消失,加上test-leave-active
transition的name属性是与css中的类名的第一个单词对应的,类名的后面部分不能随意更改

<transition name="test" appear>

appear属性能让transition在初始的时候使用出现动画

9 过度

9.1 单个元素

过度就是不用keyframes实现动画,需要定义四个css类名

/* 进入的起点 */
.test-enter{
 transform: translateX(-100%);
}
/* 进入的终点 */
.test-enter-to{
 transform: translateX(0);
}
/* 离开的起点 */
.test-leave{
 transform: translateX(0);
}
/* 离开的终点 */
.test-leave-to{
 transform: translateX(-100%);
}
/* 动画的播放时长和方式 */
.test-enter-active,.test-leave-active{
 transition: 1s linear;
}

还需要给这个过度的动画设定一个transition属性,用来定义播放时长和播放方式

9.2 多个元素

transition里不能有多个子元素
需要改为transition-group,每个元素都加上key值

<transition-group name="test2">
  <h1 key="1">1</h1>
  <h1 key="2">2</h1>
</transition-group>

10 配置代理

10.1 介绍

解决的是一个跨域的问题
数据请求有xhr和对xhr封装的jquery和axios,还有与xhr同级的fetch,但是一般使用的是axios
安装axios

npm i axios

跨域是什么
跨域
浏览器的协议,主机,端口和服务器的协议,服务器,端口有一个不一致就会形成跨域问题,跨域是服务器的数据放在浏览器那里,页面获取不到
解决跨域的方法

  1. cors后端写的时候加特殊的响应头,根本解决,但是不能所有的都配置这个,不能让所有人都能访问数据
  2. jsonp一种用的很少的方式,用script标签的src,需要前后端配合
  3. 代理服务器,代理服务器是设置一个暂时的服务器,服务器与主机是同域的,服务器之间获取数据是没有跨域问题的,所以代理服务器获取到服务器的数据,再将数据给主机

代理服务器通常使用nginx开启,还有一种方式是用vue的脚手架

10.2 方法一

在vue.config.js中写

devServer: {
	proxy: "http://localhost:5000"
}

proxy是服务器的地址,在使用的时候,就需要直接向代理服务器发送请求,而不是向服务器,地址改为主机地址
缺点:

  1. 不能使用假如主机有个相同路径的资源,就不会去获取服务器的,比如在index.html同级下有一个name文件,而你想要服务器的name文件,就不会获取服务器的,而是获取主机的
  2. 只能访问一个服务器,而不能访问多个不能跨域的服务器

10.3 方法二

其实就是把devServer写完整

devServer: {
	proxy: {
		"api1":{
			target: "http://localhost:5000",//代理目标的基础路径
			changeOrigin: true,
			pathRewrite: {"^/api1": ""}//将所有/api1在路径中去除
		},
		"api2": {
			target: "...",
		}
	}
}

将proxy写成对象的形式
用法:请求路径写成/api1/data
不需要在前面添加服务器名称,axios会自动设置为本地,使得在本地时为localhost并使用代理,在线上时则使用服务器地址
配置解析

  1. api1是自定义的名字,如果想要使用代理服务器,就在端口号后加上api1,使得知道是访问服务器,而不是原有的资源
  2. target 服务器的路径
  3. pathRewrite 使用正则表达式,将路径中的/api11去除,使得路径正确

11 slot插槽

页面上如果有结构相似,只有部分地方不一样的,就可以用插槽,将相同的地方用一个组件,不同的地方放一个占位的插槽,等到使用组件的时候,决定在插槽里面放什么东西

Test组件

<div class="list">
  <h1>数据列表</h1>
  <slot>没填东西的时候,默认显示这行字</slot>
</div>

slot标签就是一个插槽

使用上面的组件,在组件中间的元素会被填充到slot中

<div id="root">
	<Test>
	  <h2>可以是图片</h2>
	</Test>
	<Test>
	  <p>可以是视频</p>
	</Test>
	<Test>
	
	</Test>
</div>

在这里插入图片描述
具名插槽

如果想要写多个slot,需要使用name和slot属性进行匹配

<slot name="test">没填东西的时候,默认显示这行字</slot>

在使用的时候,会根据组件中内容的属性,放到子组件对应的插槽中去

<Test>
  <h2 slot="test">可以是图片</h2>
</Test>

假如有多个元素要放到插槽位置,可以用template包裹所有的元素,在template上写下插槽的名字

使用插槽所在组件的父组件, 能直接获取插槽所在组件的数据,slot上写上传过去的属性,填充元素上写上scope接收即可
使用方法

<slot name="test" :userName="user">没填东西的时候,默认显示这行字</slot>

这里传了一个userName,由于user是一个表达式,所以用:数据绑定

<Test>
  <template scope="data" slot="test">
    <p>可以是视频{{data.userName}}</p>
  </template>
</Test>

必须使用template将所有的填充元素包裹起来,用scope属性接收数据,data这个名字可以随便起,data是一个对象,里面都是传进来的数据,可以当成是这一个组件的数据来使用

12 vuex

12.1 引入

vuex也是一个组件间通信的技术,可以称为一个共享的数据库,如果多个组件都需要一个数据,那么就可以放在vuex里面
下面是一个用vue之前的知识写的一个案例,用多个部分控制一个数据
在这里插入图片描述

<template>
  <div>
    <h1>当前求和为{{sum}}</h1>
    <select v-model.number="n">
        <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="increamentWait">等一等再加</button>

  </div>
</template>

<script>
    export default {
        name: "Count",
        data() {
            return {
                sum: 0,
                n: 1,
            }
        },
        methods: {
            increment(){
                this.sum += this.n;
            },
            decrement(){
                this.sum -= this.n;
            },
            incrementOdd(){
                if(this.sum & 1){
                    this.sum += this.n; 
                }
            },
            increamentWait(){
                setTimeout(() => this.sum += this.n, 1000);
            }
            
        },
    }
</script>

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

12.2 介绍与环境搭建

vuex由三部分组成

  1. Actions 组件通过dispatch(“functionName”, data)函数传进来一个函数名和一个数据,actions有一个同名的函数,会调用,然后将data进行一定的加工,调用commit(“functionName2”, data2)
  2. Mutations 电泳commit中的同名函数,找到对应数据进行修改,修改量为data2
  3. State 用于存储数据的地方
    三部分的特点:
  4. 三部分由store管理,如果要使用dispatch,使用store.dispatch();
  5. 三部分都是对象类型的数据
    安装vuex3
    由于vuex4只能用于vue3,所以这里安装vuex3,vuex还是一个插件所以需要use
npm i vuex@3
import Vuex from "vuex";
Vue.use(Vuex);

store与事件总线有些类似,vm和所有的组件都可以访问
而且store是写在vm配置项里面的,但是如果如下所示直接写在配置项中,那vm无法识别store,vm中不会保存store,需要写上面的

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

这样就能够让所有的组件访问到store,应该叫$store
接下来是配置store
store是写在与main.js同级的store文件夹下的index.js中

import Vuex from "vuex";

const actions = {

};
const mutations = {
};
const state = {

};
export default new Vuex.Store({
    actions,
    mutations,
    state,
});

然后再在main.js中引入store,将之前那个假的store改为真的

import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex";
import store from "./store";
Vue.config.productionTip = false;
Vue.use(Vuex);
new Vue({
    el: "#app",
    store,
    render: h=>h(App),
})

这个时候会有一个错误,就是说必须在创建store实例之前使用vuex
import的时候,被引入的文件应该是执行完了,但是vue.use(Vuex)放在import store的后面,但是调换位置其实也不行
Vue cli 有一个特性,就是会将import提升,就是无论你写在哪,都会把它们放在最前面,所以这个use只能写在store里面

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const actions = {

};
const mutations = {

};
const state = {

};
export default new Vuex.Store({
    actions,
    mutations,
    state,
});
import Vue from "vue";
import App from "./App.vue";
import store from "./store/index.js";
Vue.config.productionTip = false;
new Vue({
    el: "#app",
    store,
    render: h=>h(App),
})

12.3 vuex版案例

先将数据sum放在state里面

const state = {
    sum: 0,
};

然后把所有更改数据的地方交由vuex管理

methods: {
    increment(){
        this.$store.dispatch("increment", this.n);
    },
    decrement(){
        this.$store.dispatch("decrement", this.n);
    },
    incrementOdd(){
        this.$store.dispatch("incrementOdd", this.n);
    },
    increamentWait(){
        this.$store.dispatch("incrementWait", this.n);
    }
},

actiions是逻辑层,mutations的意思为修改,只修改数据,不涉及逻辑,什么偶数再加,异步任务,全交由actions管理
所以第一个和第二个可以直接用commit交给mutatins修改

increment(){
    this.$store.commit("INCREMENT", this.n);
},

然脏在mutations和actions都有对应的函数,为了区分两个函数,mutations里的函数使用大写
action里的函数有两个参数,第一个context是一个mini的store,一般需要的数据都在里面,可以当成一个store使用,而value就是传进来的值
而mutation函数中的也有两个参数,一个是state,另一个是value,state可以
可以直接通过state.某个变量来更改state中的值.

const actions = {
    increment(context, value){
        context.commit("INCREMENT", value);
    },
    decrement(context, value){
        context.commit("DECREMENT", value);
    },
    incrementOdd(context, value){
        if(context.state.sum & 1){
            context.commit("INCREMENT", value);
        }
    },
    incrementWait(context, value){
        setTimeout(()=>{
            context.commit("INCREMENT", value);
        }, 1000)
    }
};

12.4 getters

getters与state平级,也是store里的一个配置项,getters的作用是将state里的数据进行加工,state与getters的关系就像是data与computers的关系一样。

const getters = {
    test(state){
        return state.sum * 10;
    }
};
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters,
});

使用的时候通过this.$store.getter.test访问

12.5 简化(代码自动生成)

我们在写state里的数据的时候,都需要使用$store.state.sum或者$store.getter.test,如果想要只写sum和test读取到这些数据,也是有办法的,使用计算属性,一个个来,但是这样也很麻烦,除非你需要写大量的,不然还不如一个个写。
所以我们可以使用mapState和mapGetters来帮助我们生成这些计算属性
本质是代码帮助你写好

12.5.1 mapState与mapGetters

引入

    import {mapState, mapGetters} from "vuex";

如果你想给这些数据起个别名的话,比如sum在这里叫sum2

computed: {
    ...mapState({sum2:"sum"}),
}

。。。是扩展运算符,mapState函数返回的是一个对象,将对象里的键值拆分出来
这样的效果相当于

computed: {
    sum2(){
        return this.$store.state.sum;
    }
}

这里第一个的意思就是计算属性的名字,第二个是state中的变量名,如果不加引号,那就是一个变量,当然不对
如果不需要器别名,原生的js是没有这种简写的,毕竟是是字符串作为值

computed: {
	...mapState(["sum"]),

数组形式

12.5.2 mapActions与mapMutations

mapState与mapGetters是简化computed里的代码,而mapActions与mapMutations简化的是methods的方法
还是需要引入,方法如上,还是有两种写法,对象写法和数组写法,写法如上
原版写法

methods: {
	increment(){
	    this.$store.commit("INCREMENT", this.n);
	},
	decrement(){
	    this.$store.commit("DECREMENT", this.n);
	},
	incrementOdd(){
	    this.$store.dispatch("incrementOdd", this.n);
	},
	increamentWait(){
	    this.$store.dispatch("incrementWait", this.n);
	}    
},

简化版本

methods: {
    ...mapMutations(["INCREMENT", "DECREMENT"]),
    ...mapActions(["incrementOdd", "incrementWait"]),
},

这里就有点不一样了,就是简化版本的this.n从哪里来,下面的代码其实不是上面代码的简写,而是需要传参的。

increment(value){
    this.$store.commit("INCREMENT", value);
},

传参就在事件绑定的时候传

<button @click="INCREMENT(n)">+</button>

12.6 vuex模块化

比如目前有商品的数据和个人信息的数据存在vuex中,模块化就是将这两类数据分开存放,分开处理,以便于维护

const items = {
    state: {},
    actions: {},
    mutations: {},
    getters: {}
}
const person = {
	//...
}
export default new Vuex.Store({
    modules:{
        items,
        person,
    }
});

将state,actions,mutations和getters分别存放在对应的对象中,items管理的东西也变成了对象
但是数据的读取又有了问题,需要通过this.$stote.items.state.a来获取
如果用mapState,也只能写成..mapState(["items", "person"])
然后就可以使用items.a ,person.a获取数据
如果还要进一步简化
就可以使用命名空间

export default new Vuex.Store({
	namespaced: true,
    modules:{
        items,
        person,
    }
});

加上命名空间之后,就可以这样写

computed: {
	...mapState("person", {a1: "a", b1: "b"}),
	...mapState("items",{a2: "a", b2: "b"})
}

同理,对于mapActions和mapMutations也需要做同样的设置
不用mapState等

  1. state,this.$store.items.a
  2. getters,this.$store.getters[“items/test”] getters[“”]也是对象读取属性的一种方式,与getters.items一样,但是由于getters里的数据是直接存放在getters下的,名字为模块名加斜线加变量名,而用.就无法使用斜线,所以要使用这种方式
  3. actions,this.$store.dispatch(“items/function1”, 5);
  4. mutatins,,this.$store.commit(“items/TEST”, 5);

模块化的意义: 能够解决命名冲突,简洁,在store文件夹下,可以将模块化的部分从index.html中分离出来,形成一个新的文件夹,然后导出,在index中引入再使用。

运用实例

我在store文件夹下定义了一个index.js和一个cart.js

先引入vue和vuex,并使用vuex,再引入cart中的默认暴露内容

再默认暴露一个new 的vuex.store

开启命名空间

import Vue from "vue";
import Vuex from "vuex";
import moduleCart from "@/store/cart.js"

Vue.use(Vuex);

export default new Vuex.Store({
	namespaced: true,
	modules: {
		"m_cart": moduleCart
	}
})

cart

定义个一个cart的数据和一个addCart的mutation属性

export default{	
	namespaced: true,
	state: {
		cart: []
	},
	
	mutations: {
		addCart(state, goods){
			const findResult = state.cart.find(x=>x.goods_id === goods.goods_id);
			if(!findResult){
				state.cart.push(goods);
			}else{
				findResult.goods_count++;
			} 
			console.log(state.cart);
		}
	},
	getters: {}
}

main

加载到vue中

import store from "@/store/store.js"
new Vue({
	app,
	store
})

使用

import {mapState, mapMutaions} from "vuex"
export default {
	computed: {
		...mapState("m_cart", ["cart"])//使用命名空间m_cart中的cart数据
	},
	methods: {
		...mapMutaions("m_cart", ["addCart"])
	}
	mounted(){
		console.log(this.cart);
	}
}

13 路由

13.1 介绍

路由与路由器的区别,路由的英文名是route,而路由器的名字为router,路由是一组对应关系。路由器是管理路由的设备,路由器的接口就是一个路由
前端的路由是用来实现spa(single page website application)单页面应用
只有一个index.html,这个页面中的跳转和导航中的跳转都是由路由实现的
比如https://blog.csdn.net/nav/back-end和https://blog.csdn.net/nav/web
这两个网址的区别就在于域名后面的东西不一样,其实这是一个网站,后面的东西不能说是域名,就是通过/back-end关联一个组件,当出现/back-end就呈现对应的组件
前端的路由有些类似插槽
后端的就不一样了

13.2 基本使用

图
实现这样一个功能,能够通过点击a和b切换文字
注意路径中有一个/#/,这是路由的两种规则导致的
路由是通过一个插件库vue-router 实现的
与vuex一样,需要使用3版本,而不能是4

npm i vue-router@3

需要专门一个文件来写路由规则
router/index.js

import Vue from "vue";
import VueRouter from "vue-router";
import A from "../components/A.vue"
import B from "../components/B.vue"

Vue.use(VueRouter);

export default new VueRouter({
    routes: [
        {
            path: "/a",
            component:A,
        },
        {
            path: "/b",
            component: B,
        }
    ]
})

main.js

import router from "./router";
new Vue({
    el: "#app",
    router,
    render: h=>h(App),
})

这样就配置好路由器了
接下来是按键A和B,这两个按键的实质是a标签,但是router对有路由规则的a有新的标签,router-link

<router-link id="btn1" class="ctr" active-class="active" to="/a">A</router-link>
<router-link id="btn2" class="ctr" active-class="active" to="/b">B</router-link>

a变成router-link,href变成to,to的内容就是相对应的路由规则,写在router里的路径
其中active-class也是路由独有的属性,就是链接激活时的样式类名,只需要将写好的类名放上去,就能自动切换样式
最后将组件放在对应的位置,与插值相似,有个占位的

<div>
  <router-view></router-view>
</div>

注意点:

  1. 用于路由的组件一般放在一个单独的文件夹/page下
  2. 组件的切换是一次次的挂载组件和销毁组件的过程
  3. /pages下的组件都有两个新的属性this.$routethis.$router其中route是每个组件的内容都不一样,而router是每个组件的其实都是一个东西,可以用三等得到true

13.3 路由嵌套

导航的内容里还有导航的情况会用到
路由规则

{
   path: "/a",
   component:A,
   children: [
       {
           path: "c",
           component: C,
       }
   ]
},

写在children里,path不用加斜线了
接下来就是link和view的配置,一样的,注意link的to属性需要写全,写成/a/c

13.4 query参数

向路由组件传参,有的时候不同导航内容的结构一致,只是部分数据不同,需要从父组件获取数据

<router-link to="/a/c?a=test&b=我想吃火锅">C</router-link>
<h1>我是A组件下的路由组件C{{$route.query.a}}</h1>

通过this.$route.query访问传过来的参数,是一个对象类型
但是如果/a/c?a=test&b=我想吃火锅test是一个变量,放在data里的变量,那么就会在这里被当成是字符串
第一种写法

<router-link :to="`/a/c?a=${test}&b=我想吃火锅`">C</router-link>

先用:to数据绑定,将内容按js解析,再使用模板字符串,把它们变回字符串,将变量用{}包裹
第二种写法

<router-link 
:to="{
   path: '/a/c',
   query: {
       a: test,
   },
}">C</router-link>

就是对象写法

13.5 params参数

params与query参数作用一样,都是用来传参的

<router-link :to="`/a/c/${test}`">C</router-link>

/a/c的路径后面/test就是传的参数
需要在命名规则里使用一个占位符,使其成为一个参数,而不是路径的一部分

{
    path: "c/:test",
    component: C,
}

/:变量名 的形式,网址变成了/#/a/c/我是测试文字的形式
读取方式与query相似,this.$route.params.a

<h1>我是A组件下的路由组件C{{$route.params.a}}</h1>

当使用to的对象形式携带params参数时,需要使用name,而不能使用path

13.6 props

路由的props与组件的props差不多是一个东西,路由的props是在路由规则里面传递参数,组件的props配置项接收参数
比如

name: "b",
path: "/b",
component: B,
props: {a: 1, b: 2},

B.vue

export default {
    name: "B",
    props: ["a", "b"]
}

这样就能在b组件里使用这些数据,但是很没必要,因为这个是写死的
第二种写法

name: "b",
path: "/b",
component: B,
props: true,

会将所有的params参数以props的形式传给组件,简化写法,可以不用this.$route.params.a,this.a就能读取到
但是这种写法不适用于query参数,
第三种写法
函数写法

props($route){
	return {a: $route.query.a, b: $route.query.b, }
}

也可以用解构赋值简化写法

props({query}){
	return {a: query.a, b: query.b}
}

这样在使用query参数的时候,就很简便,只需要使用a和b而不是this.$route.query.a

13.7 replace

路由对历史记录的影响有两种模式,push和replace,
默认就是push,每一次点击导航,都会形成一个历史记录,可以回退到上一条记录里面
replace,替换当前记录

<router-link replace>A</router-link>

替换当前的历史记录
比如你现在在/#
然后/#/b,/#/a,/#/a/c,可以回退三次到/#
但是我对a开启了replace模式,只能/#/a的时候,/#/b这条记录没了,你在/#/a再回退,就会是/#

13.8 name

当有的时候path特别长,可以使用name参数

{
   name: "b",
   path: "/b",
   component: B,
}

先要在路由规则里配置好name参数
然后再使用的时候,需要搭配对象式的to来使用

    <router-link :to="{name:'b'}">B</router-link>

13.9 编程式路由导航

不借用route-link,也能实现导航,只需要使用一个按钮,再绑定事件即可,这样更为灵活
5个函数,都是$router上的

  1. push(to),以push的方式打开对应路由地址,to代表参数就是view-link中的to
  2. replace(to),以replace的形式打开
  3. back(),后退
  4. forward(), 前进
  5. go(number) 根据number前进后退
methods: {
	test1() {
	  this.$router.push({
	    path: "/a",
	  })
	},
	test2(){
	  this.$router.replace({
	    name: "b",
	  })
	},
	back(){
	  this.$router.back();
	},
	forward(){
	  this.$router.forward();
	},
	goto(){
	  this.$router.go(2);
	}
},

13.10 缓存

路由组件会在切换时被销毁,但是有些时候有些用户输入的东西需要你缓存一下
将router-view包裹在keep-alive里即可

<keep-alive>
 	<router-view></router-view>
</keep-alive>

因为view是导航区的展示区,有多个组件,有的组件我们不需要它缓存
用include属性,将要缓存的组件写在里面,里面的名字就是在写组件的时候的name配置项

<keep-alive include="B">
 	<router-view></router-view>
</keep-alive>

当有多个组件需要缓存的时候,使用数组即可

<keep-alive :include="["A", "B"]">
 	<router-view></router-view>
</keep-alive>

router特有的生命周期钩子
当缓存的组件里有一个定时器的时候,在切换的时候就不会停下,因为没有beforeDestroyed这个函数了,要等到父组件消失的时候才会被销毁
所以有两个新的函数

export default {
   name: "B",
   props: ["a", "b"],
   activated() {
       console.log("ji huo");
   },
   deactivated() {
       console.log("2");
   },
}

activated是这个页面在眼前的时候
可以将定时器之类放在里面

13.11路由守卫

13.11.1 全局前置

前置路由守卫就是每次组件切换之前,做一个逻辑,在一定情况下才能访问一个路由组件,比如你是会员,你是管理员
router/index.js

const router = new VueRouter({});
router.beforeEach((to, from, next)=>{
	
})
export default router;

router.beforeEach在初始化与每次组件切换之前调用,有三个参数
to与from是此次切换的起点与终点
拥有属性如下
在这里插入图片描述
next是一个函数,直接调用,表示放行此次切换,能够切换成功
可以据此写一个类似的逻辑

if(to.path === "/a/c"){
   if(sss === "fwif"){
       next();
   }
   else{
       console.log("缺失访问权限");
   }
}

但是当需要判断的地方多,一直绑定path有些麻烦
to与from中有一个meta是路由元信息,可以在里面自定义内容

{
   path: "c/:test",
   component: C,
   meta: {isAuth: false}
}

在里面定义一个bool值的变量,用来标识这个路由组件是否需要判断

13.11.2 全局后置

后置路由守卫与前置的作用不一样,前置的是在切换前判断你的权限,后置已经切换完了。
router.afterEach()只有两个参数,没有next函数
用来实现一些切换后的逻辑,比如你想在每次切换之后输出一个字,改一下title,如果将这个逻辑放在前置守卫中,需要在有权限的时候才能执行,也就是说需要在每个next前写一遍这个逻辑。

13.11.3 独享

在一个单独的路由规则里面的守卫

{
    path: "c/:test",
    component: C,
    meta: {isAuth: true},
    beforeEnter: (to, from, next) => {
        // ...
    }
}

里面的写法还是和全局的守卫一样,不过我不理解的是为什么要有to这个,写在这里不是都知道是enter那个组件里吗

13.11.4 组件内路由

export default {
    name: "C",
    beforeRouteEnter (to, from, next) {
        // ...
    },
    beforeRouteLeave (to, from, next) {
        // ...
    } 
}

通过路由规则,切换到这个组件之前和切换到其它组件之前

13.11.5 history hash

路由有两种模式,一种是/#/是hash模式/#及其之后的内容都是hash值
hash的作用在于标识后面的内容不是网址,当给服务器发请求时,就不会有后面的内容
另一种模式是history,默认是hash模式,

mode: "history",
routes: []

history就是没有办法分辨出来你哪些是网址,哪些是路由,而且兼容性较差,但是好处在与美观,所以很多地方都会用到history模式
history模式无法分辨的话,就会把后面的内容也发给服务器,所以后端就要设置好对应的服务,当然后端是有办法自动设置好这个的,不需要对应好路径

14 组件库

下面待完善,有点问题
移动端常用UI组件库

  1. Vant
  2. Cube Ul
  3. Mint Ul

PC端常用UI组件库

  1. Element Ul
  2. IView Ul

element ui的使用方式
安装

npm i element-ui -S
//完整引入
//引入element-ui组件库
// import ElementUI from 'element-ui';
//引入element全部样式
// import 'element-ui/lib/theme-chalk/index.css';

//使用element ui插件库
// Vue.use(ElementUI);

//按需引入
import { Button, Input, Row, DatePicker } from 'element-ui';
Vue.use(Button);
Vue.use(Input);
Vue.use(Row);
Vue.use(DatePicker);

按需引入减少打包后的项目体积,使用下面的插件

npm install babel-plugin-component -D

修改babel.config.js

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

官网中的修改方式估计没有更新,不太对,我发现值用加上pulgins的配置即可
不用修改第一个,修改第一个会提示es2015没有找到
做轮播图的时候我喜欢用swiper库,挺好用的

注:不知道为什么,后来又需要全局引入css

在main.js中

import "element-ui/lib/theme-chalk/index.css"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值