Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Lifecycle Injection #23

Closed
wants to merge 1 commit into from
Closed

Dynamic Lifecycle Injection #23

wants to merge 1 commit into from

Conversation

yyx990803
Copy link
Member

Rendered

import { onMounted, onUnmounted } from 'vue'

export default {
  beforeCreate() {
    onMounted(() => {
      console.log('mounted')
    })

    onUnmounted(() => {
      console.log('unmounted')
    })
  }
}

@yyx990803 yyx990803 added core 3.x This RFC only targets 3.0 and above labels Mar 26, 2019
@jvbianchi
Copy link

jvbianchi commented Mar 26, 2019

In the Data Fetching example:

const res = value({
  status: 'pending',
  data: null,
  error: null
})

could we use state instead of value?

const res = state({
  status: 'pending',
  data: null,
  error: null
})

@LinusBorg
Copy link
Member

Yes, that would work as well.

@jvbianchi
Copy link

jvbianchi commented Mar 26, 2019

Is there any reason to use one over the other, in this case?

@LinusBorg
Copy link
Member

LinusBorg commented Mar 26, 2019

No, except to demonstrate that refs work in this context as well, I guess. They are the new concept, while state is just 2.*'s Vue.observable()

@crutchcorn
Copy link

crutchcorn commented Mar 26, 2019

I love the verbosity in the later examples where custom logic was extracted to their own variables. These have an easy to read/understand intuitive API in my opinion.

I hadn't seen it explicitly mentioned in the docs, but can there be many of these variables in a single data component method? (I'm assuming so, but just double checking, I'll bet I missed something)

@LinusBorg
Copy link
Member

LinusBorg commented Mar 26, 2019

I hadn't seen it explicitly mentioned in the docs, but can there be many of these variables in a single data component method?

Yes, that's possible, and kind of the point. This allows us to pass state from one of these functions to another, which is something that was not really/cleanly possible before with mixins.

(silly example incoming:)

data() {

  const { x, y } = useMousePosition()

  const showSideBar = computed(() => x.value < 10)

  return  {
    x,
    y,
    showSideBar,
  }
}

@voluntadpear
Copy link

Are there any intentions to deprecate mixins on version 3? I think that the new Advanced Reactivity API along with this proposal are enough for building reusable bits of logic (and besides, we have renderless components and higher-order components if for some reason this is not enough) without the usual drawbacks of mixins (implicit and hard to follow population of state values in data() etc.)

@LinusBorg
Copy link
Member

LinusBorg commented Mar 26, 2019

It's being discussed, yes. But nothing final.

We obviously won't remove mixins in v3, to ensure backwards compatibility in this regard, but personally i think we can drop them in v4.

@aztalbot
Copy link

Data fetching example is great; can’t wait for this!

@LinusBorg, looking at your example above, you pass a computed pointer to the returned data object. Would it be a read-only property in the component’s state same as an ordinary computed property?

In practice, would you expect react-hooks-like external functions to return read-only computed pointers alongside mutable pointers (or even methods/functions)? If so I’m not sure how clear it would be to quickly tell which data object properties are read-only vs mutable (I don’t mean it’s bad, I only mean it’s less obvious than ordinary getters/computed properties).

One other question I had is: what order do these injected lifecycle hooks fire? If I inject onUpdated in the data method and onUpdated in the created method, which onUpdated fires first? Is it FIFO based on component lifecycle? I only see it mattering if there is an immediate side effect to the DOM or some external API.

@LinusBorg LinusBorg mentioned this pull request Mar 27, 2019
@LinusBorg
Copy link
Member

@aztalbot

Would it be a read-only property in the component’s state same as an ordinary computed property?

Yes.

In practice, would you expect react-hooks-like external functions to return read-only computed pointers alongside mutable pointers (or even methods/functions)? If so I’m not sure how clear it would be to quickly tell which data object properties are read-only vs mutable (I don’t mean it’s bad, I only mean it’s less obvious than ordinary getters/computed properties).

That's indeed a valid point - if such a helper returns a normal stateful object, a pointer, and a computed pointer, it's not entirely obvious which is which. 🤔

