一、通过路由带参数进行传值
①两个组件 A和B,A组件通过query把orderId传递给B组件
this.$router.push({ path: '/conponentsB', query: { orderId: 123 } }) // 跳转到B
②在B组件中获取A组件传递过来的参数
this.$route.query.orderId
二、通过设置Web存储缓存的形式进行传递
①两个组件A和B,在A组件中设置缓存orderData
const orderData = { 'orderId': 123, 'price': 88 }
sessionStorage.setItem('缓存名称', JSON.stringify(orderData))
②B组件就可以获取在A中设置的缓存了
const dataB = JSON.parse(sessionStorage.getItem('缓存名称'))
三、父子间通信
父组件通过props向子组件传递数据
子组件通过事件向父组件发送消息
真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。
(1)父级向子级传递 props
在子组件中使用选项 props 来声明需要从父级接收到的数据。
props的值有两种方式:
方式一:字符串数组,数组中的字符串就是传递时的名称。
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
①数组形式
字符串数组形式:(不常用)
<div id="app">
<!-- 调用子组件且子组件需要父组件的数据时,必须v-bind绑定属性 -->
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<template id="cpn">
<div>
<p>{{cmovies}}</p>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
props: ['cmovies', 'cmessage']
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
</script>
②对象形式
对象形式:(当需要对props进行类型等验证时,就需要对象写法了)
验证支持的数据类型:String、Number、Boolean、Array、Object、Date、Function、Symbol
<div id="app">
<!-- 调用子组件且子组件需要父组件的数据时,必须v-bind绑定属性 -->
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<template id="cpn">
<div>
<p>{{cmovies}}</p>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
props: {
// 1.类型限制
// cmovies: Array,
// cmessage: String,
// 2.提供一些默认值, 以及必传值
cmessage: {
type: String, // 数据类型
default: 'aaaaaaaa', // 默认值
required: true // true表示调用这个组件时,必须调用cmessage这个属性
},
// 类型是对象或者数组时, 默认值必须是一个函数
cmovies: {
type: Array,
default() {
return []
}
}
}
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
</script>
③驼峰标识的注意点:
当我们在props中声明变量时,采用的驼峰命名法的话,即cMessage,在调用子组件且传递父组件数据时,v-bind绑定属性名不能驼峰标识,需要变换。cMessage 转换成 c-message
<div id="app">
<!-- 错误写法 -->
<cpn :cMessage="message"></cpn>
<!-- 正确写法 -->
<cpn :c-message="message"></cpn>
</div>
<template id="cpn">
<div>
<h2>{{cMessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
props: {
cMessage: {
type: String,
default: '很好'
}
}
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn
}
})
</script>
④父子组件传值,数据是异步请求,有可能数据渲染时报错
原因:异步请求时,数据还没有获取到但是此时已经渲染节点了
解决方案:可以在 父组件需要传递数据的节点加上 v-if = false
,异步请求获取数据后,v-if = true
(2)子级向父级传递
子组件传递数据或事件到父组件中需要使用自定义事件来完成。
自定义事件的流程:
在子组件中,通过$emit(‘事件名’,参数)来触发事件。(emit是发射的意思)
在父组件中,通过v-on来监听子组件事件。 @事件名=‘父组件的事件’
<!--父组件模板-->
<div id="app">
<cpn @item-click="cpnClick"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories"
@click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.子组件
const cpn = {
template: '#cpn',
data() {
return {
// 类别
categories: [
{id: 'aaa', name: '热门推荐'},
{id: 'bbb', name: '手机数码'},
{id: 'ccc', name: '家用家电'},
{id: 'ddd', name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
// 发射事件: 自定义事件
this.$emit('item-click', item);
// item-click为父组件的绑定事件名 @item-click='' ,可以传参数
}
}
}
// 2.父组件
const app = new Vue({
el: '#app',
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
</script>
(3)父子组件的访问 $children $refs $parent $root
父组件访问子组件:使用 children或 refs (reference 引用)
子组件访问父组件:使用 parent
访问根组件:使用 root
最常用的是 refs
1、children 获取到的是子组件的集合,是一个数组,访问其中的子组件必须通过索引值。
children的缺陷:
当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
2、refs 相当于key,先给每个子组件定义ref属性,再使用refs调用,就可以访问特定的子组件,不会随着位置数目得变化而出错。
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
// 1.$children
console.log(this.$children);
for (let c of this.$children) {
console.log(c.name);
}
console.log(this.$children[1].name);
// 2.$refs => 对象类型, 默认是一个空的对象
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
}
},
}
})
</script>
3、parent:
①尽管在Vue开发中,允许我们通过 parent来访问父组件,但是在真实开发中尽量不要这样做。
②子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
③如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
④另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于调试和维护。
四、vuex进行传值
vuex主要是是做数据交互,父子组件传值可以很容易办到,但是兄弟组件间传值(兄弟组件下又有父子组件),或者大型spa单页面框架项目,页面多并且一层嵌套一层的传值,异常麻烦,用vuex来维护共有的状态或数据会显得得心应手。
1、Vuex的基本使用
安装Vuex插件:npm install vuex --save
在src下建文件夹:store(仓库)
再建index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 安装插件
Vue.use(Vuex)
const store = new Vuex.Store({
state{},
mutations{},
actions{},
getters{},
modules: {}
})
// 导出
export default store
main.js引用(挂载到vue实例中)
这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
import Vue from 'vue'
import App from './App'
import store from './store' // 导入store
Vue.config.productionTip = false
new Vue({
el: '#app',
store, // 导入store
render: h => h(App)
})
1-1、Vuex状态管理图例
1-2、小案例
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state{
count = 1000
},
mutations{
increment(state){ // 调用state
state.count++
},
decrement(state){
state.count--
}
}
})
export default store
挂载到Vue
import Vue from 'vue'
import App from './App'
import store from './store' // 导入store
Vue.config.productionTip = false
new Vue({
el: '#app',
store, // 导入store
render: h => h(App)
})
使用vuex
<template>
<div id='app'>
<h2>{{$store.state.count}}</h2>
<button @click="add">+</button>
<button @click="sub">-</button>
</div>
</template>
<script>
export default{
name: 'App',
methods:{
add(){
this.$store.commit('increment') // 通过commit(提交)mutation的方式引用increment方法
},
sub(){
this.$store.commit('decrement')
}
}
}
</script>
2、Vuex的核心
2-1、State
2-2、Getters
类似于computed(计算属性)
基本使用:
需要从store中获取一些state变异后的状态,就使用getters。
作为参数:
需要传递参数:
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数
state:{
student:[{
name:'durant',
age:35
},{
name:'curry',
age:30
}]
},
getters:{
more20stu(state){ // 基本使用
return state.students.filter(s => s.age > 20)
},
more20stuNumber(state,getters){ // getters作为参数
return getters.more20stu.length
},
moreAgeStu(state) { // 传递参数的方式:返回一个函数
return age => {
return state.students.filter(s => s.age > age)
}
}
}
引用:
<template>
<div>
<h2>{{$store.getters.more20stu}}</h2>
<h2>{{$store.getters.more20stuNumber}}</h2>
<h2>{{$store.getters.moreAgeStu(32)}}</h2>
</div>
</template>
2-3、Mutation(状态更新)
(1)基本使用
Vuex的store状态的更新唯一方式:提交Mutation
mutations{
increment(state){ // 第一个参数必须为state
state.count++
},
decrement(state){
state.count--
}
}
必须用commit调用
methods:{
add(){
this.$store.commit('increment') // 通过commit(提交)mutation的方式引用increment方法
},
sub(){
this.$store.commit('decrement')
}
}
(2)传递参数
事件名(state,payload)
payload(负载),可以是一个参数,也可以是一个对象(需要传递多个参数时,可以包装在对象里传递)。
mutations{
incrementCount(state,counter){ // 第一个参数必须为state
state.count += counter
}
}
调用
methods:{
addCounter(counter){
this.$store.commit('increment',counter)
}
}
(3)提交风格
①普通:commit(‘事件名’,参数)
②commit({type:‘事件名’,参数}) 对象
methods:{
addCounter(counter){
this.$store.commit({
type:'increment',
counter
})
}
}
mutations{
incrementCount(state,payload){ // 此时的payload是一个对象,而之前的提交风格payload是counter
state.count += payload.counter
}
}
(4)响应式
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
Vuex对应的响应式规则:提前在store中初始化好所需的属性.
state:{
info:{
name:'durant',
number:7,
height:2.11
}
},
mutations{
change(state){
state.info.name = 'curry' // 响应式
state.info['address'] = '布鲁克林' // 非响应式,但是info会添加 address:'布鲁克林'
Vue.set(state.info,'address','华盛顿') // 响应式
delete state.info.number // 非响应式,但是info会删除 number:7
Vue.delete(state.info,'number') // 响应式
// Vue.set和Vue.delete 的第二个参数只能是string或者number
}
}
<template>
<div>
<h2>{{$store.state.info}}</h2>
<button @click='change'>改变<button>
</div>
</template>
<script>
export default{
name: 'App',
methods:{
change(){
this.$store.commit('change')
}
}
}
</script>
(5)常量类型
在mutation中, 我们定义了很多事件类型(也就是其中的方法名称).
当我们的项目增大时,Vuex管理的Mutation中的方法越来越多,方法过多,使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
官方推荐: 使用常量替代Mutation事件的类型,我们可以将这些常量放在一个单独的文件中,方便管理以及让整个app所有的事件类型一目了然。
新建mutations-type.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import {INCREMENT,DECREMENT} from "./mutations-types" // 导入常量类型
Vue.use(Vuex)
const store = new Vuex.Store({
state{
count = 1000
},
mutations{
[INCREMENT](state){ // [常量](){}
state.count++
},
[DECREMENT](state){
state.count--
}
}
})
export default store
使用vuex
<template>
<div id='app'>
<h2>{{$store.state.count}}</h2>
<button @click="add">+</button>
<button @click="sub">-</button>
</div>
</template>
<script>
import {INCREMENT,DECREMENT} from "./mutations-types" // 导入常量类型
export default{
name: 'App',
methods:{
add(){
this.$store.commit(INCREMENT) // 不再是字符串形式
},
sub(){
this.$store.commit(DECREMENT)
}
}
}
</script>
(6)只能同步操作
Vuex要求我们Mutation中的方法必须是同步方法.
主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.
2-4、Action
Action类似于Mutation, 但是是用来代替Mutation进行异步操作的。
第一个参数context(上下文),它是和store对象具有相同方法和属性的对象。
也可以传递payload。
调用:是dispatch。
当我们的异步操作或者说网络请求成功或者失败的时候执行一些代码时,我们可以利用Promise。
将异步操作或者网络请求放在Promise里,但是then()和catch()是跟在dispatch后面的。
2-5、Module
Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutations、actions、getters等。但是模块最好最多两个。