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

watch behavior in v3 is different to v2 #671

Closed
lmiller1990 opened this issue Jan 29, 2020 · 16 comments
Closed

watch behavior in v3 is different to v2 #671

lmiller1990 opened this issue Jan 29, 2020 · 16 comments

Comments

@lmiller1990
Copy link
Member

lmiller1990 commented Jan 29, 2020

Version alpha 4

Reproduction link

https://codesandbox.io/s/v-model-decomposed-camel-case-lx173

Steps to reproduce

Take this simple app. In Vue.js 2, it will not do the console.log, unless you set immediate: true in watch options.

const App = {
  data() {
    return {
      message: "Hello"
    };
  },
  watch: {
    message: {
        handler() {
          console.log("Immediately Triggered")
        }
    }
  }
}

In Vue.js 3, however, watch triggers immediately by default. That is because the default for the watch hook is { lazy: true } as discussed here under "Lazy Invocation". This means in the current state, this would be a breaking change for Vue.js 2 apps relying on the immediate option defaulting to false.

Also, in Vue 2.x you can do:

data() {
  foo: { 
    bar: { 
      qux: 'val' 
    }  
  } 
},

watch: {
  'foo.bar.qux' () { /* */ }
}

And watch a deeply nested property. This appears to not work in Vue 3.X - nothing happens.

What is expected?

Behavior same as Vue 2.X, or open RFC to change behavior:

  • watch does not trigger immediately
  • can watch nested property

What is actually happening?

  • watch triggers immediately
  • cannot watch deeply nested property

I like the new changes, they rock. Just need to make sure we have either a clear migration path for people relying on old behavior if making changes.

I am happy to make a PR making changes to the code base if needed, or an RFC regarding the breaking changes.

@yyx990803
Copy link
Member

yyx990803 commented Jan 29, 2020

Yes, these are somewhat intended.

  • The watch in Composition API exhibits the new behavior described. In 3.0, watchers are fired immediately after component mount.

    I think it's better for the watch option to be made consistent with the Composition API instead of creating a mismatch between the two. To retain the old behavior, use the { lazy: true } option (basically the opposite of immediate) The downside, obviously, is the migration cost.

  • We want to reduce the reliance on magic strings. In 2.x the watch option only supported dot-eliminated paths (not full JS expressions), which was somewhat weird in the first place.

    • The downside of this one is that there is no easy migration option other than refactoring into an imperative call using a getter:

      created() {
        this.$watch(() => this.foo.bar.baz, (value) => {
        })
      }

I think an RFC would be great to gather more feedback on these changes.

@lbogdan
Copy link

lbogdan commented Jan 29, 2020

Re: your first point: while it makes sense that having different APIs for watchers between Composition and Object API might be confusing, in my personal experience, I use immediate much less than not. So while the breaking change could be documented (and maybe even addressed by a codemod?), having to explicitly specify lazy: true to all non-immediate watchers would be quite irritating, especially that you wouldn't be able to use the short someProp() { ... } form and need to use the object one someProp: { handler() { ... }, lazy: true }.

@yyx990803
Copy link
Member

@lbogdan I think there is a force of habit here. What I've found is that in a lot of cases people are actually doing this:

created() {
  this.fetchData(this.id)
},
watch: {
  id: fetchData
},
methods: {
  fetchData(id) {
    // ...
  }
}

Where with immediate by default this becomes:

watch: {
  id(id) {
    // fetch data...
  }
}

Would be great to learn more about cases where lazy: true is absolutely required.

@lbogdan
Copy link

lbogdan commented Jan 29, 2020

Yes, that's exactly the use-case I use immediate: true for. But pretty much all my other imperative watchers ("do something when some state changes") are non-immediate, and would probably break in subtle ways if suddenly made immediate. I could go through my code bases and come up with examples, if that helps. We can also probably look at popular Vue (UI) libraries and see what the distribution of immediate vs. non-immediate watchers is.

@yyx990803
Copy link
Member

@lbogdan yeah, that would be really helpful.

@pimlie
Copy link

