Giter VIP home page Giter VIP logo

Comments (15)

LinusBorg avatar LinusBorg commented on May 3, 2024 5

Hey Evan!

I like this. I don't have any real opinion on the details like created() vs. data() vs. state(), but the general syntax/workflow seems great.

I would like to see the risk of overwhelming users with too many new things, or rather - too many choices with 3.0 - being adressed, though.

I really think there will be FUD over object vs. class syntax already. We will of course adress that by recommending for people to stick to what they have similar to how React doesn't recommend converting all class components to functional components + hooks.

But just like in the React community, despite that recommendation, some level of FUD will exist.

Introducing this totally new way of doing things in 3.0 will probably have the same effect:

  • "When I migrate to Vue 3.0, should I migrate my mixins to hooks or not? are mixins dead? They seem inferior, so why stick with them?"
  • some random Blog post "7 reasons to switch to Vue hooks NOW" (it's always 7 reasons, btw - why?)

etc, pp.

We can only do so much to contain that, and I would hate for people getting the impression that 3.0 does an Angular move by changing "everything" (however wrong that impression may be).

Just as an idea worth discussing - Would we maybe consider introducting this as explicitly experimental, or publish it in 3.1, after 3.0 has stabilized after release?

from core.

yyx990803 avatar yyx990803 commented on May 3, 2024 2

Another important aspect is the ability to compose multiple "logic modules" together by passing state between them (while not polluting the namespace of the host component):

export default {
  props: ['id'],
  created() { 
    const position = useMousePosition()
    const orientation = useDeviceOrientation()
    const matrix = useMatrix(x, y, orientation)

    return {
      matrix
    }
  }
}

So instead of multiple mixins all competing on the same this namespace for state persistence (and risk clashing with one another), we can just pass refs around via function arguments.

from core.

LinusBorg avatar LinusBorg commented on May 3, 2024 1

I think another aspect, not necessarily related to mixins, is that you don't spread parts of a behaviour over different parts of your component, i.e. data&created&watch&destroyed. You can define the whole lifecycle of a piece of behaviour in one place.

from core.

LinusBorg avatar LinusBorg commented on May 3, 2024

Another thought crossing my mind: Since this is a completely new API, shouldn't we be able to port it to v2, or at least 2.x-next (which will be using the new Porxy-based Reactivity system?

Would make it possible to use new packages that expose hooks in in 2.0 apps that are not ready tpo be migrated.

And yes, I'm aware that this kind of contradicts my previous point about releasing hooks later :-P

from core.

yyx990803 avatar yyx990803 commented on May 3, 2024

@LinusBorg I totally agree. This is in fact my biggest concern as well.

But I think that really depends on how we present it. Here this internal proposal is presenting it directly as a hooks equivalent because that's what it is aiming to be - however, we don't really have to introduce it to the users that way.

Notice that the reactivity APIs (value, computed and watch) are not really bound to hooks - they can be used anywhere and are useful in their own right, exposing more capabilities of Vue's reactivity system. The standalone watch function can be a direct replacement of the current watch option and this.$watch method (and it is tree-shakable!).

If we use data() instead of created(), then all we are adding is the ability to bind refs in the returned object, which also makes sense.

The only part that is really hooks-like is the useXXX APIs - this is in fact similar to 2.x's vm.$on('hook:mounted'). So we actually already have the equivalent (but less ergonomic) capabilities in 2.x today:

function useSomeLogic(vm) {
  const count = Vue.observable({ value: 0 })
  vm.$watch(() => count.value, value => {
    // ...
  }, { immediate: true })
  vm.$on('hook:mounted', () => {
    // ...
  })
  return count
}

export default {
  template: `<div>{{ count.value }}</div>`,
  data() {
    return {
      count: useSomeLogic(this)
    }
  }
}

This proposal isn't really introducing anything drastically different, but rather polishing some existing APIs to make them work better when used to encapsulate and reuse logic.

from core.

Akryum avatar Akryum commented on May 3, 2024

Some thoughts:

  • What about computed setters?
  • Why not onMounted instead of useMounted? I find it more natural to write. Maybe having onXXX for side-effects only "'hooks" is more Vue-like, and useXXX could be for "hooks" that returns a reference?
  • I'm not so sure about having watch being immediate by default, it clashes with the existing watch API we use everywhere in Vue 2.x, adding to the FUD. I get it's useful though, so maybe we could expose another hook like runWatch, doWatch or watchImmediate?
  • What if we want to watch a ref returned in created, or use it in a computed property? Since those are defined before created is called, won't there be issues? What about having a new lifecycle hook, like bind for example that gets called after data and computed but before watchers are setup? How about "hooks" that rely on computed properties that in turn could rely on references?

from core.

LinusBorg avatar LinusBorg commented on May 3, 2024

What about computed setters?

Would that work?

function computedWithSetter(getter, setter) {

  const computedRef = computed(getter)
  return Object.defineProperty(res, 'value',{
    get() { return computedRef.value }
    set: setter,
  })
}
created() {
  const computedWSetterRef = computedWithSetter(() => someRef.value, () => /*do whatever*/ )

  return {
    computedWSetterRef
  }
}
<input v-model="computedWSetterRef.value">

I'm not so sure about having watch being immediate by default,

I think it's fine. There's a lot of use cases where I feel watchers would be more useful and code shorter if they were immediate by default.

What if we want to watch a ref returned in created, or use it in a computed property? Since those are defined before created is called, won't there be issues?

Tricky ... :/ Tried to simulate it in 2.0 and failed to make it work.

I like adding it to created as I think it fits the mental model - we create a component, then add additional behaviours. but that indeed makes it hard / impossible to reference them in computed props or watchers afaict.

beforeCreate doesn't seem to fit as well, as now we can't access any data, $store and whatnot?

from core.

yyx990803 avatar yyx990803 commented on May 3, 2024

@LinusBorg if a ref is returned there's no need to use .value in the template so it could just be v-model="computedWSetterRef"

What if we want to watch a ref returned in created, or use it in a computed property? Since those are defined before created is called, won't there be issues?

Yeah I think in that case a new hook that is called after props initialization but before computed initialization would be useful. As for watchers - I'm thinking of removing watch option and this.$watch altogether because they are non-treeshakable.

from core.

LinusBorg avatar LinusBorg commented on May 3, 2024

I'm thinking of removing watch option and this.$watch altogether because they are non-treeshakable.

Considering our promise of keeping the Vue 3 API largely compatible to 2.0, I don't think we should/can remove APIs for the sole reason of shaving a few bytes of the final bundle in a few use cases.

from core.

yyx990803 avatar yyx990803 commented on May 3, 2024

@LinusBorg it is trivial to support in the migration build, and in most cases can be automatically converted. The only reason this.$watch is exposed on this is so that the watcher can be stopped when the component is destroyed. A generic watch API can and should be usable in any context. However if we introduce that we end up with 3 different ways of watching things:

  1. this.$watch
  2. the watch option
  3. the globally imported watch method.

Where (3) provides a superset of (1) and (2). In this case, (1) and (2) just seem pointless and redundant.

from core.

posva avatar posva commented on May 3, 2024

is it possible to hit a middleground with a compat plugin that adds those features (watch for example) with warnings an cli hints to do the automatic conversion?

from core.

LinusBorg avatar LinusBorg commented on May 3, 2024

Unlike React Hooks, created is called only once, so these calls are not subject to call order and can be conditional.

Good point. Another positive effect of this, combined with the reactivity system v. React's immutable state, it that we don't have to worry about closures breaking stuff if the wrong references are included in it, or dependency tracking in React's useEffect.

We should really stress this point as from what I read about problems with hooks, people really fall a lot for these kind of unwritten / hidden rules - which is why React also did an eslint plugin to keep people from shooting themselves in the foot.

We don't really need that, do we?

OTOH, The point about referencing hook refs from within computed etc. might be our version of such footguns.

from core.

yyx990803 avatar yyx990803 commented on May 3, 2024

@LinusBorg

Yes, this proposal should work more along the lines of typical JavaScript intuitions than React hooks.

Re refs: once a ref is exposed to the instance they just become normal properties.

For the sake of discussion, let's call it "Vue blocks" (to differentiate from React hooks and lifecycle hooks)... we extract and encapsulate logic inside a block, and then expose it to the instance. Let's call it "the block side" and "the instance side":

import { value } from 'vue'

funciton useCustomBlock() {
  // the block side
  return value(0)
}

export default {
  created() {
    return {
      // expose a ref to the instance (becomes a normal root-level property)
      count: useCustomBlock()
    }
  },
  computed: {
    countPlusOne() {
      // the instance side
      // no need to use .value
      return this.count + 1
    }
  }
}

You will only create and use refs on the blocks side, because there is not a persistent this context to reference values that will change over time. But once refs are bound to an instance, there's no longer the need for them to be refs, so they can just work like normal properties.

from core.

chrisvfritz avatar chrisvfritz commented on May 3, 2024

@yyx990803 I might have some ideas for how we could build on top of current mixins instead and avoid adding much new API or concepts, but first I want to make sure I'm understanding the full advantages this syntax provides. The ones I've seen are:

  • We're explicit about what's exposed to the component rather than forcing users to jump through hoops to make some properties private.
  • Components explicitly define what's added to the instance from a mixin, rather than this information being invisible.
  • It's possible to mix in behavior after the component is created, e.g. using data from props like in the useFriendStatus example.

Is that everything, or are there other advantages/goals?

from core.

yyx990803 avatar yyx990803 commented on May 3, 2024

This has been split into two separate RFCs: #24 & #25

from core.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.