Browse Source

add prop mutation warning

Evan You 10 years ago
parent
commit
03e0dab1f4

+ 6 - 0
src/core/instance/lifecycle.js

@@ -102,12 +102,18 @@ export function lifecycleMixin (Vue: Class<Component>) {
     // update props
     if (propsData && vm.$options.props) {
       observerState.shouldConvert = false
+      if (process.env.NODE_ENV !== 'production') {
+        observerState.isSettingProps = true
+      }
       const propKeys = vm.$options._propKeys || []
       for (let i = 0; i < propKeys.length; i++) {
         const key = propKeys[i]
         vm[key] = validateProp(vm, key, propsData)
       }
       observerState.shouldConvert = true
+      if (process.env.NODE_ENV !== 'production') {
+        observerState.isSettingProps = false
+      }
     }
     // update listeners
     if (listeners) {

+ 16 - 1
src/core/instance/state.js

@@ -37,7 +37,22 @@ function initProps (vm: Component) {
     observerState.shouldConvert = isRoot
     for (let i = 0; i < keys.length; i++) {
       const key = keys[i]
-      defineReactive(vm, key, validateProp(vm, key, propsData))
+      /* istanbul ignore else */
+      if (process.env.NODE_ENV !== 'production') {
+        defineReactive(vm, key, validateProp(vm, key, propsData), () => {
+          if (vm.$parent && !observerState.isSettingProps) {
+            warn(
+              `Avoid mutating a prop directly since the value will be ` +
+              `overwritten whenever the parent component re-renders. ` +
+              `Instead, use a data or computed property based on the prop's ` +
+              `value. Prop being mutated: "${key}"`,
+              vm
+            )
+          }
+        })
+      } else {
+        defineReactive(vm, key, validateProp(vm, key, propsData))
+      }
     }
     observerState.shouldConvert = true
   }

+ 11 - 2
src/core/observer/index.js

@@ -22,7 +22,8 @@ const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
  * under a frozen data structure. Converting it would defeat the optimization.
  */
 export const observerState = {
-  shouldConvert: true
+  shouldConvert: true,
+  isSettingProps: false
 }
 
 /**
@@ -126,7 +127,12 @@ export function observe (value: any): Observer | void {
 /**
  * Define a reactive property on an Object.
  */
-export function defineReactive (obj: Object, key: string, val: any) {
+export function defineReactive (
+  obj: Object,
+  key: string,
+  val: any,
+  customSetter?: Function
+) {
   const dep = new Dep()
 
   const property = Object.getOwnPropertyDescriptor(obj, key)
@@ -163,6 +169,9 @@ export function defineReactive (obj: Object, key: string, val: any) {
       if (newVal === value) {
         return
       }
+      if (process.env.NODE_ENV !== 'production' && customSetter) {
+        customSetter()
+      }
       if (setter) {
         setter.call(obj, newVal)
       } else {

+ 2 - 0
test/unit/features/options/props.spec.js

@@ -21,6 +21,7 @@ describe('Options props', () => {
       vm.$refs.child.b = 'qux'
     }).then(() => {
       expect(vm.$el.innerHTML).toBe('qux')
+      expect('Avoid mutating a prop directly').toHaveBeenWarned()
     }).then(done)
   })
 
@@ -44,6 +45,7 @@ describe('Options props', () => {
       vm.$refs.child.b = 'qux'
     }).then(() => {
       expect(vm.$el.innerHTML).toBe('qux')
+      expect('Avoid mutating a prop directly').toHaveBeenWarned()
     }).then(done)
   })