pimlie commented Jan 30, 2020

Want to chime in that currently I often rely on watchers not being triggered immediately because I put client-only code in them when I know that a change wont ever happen on the server (eg because a change can only happen after user interaction).

@lmiller1990
Copy link
Member Author

lmiller1990 commented Jan 30, 2020

I opened an RFC as suggested; I'll close this so we can keep discussion in one place.

@yyx990803
Copy link
Member

@pimlie FYI watch in Vue 3 is not fired synchronously (even for the initial call) - they are in fact deferred until the component is mounted - so even with immediate by default they won't fire during SSR unless you explicitly use { flush: 'sync' } in the watcher's options.

@cawa-93
Copy link
Contributor

cawa-93 commented Jan 31, 2020

@yyx990803

@lbogdan I think there is a force of habit here. What I've found is that in a lot of cases people are actually doing this:

created() {
  this.fetchData(this.id)
},
watch: {
  id: fetchData
},
methods: {
  fetchData(id) {
    // ...
  }
}

Where with immediate by default this becomes:

watch: {
  id(id) {
    // fetch data...
  }
}

Would be great to learn more about cases where lazy: true is absolutely required.

I do it myself all the time. But it's not out of habit, it's about how you think about the watcher. When I make an algorithm in my head I think something like this:

  1. I create variable. Set initial value.
  2. Then I create watcher.
  3. Since the variable has not changed its value since the watcher was created, you expect the handler to not be launched.

And the fact that an watcher can launch a handler right at the moment of installation is a little confusing.

I want to say that this is how you perceive the logic of work by its name. Perhaps if this api was called otherwise, say ComputedFunction or effect then such a misunderstanding would not arise.

@yyx990803
Copy link
Member

@cawa-93 that's a good point.

So another option we have is:

  • Keep the 2.x behavior of the watch option;
  • Rename the current watch in Composition API to:
    • autorun?
    • runAndWatch?
    • effect?

@lbogdan
Copy link

lbogdan commented Jan 31, 2020

@yyx990803 I too think that's a much better compromise.

In regard to naming:

  • autorun - sounds like something that only runs once, at initialization
  • runAndWatch - too many words, hard to remember (the order)
  • effect - I think that's the best one: short and expressive; also it's kind of similar to useEffect, so React people will get it right away
  • watchImmediate - still two (long) words

Actually, I think that effect is also a better name for composition's API watch, although it's probably too late to rename it. Maybe add effect as an alias of watch, and deprecate watch?

L.E. Nvm, I misunderstood adding a new option to the options API vs. renaming watch in composition API.

@pimlie
Copy link

pimlie commented Jan 31, 2020

@lbogdan I've always disliked useEffect because for some reason its never immediately clear to me what it means. An effect has a cause but what exactly is the cause here? Watch (or observe, listen) is much more clear to me in terms of what to expect.
Eg if you 'watch' your children then you make sure that when they do something its not dangerous. An effect would be that you have to tell them to stop doing something, but that means you had to watch/observe them doing that dangerous thing already.

But Im not a native speaker, so maybe thats just me.

@lmiller1990
Copy link
Member Author

I'm not a fan of effect either - watch is definitely more intuitive to me. I don't think it's too late to change the name - Vue 3 is still in alpha. Other words that come to mind areobserve and perform.

@wvffle
Copy link

wvffle commented Feb 2, 2020

I also think that observe is much more clear in meaning than effect. However watch has less letters so it's faster to type. I'd rathet stay with watch

@lbogdan
Copy link

lbogdan commented Feb 2, 2020

My reasoning for effect is that you're defining a (side-)effect of state changing. I'm not a native speaker either, but for me, both watch and observe are semantically passive actions, meaning more like "watch state for changes" than actually "do something when state changes". I've been using watch for so long that I've never really thought about semantics until now, though. 🙂

@yyx990803
Copy link
Member

Please use the RFC for further discussions so that we can have it all in one place, thanks!

@vuejs vuejs locked as resolved and limited conversation to collaborators Feb 2, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants