父组件动态数据子组件props无法获取

我的代码是这样的
父组件:

export default {
  data() {
    return {
      choice: []
    }
  }
  methods: {
    targetClass() {
      var item = {};
      ......
      this.choice.push(item); // 动态的向 choice 中追加新对象
    }
  }
}

子组件:

export default {
  props: {
    choice: {
      type: Array,
      default: () => {
        return [];
      }
    }
  }
  ......
}

经测试

<mip-form2 :choice="choice"></mip-form2>

子组件props无法获取到choice
但这么写就可以获取到

<mip-form2 :choice="Array.from(choice)"></mip-form2>

请问是为什么呢?还望指点,谢谢!

阅读 3k
4 个回答

vue内部的数据监听其实并没有想象中那么健全,在层级比较深的数据中,监听失效导致页面没有渲染的情况其实是老生常谈了。
vue官方文档提供了一个专门的方法用来处理这个问题传送门,请在页面内搜索Vue.set。
但是我一般比较嫌弃这种写法,太麻烦了。
父组件试下这种改法

 targetClass() {
      var item = {};
      ......
      let choice = this.choice
      choice.push(item)
      this.choice = [].concat(choice)
    }

我这种写法是告诉vue整个choice都被我改朝换代了,你不要再监听里面有什么变换,直接给我全部重新渲染吧。其实相对的性能会差些。

当父组件 mip-form2 获取到数据,子组件使用 props 接收数据时,执行 mounted 的时候 这个方法 还没有运行到,而且 mounted 只执行一次,这时 props 中接收的数据为空。所以需要加个判断 <mip-form2 :choice="choice" v-if="choice.length > 0"></mip-form2>

// 或者在子组件箭头这个属性

    watch: {
      choice: {
        deep: true,
        handler(newVal) {
          //你需要执行的代码
           console.log(newVal)
        }
      }
    },

这个可以从Vue源码结合Vue生命周期进行分析:
父组件先beforeCreate => created => beforeMount , 然后子组件开始beforeCreate => created => beforeMount => mounted,父组件再mounted
从如下源码:

Vue.prototype._init = function (options?: Object) {
  const vm: Component = this
  /// 略
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')

  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
  }

  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

可以得知,对dataprops的响应式化处理发生在beforeCreatecreated之间
响应式化部分代码如下

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

由于父组件的data先进行响应式化处理,在对父组件的choice进行响应式化时,会为其添加__ob__属性,而当对子组件的props进行响应式化处理时,由于子组件的props.choice已经和父组件的choice是同一个对象,响应式化处理是,判断其已经有__ob__属性,所以不会进行深度遍历,也就是说子组件无法监听到choicepush等数组操作方法,只能监听到父组件中choice引用对象的变化

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题