vuejs / docs

📄 Documentation for Vue 3
https://vuejs.org
Other
2.89k stars 4.3k forks source link

Document removal of $children API #454

Closed bencodezen closed 3 years ago

bencodezen commented 4 years ago

Per @posva:

I think it's worth documenting the removal of $parent (and $children) as well vuejs/vue-next#2044 (comment)

rowanwins commented 3 years ago

+1 , I've run into the lack of $children in porting ViewUI component library and haven't found any advice yet as to how I ought to replace it.

LinusBorg commented 3 years ago

@rowanwins in what way do you use $children that is hard / can'T be done with a ref in an ergonomic way?

I'll document the removal and would like to add guidance for use cases that aren't easily solved with refs.

tim-janik commented 3 years ago

@rowanwins in what way do you use $children that is hard / can'T be done with a ref in an ergonomic way?

Here are a few examples I'm running into during my Vue2->Vue3 porting work:

Basically, Vue allows creating components by composing other elements/components in a nested fashion. Recursively walking those elements is a very natural way of programming a large variety of algorithms. That's why all comparable toolkits allow walking of children, e.g. Gtk+, the DOM, Qt, etc. Some uses can be implemented differently, e.g. by contextmenu/menubar providing hooks for hotkey registrations in menuitems received via inject, but that gets ugly for e.g. tree items that may or may not be embedded in a menu and doesn't scale to future algorithms needing recursive walks. So its often cleaner to define a particular interface on components, e.g. an extract_activation_hotkey() function, and then walk subtrees recursively, while handling components that follow the interface and special DOM elements, while ignoring others.

That said, for my uses, I'm not going to complicate my component implementations further [*], I rather "fix" my recursive walk to deal with yet another Vue3 oddity, like resorting to DOM walks plus extracting __vnode or similar.

[*] I'm starting to regret engaging in the port, I've had to add lots of boilerplate to my Vue2 components, because of v-model incompatibilities, emits (just a pain if you've been satisfied with Vue2 here), v-slot incompatibility, v-for keys, and the most nasty one: replace all component?.maybefoo accesses with Object.hasOwnProperty.call (component, 'maybefoo') && component.maybefoo just to avoid Proxy warnings that have been largely useless for me.

Let me know when you have a mailing list to discuss these kind of "war stories" and want to hear more about a user perspective in porting ;-)

rowanwins commented 3 years ago

Thanks for chiming in @LinusBorg , even knowing that using ref's would be the suggested approach is helpful 👍

In the codebase I'm looking at there are a whole bunch of scenarios, a couple for example

Moving to refs seems feasible and I don't think it would add too much complexity. Also I'm not the owner/maintainer of the lib so I won't argue whether this was ever the most optimal vue-esque way of doing things, so I may do some other refactoring of that code also.

So far most of my migration has gone reasonably well, I've found the migration docs pretty good so far so thank you!

tim-janik commented 3 years ago

If the doc writers concede that cases exist that do require $children, here is an emulating mixin I'm using now. Turns out that vue-3.0.0 still provides $parent, but it's inaccurate (null) for components nested inside transitions. So I had to resort to provide+inject on every component (yuk!). That however produces a Vue-warning about not finding an injection for the toplevel - and it cannot be hacked around, because inject cannot be a function, while provide can.

/// Provide `$children` (and `$vue_parent`) on every component.
const mixin_$children = {
  provide() { return { '$vue_parent': this }; },
  inject: [ '$vue_parent' ],    // warns on toplevel component
  beforeCreate () {
    console.assert (this.$children === undefined);
    this.$children = [];
  },
  created() {
    if (this.$vue_parent)       // using $parent breaks for transitions
      this.$vue_parent.$children.push (this);
  },
  unmounted() {
    if (!this.$vue_parent)
      return;
    const pos = this.$vue_parent.$children.indexOf (this);
    if (pos < 0)
      throw Error ("failed to locate this in $vue_parent.$children:", this);
    this.$vue_parent.$children.splice (pos, 1);
  },
};
LinusBorg commented 3 years ago

Turns out that vue-3.0.0 still provides $parent, but it's inaccurate (null) for components nested inside transitions.

Seems like a bug in itself.

LinusBorg commented 3 years ago

Tipp for your mixin:

inject: {
  $vue_parent: {
    from: '$vue_parent',
    default: null,
  }
},
LinusBorg commented 3 years ago

@tim-janik I'd be interested both in a conversation about your migration pain (some of the ones you listed are a bit of suprise, others not so much) as well as one about the details of your implementation of e.g. the tree selector.

For the former, I'm afraid we don't have a mailing list and likely won't start one (I personally would be hard pressed to say where one would do that), but I'd be available on Discord (chat.vuejs.org).

Concerning the latter: I don't doubt that you get something out of $children that you can't get with sufficent ease from other APis. I'd like to get a better understanding why that is and how we can approve in that area.

You see, I get that it's convenient to access components this way and imperatively control them. It doesn't fit very well into the rest of the self-sufficient / encapsulated component philosophy though, and I'd like to get a better understanding where members of the community feel the need to fall back to such APIs.

I have built stuff like trees and dropdowns, modals etc - but have usually not felt a need to use $children - which doesn't negate your experience, I just wanna broaden mine. So it would be great if you could provide a more specific example, with code if possible.


Sidenote: I tried to replicate the problem with <transition> but failed:

https://codesandbox.io/s/shy-microservice-yxw0j

I get a component reference for $parent no matter where I call it. The only thing to work around is that when in a transition, we have to walk the chain further up.