【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
构造方法中有:
state
getters
actions
mutations
modules
:模块plugins
strict
devtool
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 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。