what order do these injected lifecycle hooks fire? If I inject onUpdated in the data method and onUpdated in the created method, which onUpdated fires first? Is it FIFO based on component lifecycle? I only see it mattering if there is an immediate side effect to the DOM or some external API.

I think we should specificy this, but haven't so far.

I would say it should work like mixins would: Mixed-in lifecycle hooks run first (and here order is FIFO, yes), component's own hooks after that.

@amatthieu
Copy link

Hello there.

Will there be some restrictions on an dynamic lifecycle injection usage ?
For example, using onMounted() in the beforeDestroy() lifecycle.

@LinusBorg
Copy link
Member

I guess that would be something we could with lint rules in solve in eslint-plugin-vue

@gwardwell
Copy link

@LinusBorg This might not be the right place for this question, so please feel free to point me to the right place to ask. Picking up your dynamic lifecycle injection illustration as the future replacement for mixins:

data() {

  const { x, y } = useMousePosition()

  const showSideBar = computed(() => x.value < 10)

  return  {
    x,
    y,
    showSideBar,
  }
}

I see examples of data, computed values and I get how it would work for lifecycle hooks, but how would I handle props and methods?

@trickstival
Copy link
Contributor

trickstival commented May 29, 2019

Awesome RFC!

I'm really excited about seeing the power of hooks being brought to Vue in such native feeling.

In the data fetching example, could the computed() function be used inside the "useFetch" func?

Instead of this:

  data() {
    return {
      postData: useFetch(computed(() => `/api/posts/${this.id}`))
    }
  }

Maybe something like that:

function useFetch(endpointFunc) {
  const endpointRef = computed(endpointFunc)
  // rest of the function
}

const App = {
  // ...
  data() {
    return {
      // Now there's no need to call computed() everytime we fetch the api
      postData: useFetch(() => `/api/posts/${this.id}`)
    }
  }
}

@TotooriaHyperion
Copy link

What's the recommended pattern to use this when the injected is dependent on props change?
And when with custom functionality that declared through function call to compose with each other?

I mean to achieve something like this.

watch: {
    someUniqueToken(newToken, oldToken) {
        // unsubscribe old service
        this.XXXunsub && this.XXXunsub();

        // get a service instance that related to this token. inst.in/inst.out is a Rx Subject
        const inst = getServiceXXX(newToken);
        const subscription = inst.out.subscribe(....);

        this.XXXunsub = () => subscription.unsubscribe();
        this.XXXin = inst.in;
    },
    XXXin(newSubject, oldSubject) {
        // do some logic composition.
    },
}

@LinusBorg
Copy link
Member

LinusBorg commented Jun 3, 2019

data() {
  const unsub = value(null)
  const XXXIn = value(null)

  watch(
    vm => vm.someUniqueToken,
    (newToken, oldToken) => {
      unsub.value && unsub.value()

      const inst = getServiceXXX(newToken);
      const subscription = inst.out.subscribe(....);

        unsub.value = () => subscription.unsubscribe();
        XXXin.value = inst.in;
    },
  )

  watch(
    () => XXXin,
    (newSubject, oldSubject) {
        // do some logic composition.
    }
  )

  return {
    XXXIn
  }
}

You wouldn't even have to return XXXIn from data for it to work (unless you need it in the template or another method).

And could extract all of this beahvour in a function that you import run in data.

useMyRxLogic(propName) {
  const unsub = value(null)
  const XXXIn = value(null)

  watch(
    vm => vm[propName],
    (newToken, oldToken) => {
      unsub.value && unsub.value()
      const inst = getServiceXXX(newToken);
      const subscription = inst.out.subscribe(....);

      unsub.value = () => subscription.unsubscribe();
      XXXin.value = inst.in;
    },
  )

  watch(
    () => XXXin,
    (newSubject, oldSubject) {
        // do some logic composition.
    }
  )

  return {
    XXXIn,
    unsub,
  }
}
data() {
  const { unsub, XXXIn } = useMyRxLogic('someUniqueToken')

  return {
    myUnSub: unsub, // easy to rename before exposing on the component, so no naming conflicts
    in: XXXIn
  }
}

@yyx990803
Copy link
Member Author

Closing as part of #42.

@yyx990803 yyx990803 closed this Jun 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.x This RFC only targets 3.0 and above core
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants