Vue组件相关记录

Vue组件开发

非单文件组件

创建组件api Vue.extend({})

    const student = Vue.extend({
        template: `
            <div>{{studentName}} - {{age}}</div>

        `,
        data() {
            return {
                studentName: 'jjking',
                age: 12
            }
        }
    })


 new Vue({
      el: '#app',
      //局部注册
      components: {
          student: student
      }
  })

不能使用el,因为按理来说,组件不应该固定挂载谁,
并且data返回的是函数,不能写成对象

局部注册
在components里边

使用就是用<student></student>

全局注册
Vue.component('student',student);
前面是组件名,后面是组件

注意事项
组件名相关问题

组件名在开发者工具中,都是首字母大写的

一个单词组成: 大小写都可以
school
School

但是组件注册的时候写什么名字,你的标签就得写什么名字,避免出错

局部注册
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)

其他,我们可以使用name来指定开发者工具里边的呈现的名字,所以只是为了好看,我么实际用还是用注册用的名字

关于组件的本质

  1. school组件的本质是VueComponent的构造函数,每次vm会帮助我们创建一个全新的VueComponent,但是这样的工作不用我们程序员干
  2. 关于this的指向
    (1) 在组件中,this指向组件实例对象
    (2) 在vm中,指向的是Vue实例对象

一个重要的内置关系

vc的prototype.__proto__ 指向的是Vue的原型对象

也就是vc可以访问到Vue原型对象上的属性和方法

换个意思讲,如果我们在Vue的原型对象上写了一个属性,我们在vc中可以拿得到

单文件组件

默认来看,生成一个vue组件是
const a = Vue.extend({options})

但是我们写单文件组件的时候

<template>
  <div>
    <h2>学校名称: {{name}}</h2>

    <h2>学校地址: {{address}}</h2>

  </div>

</template>

<script>
export default {
    name: 'School',
    data() {
        return {
            name: '光景',
            address: '白云区'
        }
    }
} 
</script>

<style>

</style>

export的时候,是直接抛出一个{}也就是一个对象,这里是简写的形式
const a = options
在app页面中,我们导入一个组件的时候,他会自动识别

组件通信

父子组件通信

父传子 -> props

子传父(初级用法)
例如父亲是app.vue
儿子是MyHeader.vue

父组件发一个函数给儿子
儿子在这个函数中把要传的数据传给父亲

准确的说,儿子把要传的数据传到这个函数的参数上

App.vue

<!-- 头部 -->
<MyHeader :addTodo="addTodo"></MyHeader>

...
methods: {
  addTodo(todoObj) {
    // console.log('我是App组件,我收到了数据x',todoObj);
    this.todoList.unshift(todoObj)
  }
}

子组件是MyHeader

export default {
  name: 'MyHeader',
  props: ['addTodo'],
  methods: {
    add(e) {
        const todoObj = ...;
        this.addTodo(todoObj);
      }
  }
}

这个方法十分的诡异,但是确实是可以用的

组件自定义事件 子 -> 父

子传父,我们可以通过props 父亲给儿子传递一个函数,儿子在合适的时候触发这个事件,父亲就可以收到儿子传递过来的数据

这里的小项目就是为了说明组件自定义事件的,非常小的项目
父亲是app.vue
儿子分别是student.vue 和 school.vue

App.vue

<template>
    <div class="app">
        <h1>{{msg}}</h1>

        <School/>
        <Student/>
    </div>

</template>

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

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

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

Student.vue

<template>
    <div class="student">
        <h2>学生姓名:{{name}}</h2>

        <h2>学生性别:{{sex}}</h2>

    </div>

</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'张三',
                sex:'男',
            }
        },
    }
</script>

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

School.vue

<template>
    <div class="school">
        <h2>学校名称:{{name}}</h2>

        <h2>学校地址:{{address}}</h2>

    </div>

</template>

<script>
    export default {
        name:'School',
        props:['getSchoolName'],
        data() {
            return {
                name:'Tom学校',
                address:'北京',
            }
        }
    }
</script>

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

页面显示

props传

props传当然是最为简陋的方式,并且传的是,函数
在App.vue上,我们绑定一个函数来接收来自儿子的数据

<School :getSchoolName="getStudentData"/>
...
methods: {
   getStudentData(data) {
     console.log('App组件收到了来自儿子的事件',data);
   }
 }

