# 父->子
单向数据流,子组件不能修改父组件参数
<body>
<div id="app">
<child :name="name"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `<div>我的名字是:{{ name }}</div>`,
props: [ "name" ]
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸',
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 非props传参:父组件用传参,子组件未用props接收,就会被收集到
$attrs
属性里面
父组件向子组件传递两个参数,一个被props接收,另一个被$attrs收集
<body>
<div id="app">
<child :msg="msg" className="hah"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `<div>{{ msg }}</div>`,
props: ["msg"],
mounted() {
console.log(this.$attrs); // { classname: 'hah' }
}
};
const app = new Vue({
el: '#app',
data() {
return {
msg: "hello"
}
},
components: {
child
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 官方不建议子直接调用、修改父数据
<body>
<div id="app">
<child></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `<div>我的名字是:{{ name }}</div>`,
data() {
return {
name: this.$parent.name,
}
}
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸',
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- props的修饰符:子组件可以修改props参数
- 子组件要修改时emit一个update事件,父组件就同步修改了
- tip:非特殊情况下不要直接改父组件数据
<body>
<div id="app">
父组件内:{{name}}
<child :name.sync="name"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `
<div>
子组件内:{{ myName }}
</div>
`,
props: [ 'name' ],
mounted() {
setTimeout(() => this.myName = '儿子', 2000)
},
computed: {
myName: {
get() {
return this.name;
},
set(newVal) {
this.$emit('update:name', newVal);
}
}
}
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸'
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- v-model子组件拿的props一定是
value
- 发射的事件名默认是
input
,可以通过【model:{event: 'eName'}】来修改
<body>
<div id="app">
父组件内:{{name}}
<child v-model="name"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `
<div>
子组件内:{{ myName }}
</div>
`,
props: [ 'value' ],
model: { event: 'inputVal' },
mounted() {
setTimeout(() => this.myName = '儿子', 2000)
},
computed: {
myName: {
get() {
return this.value;
},
set(newVal) {
this.$emit('inputVal', newVal);
}
}
}
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸'
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 子->父
<body>
<div id="app">
父组件内的name:{{name}}
<child @btn-clk="change"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `
<div>
<button @click='clk'>按钮</button>
</div>
`,
data() {
return {
name: '儿子',
}
},
methods: {
clk() {
this.$emit('btn-clk', this.name);
}
}
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸',
}
},
methods: {
change(newName) {
this.name = newName;
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
- 通过ref收集子组件实例,通过实例获取子组件上的属性或调用方法
- $refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs。
<body>
<div id="app">
<child ref="child"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `
<div>
子组件name:{{name}}
</div>
`,
data() {
return {
name: '儿子',
}
}
};
const app = new Vue({
el: '#app',
components: {
child
},
mounted() {
console.log('父组件mounted:', this.$refs.child.name)
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- 可以拿到所有子组件的数组
- 一般不这么用,一般都是【$refs】拿。除非某个父组件的子组件都是同一个子组件的复用,这个方法比较适合
<body>
<div id="app">
父组件内:{{name}}
<child></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `
<div>
子组件:{{ name }}
</div>
`,
data() {
return {
name: '儿子',
}
},
methods: {
changeName() {
this.name = "张三"
}
}
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸'
}
},
mounted() {
setTimeout(() => this.$children[0].changeName(), 2000)
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
- 父组件使用子组件slot的时候,可以使用作用域slot,子组件可以传递
<body>
<div id="app">
<child v-slot="data">
{{data.money}} 元
</child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `
<div>
<slot :money="money"/>
</div>
`,
data() {
return {
money: 1000
}
}
};
const app = new Vue({
el: '#app',
components: {
child
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 跨级共享
- 获取根组件,无论多少级都可以获取
<body>
<div id="app">
根组件内:{{name}}
<child></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `
<div>
<button @click="change">子组件按钮</button>
</div>
`,
methods: {
change() {
this.$root.changeName("张三")
}
}
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸'
}
},
methods: {
changeName(newName) {
this.name = newName;
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
- 跨级传参,子组件对下级【v-bind="$attrs"】
- 子组件内不能对数据有任何处理,也不能用props接收,要使用可以用
$attrs
来获取值。 - 如果子组件要接受props的话,传给孙组件的时候可以用【v-bind="$props"】替代
<body>
<div id="app">
父组件内:{{name}}
<child :name="name"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const grandchild = {
template: `
<div>
孙组件内:{{ $attrs.name }}
</div>
`,
};
const child = {
template: `
<div>
子组件内:{{ $attrs.name }}
<grandchild v-bind="$attrs" />
</div>
`,
components: {
grandchild
}
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸'
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- 父组件对子组件的所有事件监听器
- 可以直接把父组件的所有监听器传给孙组件【v-on="$listeners"】
<body>
<div id="app">
父组件内:{{name}}
<child @change-name="change"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const grandchild = {
template: `
<div>
<button @click="btnClk">孙组件按钮</button>
</div>
`,
methods: {
btnClk() {
this.$emit('change-name', '张三')
}
}
};
const child = {
template: `
<div>
<grandchild v-on="$listeners" />
</div>
`,
components: {
grandchild
},
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸'
}
},
methods: {
change(newVal) {
this.name = newVal;
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- 也可以只传某一个
<grandchild @change-name="$listeners['change-name']" />
1
- 提供给后代组件公用的数据或方法。不是响应式的,所以尽量传递常量或者方法
- 后代需要使用的位置用inject来注入方法。
- 注:【provide:{}】不能拿到this,可以传递函数或常量,【provide()】可以拿到this
<body>
<div id="app">
根组件内:{{name}}
<child></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const child = {
template: `
<div>
子组件内:{{ name }}
<button @click="changeName('张三')">子组件按钮</button>
</div>
`,
inject: [ 'changeName', 'name' ]
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
name: '爸爸'
}
},
provide() {
return {
changeName: this.changeName,
name: this.name
}
},
methods: {
changeName(newName) {
this.name = newName;
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
- 通过原型链上共享一个VUE实例来共享事件监听
- 三种定义方式
// Bus.js
import Vue from "vue"
export default new Vue()
// 方法二 直接挂载到全局
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()
// 方法三 注入到 Vue 根对象上
// main.js
import Vue from "vue"
new Vue({
el:"#app",
data:{
$bus: new Vue()
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 例:
<body>
<div id="app">
根组件内:{{ count }}
<child></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.prototype.$bus = new Vue();
const child = {
template: `
<div>
<button @click="btnClk">子组件按钮</button>
</div>
`,
methods: {
btnClk() {
this.$bus.$emit('addCount');
}
}
};
const app = new Vue({
el: '#app',
components: {
child
},
data() {
return {
count: 0,
}
},
mounted() {
this.$bus.$on('addCount', () => {
this.count++;
})
},
destroyed() {
this.$bus.$off('addCount');
},
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# VueX3.x
- VueX官网 (opens new window)
- 状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- 配合vue-devtools,可以生成状态快照,方便调试
- 通过store.state获取数据
- dispatch调用actions
- commit调用mutations
- mutations触发数据源state改变能够被devtools记录前后的数据快照,从而方便调试
常被管理的状态:用户名、头像、地理位置等信息,商品收藏、购物车等信息。
<body>
<div id="app">
{{$store.state.count}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3.6.2/dist/vuex.js"></script>
<script>
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++;
}
}
});
const app = new Vue({
el: '#app',
store,
mounted() {
setInterval(() => this.$store.commit('increment'), 1000);
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- 状态源,每个应用只应该包含一个状态源。定义:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++;
}
}
});
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 组件内调用:
computed: {
count() {
return this.$store.state.count;
}
}
1
2
3
4
5
2
3
4
5
mapState:提供更简单的获取方式:
<body>
<div id="app">
{{count}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3.6.2/dist/vuex.js"></script>
<script>
Vue.use(Vuex);
const { mapState } = Vuex;
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++;
}
},
computed: mapState(['count']),
// 写法二:
// computed: mapState({
// count: state => state.count,
// countAlias: 'count',
// countPlusLocalState (state) {
// return state.count + this.localCount
// }
// })
});
const app = new Vue({
el: '#app',
store,
computed: {
...mapState([ 'count' ]),
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- 作用:类似组件内的computed,能将store内的值进行计算制作出新的变量名,并且能够随着依赖变量的改变而改变
- 入参:
state
<body>
<div id="app">
{{ dollar }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3.6.2/dist/vuex.js"></script>
<script>
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
money: 10
},
mutations: {
increment (state) {
state.money++;
}
},
getters: {
dollar(state) {
return '$' + (state.money * 7.2).toFixed(2);
}
}
});
const app = new Vue({
el: '#app',
store,
mounted() {
setInterval(() => this.$store.commit('increment'), 1000);
},
computed: {
dollar() {
return this.$store.getters.dollar;
}
}
})
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
getters也有类似的
mapGetters
辅助函数
- 更改vuex数据源的唯一方法是
commit
触发mutations
- 只能进行同步修改,异步需要借助
actions
- mutations:
- 入参:state, payload
- payload:提交的
载荷
数据 - 名字是唯一,一般使用一个
mutations-types.js
文件保存所有的mutations名,以便统一。
// mutations-types.js
export const ADD_COUNT = 'addCount';
1
2
2
mutations: {
[ADD_COUNT](state, payload) {
const { num } = payload;
state.count += num;
}
}
1
2
3
4
5
6
2
3
4
5
6
- commit:
- 组件内触发mutations的方式。
- 入参:{type, payload} | type, payload
this.$store.commit([ADD_COUNT], {
num: 10
})
1
2
3
2
3
- 也支持
mapMutations
简写
// 导出,组件内在 methods 混入
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- action内支持异步操作,然后再commit让mutation去修改数据源状态。
- 组件内用dispatch去触发actions
- 入参:
- context。可以拿到commit方法和state、getters。
- payload。dispatch的载荷。
不直接传store的原因是:store可以设置modules模块划分,但是这里的数据一般都是只要管自己的模块的state,不应该直接拿到全部的store。要拿全局的对象可以用{ rootState }
actions: {
increment ({ commit }) {
commit('increment');
}
}
1
2
3
4
5
2
3
4
5
- 处理异步:
actions: {
increment ({ commit }) {
return new Promise(resolve => {
fetch('xxx')
.then(res => res.json())
.then(res => resolve(res));
}).then(res => commit('increment'));
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 异步调用:
this.$store.dispatch('increment').then(...)
1
- 将store内的状态和对应管理的mutations、actions、getters切割到不同的模块,方便管理
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 所有module的getters/actions默认会合并到$store里面去
this.$store.getters.counter
1
想区分模块,模块内就要开一个namespace属性
const homeModule = {
namespaced: true,
getters: {
counter(state) {
reutrn state.counter + '个'
}
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
this.$store.getters['/home/counter']
1
- 如果需要用到双向绑定的位置
<input v-model="message">
1
computed: {
message: {
get () {
return this.$store.state.message;
},
set (value) {
this.$store.commit('updateMessage', value);
}
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# Vuex4.x
npm i vuex@4
1
// store/index.js
import { createStore } from 'vuex';
const store = createStore({
state: {
count: 0,
},
mutations: {
add(ctx) {
ctx.count++;
}
}
})
export default store;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
// 安装
app.use(store);
1
2
2
// 组件内使用变量
console.log(this.$store.state.count);
1
2
2
useStore
可以在vue3的setup中没有this的情况下读取store实例- 同时数据要保持响应式特性,就要用
computed
包裹
<template>
<div>
<div>计数是:{{count}}</div>
<button @click="add">+</button>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore()
let count = computed(() => store.state.count);
const add = () => store.commit('add');
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13