如何监听 'props' 变化
How to listen for 'props' changes
在 VueJs 2.0 docs 中,我找不到任何可以侦听 props
更改的挂钩。
VueJs 有像 onPropsUpdated()
或类似的钩子吗?
更新
正如@wostex 所建议的,我尝试 watch
我的 属性 但没有任何改变。然后我意识到我有一个特例:
<template>
<child :my-prop="myProp"></child>
</template>
<script>
export default {
props: ['myProp']
}
</script>
我正在将父组件接收到的 myProp
传递给 child
组件。然后 watch: {myProp: ...}
不工作。
你可以watch
props 在 props 改变时执行一些代码:
new Vue({
el: '#app',
data: {
text: 'Hello'
},
components: {
'child' : {
template: `<p>{{ myprop }}</p>`,
props: ['myprop'],
watch: {
myprop: function(newVal, oldVal) { // watch it
console.log('Prop changed: ', newVal, ' | was: ', oldVal)
}
}
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<child :myprop="text"></child>
<button @click="text = 'Another text'">Change text</button>
</div>
不确定您是否已解决(如果我理解正确),但这是我的想法:
如果父级接收到 myProp,并且您希望将其传递给子级并在子级中观看,则父级必须拥有 myProp 的副本(不是引用)。
试试这个:
new Vue({
el: '#app',
data: {
text: 'Hello'
},
components: {
'parent': {
props: ['myProp'],
computed: {
myInnerProp() { return myProp.clone(); } //eg. myProp.slice() for array
}
},
'child': {
props: ['myProp'],
watch: {
myProp(val, oldval) { now val will differ from oldval }
}
}
}
}
并在 html 中:
<child :my-prop="myInnerProp"></child>
实际上在这种情况下处理复杂集合时必须非常小心(传递几次)
你试过这个吗?
watch: {
myProp: {
// the callback will be called immediately after the start of the observation
immediate: true,
handler (val, oldVal) {
// do your stuff
}
}
}
如果 myProp 是一个对象,它通常不会被改变。所以, watch 永远不会被触发。 myProp 不被改变的原因是你在大多数情况下只是设置了 myProp 的一些键。 myProp 本身仍然是那个。
尝试观看 myProp 的道具,例如 "myProp.a",它应该可以工作。
你需要了解,你拥有的组件层次结构以及你如何传递道具,你的情况绝对是特殊的,开发人员通常不会遇到。
Parent Component -myProp-> Child Component -myProp-> Grandchild
Component
如果 myProp 在父组件中发生更改,它 也会反映在子组件中 。
如果 myProp 在子组件中发生变化,它 也会在孙组件中反映。
因此,如果 myProp 在父组件中发生更改,那么它 将反映在孙组件中 。 (到目前为止一切顺利)。
因此在层次结构中你不需要做任何事情 props 本身就是反应性的。
现在谈升职
如果在 grandChild 组件中更改了 myProp,它不会反映在子组件中。您必须在子组件中使用 .sync 修饰符并从 grandChild 组件发出事件。
如果在子组件中更改 myProp,它不会反映在父组件中。您必须在父组件中使用 .sync 修饰符并从子组件发出事件。
如果在 grandChild 组件中更改了 myProp,它不会反映在父组件中(很明显)。您必须使用 .sync 修饰符 child 并从孙组件发出事件,然后观察子组件中的 prop 并发出更改事件,父组件使用 .sync 修饰符监听该事件。
让我们看一些代码以避免混淆
Parent.vue
<template>
<div>
<child :myProp.sync="myProp"></child>
<input v-model="myProp"/>
<p>{{myProp}}</p>
</div>
</template>
<script>
import child from './Child.vue'
export default{
data(){
return{
myProp:"hello"
}
},
components:{
child
}
}
</script>
<style scoped>
</style>
Child.vue
<template>
<div> <grand-child :myProp.sync="myProp"></grand-child>
<p>{{myProp}}</p>
</div>
</template>
<script>
import grandChild from './Grandchild.vue'
export default{
components:{
grandChild
},
props:['myProp'],
watch:{
'myProp'(){
this.$emit('update:myProp',this.myProp)
}
}
}
</script>
<style>
</style>
Grandchild.vue
<template>
<div><p>{{myProp}}</p>
<input v-model="myProp" @input="changed"/>
</div>
</template>
<script>
export default{
props:['myProp'],
methods:{
changed(event){
this.$emit('update:myProp',this.myProp)
}
}
}
</script>
<style>
</style>
但是在这之后你就不会注意到 vue 的尖叫警告说
'Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders.'
同样,正如我之前提到的,大多数开发人员不会遇到这个问题,因为它是一种反模式。这就是您收到此警告的原因。
但是为了解决你的问题(根据你的设计)。我相信您必须解决上述问题(老实说是 hack)。我仍然建议您重新考虑您的设计并减少错误的发生。
希望对您有所帮助。
在我的例子中,我需要一个解决方案,任何时候任何道具都会改变,我需要再次解析我的数据。我厌倦了为我所有的道具制作单独的观察者,所以我使用了这个:
watch: {
$props: {
handler() {
this.parseData();
},
deep: true,
immediate: true,
},
},
从这个例子中拿走的关键点是使用 deep: true
所以它不仅监视 $props
而且它的嵌套值如props.myProp
您可以在此处了解有关此扩展监视选项的更多信息:https://vuejs.org/v2/api/#vm-watch
手表功能应该放在Child组件中。不是 parent。
对于双向绑定,您必须使用 .sync 修饰符
<child :myprop.sync="text"></child>
并且您必须在子组件中使用 watch 属性 来监听和更新任何更改
props: ['myprop'],
watch: {
myprop: function(newVal, oldVal) { // watch it
console.log('Prop changed: ', newVal, ' | was: ', oldVal)
}
}
我使用计算 属性 像:
items:{
get(){
return this.resources;
},
set(v){
this.$emit("update:resources", v)
}
},
Resources 在这种情况下是 属性:
props: [ 'resources' ]
您可以使用监视模式检测变化:
在原子级别做所有事情。所以首先通过安慰内部的东西来检查 watch 方法本身是否被调用。一旦确定 watch 正在被调用,就用您的业务逻辑将其粉碎。
watch: {
myProp: function() {
console.log('Prop changed')
}
}
@JoeSchr 有答案。如果您不想 deep: true
,这是另一种方法
mounted() {
this.yourMethod();
// re-render any time a prop changes
Object.keys(this.$options.props).forEach(key => {
this.$watch(key, this.yourMethod);
});
},
如果我需要在接收更改后创建逻辑,我会使用 props
和变量 computed
属性
export default {
name: 'getObjectDetail',
filters: {},
components: {},
props: {
objectDetail: {
type: Object,
required: true
}
},
computed: {
_objectDetail: {
let value = false
...
if (someValidation)
...
}
}
对我来说,这是一种礼貌的解决方案,可以更改一个特定的道具并用它创建逻辑
我会使用 props
和变量 computed
属性在接收更改后创建逻辑
export default {
name: 'getObjectDetail',
filters: {},
components: {},
props: {
objectDetail: { // <--- we could access to this value with this.objectDetail
type: Object,
required: true
}
},
computed: {
_objectDetail: {
let value = false
// ...
// if || do || while -- whatever logic
// insert validation logic with this.objectDetail (prop value)
value = true
// ...
return value
}
}
所以,我们可以在 html 渲染器上使用 _objectDetail
<span>
{{ _objectDetail }}
</span>
或以某种方式:
literallySomeMethod: function() {
if (this._objectDetail) {
....
}
}
如果你的 prop myProp
有嵌套的项目,那些嵌套的不会是反应性的,所以你需要使用像 lodash deepClone :
这样的东西
<child :myProp.sync="_.deepClone(myProp)"></child>
就是这样,不需要观察者或其他任何东西。
道具和 v-model 处理。如何将值从 parent 传递到 child 以及从 child 传递到 parent.
Watch 不是必需的! Vue 中的变异道具也是 anti-pattern,所以你永远不应该改变道具child 或组件中的值。使用 $emit 更改值,Vue 将始终按预期工作。
/* COMPONENT - CHILD */
Vue.component('props-change-component', {
props: ['value', 'atext', 'anumber'],
mounted() {
var _this = this
this.$emit("update:anumber", 6)
setTimeout(function () {
// Update the parent binded variable to 'atext'
_this.$emit("update:atext", "4s delay update from child!!")
}, 4000)
setTimeout(function () {
// Update the parent binded v-model value
_this.$emit("input", "6s delay update v-model value from child!!")
}, 6000)
},
template: '<div> \
v-model value: {{ value }} <br> \
atext: {{ atext }} <br> \
anumber: {{ anumber }} <br> \
</div>'
})
/* MAIN - PARENT */
const app = new Vue({
el: '#app',
data() {
return {
myvalue: 7,
mynumber: 99,
mytext: "My own text",
}
},
mounted() {
var _this = this
// Update our variable directly
setTimeout(function () {
_this.mytext = "2s delay update mytext from parent!!"
}, 2000)
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<props-change-component
v-model='myvalue'
:atext.sync='mytext'
:anumber.sync='mynumber'>
</props-change-component>
</div>
一些用例的有趣观察。
如果您通过道具观看商店中的数据项,并且您在同一个商店突变中多次更改数据项,则不会被观看。
但是,如果您将数据项更改分成同一突变的多个调用,它将被监视。
此代码不会触发观察者:
// Somewhere in the code:
this.$store.commit('changeWatchedDataItem');
// In the 'changeWatchedDataItem' mutation:
state.dataItem = false;
state.dataItem = true;
此代码将在每次突变时触发观察者:
// Somewhere in the code:
this.$store.commit('changeWatchedDataItem', true);
this.$store.commit('changeWatchedDataItem', false);
// In the 'changeWatchedDataItem' mutation:
changeWatchedDataItem(state, newValue) {
state.dataItem = newValue;
}
我的以下回答适用于使用 Vue 2 和组合 API 的人。
所以设置函数将是
setup: (props: any) => {
watch(() => (props.myProp), (updatedProps: any) => {
// you will get the latest props into updatedProp
})
}
但是,您需要从组合中导入 watch 函数 API。
我认为在大多数情况下,Vue 会在 prop 更改时更新组件的 DOM。
如果是这种情况,那么您可以使用 beforeUpdate()
或 updated()
挂钩 (docs) 来观看道具。
如果你只对 newVal
感兴趣,不需要 oldVal
,你可以这样做
new Vue({
el: '#app',
data: {
text: ''
},
components: {
'child': {
template: `<p>{{ myprop }}</p>`,
props: ['myprop'],
beforeUpdate() {
console.log(this.myprop)
},
updated() {
console.log(this.myprop)
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child :myprop="text"></child>
<input v-model="text" placeholder="Type here to view prop changes" style="width:20em">
</div>
如果你添加道具将会改变
<template>
<child :my-prop="myProp"/>
</template>
<script>
export default {
props: 'myProp'
}
</script>
默认情况下,组件中的道具是反应式的,您可以在组件内的道具上设置监视,这将帮助您根据需要修改功能。这是一个简单的代码片段来展示它是如何工作的
setup(props) {
watch(
() => props.propName,
(oldValue, newValue) => {
//Here you can add you functionality
// as described in the name you will get old and new value of watched property
},
{ deep: true },
{ immediate: true } //if you need to run callback as soon as prop changes
)
}
希望这可以帮助您从中获得想要的结果。
祝你有美好的一天。
在 VueJs 2.0 docs 中,我找不到任何可以侦听 props
更改的挂钩。
VueJs 有像 onPropsUpdated()
或类似的钩子吗?
更新
正如@wostex 所建议的,我尝试 watch
我的 属性 但没有任何改变。然后我意识到我有一个特例:
<template>
<child :my-prop="myProp"></child>
</template>
<script>
export default {
props: ['myProp']
}
</script>
我正在将父组件接收到的 myProp
传递给 child
组件。然后 watch: {myProp: ...}
不工作。
你可以watch
props 在 props 改变时执行一些代码:
new Vue({
el: '#app',
data: {
text: 'Hello'
},
components: {
'child' : {
template: `<p>{{ myprop }}</p>`,
props: ['myprop'],
watch: {
myprop: function(newVal, oldVal) { // watch it
console.log('Prop changed: ', newVal, ' | was: ', oldVal)
}
}
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<child :myprop="text"></child>
<button @click="text = 'Another text'">Change text</button>
</div>
不确定您是否已解决(如果我理解正确),但这是我的想法:
如果父级接收到 myProp,并且您希望将其传递给子级并在子级中观看,则父级必须拥有 myProp 的副本(不是引用)。
试试这个:
new Vue({
el: '#app',
data: {
text: 'Hello'
},
components: {
'parent': {
props: ['myProp'],
computed: {
myInnerProp() { return myProp.clone(); } //eg. myProp.slice() for array
}
},
'child': {
props: ['myProp'],
watch: {
myProp(val, oldval) { now val will differ from oldval }
}
}
}
}
并在 html 中:
<child :my-prop="myInnerProp"></child>
实际上在这种情况下处理复杂集合时必须非常小心(传递几次)
你试过这个吗?
watch: {
myProp: {
// the callback will be called immediately after the start of the observation
immediate: true,
handler (val, oldVal) {
// do your stuff
}
}
}
如果 myProp 是一个对象,它通常不会被改变。所以, watch 永远不会被触发。 myProp 不被改变的原因是你在大多数情况下只是设置了 myProp 的一些键。 myProp 本身仍然是那个。 尝试观看 myProp 的道具,例如 "myProp.a",它应该可以工作。
你需要了解,你拥有的组件层次结构以及你如何传递道具,你的情况绝对是特殊的,开发人员通常不会遇到。
Parent Component -myProp-> Child Component -myProp-> Grandchild Component
如果 myProp 在父组件中发生更改,它 也会反映在子组件中 。
如果 myProp 在子组件中发生变化,它 也会在孙组件中反映。
因此,如果 myProp 在父组件中发生更改,那么它 将反映在孙组件中 。 (到目前为止一切顺利)。
因此在层次结构中你不需要做任何事情 props 本身就是反应性的。
现在谈升职
如果在 grandChild 组件中更改了 myProp,它不会反映在子组件中。您必须在子组件中使用 .sync 修饰符并从 grandChild 组件发出事件。
如果在子组件中更改 myProp,它不会反映在父组件中。您必须在父组件中使用 .sync 修饰符并从子组件发出事件。
如果在 grandChild 组件中更改了 myProp,它不会反映在父组件中(很明显)。您必须使用 .sync 修饰符 child 并从孙组件发出事件,然后观察子组件中的 prop 并发出更改事件,父组件使用 .sync 修饰符监听该事件。
让我们看一些代码以避免混淆
Parent.vue
<template>
<div>
<child :myProp.sync="myProp"></child>
<input v-model="myProp"/>
<p>{{myProp}}</p>
</div>
</template>
<script>
import child from './Child.vue'
export default{
data(){
return{
myProp:"hello"
}
},
components:{
child
}
}
</script>
<style scoped>
</style>
Child.vue
<template>
<div> <grand-child :myProp.sync="myProp"></grand-child>
<p>{{myProp}}</p>
</div>
</template>
<script>
import grandChild from './Grandchild.vue'
export default{
components:{
grandChild
},
props:['myProp'],
watch:{
'myProp'(){
this.$emit('update:myProp',this.myProp)
}
}
}
</script>
<style>
</style>
Grandchild.vue
<template>
<div><p>{{myProp}}</p>
<input v-model="myProp" @input="changed"/>
</div>
</template>
<script>
export default{
props:['myProp'],
methods:{
changed(event){
this.$emit('update:myProp',this.myProp)
}
}
}
</script>
<style>
</style>
但是在这之后你就不会注意到 vue 的尖叫警告说
'Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.'
同样,正如我之前提到的,大多数开发人员不会遇到这个问题,因为它是一种反模式。这就是您收到此警告的原因。
但是为了解决你的问题(根据你的设计)。我相信您必须解决上述问题(老实说是 hack)。我仍然建议您重新考虑您的设计并减少错误的发生。
希望对您有所帮助。
在我的例子中,我需要一个解决方案,任何时候任何道具都会改变,我需要再次解析我的数据。我厌倦了为我所有的道具制作单独的观察者,所以我使用了这个:
watch: {
$props: {
handler() {
this.parseData();
},
deep: true,
immediate: true,
},
},
从这个例子中拿走的关键点是使用 deep: true
所以它不仅监视 $props
而且它的嵌套值如props.myProp
您可以在此处了解有关此扩展监视选项的更多信息:https://vuejs.org/v2/api/#vm-watch
手表功能应该放在Child组件中。不是 parent。
对于双向绑定,您必须使用 .sync 修饰符
<child :myprop.sync="text"></child>
并且您必须在子组件中使用 watch 属性 来监听和更新任何更改
props: ['myprop'],
watch: {
myprop: function(newVal, oldVal) { // watch it
console.log('Prop changed: ', newVal, ' | was: ', oldVal)
}
}
我使用计算 属性 像:
items:{
get(){
return this.resources;
},
set(v){
this.$emit("update:resources", v)
}
},
Resources 在这种情况下是 属性:
props: [ 'resources' ]
您可以使用监视模式检测变化:
在原子级别做所有事情。所以首先通过安慰内部的东西来检查 watch 方法本身是否被调用。一旦确定 watch 正在被调用,就用您的业务逻辑将其粉碎。
watch: {
myProp: function() {
console.log('Prop changed')
}
}
@JoeSchr 有答案。如果您不想 deep: true
mounted() {
this.yourMethod();
// re-render any time a prop changes
Object.keys(this.$options.props).forEach(key => {
this.$watch(key, this.yourMethod);
});
},
如果我需要在接收更改后创建逻辑,我会使用 props
和变量 computed
属性
export default {
name: 'getObjectDetail',
filters: {},
components: {},
props: {
objectDetail: {
type: Object,
required: true
}
},
computed: {
_objectDetail: {
let value = false
...
if (someValidation)
...
}
}
对我来说,这是一种礼貌的解决方案,可以更改一个特定的道具并用它创建逻辑
我会使用 props
和变量 computed
属性在接收更改后创建逻辑
export default {
name: 'getObjectDetail',
filters: {},
components: {},
props: {
objectDetail: { // <--- we could access to this value with this.objectDetail
type: Object,
required: true
}
},
computed: {
_objectDetail: {
let value = false
// ...
// if || do || while -- whatever logic
// insert validation logic with this.objectDetail (prop value)
value = true
// ...
return value
}
}
所以,我们可以在 html 渲染器上使用 _objectDetail
<span>
{{ _objectDetail }}
</span>
或以某种方式:
literallySomeMethod: function() {
if (this._objectDetail) {
....
}
}
如果你的 prop myProp
有嵌套的项目,那些嵌套的不会是反应性的,所以你需要使用像 lodash deepClone :
<child :myProp.sync="_.deepClone(myProp)"></child>
就是这样,不需要观察者或其他任何东西。
道具和 v-model 处理。如何将值从 parent 传递到 child 以及从 child 传递到 parent.
Watch 不是必需的! Vue 中的变异道具也是 anti-pattern,所以你永远不应该改变道具child 或组件中的值。使用 $emit 更改值,Vue 将始终按预期工作。
/* COMPONENT - CHILD */
Vue.component('props-change-component', {
props: ['value', 'atext', 'anumber'],
mounted() {
var _this = this
this.$emit("update:anumber", 6)
setTimeout(function () {
// Update the parent binded variable to 'atext'
_this.$emit("update:atext", "4s delay update from child!!")
}, 4000)
setTimeout(function () {
// Update the parent binded v-model value
_this.$emit("input", "6s delay update v-model value from child!!")
}, 6000)
},
template: '<div> \
v-model value: {{ value }} <br> \
atext: {{ atext }} <br> \
anumber: {{ anumber }} <br> \
</div>'
})
/* MAIN - PARENT */
const app = new Vue({
el: '#app',
data() {
return {
myvalue: 7,
mynumber: 99,
mytext: "My own text",
}
},
mounted() {
var _this = this
// Update our variable directly
setTimeout(function () {
_this.mytext = "2s delay update mytext from parent!!"
}, 2000)
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<props-change-component
v-model='myvalue'
:atext.sync='mytext'
:anumber.sync='mynumber'>
</props-change-component>
</div>
一些用例的有趣观察。
如果您通过道具观看商店中的数据项,并且您在同一个商店突变中多次更改数据项,则不会被观看。
但是,如果您将数据项更改分成同一突变的多个调用,它将被监视。
此代码不会触发观察者:
// Somewhere in the code: this.$store.commit('changeWatchedDataItem'); // In the 'changeWatchedDataItem' mutation: state.dataItem = false; state.dataItem = true;
此代码将在每次突变时触发观察者:
// Somewhere in the code: this.$store.commit('changeWatchedDataItem', true); this.$store.commit('changeWatchedDataItem', false); // In the 'changeWatchedDataItem' mutation: changeWatchedDataItem(state, newValue) { state.dataItem = newValue; }
我的以下回答适用于使用 Vue 2 和组合 API 的人。 所以设置函数将是
setup: (props: any) => {
watch(() => (props.myProp), (updatedProps: any) => {
// you will get the latest props into updatedProp
})
}
但是,您需要从组合中导入 watch 函数 API。
我认为在大多数情况下,Vue 会在 prop 更改时更新组件的 DOM。
如果是这种情况,那么您可以使用 beforeUpdate()
或 updated()
挂钩 (docs) 来观看道具。
如果你只对 newVal
感兴趣,不需要 oldVal
new Vue({
el: '#app',
data: {
text: ''
},
components: {
'child': {
template: `<p>{{ myprop }}</p>`,
props: ['myprop'],
beforeUpdate() {
console.log(this.myprop)
},
updated() {
console.log(this.myprop)
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child :myprop="text"></child>
<input v-model="text" placeholder="Type here to view prop changes" style="width:20em">
</div>
如果你添加道具将会改变
<template>
<child :my-prop="myProp"/>
</template>
<script>
export default {
props: 'myProp'
}
</script>
默认情况下,组件中的道具是反应式的,您可以在组件内的道具上设置监视,这将帮助您根据需要修改功能。这是一个简单的代码片段来展示它是如何工作的
setup(props) {
watch(
() => props.propName,
(oldValue, newValue) => {
//Here you can add you functionality
// as described in the name you will get old and new value of watched property
},
{ deep: true },
{ immediate: true } //if you need to run callback as soon as prop changes
)
}
希望这可以帮助您从中获得想要的结果。 祝你有美好的一天。