【Vue】数据通信——我们从组件通信说起
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>
props—endVal接收父组件的传值
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默认会利用名为value的prop和名为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对$bus的Vue实例的事件绑定监听 
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构造方法中有:
stategettersactionsmutationsmodules:模块pluginsstrictdevtool
5.3 state

看下这个图吧,着重看一下Vuex的范围。按单向数据流:数据流总是 Actions→Mutations→State,但是我们使用时也不一定总是全部使用,灵活一点。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才能修改。帮助我们梳理数据改变,提供一个清晰的数据流。如果强行赋值修改,严格模式下,便会报错:

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: {
    //
  }
}
- 原文作者:Garfield
 - 原文链接:http://www.randyfield.cn/post/2021-04-17-vuex/
 - 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。