vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要;我们Vue数据通信就从组件通信开始说起。

1.写在前面

Vue崇尚或者说机制就是单向数据流。

2.父子组件

Vue崇尚的是单向数据流,包括父子组件之间的传值,值的修改:

  • 父组件向子组件传值一定是通过属性props
  • 子组件修改父组件值一定是通过事件
    • 以参数的形式 this.$emit(“eventName”,value) 通过事件,提交给父组件,然后在父组件绑定事件

2.1 父组件→子组件

父组件向子组件传值,便是在父组件调用子组件时,用:冒号传递属性值,在子组件中用props接收值。

示例:向子组件传递数字,由子组件自行处理UI逻辑。

父组件

 <count-to ref="countTo" :end-val="endVal"></count-to>
 //omit import component code 
 //omit 
 <script>
     export default {
         //omit other code
          data () {
            return {
              endVal: 100
            }
          },
 </script>
  • :end-val—父组件传值
  • endVal—值

子组件

<template>
	<!--omit component code-->
</template>
<script>
    export default {
        props: {
            endVal: {
                type: Number,
                required: true
            },
        }
    }
</script>
  • propsendVal 接收父组件的传值

2.2 子组件→父组件

子组件向父组件传值,便是在子组件中使用*this.$emit(“eventName”,value)*向父组件传递值

子组件

示例:子组件传递值,并由父组件获取且作其他处理。

<script>
export default {
    methods: {
        getCount () {
            return this.$refs.number.innerText
        },
        emitEndEvent () {
            setTimeout(() => {
                this.$nextTick(() => {
                    this.$emit('on-animation-end', Number(this.getCount()))
                })
            }, this.duration * 1000 + 5)
        }
    },
    mounted () {
        this.$nextTick(() => {
            setTimeout(() => {
                this.counter.start()
                this.emitEndEvent()  //DOM变化
            }, this.delay)
        })
    }
}
</script>

this.$nextTick,是将回调函数延迟在下一次dom更新数据后调用

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

  • this.$emit('on-animation-end', Number(this.getCount())):触发on-animation-end事件,及传递值

父组件

//omit other code
 <count-to ref="countTo" :end-val="endVal" @on-animation-end="handleEnd"></count-to>
<script>
export default {
  data () {
    return {
      endVal: 100
    }
  },
  methods: {
    handleEnd (endVal) {
      console.log('end -> ', endVal)
    }
  }
}
</script>   
  • @on-animation-end—父组件中绑定子组件事件
  • endVal—接收子组件传递的值

2.3 v-model

v-model其实是一个语法糖,实质还是走的子组件向父组件传递值的套路,只是这里指定一下调用this.$emit的是子组件的@input事件:

<input @input="handleInput" :value="value">
<script>
export default{
    props:{
        value:{
            type:[String,Number],
            default:''
        }
    },
    methods:{
        handleInupt(event){
            const value=event.target.value
            this.$emit("input",value)
        }
    }
}
</script>

然后在父组件中定义@input事件,在事件中处理子组件传递的值

<child :value="inputValue" @input="handleInput"></child>
<script>
export default{
    data(){
      return {
          inputValue:''
      }  
    },
    methods:{
        handleInupt(val){
            this.inputValue=val
        }
    }
}
</script>

所以:

  • 改变父组件的值,子组件会因为父组件中的:value改变,
  • 向子组件输入值,父组件会因为事件触发,改变值

v-model

<!--父组件-->
<child v-model="value">
<!--子组件-->
<script>
export default{
    props:{
        value:{
            type:[String,Number],
            default:''
        }
    }
}
</script>

当然这里子组件改变值的事件是input事件,但是像checkbox改变值的事件是change,而v-mode默认会利用名为valueprop和名为input事件,此时就需要做出调整:

<input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change',$event.target.checked)">
<script>
    export default{
        model:{
            prop:'checked',
            event:'change'
        },
        props:{
            checked:Boolean
        }
    }
</script>

使用mode选项改变默认的v-model的绑定属性与抛出事件。此时父组件就可以直接使用v-model绑定从而改变的checkbox的值。

3.兄弟组件

回顾了父子组件的传值,那么兄弟组件呢?其实也很简单,运用上面父子组件之间传值的机制,把父组件作为媒介即可。通俗一点:

  • 通过A组件触发事件改变父组件值
  • 改变的这个值作为兄弟B组件的值

体会一下,就不赘述示例了。

4.bus

跨文件的组件之间又怎么传值呢?又没有父组件作为媒介。

4.1 定义总线bus

new一个Vue实例

import Vue from 'vue'
const Bus=new Vue()
export default Bus

4.2 全局引入总线bus

Vue的原型对象上添加上面定义的bus,一般使用$符号

//main.js
import Vue from 'vue'
import App from './App.vue'
import Bus from './bus'
import router from './router'
Vue.prototype.$bus=Bus

new Vue({
    router,
}).$mount('#app')

原型对象上添加的属性,通过new会传递给对象实例。JavaScript对象实例中取一个属性值,会像俄罗斯套娃一样找__prop__,前端童鞋称为原型链,一直找到根儿上的Object.prototype,如果在根儿上都没有找到,那么这个属性就是undefined

所以整个上面那样为原型对象添加bus后,整个项目中的vue实例,都能访问这个bus,当然这个bus可以以$bus获取,获取的是另一个vue实例,专用于通信的实例。

4.3 使用bus

4.3.1 定义事件方法

像子组件向父组件一样,定义事件方法,只是现在我们使用的是$bus属性取到的Vue实例:

this.$bus.$emit('on-click','hi')
  • $emit触发$bus实例事件

4.3.2 取值

this.$bus.$on('on-click',msg=>{
    console.log=msg
})
  • 使用$on$busVue实例的事件绑定监听

5.Vuex

Vuex是什么?官方给的定义:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。太抽象了,太官方了,并不能帮助我们理解记忆。博主给定义:Vuex就是一个集中管理数据并作为通信中介的工具

5.1 引入Vuex

import Vue from 'vue' //引入vue
import Vuex from 'vuex' //引入vuex

Vue.use(Vuex) //Vuex作为插件加载

5.2 Store

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

Vue.use(Vuex)
//创建vuex实例
export default new Vuex.Store({
    //strict:
    //state:
    //getters:
    //mutations:
    //actions:
    //modules:
    //plugins:
})
constructor(options: StoreOptions<S>);

export interface StoreOptions<S> {
  state?: S | (() => S);
  getters?: GetterTree<S, S>;
  actions?: ActionTree<S, S>;
  mutations?: MutationTree<S>;
  modules?: ModuleTree<S>;
  plugins?: Plugin<S>[];
  strict?: boolean;
  devtools?: boolean;
}

从上面的定义可以看出Store构造方法中有:

  • state
  • getters
  • actions
  • mutations
  • modules:模块
  • plugins
  • strict
  • devtool

5.3 state

vuex

看下这个图吧,着重看一下Vuex的范围。按单向数据流:数据流总是 ActionsMutationsState,但是我们使用时也不一定总是全部使用,灵活一点。state作为vuex数据的终点,称为根状态,定义的值称为状态值。这些状态值便可以在各个组件中使用:

5.3.1 定义state

state就是一个json对象,键值对集合

const state={
    projectName:"test"
}

5.3.2 取值

一般都在组件中的计算属性中获得state值

方法一:this.$store.state

export default {
    computed:{
        projectName(){
            this.$store.state.projectName
        }
    }
}

方法二:vuex工具函数mapState

import {mapState} from 'vuex' //es6解构赋值

export default {
    computed:{
        ...mapState({
            projectName:state=>state.appName
        })
    }
}

5.4 getter

5.4.1 定义getter

相当于state的计算属性,它依然是一个json对象

const getters={
    projectNameWithVersion:(state)=>{
        return state.projectName+'v1.0.0'
    }
}

state代表当前目录同级的state,通过state获取state定义的值

5.4.2 取值

跟state取值方式类似

方法一:this.$store.getters

export default {
    computed:{
        projectNameWithVersion(){
            this.$store.getters.projectNameWithVersion
        }
    }
}

方法二:vuex工具函数mapGetters

import { mapGetters } from 'vuex'

export default {
    computed:{
        ...mapGetters([
            'projectNameWithVersion'
        ]),
    }
}

5.5 mutation

如果操作没有异步操作,那么我们也是可以不走action,可以在组件中通过commit mutation 提交state的改变

另外,当我们在组件中,需要修改一个state状态值,不可以通过赋值的方式,在组件中直接修改state状态值,而是通过commit,提交一个mutation,或者dispatch一个action才能修改。帮助我们梳理数据改变,提供一个清晰的数据流。如果强行赋值修改,严格模式下,便会报错:

image-20210417174910711

5.5.1 定义mutation

const mutations={
    Set_projname(state,params){
        state.projectName=params.pjname
    }
}
  • state代表当前目录同级的state,通过state获取state定义的值
  • params为组件commit的对象

5.5.2 调用mutation

方法一:commit

this.$store.commit('set_pjname',{
    pjname:"testnew"
})

方法二:vuex工具函数mapMutations

import { mapMutations } from 'vuex'
export default {
    methods:{
        ...mapMutations([
			'Set_projname'
        ])  
    },
}

调用

this.Set_pjname("testnew")

5.5.3 增加state值

可以使用vue.set增加state中没有定义的值

import vue from 'vue'
const mutations={
    Set_pjname(state,params){
        state.projectName=params.pjname
    },
    SET_VERSION(state){
        vue.set(state,"version","v2.0")
    }
}

5.6 action

mutation只能做同步操作,而异步操作就需要action作异步操作,这就是前后端开发的边界,请求接口。

5.6.1 定义action

const actions={
    updateProjectName({commit}){
        //omit request api
        commit('Set_projname',res.info.projname)
    }
}
  • 参数中取到commit
  • 调用commit一个mutation

5.6.2 获取action

import {mapActions} from 'vuex'

export default {
    methods:{
        ...mapActions([
			'updateProjectName'
        ])  
    },
}

5.6.3 触发action

使用dispatch触发上面取到的action

this.$store.dispatch("updateProjectName","testNew")

5.7 模块

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

Vue.use(Vuex)
//创建vuex实例
export default new Vuex.Store({
    //strict:
    //state:
    //getters:
    //mutations:
    //actions:
    //modules:  //模块
    //plugins:
})

项目大的时候,可以拆分成模块,模块还可以继续包含模块:

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

Vue.use(Vuex)
//创建vuex实例
export default new Vuex.Store({
    //strict:
    //state:
    //getters:
    //mutations:
    //actions:
    modules:{
        m1,
        m2
    }
    //plugins:
})

每一个模块又可以全包含或者部分包含state getters mutations actions

const state = {
  userName: 'Lison'
}
const getters = {
  firstLetter: (state) => {
    return state.userName.substr(0, 1)
  }
}
const mutations = {
  SET_USER_NAME (state, params) {
    state.userName = params
  }
}
const actions = {
  updateUserName ({ commit, state, rootState, dispatch }) {
    // rootState.appName
    dispatch('xxx', '')
  },
  xxx () {
    //
  }
}

export default {
  getters,
  state,
  mutations,
  actions,
  modules: {
    //
  }
}