在子组件上,我们在合适的时机来触发这个事件,把数据传回App.vue
School.vue

        <!-- 通过props,子传父数据 -->
        <button @click="sendSchoolName">点我发送给父亲app.vue数据</button>

...

​```bash
        props:['getSchoolName'],
        data() {
            return {
                name:'Tom学校',
                address:'北京',
            }
        },
        methods: {
            sendSchoolName() {
                this.getSchoolName(this.name)
            }
        }

这样的写法,实在称不上好,但是能用

绑定自定义事件

通过v-on绑定自定义事件

我们给组件绑定绑定自定义事件
在App.vue上

<School v-on:jjking="getStudentData"/>
...
methods: {
    getStudentData(data) {
      console.log('App组件收到了来自儿子的事件',data);
    }
}

这个自定义事件是在vc上的,触发条件就是我们在子组件调用
this.$emit('自定义事件名换',data)

这个emit,意思就是发射,就像子组件发射数据到父组件一样,实际上,也是触发父组件的方法

在这里就是在子组件中,我们绑定了一个事件叫做jjking
我们子组件只要触发这个jjking事件就行

事件的回调方法叫做getStudentData

在子组件

<!-- 通过自定义事件$emit触发父亲给儿子绑定的事件 -->
<button @click="sendSchoolName">点我发送给父亲app.vue数据</button>

...
methods: {
    sendSchoolName() {
        this.$emit('jjking',this.name)
    }
}

通过ref绑定自定义事件

<!-- 通过ref绑定自定义事件 -->
<School ref="student"/>

methods: {
  getStudentData(data) {
    console.log('App组件收到了来自儿子的事件',data);
  }
},
mounted() {
  this.$refs.student.$on('jjking',this.getStudentData)
}

this.$refs.student 其实就是组件vc我们在这上面绑定jjking自定义事件,回调函数时this.getStudentData

相比看下来,其实第一种绑定的方式更为简单,但为什么我们要用ref来绑定呢,原因是这样子会更加的灵活

例如我们如果想要触发3秒之后,再启用getStudentData的回调函数,此时第一种方式就直接写死了,模版解析到<School/>组件的时候,啪一下,就执行了回调函数了,然而我们通过ref来绑定,我们可以直接直接写

mounted() {
  setTimeout(() => {
    this.$refs.student.$on('jjking',this.getStudentData)
  },3000)
}

如果我们想要触发一次,我们就可以换成$once

mounted() {
  this.$refs.student.$once('jjking', this.getStudentData)
}

注意:

这里的this.getStudentData不能写成普通的函数类似于下面这样

    this.$refs.student.$on('jjking', function getStudentData(data) {
      console.log('App组件收到了来自儿子的事件', data);
    })

眼下看的时候没有问题,但是如果涉及到this的操作的话,就会出问题,这里的this不再是App这个组件了,而是他的儿子组件School.vue,原因是在Vue中,谁触发了事件,this就指向谁,此时this就是School的vc

这里的解决办法就是我们可以写箭头函数,因为箭头函数没有this,他的this会去外边一层去找this的指向,所以这里的this会找到mounted的this

而vue中,已经把mounted的this指向指向了App,所以箭头函数是不会出bug的

触发事件时传递多个参数

// 方式一

school.vue
<script>
        methods: {
            sendStudentlName(){
                this.$emit('atguigu',this.name,666,888,900)
            },
        },
</script>

app.vue
<script>
        methods: {
            getStudentName(name,x,y,z){
                console.log('App收到了学生名:',name,x,y,z)
            },
        },
    }
</script>

开发中的方式
// 方式一 :
    把数据包装成一个对象传递过去
school.vue
<script>
        methods: {
            sendStudentlName(){
                this.$emit('atguigu',{})
            },
        },
</script>

// 方式二:
    es6 写法  正常传递,接收
school.vue
<script>
        methods: {
            sendStudentlName(){
                this.$emit('atguigu',this.name,666,888,900)
            },
        },
</script>

app.vue
<script>
        methods: {
           // name 正常结构,其他的参数不管传递多少,整理到params数组上
            getStudentName(name,...params){
                console.log('App收到了学生名:',name,params)
            },
        },
    }
</script>

要么参数一个一个写,要么用...params这是es6的语法

解绑自定义事件

<!-- 解绑自定义事件 -->
<button @click="unbind">点我解绑自定义事件</button>


unbind() {
    this.$off('jjking')
    // this.$off(['jjking','demo']) //解绑多个自定义事件
    // this.$off() //解绑所有的自定义事件
}

组件绑定事件默认不使用内置事件

// 这么写会被默认当做自定义事件
<Student ref="student" @click="show"/> 

//加上native 原生的,本来的,才会使用到内置事件
<Student ref="student" @click.native="show"/> 

必须写native才行

总结

全局事件总线(任意组件间通信)

安装全局事件总线,在main.js中

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

Vue.config.productionTip = false

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

Student.vue

<template>
    <div class="student">
    </div>

</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'张三',
                sex:'男',
            }
        },
        mounted() {
            this.$bus.$on('getData',(data) => {
                console.log(data);      
            })
        }
    }
</script>

绑定事件

在兄弟组件School.vue发送数据到Student.vue

<template>
    <div class="school">
        <button @click="sendData">点我发送数据</button>

    </div>

</template>

<script>
    export default {
        name:'School',
        data() {
            return {
                name:'Tom学校',
                address:'北京',
            }
        },
        methods: {
            sendData() {
                this.$bus.$emit('getData',this.name);
            }
        }
    }
</script>

和子传父里边的写法是一样不过,这里的突然加了一个中间商,不再是App.vue
而是一个新的人$bus

注意
不用这个这个组件的时候,需要自己去解绑当前组件所用到的事件

beforeDestroy() {
    this.$bus.$off('getData')
}

消息订阅和发布(任意组件)

消息订阅库有很多,这里使用pubsub

安装pubsub

npm i pubsub-js

引入

import pubsub from 'pubsub-js'

组价需要收消息,就订阅消息,回调留在自身

methods: {
    demo(msgName,data) {
        ...
    }
},
mounted() {
    this.pid = pubsub.subscribe('xxx',this.demo);
},
beforeDestroy() {
    pubsub.unsubscribe(this.pid);
}

需要注意的是,回调的第一个参数是消息名字,第二个才是data,
且和全局事件总线一样,也需要解绑消息

组件发送消息

    pubsub.publish('xxx',数据)

vuex

共享数据,用于多个组件之间的通信

原理

在这里插入图片描述
Actions
Mutations
State
三个都是对象类型

组件想要加2,
然后dispatch(‘jia’,2) 发给Actions,
Actions再commit给Mutations
Mutations进行操作State
最后重新渲染给Vue组件

这三个由store来管理,并且dispatch commit这些api都是store提供的

Actions类似服务员,Mutations类似餐厅后厨,State类似菜
组件类似客人

我们可以跳过Actions(服务员)来直接叫他点菜,但是有时候,服务员需要介入点菜的过程,例如ajax请求,可以在这个时候发

搭建vuex环境

vue2 ,要用vuex3版本
vue3,要用vuex4版本

npm i vuex 默认安装的是vuex4

npm i vuex@3

如上我们的vue2需要vuex3

在src中创建一个store文件夹,添加index.js

在这里插入图片描述

index.js

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
})

main.js

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

Vue.config.productionTip = false

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

需要注意一个小问题,Vue.use(Vuex)必须写到index.js里边,如果我们在main.js
中使用的话,会报错
在这里插入图片描述
不管你Vuex是否是写到import之前,js解析,会先把所有import的代码先执行,不会先执行你的代码,所以我们得把Vue.use(Vuex)写到index.js里边

基本使用

<template>
    <div class="category">
        <h1>当前求和为{{$store.state.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="add">+</button>
        <button @click="minus">-</button>
        <button @click="oddAdd">当前求和为奇数再加</button>
        <button @click="delayAdd">等一等再加</button>
    </div>
</template>

<script>
export default {
    name: 'Category',
    data() {
        return {
            n: 1
        }
    },
    methods: {
        add() {
            this.$store.dispatch('jia',this.n);
        },
        minus() {
            this.$store.commit('JIAN',this.n);
        },
        oddAdd() {
            if(this.$store.state.sum % 2 == 1) {
                this.$store.dispatch('jia',this.n);
            }
        },
        delayAdd() {
            setTimeout(() => {
                this.$store.dispatch('jia',this.n);
            }, 5000);
        },
    },
}
</script>

拿数据就在$store.state里边拿取

调用的是dispatch,走的就是actions,
如果我们直接调用commit就是跳过actions,直接找到mutations

index.js

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const actions = {
    jia(miniStore,value) {
        console.log('actions里边的jia方法',miniStore,value);
        miniStore.commit('JIA',value);
    },
    jian(miniStore,value) {
        console.log('actions里边的jian方法',miniStore,value);
        miniStore.commit('JIAN',value);
    }
};
const mutations = {
    JIA(state,value) {
        console.log('mutations里边的JIA方法',state,value);
        state.sum += value;
    },
    JIAN(state,value) {
        console.log('mutations里边的JIAN方法',state,value);
        state.sum -= value;
    },
};
const state= {
    sum: 0
};

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

一般来说,mutations里边的方法是大写,actions里边的方法是小写

我们整体的看,actions的存在即合理,如果我们有一些业务逻辑,我们就可以写到actions中,这里的actions有点类似于后端的service层,mutations有点类似mapper层,我们的业务逻辑可以写到service层

getters

getters有点类似于computed
不过数据源是state中的数据

配置
在这里插入图片描述
拿取

{{$store.getters.bigSum}}

mapGetters,mapState

vue建议{{}}插值语法中,里边的方法尽量简写
在这里插入图片描述
类似这种写法不合理,我们一般写到计算属性里边
但是如果全部都写计算属性就会有大量重复的代码,这个时候我们就需要mapState帮我们生成这样的代码

    computed: {
        sum() {
            return this.$store.state.sum();
        },
        //对象写法
        ...mapState({sum:'sum',school:'school'}),
        //数组写法
        ...mapState(['sum','school'])
    }

需要注意的是,mapState()返回的是一个对象,如果我们直接在computed: {},里边给人添加一个对象是不合理的,所以就会用到es6的语法...他可以将里边的内容加到computed这个对象中去

我们也可以看到,生成的代码就是sum()这样类似的计算属性,并且需要注意的是,如果说,你计算属性的名字和state中的名字是一样的,是可以用数组的写法的,如果不是的话,还是老老实实用对象写法的

mapGetters的使用方法和mapState是一模一样的

mapActions,mapMutations

    methods: {
        add() {
            this.$store.dispatch('jia',this.n);
        },
        oddAdd() {
            if(this.$store.state.sum % 2 == 1) {
                this.$store.dispatch('jia',this.n);
            }
        },
        delayAdd() {
            setTimeout(() => {
                this.$store.dispatch('jia',this.n);
            }, 5000);
        },
        ...mapActions({oddAdd:'jia',delayAdd:'jia'}),
        minus() {
            this.$store.commit('JIAN',this.n);
        },
        ...mapMutations({minus:'JIAN'})
    },

需要注意的是,如果我们需要传参数的话,需要在模版代码中传
在这里插入图片描述
如果我们不传的话,会是event事件

模块化编程

不同业务写到不同js中

export default {
    namespaced: true,
    actions: {
        jia(miniStore,value) {
            console.log('actions里边的jia方法',miniStore,value);
            miniStore.commit('JIA',value);
        },
        jian(miniStore,value) {
            console.log('actions里边的jian方法',miniStore,value);
            miniStore.commit('JIAN',value);
        }
    },
    mutations: {
        JIA(state,value) {
            console.log('mutations里边的JIA方法',state,value);
            state.sum += value;
        },
        JIAN(state,value) {
            console.log('mutations里边的JIAN方法',state,value);
            state.sum -= value;
        },
    },
    state: {
        sum: 0,
        school: '尚硅谷',
        subject: '前端'
    },
    getters: {
        bigSum(state) {
            return state.sum * 10;
        }
    }
}

必须写命名空间,这样我们使用mapActions 等等这些map操作的时候,才可以写不同的模块名字

import Vue from 'vue';
import Vuex from 'vuex';
import countoption from './countoption';

Vue.use(Vuex);

export default new Vuex.Store({
    modules: {
        countAbout: countoption
    }
})

修改非模块化代码

    methods: {
        ...mapActions('countAbout',{add:'jia',oddAdd:'jia'}),
        ...mapMutations('countAbout',{minus:'JIAN'})
    },
    computed: {
        //对象写法
        ...mapState('countAbout',{sum:'sum',school:'school'}),
        //数组写法
        // ...mapState(['sum','school'])
        ...mapGetters('countAbout',['bigSum'])
    }

对于mapGetters这些,都得在前面写一个参数也就是我们在module中配置的模块名countAbout

原始的拿取state中的数据
在这里插入图片描述

getters
在这里插入图片描述
要在前面加个模块名/

commit操作

在这里插入图片描述
要在前面加个模块名/

dispatch纯手写和commit一致

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

憨憨小江

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值