功能:让组件接收外部传过来的数据
传递数据:
接收数据:
第一种方式(只接收):props:['name']
第二种方式(限制类型):props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, //类型
required:true, //必要性
default:'老王' //默认值
}
}
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
props是让父组件向子组件传递数据,而插槽就是让父组件向子组件传递html结构
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
分类:默认插槽、具名插槽、作用域插槽
使用方式:
默认插槽:
父组件中:
<Category>
<div>html结构1div>
Category>
子组件中:
<template>
<div>
<slot>插槽默认内容...slot>
div>
template>
具名插槽:
父组件中:
<Category>
<template slot="center">
<div>html结构1div>
template>
<template v-slot:footer>
<div>html结构2div>
template>
Category>
子组件中:
<template>
<div>
<slot name="center">插槽默认内容...slot>
<slot name="footer">插槽默认内容...slot>
div>
template>
作用域插槽:
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
具体编码:
父组件中:
<Category>
<template scope="scopeData">
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}li>
ul>
template>
Category>
<Category>
<template slot-scope="scopeData">
<h4 v-for="g in scopeData.games" :key="g">{{g}}h4>
template>
Category>
子组件中:
<template>
<div>
<slot :games="games">slot>
div>
template>
<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
script>
如果父组件要向子组件传递数据,我们可以使用props。而如果子组件要向父组件传递数据,我们可以先在父组件中定义好方法,使用props传递给子组件,然后让子组件去调用它。
我们可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即:
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。
绑定事件的两种方式:
方式一:
<SchoolName :getSchoolName="getSchoolName">SchoolName>
<StudentName v-on:stuName="getStudentName">StudentName>
方式二:
<StudentName ref="Student"></StudentName>
mounted() {
this.$refs.Student.$on('stuName',this.getStudentName)
}
触发事件:
methods:{
studentNameGetter(name){
// 触发Student组件实例身上的stuName事件
this.$emit('stuName',name)
}
}
解绑自定义事件this.$off(‘eventName’)
在实际开发中基本很少使用到下面的两种办法去实现任意组件通信,通常是使用集中的状态管理插件来达到同样的效果,例如Vuex。所以下面的方法了解即可。
此方法一般用的少,想要深入了解可以直接参考我的另一篇文章全局事件总线
需要借助第三方库,例如pubsub.js
可以参考我的文章消息订阅与发布
专门在 Vue 中实现集中式状态管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
使用场景:
npm i vuex
注意:
npm i vuex@3
然后我们在main.js中引入,并使用use来使用这个插件:
import Vuex from 'vuex'
Vue.use(Vuex)
完成了上面的步骤之后,我们就可以在创建vm(或者组件实例对象vc)的时候传入一个store配置项,这个配置项在vm(或者组件实例对象vc)中体现为$store。
接下来最关键的一步:我们要创建这个store。我们单独把它写在一个js文件中。
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//准备actions——用于响应组件中的动作
const actions = {}
//准备mutations——用于操作数据(state)
const mutations = {}
//准备state——用于存储数据
const state = {}
Vue.use(Vuex)
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
import Vuex from 'vuex'
import store from 'store/index'
new Vue({
render: h => h(App),
store
}).$mount('#app')
这样我们的引入工作就做完了。
看一个例子:
<template>
<div>
<h1>当前求和为:{{$store.state.sum}}h1>
<select v-model.number="n">
<option value="1">1option>
<option value="2">2option>
<option value="3">3option>
select>
<button @click="increment">+button>
<button @click="decrement">-button>
<button @click="incrementOdd">当前求和为奇数再加button>
<button @click="incrementWait">等一等再加button>
div>
template>
<script>
export default {
name:'MyCount',
data() {
return {
n:1, //用户选择的数字
}
},
methods: {
increment(){
this.$store.commit('JIA',this.n)
},
decrement(){
this.$store.commit('JIAN',this.n)
},
incrementOdd(){
this.$store.dispatch('jiaOdd',this.n)
},
incrementWait(){
this.$store.dispatch('jiaWait',this.n)
},
},
mounted() {
console.log('Count',this)
},
}
script>
<style lang="css">
button{
margin-left: 5px;
}
style>
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions——用于响应组件中的动作
const actions = {
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(()=>{
context.commit('JIA',value)
},500)
}
}
//准备mutations——用于操作数据(state)
const mutations = {
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
}
}
//准备state——用于存储数据
const state = {
sum:0 //当前的和
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
注意点:
相当于Store中的计算属性,不过我们的方法中会传入一个参数state。
<template>
<div>
<h1>当前求和为:{{$store.state.sum}}h1>
<h3>当前求和放大10倍为:{{$store.getters.bigSum}}h3>
div>
template>
mapState 和 mapGetters 都是 Vuex 中的辅助函数,用于简化组件中对 store 中的状态和计算属性的访问。
以下是 mapState 和 mapGetters 的用法示例¹:
// 在 store 中定义 state 和 getters
const store = new Vuex.Store({
state: {
count: 0,
todos: [
{ id: 1, text: "todo1", done: true },
{ id: 2, text: "todo2", done: false },
],
},
getters: {
doneTodos: (state) => {
return state.todos.filter((todo) => todo.done);
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length;
},
},
});
// 在组件中使用 mapState 和 mapGetters
import { mapState, mapGetters } from "vuex";
export default {
// 使用对象展开运算符将 mapState 和 mapGetters 的结果混入 computed 对象中
computed: {
// 使用数组语法映射 state 和 getters
...mapState(["count"]),
...mapGetters(["doneTodos", "doneTodosCount"]),
// 使用对象语法映射 state 和 getters,并使用别名
...mapState({
countAlias: (state) => state.count,
}),
...mapGetters({
doneCountAlias: "doneTodosCount",
}),
},
};
mapActions 和 mapMutations 都是 Vuex 中的辅助函数,用于简化组件中对 store 中的 actions 和 mutations 的访问。
以下是 mapActions 和 mapMutations 的用法示例:
// 在 store 中定义 actions 和 mutations
const store = new Vuex.Store({
state: {
count: 0,
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit("increment");
}, 1000);
},
},
mutations: {
increment(state) {
state.count++;
},
},
});
// 在组件中使用 mapActions 和 mapMutations
import { mapActions, mapMutations } from "vuex";
export default {
// 使用对象展开运算符将 mapActions 和 mapMutations 的结果混入 methods 对象中
methods: {
// 使用数组语法映射 actions 和 mutations
...mapActions(["incrementAsync"]),
...mapMutations(["increment"]),
// 使用对象语法映射 actions 和 mutations,并使用别名
...mapActions({
add: "incrementAsync",
}),
...mapMutations({
addOne: "increment",
}),
},
};
我们来看一个完整使用的例子:
<template>
<div>
<h1>当前求和为:{{sum}}h1>
<h1>当前求和放大10倍为:{{bigSum}}h1>
<h1>学生姓名:{{student}}h1>
<h1>学校名称:{{school}}h1>
<select v-model.number="n">
<option value="1">1option>
<option value="2">2option>
<option value="3">3option>
select>
<button @click="increment(n)">+button>
<button @click="decrement(n)">-button>
<button @click="incrementOdd(n)">当前求和为奇数再加button>
<button @click="incrementWait(n)">等一等再加button>
div>
template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
name:'MyCount',
data() {
return {
n:1, //用户选择的数字
}
},
computed:{
...mapState({sum:'sum',school:'school',student:'student'}),
...mapGetters(['bigSum']),
},
methods: {
...mapMutations({increment:'JIA',decrement:'JIAN'}),
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}),
},
}
script>
<style lang="css">
button{
margin-left: 5px;
}
style>
注意:
那就是多个组件的代码都放在了唯一的actions、mutations、state、getters中,我们前面的案例中只涉及到了两个组件,但是如果我们有几百个几千个组件,这些代码全部堆积到一起,会非常的繁杂。所以我们想对他进行一个分类,将各组件的代码分离开来。
我们将每个模块的store分开放,最后在index文件中进行引入就行,我们来看一个例子:
count.js
//求和相关的配置
export default {
namespaced:true,
actions:{
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(()=>{
context.commit('JIA',value)
},500)
}
},
mutations:{
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
},
},
state:{
sum:0, //当前的和
school:'尚硅谷',
subject:'前端',
},
getters:{
bigSum(state){
return state.sum*10
}
},
}
person.js:
//人员管理相关的配置
import axios from 'axios'
import { nanoid } from 'nanoid'
export default {
namespaced:true,
actions:{
addPersonWang(context,value){
if(value.name.indexOf('王') === 0){
context.commit('ADD_PERSON',value)
}else{
alert('添加的人必须姓王!')
}
},
addPersonServer(context){
axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
response => {
context.commit('ADD_PERSON',{id:nanoid(),name:response.data})
},
error => {
alert(error.message)
}
)
}
},
mutations:{
ADD_PERSON(state,value){
console.log('mutations中的ADD_PERSON被调用了')
state.personList.unshift(value)
}
},
state:{
personList:[
{id:'001',name:'张三'}
]
},
getters:{
firstPersonName(state){
return state.personList[0].name
}
},
}
index.js
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)
//创建并暴露store
export default new Vuex.Store({
modules:{
countAbout:countOptions,
personAbout:personOptions
}
})
组件中使用:
computed:{
//借助mapState生成计算属性,从state中读取数据。(数组写法)
...mapState('countAbout',['sum','school','subject']),
...mapState('personAbout',['personList']),
//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters('countAbout',['bigSum'])
},
methods: {
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
},
注意:
最后总结一下:
模块化+命名空间
目的:让代码更好维护,让多种数据分类更加明确。
修改store.js
const countAbout = {
namespaced:true,//开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
开启命名空间后,组件中读取state数据:
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']),
开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
路由在前端开发中的作用是指根据不同的 URL 地址,显示不同的页面或组件,从而实现单页应用的功能。路由可以让前端开发者更方便地管理和切换页面或组件,提高用户体验和开发效率。
路由的基本原理是通过监听 URL 的变化,匹配相应的路由规则,然后动态地渲染或更新页面或组件。
路由可以分为两种模式:hash 模式和 history 模式。
我们开发时将一般组件和路由组件分成两个文件夹放:
每个路由组件身上多了两个属性:
$route
$router
每个组件都有自己的$route
属性,里面存储着自己的路由信息。
整个应用只有一个router,可以通过组件的$router
属性获取到。
<template>
<div>
<div class="row">
<Banner/>
div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<router-link class="list-group-item" active-class="active" to="/about">Aboutrouter-link>
<router-link class="list-group-item" active-class="active" to="/home">Homerouter-link>
div>
div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<router-view>router-view>
div>
div>
div>
div>
div>
template>
<script>
import Banner from './components/Banner'
export default {
name: 'App',
components: {Banner}
}
script>
然后要专门用一个文件来配置路由规则:
// 该文件专门用于创建整个应用的路由器
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', //此处一定不要写:/news
component:News,
},
{
path:'message', //此处一定不要写:/message
component:Message,
}
]
}
]
})
传递参数:
<router-link :to="`/home/message/detail?id=666&title=你好`">跳转router-link>
<router-link
:to="{
path:'/home/message/detail',
query:{
id:666,
title:'你好'
}
}"
>跳转router-link>
接收参数:
$route.query.id
$route.query.title
配置路由:
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', //使用占位符声明接收params参数
component:Detail
}
]
}
]
}
传递参数:
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置,也就是说命名路由!
接收参数
$route.params.id
$route.params.title