admin 管理员组文章数量: 887017
一、脚手架
1.1、安装脚手架:@vue/cli
第一步:执行安装命令
npm install -g @vue/cli
安装完毕后查看:
vue -V
@vue/cli 4.5.15
安装缓慢时,修改为淘宝镜像:npm config set registry https://registry.npm.taobao
第二步:切换到你要创建项目的目录,创建项目
vue create 项目名 (选择vue2版本)
项目创建成功提示:
第三步:启动项目
npm run serve (不是server)
输入地址,进入到默认的hello world页面
1.2、脚手架结构分析(项目名称vue_test_two)
src/main.js文件分析
// 这个文件是项目的入口文件
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭cue的生成提示
Vue.config.productionTip = false
// 创建Vue实例对象
// new Vue({
// render: h => h(App),
// }).$mount('#app')
// 创建Vue实例对象(和上面的写法的区别,多了el)
new Vue({
el:'#app',
render: h => h(App),
})
public/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">
<!-- <%= BASE_URL %> 就是public目录,这里就是配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页标题 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
// 浏览器不支持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>
1.3、render函数(分析main.js时,里面的render函数是做什么用的)
// 这个文件是项目的入口文件
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭cue的生成提示
Vue.config.productionTip = false
// 创建Vue实例对象(创建脚手架后的原生配置)
// new Vue({
// render: h => h(App),
// }).$mount('#app')
// 创建Vue实例对象(自定义配置,用到了el)
new Vue({
// el:'#app' 等于:$mount('#app')
el:'#app',
// 最终版(脚手架默认版)
render: h => h(App),
// 第三版(箭头函数简写)这里传入2个参数是因为'h1'是HTML里面的内置元素,元素内需要传入具体的内容'你好啊'。
// render:createElement=> createElement('h1','你好啊')
// 第二版(箭头函数方式)
// render要写成函数类型,加上返回值
// render:(createElement)=>{
// createElement是一个function类型(可以渲染模版内容),可以传值'h1'就是<h1>,'你好啊'就是内容,完整版就是"<h1>你好啊</h1>"
// return createElement('h1','你好啊')
// }
// 第一版(普通函数方式)
// render要写成函数类型,加上返回值
// render(createElement){
// createElement是一个function类型(可以渲染模版内容),可以传值'h1'就是<h1>,'你好啊'就是内容,完整版就是"<h1>你好啊</h1>"
// return createElement('h1','你好啊')
// }
})
// 单文件组件是这么实现的
// const vm = new Vue({
// el:"#root",
// template:`<App></App>`,
// components:{
// App:App,
// }
// })
1.4、总结不同版本的vue.js文件的区别
1.vue.js与vue.runtime.xxx.js的区别
1.vue.js是完整版的Vue,包含:核心功能 + 模块解析器
2.vue.runtime.xxx.js是运行版的Vue,只包含:核心功能,没有模块解析器
2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,
需要使用render函数接收到的createElement函数去指定具体内容。
1.5、vue.config.js 文件
官方文档:配置参考 | Vue CLI
vue.config.js 配置文件对可以对脚手架进程个性定制(这里仅做参考)
module.exports = {
pages:{
index:{
// 配置主文件的入口是main.js文件
entry: "src/main.js"
},
},
// 关闭语法检查
lintOnSave:false
}
1.6、组件的ref 属性
组件代码:
<template>
<div>
<!-- 在标签内添加一个ref="xxx",然后在this.$refs.xxx就能拿到该标签的"dom元素" -->
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDom">点我展示Dom</button>
<!-- 在自定义的组件内加一个ref="sch",然后在this.$refs拿到的就是该组件的实例对象VueComponent(简称vc) -->
<School ref="sch"></School>
</div>
</template>
<script>
import School from './components/School'
import Student from './components/Student.vue'
export default {
name: "App",
components:{
School,
Student,
},
data(){
return {
msg: '欢迎'
}
},
methods:{
showDom(){
console.log(this.$refs.title) // 拿到的是真实的dom元素
console.log(this.$refs.btn) // 拿到的是真实的dom元素
console.log(this.$refs) // 拿到的是该组件的实例对象VueComponent(简称vc)
}
}
}
</script>
展示效果:
总结ref属性:
1.被用来给元素或者"子组件"注册引用信息(id的替代者)
2.应用在html标签上获取的是真是DOM元素,应用在组件标签上是组件的实例对象(VC)
3.使用方式:
打标识:<h1 ref="title"></h1>,<School ref="sch"/>
获取:this.$refs.xxx
1.7、组件的props属性(功能:让组件接收外部传过来的数据)
注意点:传参的变量不能写已经被内置使用的名称
1.7.1、父组件传递参数给子组件
父组件传参:<Student name="sudada" sex="男" :age="18"/>
<template>
<div>
<!-- 给组件Student传递参数:name="sudada" sex="男" :age="18" -->
<Student name="sudada" sex="男" :age="18"/>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name: "App",
components:{
Student,
}
}
</script>
子组件接收参数:props:{},有三种方式,详见例子
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生名字:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<h2>学生性别:{{sex}}</h2>
</div>
</template>
<script>
export default {
name: "Student",
data(){
return {
msg: '我是一个学生',
}
},
// 组件接收参数:方式1,数组写法(简单接收,不能多写接收的参数)
// props:['name','age','sex']
// 组件接收参数:方式2,对象写法(规定了接收参数的类型)
// props:{
// name:String,
// age:Number,
// sex:String,
// }
// 组件接收参数:方式3,对象写法(规定了接收参数的类型,和参数的默认值,以及参数是否必须要传)
props:{
name:{
type:String, // name的类型是字符串
required:true, // name是必须要传的参数(required表示这个属性是否是必须的)
},
age:{
type:Number, // name的类型是整数
default:99, // 默认参数的值是99
},
sex:{
type:String, // name的类型是字符串
required:true, // sex是必须要传的参数(required表示这个属性是否是必须的)
},
}
}
</script>
1.7.1、子组件接收的参数尽量不要修改(可以改,但是不要改),如果非要改的话,可以新建一个变量做替换,如下:
父组件传参
<template>
<div>
<!-- 给组件Student传递参数:name="sudada" sex="男" :age="18" -->
<Student name="sudada" sex="男" :age="18"/>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name: "App",
components:{
Student,
}
}
</script>
子组件接收参数
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生名字:{{name}}</h2>
<h2>学生年龄:{{new_age}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="updateAge">点我修改年龄</button>
</div>
</template>
<script>
export default {
name: "Student",
data(){
return {
msg: '我是一个学生',
new_age: this.age
}
},
methods:{
updateAge(){
this.new_age++
}
},
// 组件接收参数:方式3,对象写法(规定了接收参数的类型,和参数的默认值,以及参数是否必须要传)
props:{
name:{
type:String, // name的类型是字符串
required:true, // name是必须要传的参数(required表示这个属性是否是必须的)
},
age:{
type:Number, // name的类型是整数
default:99, // 默认参数的值是99
},
sex:{
type:String, // name的类型是字符串
required:true, // sex是必须要传的参数(required表示这个属性是否是必须的)
},
}
}
</script>
1.8、混合(混入) mixins功能:
两个组件内写了一个重复的配置项,那么就可以把这个重复的配置项提取出来单独定义成一个公共对象(js),然后导入这个对象(js)并使用mixins:[xxx]即可使用。
注意点:
混合(js)内配置的对象的key:value,如果组件里面的对象不存在这个key:value,那么组件里面的对象就会多一个key:value,如果组件里面的对象已经存在这个key:value了,那么以组件里面对象已存在的key:value为主。
1.8.1例子(局部混合的使用):
定义一个mixin.js文件(定义混合)
const mixin = {
methods:{
showName(){
alert(this.name)
}
}
}
export default mixin
子组件Student(局部使用混合)
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生名字:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<button @click="showName">点我显示学生名</button>
</div>
</template>
<script>
// 引入一个混合(自定义的js文件)
import mixin from "@/components/mixin";
export default {
name: "Student",
data(){
return {
msg: '学生信息',
name:'张三',
age: 19
}
},
// 使用混合(自定义的js文件)
mixins:[mixin]
}
</script>
子组件School(局部使用混合)
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我显示学校名称</button>
</div>
</template>
<script>
// 引入一个混合(自定义的js文件)
import mixin from "@/components/mixin";
export default {
name:'School',
data(){
return {
name:'上海大学',
address:'shanghai'
}
},
// 使用混合(自定义的js文件)
mixins:[mixin]
}
</script>
<style>
.demo{
background-color: orange;
}
</style>
1.8.2例子(全局混合的使用)
在main.js内使用Vue.mixin(mixin),然后在组件内即可调用mixin内定义好的方法或对象。
import mixin from "@/mixin";
Vue.mixin(mixin)
1.9、插件
功能:用于增强vue
本质:包含install方法的一个对象,install的第一个参数是vue,第二个以后的参数是使用插件传递过来的值。
定义插件:
// 1.添加全局过滤器
Vue.filter(....)
// 2.配置全局混合
Vue.mixin(....)
// 3.添加实例方法
Vue.prototype.hello = ()=>{alert('你好啊')}
使用插件:Vue.use(plugins,12,23),其中12,23是给插件的传参
1.9.1、例子
定义一个plugins.js 文件
export default {
install(Vue,a,b,c){
// 全局过滤器,这里的mySlice可以参考下面的局部过滤器写法对比即可。
Vue.filter('mySlice',function (value){
return value.slice(0,4)
})
// 接收插件的参数
console.log(a,b,c)
// 定义混入(全局混合)
Vue.mixin({
methods:{
showName(){
alert(this.name)
}
}
})
// Vue原型上的方法,vm和vc都可以使用
Vue.prototype.hello = () => {
alert('你好啊')
}
}
}
main.js文件内引入插件
// 引入插件
import plugins from "@/plugins";
//使用插件(插件还可以传多个值)
Vue.use(plugins,1,2,3)
组件内使用插件
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生名字:{{name | mySlice}}</h2>
<h2>学生年龄:{{age}}</h2>
<button @click="showName">点我</button>
</div>
</template>
<script>
export default {
name: "Student",
data(){
return {
msg: '学生信息',
name:'张三asdasdsd',
age: 19
}
},
}
</script>
1.10、scoped样式
作用:让样式在局部生效,防止冲突(组件内的样式(style)最终都是会汇总到一起,如果定义了多个组件,然后这些组件内有些样式(style)的名称重复了,就会格式错乱)
App.vue组件内一般不使用scoped
1.10.1、例子 <style scoped> .... </style>
<style scoped>
.demo{
background-color: skyblue;
}
</style>
二、todolist 案例和总结(组件化的编码流程讲解)
1、组件编码流程:
1.拆分静态组件:组件要按照功能点拆分,命名不能和html元素冲突。
2.实现动态组件:考虑好数据存放位置,数据是一个组件在用,还是多个组件在使用。
一个组件在用: 放在组件自身即可。
多个组件在使用: 放在组件的公共父组件上。(状态提升)
3.实现交互:从绑定事件开始。
2、props适用于:
1.父组件===>子组件之间的通信。
2.子组件===>父组件之间的通信(要求父组件给子组件一个函数)。
3、使用v-model时切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的。
4、props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这么做。
2.1、子组件给父组件传值(子组件给父组件间的通信)
实现方式:父组件先自定义一个方法receive(xxx),然后把这个方法传递给子组件。子组件通过props:['receive']接收,然后通过this.receive(xxx)的方式把值传递给父组件。例子如下:
父组件
<template>
<div>
<MyHeader :receive="receive"/>
</div>
</template>
<script>
import MyHeader from './components/MyHeader'
export default {
name: "App",
// 组件注册
components:{
MyHeader,
},
methods:{
// 这个函数是传递给MyHeader子组件的,然后MyHeader子组件调用这个函数并传入"参数",这个传递的"参数"就是子组件给父组件传值的方法。
receive(x){
console.log('接收到了todoobj',x)
}
}
}
</script>
子组件
<template>
<div class="todo-header">
<!-- @keyup.enter="add" 键盘回车事件,也就是敲键盘的enter键就会触发 -->
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
</div>
</template>
<script>
import {nanoid} from "nanoid"
export default {
name: "MyHeader",
props:['receive'],
data(){
return{
title:''
}
},
methods:{
add(){
// 将用户输入的值,包装成一个todo对象
const todoObj = {id:nanoid(),title:this.title,done:false}
// console.log(todoObj)
// 调用父组件传递过来的函数,把值传递给这个函数,实现了子组件给父组件传值
this.receive(todoObj)
}
}
}
</script>
2.2、浏览器的本地存储
2.2.1、localStorage:特点,浏览器关闭存的值并不会立即删除。可手动删除/清空。
对应的API如下:
添加:localStorage.setItem('name','sudada') 内容都要写字符串格式,如果不写默认转为字符串格式
查看:localStorage.getItem('name') 内容都要写字符串格式,如果不写默认转为字符串格式
删除:localStorage.removeItem('name') 内容都要写字符串格式,如果不写默认转为字符串格式
清空:localStorage.clear() 内容都要写字符串格式,如果不写默认转为字符串格式
2.2.2、sessionStorage:特点,浏览器关闭后,存的值就没了。
对应的API如下:
添加:sessionStorage.setItem('name','sudada') 内容都要写字符串格式,如果不写默认转为字符串格式
查看:sessionStorage.getItem('name') 内容都要写字符串格式,如果不写默认转为字符串格式
删除:sessionStorage.removeItem('name') 内容都要写字符串格式,如果不写默认转为字符串格式
清空:sessionStorage.clear() 内容都要写字符串格式,如果不写默认转为字符串格式
2.3、组件的"自定义事件"(子组件给父组件间的通信)
1.一种组件间通信的方式,适用于:子组件-->父组件
2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中定义"事件的回调方法"。然后A组件把这个方法通过v-on绑定给B组件(也就是下面的方式一)。然后B组件内调用这个"事件回调方法"。
3.绑定自定义事件:
1.方式一:
在父组件中给子组件绑定一个"自定义事件": <Student @atguigu="getStudentName"/> getStudentName是父组件里面一个自定义的方法。
在子组件中:调用 this.$emit("atguigu",参数),就会触发父组件里面的getStudentName方法。
2.方式二:
在父组件中给子组件打一个标识:<Student ref="student"/>
在父组件中定义: mounted(){ this.$refs.student.$on('atguigu',this.getStudentName) }
在子组件中:调用 this.$emit("atguigu",参数),就会触发父组件里面的getStudentName方法。
3.若是想让"自定义事件只能触发一次",可以使用 .once 修饰符,或者使用 $once 方法。
在父组件(使用 $once方法):mounted(){this.$refs.student.$once('atguigu',this.getStudentName)}
在父组件(使用 .once修饰符):<Student @atguigu.once="getStudentName"/>
5.解绑自定义事件:
在子组件(使用 $off方法):this.$off("atguigu") # 解绑一个自定义事件"atguigu"
在子组件(使用 $off方法):this.$off(["atguigu","demo"],参数) # 解绑多个自定义事件
在子组件(使用 $off方法):this.$off([],参数) # 解绑所有的自定义事件
6.组件上也可以绑定原生DOM事件,需要使用native修饰符。
7.注意:通过this.$ref.xxx.$on('事件名',回调方法)绑定自定义事件时,自定义事件对应的方法 "要么配置在methods中,要么用箭头函数" 否则this指向会出问题(this不是App而是Student)
// 调用自定义事件(方法1),必须写methods方法
mounted() {
this.$refs.addTodo.$on('addTodo',this.addTodo)
},
// 自定义事件的回调方法
methods:{
addTodo(todoObj){
this.todos.unshift(todoObj)
},
}
// 调用自定义事件(方法2),不需要额外再写methods:{todoObj(){}}方法了。
mounted() {
this.$refs.addTodo.$on('addTodo',(todoObj)=>{
this.todos.unshift(todoObj)
})
},
8、在组件内写原生DOM事件"@click"时,click事件就会被当做自定义事件,需要Student组件内通过this.$emit('clicl')触发,否则不能直接使用。
<Student @atguigu="getStudentName" @demo="m1" @click="show"/>
如何才能把@click当做原生DOM事件呢? 使用@click.native修饰符即可。
<Student @atguigu="getStudentName" @demo="m1" @click.native="show"/>
2.4、全局事件总线(任意组件之间的通信)★★★★★
1.安装全局事件总线:在Main.js里面
beforeCreate() {
Vue.prototype.$bus=this // 这里的this就是当前的vm。"$bus"就是创建的"全局事件总线"(当前项目所有的VC和VM都能借助这个$bus触发一些自定义事件)
}
// 创建vm
new Vue({
el: '#app',
render: h => h(App),
// 安装全局事件总线
beforeCreate() {
Vue.prototype.$bus=this
}
})
2.使用全局事件总线:以下例子(B组件作为发送者/子组件,A组件作为接收者/父组件。)
2.1.接收数据:A组件想接收B组件发送过来数据,则在A组件中给$bus绑定自定义事件,事件的"回调留在A组件自身"。以下2个都是放在A组件的。
methods(){
demo(接收的参数){....}
}
.......
mounted(){
this.$bus.$on('xxxx',this.demo) # xxxx就是自定义事件名
}
2.2.发送数据:在B组件中通过this.$bus.$emit('xxxx',回调数据)发送数据给A组件(触发自定义事件)。
3.在A组件中,最好在beforeDestroy钩子中,用this.$bus.$off('xxxx')去解绑"当前组件所用到的"自定义事件xxxx。
2.4.1、全局事件总线,例子
main.js文件"安装全局事件总线"
new Vue({
el: '#app',
render: h => h(App),
// 安装全局事件总线
beforeCreate() {
Vue.prototype.$bus=this
}
})
App.vue组件内(父组件)
mounted() {
// 使用全局事件总线的方式,实现父组件给子组件传递数据。
this.$bus.$on('addTodo',this.addTodo)
this.$bus.$on('checkTodo',this.checkTodo)
this.$bus.$on('deleteTodo',this.deleteTodo)
},
methods:{
// 回调方法函数
addTodo(todoObj){
this.todos.unshift(todoObj)
},
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id === id) todo.done = !todo.done
})
},
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !== id
})
},
},
// 解绑全局事件总线
beforeDestroy() {
this.$bus.$off('addTodo')
this.$bus.$off('checkTodo')
this.$bus.$off('deleteTodo')
}
Myitem.vue组件内(子组件)
使用this.$bus.$emit('xxxx',回调数据)发送数据给父组件(触发自定义事件)。
this.$bus.$emit('deleteTodo',todo_id)
2.5、消息订阅与发布(任意组件之间的通信)★★★★★
订阅消息:订阅消息的名称
发布消息:发布消息的内容
1.引入pubsub-js,在A和B组件内:import pubsub from 'pubsub-js' (安装:npm i pubsub-js)
2.订阅消息:A组件订阅了B组件的消息,那么订阅的回调函数写在A组件自身。
写法1:常规写法
methods(){
// msgName这个是消息名称,data是回调的数据
demo(msgName,data){....}
}
.......
mounted(){
// this.pubId:订阅消息的ID
this.pubId = pubsub.subscribe('消息名', this.demo)
}
写法2:(方法写成箭头函数,不写methods)
mounted(){
// this.pubId:订阅消息的ID
this.pubId = pubsub.subscribe('消息名',(msgName,data)=>{
// msgName这个是消息名称,data是回调的数据
console.log('消息的回调执行了',msgName,data)
}
3.发布消息:B组件中:pubsub.publish('消息名',数据)
4.最好在A组件中,通过beforeDestroy钩子,用"pubsub.unsubscribe(this.pubId)"取消消息订阅。
2.5.1、消息订阅与发布,例子
App.vue组件内(父组件)
import pubsub from 'pubsub-js'
mounted() {
// 使用"消息订阅与发布"的方式(订阅消息)
this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)
},
methods:{
deleteTodo(_,id){
this.todos = this.todos.filter((todo)=>{
return todo.id !== id
})
},
}
// 解绑全局事件总线
beforeDestroy() {
pubsub.unsubscribe(this.pubId)
}
Myitem.vue组件内(子组件)
import pubsub from 'pubsub-js'
// 使用消息订阅与发布(发布消息)
pubsub.publish('deleteTodo',todo_id)
2.6、this.$nextTick
语法:this.$nextTick(回调函数)
作用:在下一次DOM更新结束后执行其指定的回调。
什么时候使用:当改变数据后,要基于更新后DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
三、请求API拿到数据并做展示
App.vue组件内容:
<template>
<div class="container">
<Search/>
<List/>
</div>
</template>
<script>
// 引入组件
import Search from "@/components/Search";
import List from "@/components/List";
export default {
// App组件名称
name: "App",
// 注册组件
components:{
Search,
List,
},
}
</script>
Search.vue组件内容:
<template>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input
type="text"
placeholder="enter the name you search"
v-model="keyWord"
/>
<button @click="searchUsers">Search</button>
</div>
</section>
</template>
<script>
// 请求接口的方法
import axios from 'axios'
export default {
name: "Search",
data(){
return{
keyWord:""
}
},
methods:{
searchUsers(){
// 请求前更新List的数据
this.$bus.$emit("updateListdata",{isFirst:false,isLoading:true,errMsg:'',users:[]})
// ${this.keyWord}拿到的值就是this.keyWord,只不过这里用到了模板字符串解析,需要用反引号。
// .then() 就是拿到请求的数据
axios.get(`https://api.github/search/users?q=${this.keyWord}`).then(
// 请求成功的返回值:response(response.data具体的返回值信息)
response => {
console.log("请求成功了",response.data)
// 请求成功后,更新List组件信息
this.$bus.$emit("updateListData",{isLoading:true,errMsg:'',users:response.data.items})
},
// 请求成功的返回值:error(error.message具体的返回值信息)
error => {
console.log("请求失败了",error.message)
// 请求失败后,更新List组件信息
this.$bus.$emit("updateListData",{isLoading:false,errMsg:error.message,users:[]})
},
)
}
},
}
</script>
List.vue组件内容:
<template>
<div class="row">
<!-- 展示用户列表:循环this.users列表里面的值,并取值做展示 -->
<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 {
name: "List",
data () {
return {
info:{
// 是否初次展示(页面初始的欢迎词)
isFirst:true,
// 是否处于加载中
isLoading:false,
// 存储错误信息
errMsg:"",
users:[]
}
}
},
methods:{
saveObj(dateObj){
console.log("我是List组件,收到了数据",dateObj)
// 方法1:这里dateObj接收的是一个对象,刚好赋值给this.info(前提是要2边格式能对应上)
// this.info = dateObj
// 方法2:通过字面量的方式去合并一个对象(举例this.info有4个属性,dateObj有3个,那就只替换相同的3个(替换的值以dateObj为主,因为dateObj在"后面"),剩下的1个不替换)
this.info = {...this.info,...dateObj}
}
},
mounted() {
this.$bus.$on("updateListData",this.saveObj)
}
}
</script>
<style scoped>
.album {
min-height: 50rem; /* Can be removed; just added for demo purposes */
padding-top: 3rem;
padding-bottom: 3rem;
background-color: #f7f7f7;
}
.card {
float: left;
width: 33.333%;
padding: .75rem;
margin-bottom: 2rem;
border: 1px solid #efefef;
text-align: center;
}
.card > img {
margin-bottom: .75rem;
border-radius: 100px;
}
.card-text {
font-size: 85%;
}
</style>
四、插槽
4.1、默认插槽,例子(数据存放在App组件,也就是插槽的使用者,往插槽传递数据)
父组件(App):在"组件标签"里面的"标签体",写一个img标签:<Category><img><Category/>
子组件(Category):写一个"slot"标签接收父组件的<img>标签:<slot>默认展示内容</slot>
App.vue组件内:父组件
<template>
<div class="container">
<Category title="美食">
<!-- 在"组件标签"里面的"标签体"写,在"Category"组件内部要使用slot(插槽)接收 -->
<img src="https://ecmb.bdimg/kmarketingadslogo/327c77f2512faf01c9430d70a4eeabae_259_194.png" alt="">
</Category>
<Category title="游戏">
<!-- 这里的ul标签先拿到数据,然后填充到Category组件内 -->
<ul>
<li v-for="(game,index) in games" :key="index">{{game}}</li>
</ul>
</Category>
</div>
</template>
<script>
// 引入组件
import Category from "@/components/Category";
export default {
// App组件名称
name: "App",
// 注册组件
components:{
Category,
},
data () {
return {
foods:['foods1','foods2','foods3'],
games:['games1','games2','games3'],
films:['films1','films2','films3'],
}
},
}
</script>
Category.vue组件内:子组件
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<!-- slot是一个特殊的标签(插槽),等待"组件的使用者"进行填充 -->
<slot>当使用者没有传递值时,会默认展示</slot>
</div>
</template>
4.2、命名插槽,例子(数据存放在App组件,也就是插槽的使用者,往插槽传递数据)
父组件(App):在"组件标签"里面的"标签体",写一个img标签并指定名称:<Category><img slot="center"><Category/>
子组件(Category):写一个"slot"标签并"设置name属性"接收父组件的<img>标签:<slot name="center">默认展示内容</slot>
App.vue组件内:父组件
<template>
<div class="container">
<Category title="美食">
<!-- 在组件标签体内写,在组件内部要使用slot(插槽)接收,要明确插槽名称:slot="xxxx" -->
<img slot="center" src="https://ecmb.bdimg/kmarketingadslogo/327c77f2512faf01c9430d70a4eeabae_259_194.png" alt="">
<div class="foot" slot="footer">
<a href="http://www.baidu">更多美食1</a>
<a href="http://www.baidu">更多美食2</a>
</div>
</Category>
<Category title="游戏">
<ul slot="center">
<li v-for="(game,index) in games" :key="index">{{game}}</li>
</ul>
<div class="foot" slot="footer">
<a href="http://www.baidu">更多游戏1</a>
<a href="http://www.baidu">更多游戏2</a>
</div>
</Category>
</div>
</template>
<script>
// 引入组件
import Category from "@/components/Category";
export default {
// App组件名称
name: "App",
// 注册组件
components:{
Category,
},
data () {
return {
foods:['foods1','foods2','foods3'],
games:['games1','games2','games3'],
films:['films1','films2','films3'],
}
},
}
</script>
Category.vue组件内:子组件
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<!-- 定义一个插槽(并给插槽命名),等待"组件的使用者"进行填充 -->
<slot name="center">当使用者没有传递值时,会默认展示1</slot>
<slot name="footer">当使用者没有传递值时,会默认展示2</slot>
</div>
</template>
4.3、作用于插槽,例子(数据存放在Category组件,也就是插槽的使用者,接收插槽的数据)
父组件(App):在"组件标签"里面的"标签体",写一个template标签:<Category><templater scop="自定义名称"></template><Category/> # 这个'自定义名称'是一个"对象"(里面的值就是插槽对应组件传递过来的)
子组件(Category):写一个"slot"标签并绑定一个属性:<slot :games="games">默认展示内容</slot>。父组件需要通过template来触发(<template scope="自定义名称">)
App.vue组件内:父组件
<template>
<div class="container">
<Category title="游戏分类">
<!-- scope="atguigu",这个"atguigu"是自定义的名称 -->
<!-- 新写法: <template slot-scope="atguigu"> -->
<template scope="atguigu">
<!-- {{atguigu}} 拿到的值是一个"对象"(里面的值就是插槽对应组件传递过来的): { "games": [ "环境", "稀有", "马力" ] } -->
<ul>
<li v-for="(game,index) in atguigu.games" :key="index">{{game}}</li>
</ul>
</template>
</Category>
<Category title="游戏分类">
<template scope="atguigu">
<!-- {{atguigu}} 拿到的值是一个对象(里面的值就是插槽对应组件传递过来的): { "games": [ "环境", "稀有", "马力" ] } -->
<ol>
<li style="color: red" v-for="(game,index) in atguigu.games" :key="index">{{game}}</li>
</ol>
</template>
</Category>
</div>
</template>
Category.vue组件内:子组件
<template>
<div class="category">
<h3>{{ title }}</h3>
<!-- 作用域插槽::games="games"。其他组件在使用这个插槽时,需要通过template来触发(<template scope="自定义名称">) -->
<slot :games="games">我是默认的一些内容</slot>
</div>
</template>
<script>
export default {
name: "Category",
props:['title'],
data () {
return {
games:['环境','稀有','马力'],
}
},
}
</script>
五、vuex
1)、概念:
专门在Vue中实现集中式状态(数据)管理的一个Vue插件,
对vue应用中多个组件的共享状态进行集中式的管理(读写),也是一种组件间通信的方式,且适用于任意组件间通信。
2)、什么时候使用Vuex:
1.多个组件依赖于同一状态(多个组件数据共享)。
2.来自不同组件的行为需要变更同一状态。
3)、vue中使用vuex的版本选择
1.vue2中用vuex3版本
2.vue3中用vuex4版本
5.1、简单分析vuex的工作原理
action,mutations,state都是对象数据类型,都经过store管理。
5.2、搭建vuex环境
安装:npm i vuex@3
5.2.1、创建文件src/store/index.js
// 该文件用于创建Vuex最核心的store
// 准备actions:用于响应组件中的动作
const actions = {}
// 准备mutations:用于操作数据(state)
const mutations = {}
// 准备state:用于存储数据
const state = {}
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)
// 创建store,并暴露(导出) store
export default new Vuex.Store({
actions:actions,
mutations:mutations,
state:state,
})
5.2.2、在main.js中创建vm时传入store配置项
// 引入store !!!
import store from './store/index'
// 创建vm
new Vue({
el:"#app",
render: h => h(App),
// 使用store !!!。原生写法:store:store,这里用的是简写方式
store,
})
5.2.3、验证是否正常(完成5.2.1和5.2.2)
在组建内通过 "console.log(this)",查看vue实例对象里面是否包含$store
5.3、vuex写一个例子(基本用法了解)
5.3.1、在vuex的"action"里面,函数 " jia(context,value){} ",传入的参数"context和value" 对应的值如下图:
// 准备actions:用于响应组件中的动作
const actions = {
// "jia:function(){}" 可以简写为 "jia(){}"
jia(context,value){
console.log(context,value)
contextmit('JIA',value)
}
}
5.3.2、在vuex的"mutations"里面,函数 " JIA(state,value){} ",传入的参数"state和value" 对应的值如下图:
// 准备mutations:用于操作数据(state)
const mutations = {
JIA(state,value){
console.log(state,value)
}
}
5.3.3、在vuex的"state"里面,存储的就是求和的值"sum",在组件内通过" this.$store.state.sum "取出。
// 准备state:用于存储数据
const state = {
sum:0
}
在组件内取store里面的值:this.$store.state.xxx
<h1>当前值为:{{this.$store.state.sum}}</h1>
5.3.4、在vuex的"actions"里面,如果想要拿到求和的值"sum",可以通过" context.state.sum "取出.
// 准备actions:用于响应组件中的动作
const actions = {
jishu(context,value){
if (context.state.sum % 2){
contextmit('JIA',value)
}
// 奇数在做加法
},
}
5.3.5、在vuex的"actions"里面,函数 " jia(context,value){} " 如果需要继续调用"actions"里面的函数,可以通过:context.dispatch('新函数名',value)
// 1、准备actions:用于响应组件中的动作
const actions = {
jian(context,value){
context.dispatch("sudada",value)
},
sudada(context,value){
contextmit("SUDADA",value)
},
}
5.4、在vuex的"getters"的使用
1.概念:当state中的数据需要经过加工后在使用时,使用getters
2.创建:在src/store/index.js里面新增getters配置
3.在组件中使用:this.$store.getters.xxxx
index.js文件内定义getters:
// 3、准备state:用于存储数据
const state = {
sum:0,
}
// 4、用于:将state中的数据进行加工
const getters = {
bigSum(state){ // 这里的"参数state"就是上面的"state"
return state.sum*10 // 这里的值要写"return形式"的返回值
}
}
Count.vue组件内使用"getters":
<h1>当前值为10倍:{{this.$store.getters.bigSum}}</h1>
5.5、vuex的"mapState"与"mapGetters"方法(计算属性computed里面代码的优化)
前言:插值语法直接获取vuex里面的值时,使用的方法是:{{ this.$store.state.school }} ,但是这种方式不简洁。简洁写法:{{ school }},但是这样是拿不到$store.state的值。
如何使用简洁的插值语法,直接获取到$store.state的值呢?
方法1:使用计算属性computed的原生写法
模板语法:
<h1>当前值为:{{ sum }}</h1>
<h1>当前值为10倍:{{ bigSum }}</h1>
<h3>我在{{ school }} 学习{{ project }}</h3>
js:
computed: {
// 方法1:直接写计算属性
sum() {
return this.$store.state.sum
},
bigSum() {
return this.$store.getters.bigSum
},
school() {
return this.$store.state.school
},
project() {
return this.$store.state.project
},
}
vuex(store/index.js):
// 该文件用于创建Vuex最核心的store
// 1、准备actions:用于响应组件中的动作
const actions = {}
// 2、准备mutations:用于操作数据(state)
const mutations = {}
// 3、准备state:用于存储数据
const state = {
sum:0,
school:"上海大学",
project:'数学',
}
// 4、用于:将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)
// 创建store,并暴露(导出) store
export default new Vuex.Store({
actions:actions,
mutations:mutations,
state:state,
getters:getters,
})
方法2:借助mapState生成计算属性,从(vuex)$store.state中读取数据(对象写法)用这种
模板语法:
<h1>当前值为:{{ sum }}</h1>
<h1>当前值为10倍:{{ bigSum }}</h1>
<h3>我在{{ school }} 学习{{ project }}</h3>
js:
import {mapState} from 'vuex'
import {mapGetters} from 'vuex'
computed: {
...mapState({sum:'sum',school:'school',project:'project'})
// 同理:借助mapGetters生成计算属性,从(vuex)$store.getters中读取数据(数组写法),函数名和方法名默认一致。
// ...mapGetters({sum:'sum',school:'school',project:'project'})},
vuex(store/index.js):
// 3、准备state:用于存储数据
const state = {
sum:0,
school:"上海大学",
project:'数学',
}
// 4、用于:将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
方法3:借助mapState生成计算属性,从(vuex)$store.state中读取数据(数组写法),函数名和方法名默认一致。
模板语法:
<h1>当前值为:{{ sum }}</h1>
<h1>当前值为10倍:{{ bigSum }}</h1>
<h3>我在{{ school }} 学习{{ project }}</h3>
js:
import {mapState} from 'vuex'
import {mapGetters} from 'vuex'
computed: {
...mapState(['sum','school','project']),
// 同理:借助mapGetters生成计算属性,从(vuex)$store.getters中读取数据(数组写法),函数名和方法名默认一致。
// ...mapGetters(['bigSum'])
},
vuex(store/index.js):
// 3、准备state:用于存储数据
const state = {
sum:0,
school:"上海大学",
project:'数学',
}
// 4、用于:将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
5.6、vuex的"mapMutations"方法(methods里面代码的优化)
方法1:使用methods的原生写法
模板语法:
<button @click="jia">加</button>
<button @click="jian">加</button>
js:
data() {
return {
user_select: 1,
}
},
methods: {
jia() {
this.$storemit("JIA", this.user_select)
},
jian() {
this.$storemit("JIAN", this.user_select)
},
}
vuex(store/index.js):
// 1、准备actions:用于响应组件中的动作
const actions = {}
// 2、准备mutations:用于操作数据(state)
const mutations = {
JIA(state,value){
state.sum += value
},
JIAN(state,value){
state.sum -= value
}
}
// 3、准备state:用于存储数据
const state = {}
// 4、用于:将state中的数据进行加工
const getters = {}
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)
// 创建store,并暴露(导出) store
export default new Vuex.Store({
actions:actions,
mutations:mutations,
state:state,
getters:getters,
})
方法2:借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)mutations(对象写法)用这种
模板语法:
<button @click="jia(user_select)">加</button>
<button @click="jian(user_select)">减</button>
js:
import {mapMutations} from 'vuex'
data() {
return {
user_select: 1,
}
},
methods: {
// 借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)mutations(对象写法)
...mapMutations({jia:"JIA",jian:"JIAN"}),
},
vuex(store/index.js):
// 2、准备mutations:用于操作数据(state)
const mutations = {
JIA(state,value){
state.sum += value
},
JIAN(state,value){
state.sum -= value
}
}
// 3、准备state:用于存储数据
const state = {
sum:0,
school:"上海大学",
project:'数学',
}
方法3:借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)mutations(数组写法)
模板语法:
<button @click="JIA(user_select)">加</button>
<button @click="JIAN(user_select)">减</button>
js:
import {mapMutations} from 'vuex'
data() {
return {
user_select: 1,
}
},
methods: {
// 借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)mutations(数组写法),函数名和方法名默认一致。
...mapMutations(['JIA','JIAN']),
},
vuex(store/index.js):
// 2、准备mutations:用于操作数据(state)
const mutations = {
JIA(state,value){
state.sum += value
},
JIAN(state,value){
state.sum -= value
}
}
// 3、准备state:用于存储数据
const state = {
sum:0,
school:"上海大学",
project:'数学',
}
5.7、vuex的"mapActions"方法(methods里面代码的优化)
方法1:使用methods的原生写法
模板语法:
<button @click="jishu">当前求和为奇数在加</button>
<button @click="waitadd">等一等</button>
js:
data() {
return {
user_select: 1,
}
},
methods: {
jishu() {
this.$store.dispatch("jishu", this.user_select)
},
waitadd() {
this.$store.dispatch("waitadd", this.user_select)
},
},
vuex(store/index.js):
// 1、准备actions:用于响应组件中的动作
const actions = {
jishu(context,value){
if(context.state.sum % 2){
contextmit("JIA",value)
}
},
waitadd(context,value){
setTimeout(()=>{
contextmit("JIA",value)
},500)
},
}
// 2、准备mutations:用于操作数据(state)
const mutations = {
JIA(state,value){
state.sum += value
},
JIAN(state,value){
state.sum -= value
}
}
// 3、准备state:用于存储数据
const state = {
sum:0,
school:"上海大学",
project:'数学',
}
// 4、用于:将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)
// 创建store,并暴露(导出) store
export default new Vuex.Store({
actions:actions,
mutations:mutations,
state:state,
getters:getters,
})
方法2:借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)actions(对象写法)用这种
模板语法:
<button @click="jishu(user_select)">当前求和为奇数在加</button>
<button @click="waitadd(user_select)">等一等</button>
js:
import {mapActions} from 'vuex'
data() {
return {
user_select: 1,
}
},
methods: {
// 借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)actions(对象写法)
...mapActions({jishu:"jishu",waitadd:"waitadd"})
},
vuex(store/index.js):
// 1、准备actions:用于响应组件中的动作
const actions = {
jishu(context,value){
if(context.state.sum % 2){
contextmit("JIA",value)
}
},
waitadd(context,value){
setTimeout(()=>{
contextmit("JIA",value)
},500)
},
}
// 2、准备mutations:用于操作数据(state)
const mutations = {
JIA(state,value){
state.sum += value
},
JIAN(state,value){
state.sum -= value
}
}
// 3、准备state:用于存储数据
const state = {
sum:0,
school:"上海大学",
project:'数学',
}
// 4、用于:将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
方法3:借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)actions(数组写法),函数名和方法名默认一致。
模板语法:
<button @click="jishu(user_select)">当前求和为奇数在加</button>
<button @click="waitadd(user_select)">等一等</button>
js:
import {mapActions} from 'vuex'
data() {
return {
user_select: 1,
}
},
methods: {
// 借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)actions(对象写法)
...mapActions(['jishu','waitadd'])
},
vuex(store/index.js):
// 1、准备actions:用于响应组件中的动作
const actions = {
jishu(context,value){
if(context.state.sum % 2){
contextmit("JIA",value)
}
},
waitadd(context,value){
setTimeout(()=>{
contextmit("JIA",value)
},500)
},
}
// 2、准备mutations:用于操作数据(state)
const mutations = {
JIA(state,value){
state.sum += value
},
JIAN(state,value){
state.sum -= value
}
}
// 3、准备state:用于存储数据
const state = {
sum:0,
school:"上海大学",
project:'数学',
}
// 4、用于:将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
5.8、vuex的模块化编码(开启命名空间)
5.8.1、总结
1.目的:让代码更好维护,让多种数据分类更加明确。
2.代码详见src/store/index.js
3.开启命名空间后,组件中读取state的数据:
方式1:this.$store.state.person.list
方式2:...mapState('person',['sum','school','subject'])
4.开启命名空间后,组件中读取getters的数据:
方式1,自己直接读:
this.$store.getters["personAbout/firstPersonName"]
方式2,借助mapGetters读取:
...mapGetters('count',['bigSum'])
5.开启命名空间后,组件中调用dispatch
方式1,自己直接dispatch
this.$store.dispatch('person/addPersonWang',personObj)
方式2,借助mapActions
...mapActions("count",["jishu","waitadd"]),
6.开启命名空间后,组件中调用commit
方式1,自己直接commit
this.$storemit('person/ADDPERSON',personObj)
方式2,借助mapMutations
...mapMutations("count",{"jia":"JIA","jian":"JIAN"}),
5.8.2、例子如下
store/index.js文件
// 该文件用于创建Vuex最核心的store
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)
// 求和相关配置项
const countOptions = {
// 开启命名空间
namespaced: true,
actions: {
jishu(context, value) {
if (context.state.sum % 2) {
contextmit("JIA", value)
}
},
waitadd(context, value) {
setTimeout(() => {
contextmit("JIA", value)
}, 500)
},
},
mutations: {
JIA(state, value) {
state.sum += value
// console.log("mutations",state,value)
},
JIAN(state, value) {
state.sum -= value
},
},
state: {
sum: 0,
school: "上海大学",
project: '数学',
},
getters: {
bigSum(state) {
return state.sum * 10
}
},
}
// 人员相关配置项
const personOptions = {
// 开启命名空间
namespaced: true,
actions: {
addPersonWang(context, value){
if (value.name.indexOf('王') ===0){
contextmit('ADD_PERSON',value)
}else {
alert('必须姓王')
}
},
},
mutations: {
ADD_PERSON(state, value) {
state.personList.unshift(value)
},
},
state: {
personList: [
{id: '001', name: 'sudada'}
],
},
getters: {
firstPersonName(state){
return state.personList[0].name
},
},
}
// 创建store,并暴露(导出) store。
export default new Vuex.Store({
// 命名空间写法
modules: {
countOptions: countOptions,
personOptions: personOptions
}
})
vue组件文件(Count.vue),vuex使用命名空间时的"简写方式"(通过mapState,mapGetters,mapActions,mapMutations这些方法)
<template>
<div>
<h1>当前值为:{{ sum }}</h1>
<h2>当前值为10倍:{{ bigSum }}</h2>
<h3>我在{{ school }} 学习{{ project }}</h3>
<h3 style="color: red">Person组件的总人数是:{{personList.length}}</h3>
<select v-model.number="user_select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="jia(user_select)">加</button>
<button @click="jian(user_select)">减</button>
<button @click="jishu(user_select)">当前求和为奇数在加</button>
<button @click="waitadd(user_select)">等一等</button>
</div>
</template>
<script>
import {mapState} from 'vuex'
import {mapGetters} from 'vuex'
import {mapActions} from 'vuex'
import {mapMutations} from 'vuex'
export default {
name: "Count",
data() {
return {
user_select: 1,
}
},
computed: {
// 借助mapState生成计算属性,从countOptions里面的$store.state中读取数据(对象写法)
...mapState('countOptions',{sum:'sum',school:'school',project:'project'}),
// 借助mapState生成计算属性,从personOptions里面的$store.state中读取数据(对象写法)
...mapState('personOptions',{personList:'personList'}),
// 借助mapGetters生成计算属性,从countOptions里面的$store.getters中读取数据(对象写法)
...mapGetters('countOptions',{bigSum:'bigSum'})
},
methods: {
// 借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)countOptions里面的mutations(对象写法)
...mapMutations('countOptions',{jia:"JIA",jian:"JIAN"}),
// 借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)countOptions里面的actions(对象写法)
...mapActions('countOptions',{jishu:"jishu",waitadd:"waitadd"}),
},
}
</script>
<style scoped>
button {
margin-right: 5px;
}
</style>
vue组件文件(Person.vue),vuex使用命名空间时的"原生写法"(通过this.$store.xxx.xxx调用)
<template>
<div>
<h1>人员列表</h1>
<h3 style="color: red">Count组件求和值为:{{sum}}</h3>
<h3>列表中第一个人的名字:{{firstPersonName}}</h3>
<input type="text" placeholder="请输入名字" v-model="name">
<button @click="add">添加</button>
<button @click="addWang">添加一个姓王的</button>
<ul>
<li v-for="p in personList" :key="p.id">{{ p.name }}</li>
</ul>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name: "Person",
data(){
return {
name:''
}
},
computed:{
personList(){
return this.$store.state.personOptions.personList
},
sum(){
return this.$store.state.countOptions.sum
},
firstPersonName(){
return this.$store.getters['personOptions/firstPersonName']
},
},
methods:{
add(){
const personObj = {id:nanoid(),name:this.name}
this.$storemit('personOptions/ADD_PERSON',personObj)
this.name = ''
},
addWang(){
const personObj = {id:nanoid(),name:this.name}
this.$store.dispatch('personOptions/addPersonWang',personObj)
this.name = ''
},
}
}
</script>
<style scoped>
</style>
六、路由器
6.1、初识路由器
1.vue-router的理解:
vue的一个专门插件库,专门用来实现SPA应用
2.对于SPA的理解:
1.单网页应用
2.整个应用只有一个完整版的页面(index.html)
3.点击页面的导航链接不会刷新页面,只会做页面的局部刷新。
4.数据通过ajax请求获取
3.路由:就是"一组key-value的对应关系"。
4.多个路由:需要经过"路由器"的管理。
5.安装vue-router:npm install vue-router@3 vue2版本安装vue-router@3,vue3版本安装vue-router@4
6.编写router配置项:router/index,js
// 该文件专门用于创建应用的路由器
// 引入VueRouter插件
import VueRouter from 'vue-router'
// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
// "创建"并"暴露"这个路由器
export default new VueRouter({
routes:[
// 一个个路由对象:
{
path:'/about', // (key)请求路径
component:About, // (value)请求路径对应的"组件名称"
},
{
path:'/home', // (key)请求路径
component:Home, // (value)请求路径对应的"组件名称"
},
]
})
7.引用和注册路由:main.js
// 引入vue
import Vue from 'vue'
// 引入App
import App from './App'
// 关闭生产提示
Vue.config.productionTip = false
// 引入VueRouter
import VueRouter from 'vue-router'
// 应用VueRouter
Vue.use(VueRouter)
// 引入路由器
import router from "./router/index"
// 创建vm
new Vue({
el:"#app",
render: h => h(App),
// vm注册router
router:router,
})
8.使用路由器:实现路由切换,主要是 "router-link" 和 to="/about"
<div>
// 使用router-link实现页面的切换,to="/about"就是跳转到哪个路径
// active-class="" 元素被激活时候,显示的样式
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<hr>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
9.使用路由器:指定位置展示(组件内容)
<div class="panel-body">
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
10.使用路由器时的几个注意点:
1."路由器组件"通常存放在"pages"文件夹,"其他组件"放在components文件夹
2.每次组件之间的切换,没有被展示页面的组件,都会被销毁。需要用到的时候再去挂载。
3.每个组件都有自己的$route(路由)属性,里面存储自己的路由信息
4.整个应用只有一个router(路由器),可以通过组件的$router属性获取到。
6.2、多级路由(嵌套路由)
1.路由规则配置,在一级路由内使用 "children" 关键字,详见:src/router/index.js
// 该文件专门用于创建应用的路由器
// 引入VueRouter插件
import VueRouter from 'vue-router'
// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
// 暴露这个路由器
export default new VueRouter({
routes:[
{
// 一级路由
path:'/about', // 请求路径
component:About, // 请求路径对应的"组件名称"
},
{
// 一级路由
path:'/home', // 请求路径
component:Home, // 请求路径对应的"组件名称"
children:[ // children关键字定义子路由
{
// 子路由(二级路由)
path:'news', // 二级路由请求path,不加"/"
component:News,
},
{
// 子路由(二级路由)
path:'message', // 二级路由请求path,不加"/"
component:Message,
},
]
},
]
})
2.多级路由(嵌套路由)的跳转路径,要写绝对路径:
<div>
<ul class="nav nav-tabs">
//使用router-link实现页面的切换,to="/home/news"就是跳转到哪个路径(这里要写绝对路径)
<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>
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
6.3、路由传参,query方式(方法一)
1.在组件内写一个路由传参代码【to="/home/message/detail?id=666&title=Hello!"】,传递的参数是:{id:"666",title:"Hello!"}
在其他组件内可以通过:this.$route.query 拿到,如下图:
2.路由传参的query方法使用:
方式1(传参时,to的字符串写法):
【:to="`/home/message/detail?id=${m.id}&title=${m.title}`"】注释: ":to"后面的东西会当做js语法解析。加了``(模板语法):可理解为'',${message.id} 就是在模板语法(``)里面取变量的值(js里面取变量的方式)
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>
方式2(传参时,to的对象写法):
<router-link :to="{
path:'/home/message/detail', // 请求的路径,关键字path
query:{ // 请求的参数(封装成一个对象),关键字query
id:m.id,
title:m.title,
}
}">
{{m.title}}
</router-link>
3.组件内接收路由传递过来的参数:this.$route.query 即可拿到。
<template>
<div>
<ul>
<li>消息编号: {{ $route.query.id }}</li>
<li>消息标题: {{ $route.query.title }}</li>
</ul>
</div>
</template>
6.4、路由命名
作用:可以简化路由的跳转
1.给路由命名
routes:[
{
name:"guanyu", // 路由命名,关键字name
path:'/about',
component:About,
},
]
2.使用命名的路由:(前提条件,只能在对象里面使用,也就是:to={})
<router-link :to="{
name:'xiangqing', // 通过给路由命名的方式(前提条件,只能在对象里面使用,也就是:to={}),这里就不写path,写name即可。
query:{
id:message.id,
title:message.title,
}
}">
{{message.title}}
</router-link>
6.5、路由传参,params方式(方法二)
1.在组件内写一个路由传参代码【:to="`/home/message/detail/666/Hello!`"】,传递的参数是:{id:"666",title:"Hello!"}
在其他组件内可以通过:this.$route.query 拿到,如下图:
对应路由器index.js里面:(由path:'detail'改为path:'detail/:id/:title')
{
path:'detail/:id/:title', // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
component:Detail,
},
2.路由传参的params方法使用:
方式1(传参时,to的字符串写法):
【:to="`/home/message/detail/${m.id}/${m.title}!`"】
<router-link :to="`/home/message/detail/${m.id}/${m.title}!`">{{m.title}}</router-link>
对应路由器index.js里面:(由path:'detail'改为path:'detail/:id/:title')
{
path:'detail/:id/:title', // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
component:Detail,
},
方式2(传参时,to的对象写法):
<router-link :to="{
name:'xiangqing', // 这里必须使用name,也就是路由的名称。不能写path
// path:'/home/message/detail', // 这里不能写path,会报错
params:{
id:m.id,
title:m.title,
}
}">
{{m.title}}
</router-link>
对应路由器index.js里面:(由path:'detail'改为path:'detail/:id/:title'),同时还必须加上name(路由命名)
{
name:'xiangqing',
path:'detail/:id/:title', // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
component:Detail,
},
3.组件内接收路由传递过来的参数:this.$params.query 即可拿到。
<template>
<div>
<ul>
<li>消息编号: {{ $route.params.id }}</li>
<li>消息标题: {{ $route.params.title }}</li>
</ul>
</div>
</template>
6.6、路由传参,props方式(方法三)
作用:让路由组件更方便的收到参数
方式1:props传递一个对象(该对象中的所有key,value都会以props的形式传给Detail组件,Detail组件通过props接收。)
router/index.js
{
path:'message', // 子路由请求path,不加"/"
component:Message,
children:[
{
name:'xiangqing',
path:'detail',
component:Detail,
// props的第一种写法:值为对象,该对象中的所有key,value都会以props的形式传给Detail组件,Detail组件通过props接收。
props:{a:1, b:2,}
},
]
},
在组件内接收props传递的值:props:["a","b"]
<template>
<div>
<ul>
<li>a:{{a}}</li>
<li>b:{{b}}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Detail",
props:["a","b"],
}
</script>
方式2:props传递一个布尔值(props为true时,就把该路由组件收到的所有params参数,都会以props的形式传给Detail组件,Detail组件通过props接收。)
router/index.js
{
path:'message', // 子路由请求path,不加"/"
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
component:Detail,
// props的第二种写法:值为布尔值,为true就把该路由组件收到的所有params参数,都会以props的形式传给Detail组件,Detail组件通过props接收。
props:true
},
]
},
在组件内接收props传递的值:props:["id","title"]
<template>
<div>
<ul>
<li>消息编号: {{ id }}</li>
<li>消息标题: {{ title }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Detail",
props:["id",'title']
}
</script>
方式3:props传递一个值为函数,通过接收参数'$route'的方式取出值,以props的形式返回给Detail组件,Detail组件通过props接收。
router/index.js
{
path:'message', // 子路由请求path,不加"/"
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
component:Detail,
// props的第三种写法(推荐):值为函数,通过接收参数'$route'的方式取出值,以props的形式返回给Detail组件,Detail组件通过props接收。
// 通过query传参就使用$route.query查到值,
// 通过params传参就使用$route.params查到值
props($route){
console.log($route)
return {id: $route.params.id, title: $route.params.title}
},
},
]
},
在组件内接收props传递的值:props:["id","title"]
<template>
<div>
<ul>
<li>消息编号: {{ id }}</li>
<li>消息标题: {{ title }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Detail",
props:["id",'title']
}
</script>
6.7、router-link 的 push 和 replace属性
作用:控制路由跳转时,操作浏览器历史记录的模式
1.router-link默认是"push"模式,使用push模式(追加历史记录),浏览器可以正常前进后退
2.:replace="true" 简写为 replace :开启router-link的"replace"模式 (替换当前记录,开启之后浏览器不能前进后退了)
3.开启:
6.8、编程式路由导航
作用:不借助 router-link 的路由导航,让路由跳转更加灵活
router-link写法的路由导航:
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<router-link :to="{
path:'/home/message/detail', // 这里不能写path,会报错
query:{
id:m.id,
title:m.title,
}
}">
{{ m.title }}
</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Message",
data() {
return {
messageList: [
{id: "001", title: "消息001"},
{id: "002", title: "消息002"},
{id: "003", title: "消息003"},
]
}
},
}
</script>
编程时路由导航写法(不需要在写router-link了):使用了this.$router.push({xxx}) 和 this.$router.replace({xxx})
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<button @click="pushShow(m)">push查看</button>
<button @click="replaceShow(m)">replace查看</button>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Message",
data() {
return {
messageList: [
{id: "001", title: "消息001"},
{id: "002", title: "消息002"},
{id: "003", title: "消息003"},
]
}
},
methods: {
pushShow(m) {
// push 属性
this.$router.push({
path:'/home/message/detail',
query:{
id:m.id,
title:m.title,
}
})
},
replaceShow(m){
// replace 属性
this.$router.replace({
path:'/home/message/detail',
query:{
id:m.id,
title:m.title,
}
})
},
}
}
</script>
模拟浏览器的前进和后退模式:
methods:{
back(){
// 相当于浏览器的回退功能
this.$router.back()
},
forward(){
// 相当于浏览器的前进功能
this.$router.forward()
},
testgo(){
// 相当于浏览器的"前进|后退"功能,正数就是前进N步,负数就是后退N步
this.$router.go(-1)
},
6.9、缓存路由组件
作用:让不展示的组件保持挂载,不被销毁
缓存单个组件时的写法:代码里面的"News" 指的是"组件名"
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
缓存多个组件时的写法:代码里面的 'News'和'Message' 指的是"组件名"
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>
缓存所有组件时的写法:
<keep-alive>
<router-view></router-view>
</keep-alive>
6.10、路由的生命周期钩子
作用:路由组件独有的2个钩子,用户捕获路由组件的激活状态
activated:"组件"激活(被挂载)的时候,触发这个API。
deactivated:"组件"失活(从"被挂载"变为"取消挂载")的时候,触发这个API。
<script>
export default {
name: "News",
activated() {
console.log("News组件activated(激活)了")
},
deactivated() {
console.log("News组件deactivated(失活)了")
}
}
</script>
6.11、路由守卫
作用:对路由进行权限控制
分类:全局守卫,独享守卫,组件内守卫
6.11.1、全局前置路由守卫(router/index.js):router.beforeEach
// 该文件专门用于创建应用的路由器
// 引入VueRouter插件
import VueRouter from 'vue-router'
// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
import Detail from "@/pages/Detail";
// 创建一个路由器
const router = new VueRouter({
routes:[
{
// 一级路由
name:'guanyu',
path:'/about', // 请求路径
component:About, // 请求路径对应的"组件名称"
meta: {isAuth:true}, // 添加自定义的"权限校验"字段
},
]
})
// 全局"前置"路由守卫
router.beforeEach(function (to,from,next){
console.log('在每一次路由切换(路由路径改变)之前,都会调用下面的"函数"')
// to: 要去往哪个"路由"地址
console.log('to',to)
// from:从哪个"路由"地址过来的
console.log("from",from)
// next:放行(不写"next()"的话,每次请求都会被路由守卫拦住,无法前进到目标路由)
// 方式一:通过to.path判断请求路径的方式
if(to.path === '/home/news' || to.path === '/home/message'){
if(localStorage.getItem('school') === 'shanghai'){
next()
}else {
alert("当前学校无权限查看")
}
}else {
next()
}
// 方式二:通过"to.meta"(专门用来存放自定义数据的对象,需要提前在路由里面定义meta:{}),判断是否走路由守卫的校验。
if(to.meta.isAuth){
if(localStorage.getItem("school") === 'shanghai'){
next()
} else {
alert('当前学校无权限查看')
}
} else {
next()
}
})
// 暴露这个路由器
export default router
6.11.2、全局后置路由守卫(router/index.js):router.afterEach
// 该文件专门用于创建应用的路由器
// 引入VueRouter插件
import VueRouter from 'vue-router'
// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
import Detail from "@/pages/Detail";
// 创建一个路由器
const router = new VueRouter({
routes:[
{
// 一级路由
name:'guanyu',
path:'/about', // 请求路径
component:About, // 请求路径对应的"组件名称"
meta: {title:'guanyu'}, // 添加自定义字段
},
]
})
// 全局"后置"路由守卫
router.afterEach((to,from)=>{
console.log('"后置"路由守卫,在每一次路由切换(路由路径改变)之后,都会调用下面的"函数"')
// to: 要去往哪个"路由"地址
console.log('to',to)
// from:从哪个"路由"地址过来的
console.log("from",from)
// 修改页签标头(在页面切换之后)
document.title = to.meta.title
})
// 暴露这个路由器
export default router
6.11.3、独享前置路由守卫(无后置)(router/index.js):beforeEnter
某一个路由所独享的:
// 该文件专门用于创建应用的路由器
// 引入VueRouter插件
import VueRouter from 'vue-router'
// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
import Detail from "@/pages/Detail";
// 创建一个路由器
const router = new VueRouter({
routes:[
{
// 一级路由
name:'guanyu',
path:'/about', // 请求路径
component:About, // 请求路径对应的"组件名称"
meta: {title:'zhuye',isAuth:true}, // 添加自定义字段
// 组件独享的路由守卫:只有在进入到"News"组件时被调用,同时调用下面的"函数"(普通函数和箭头函数都可以)。
beforeEnter: function (to,from,next){
console.log('组件独享的路由守卫')
// to: 要去往哪个"路由"地址
console.log('to',to)
// from:从哪个"路由"地址过来的
console.log("from",from)
if(to.meta.isAuth){
if(localStorage.getItem("school") === 'shanghai'){
next()
} else {
alert('当前学校无权限查看')
}
} else {
next()
}
}
},
]
})
// 暴露这个路由器
export default router
6.11.4、组件内路由守卫
定义路由:router/index.js
// 该文件专门用于创建应用的路由器
// 引入VueRouter插件
import VueRouter from 'vue-router'
// 引入组件
import About from "@/pages/About";
// 创建一个路由器
const router = new VueRouter({
routes:[
{
// 一级路由
name:'guanyu',
path:'/about', // 请求路径
component:About, // 请求路径对应的"组件名称"
meta: {title:'guanyu',isAuth:true}, // 添加自定义字段
},
]
})
// 暴露这个路由器
export default router
组件内路由守卫写法:About.vue组件
通过路由规则,"进入"到当前组件之前,会被调用:beforeRouteEnter
通过路由规则,在"离开"当前组件之前,会被调用:beforeRouteLeave
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name: "About",
// 通过路由规则,"进入"到当前组件之前,会被调用
beforeRouteEnter( to,from,next ){
console.log("beforeRouteEnter")
if(to.meta.isAuth){
if(localStorage.getItem("school") === 'shanghai'){
next()
} else {
alert('当前About组件无权限查看')
}
} else {
next()
}
},
// 通过路由规则,在"离开"当前组件之前,会被调用
beforeRouteLeave(to,from,next){
console.log("beforeRouteLeave")
next()
},
}
</script>
6.12、路由器的两种工作模式
对于一个url来说,什么是hash值? #号及后面的内容就是hash值。
1.hash模式:
1.地址中一直带着#号,不美观。
2.若以后将地址通过第三方手机APP分享,若app校验严格,则会将地址标记为不合法。
3.兼容性较好
2.history模式:
1.地址干净,美观。
2.兼容性和hash模式相比略差。
3.应用部署上线后,需要后端人员支持,解决刷新页面404的问题。
// 该文件专门用于创建应用的路由器
// 引入VueRouter插件
import VueRouter from 'vue-router'
// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
import Detail from "@/pages/Detail";
// 创建一个路由器
const router = new VueRouter({
// 设置路由器的工作模式,默认是hash模式
mode:'history',
routes:[
{
name:'guanyu',
path:'/about', // 请求路径
component:About, // 请求路径对应的"组件名称"
meta: {title:'guanyu',isAuth:true}, // 添加自定义字段
},
]
})
// 暴露这个路由器
export default router
七、Vue UI 组件库 (PC端)
1.Element UI https://element.eleme/
安装:npm i element-ui -S
2.完整引入Element库(组件依赖过多,文件很大)
// 引入vue
import Vue from 'vue'
// 引入App
import App from './App'
// 关闭生产提示
Vue.config.productionTip = false
// 引入ElementUI组件库
import ElementUI from 'element-ui';
// 引入ElementUI全部样式
import 'element-ui/lib/theme-chalk/index.css';
// 应用ElementUI
Vue.use(ElementUI);
// 创建vm
new Vue({
el:"#app",
render: h => h(App),
})
组件内使用:App.vue
<template>
<div>
<el-button>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
</el-button>
<hr>
<el-date-picker
type="date"
placeholder="选择日期">
</el-date-picker>
</div>
</template>
<script>
export default {
// App组件名称
name: "App",
}
</script>
3.局部引用:main.js
先安装:npm install babel-plugin-component -D
// 引入vue
import Vue from 'vue'
// 引入App
import App from './App'
// 关闭生产提示
Vue.config.productionTip = false
// 按需引入(借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。)
import { Button,Row,DatePicker } from 'element-ui';
// 应用ElementUI,原生的方式
Vueponent(Button.name,Button)
Vueponent(Row.name,Row)
Vueponent(DatePicker.name,DatePicker)
// 创建vm
new Vue({
el:"#app",
render: h => h(App),
})
然后修改:babel.config.js
// 按需引入配置写法
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
["@babel/preset-env", { "modules": false }],
],
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
组件内使用:App.vue
<template>
<div>
<el-button>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
</el-button>
<hr>
<el-date-picker
type="date"
placeholder="选择日期">
</el-date-picker>
</div>
</template>
<script>
export default {
// App组件名称
name: "App",
}
</script>
<!--
checked:直接使用就是默认勾选。
:checked="true/false",动态决定标签内一个属性是否存在,true存在checked,false不存在checked。
@click="handleCheck(todoObj.id):绑定点击事件逻辑,并传入id值。
@change="handleCheck(todoObj.id)":捕捉改变的事件,这是二选一即可。
-->
版权声明:本文标题:VUE框架(二) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1729002787h1305592.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论