mobxjs / mobx-vue-lite

Lightweight Vue 3 bindings for MobX based on Composition API.
MIT License
69 stars 3 forks source link

When a parent component is wrapped with Observer, the child components will be re-rendered when data changes. #131

Closed 1eeing closed 8 months ago

1eeing commented 1 year ago

如题,求教这个问题如何解决

1eeing commented 1 year ago

I have a problem, when state chanegd if a parent vue component use Observer to wrap it, the child component will rerender. How to memo the child vue component so that it can render once.

iChenLei commented 1 year ago

@1eeing we need a reproduce online demo to check your issue (codesandbox or whatever) , thanks.

huybuidac commented 1 year ago

@1eeing we need a reproduce online demo to check your issue (codesandbox or whatever) , thanks.

@iChenLei current Observer implementation will re-render every child componenets, without tree-seeking or something like this.

https://vuejs.org/api/built-in-special-attributes.html#key

  mounted() {
    this.dispose = reaction(() => this.$slots.default?.(), () => {
      this.forceUpdate()
    }, {
      requiresObservable: true,
    })
  },
  unmounted() {
    this.dispose()
  },
  methods: {
    forceUpdate() {
      this.key++ 
    },
  },
  render() {
    return h(this.$slots.default!, { key: this.key })  // here
  },
iChenLei commented 1 year ago

@wobsoriano Any idea ?

wobsoriano commented 1 year ago

I have no idea yet of a way to only apply changes to elements that have been updated and are using an observable.

For now, it's best to use useLocalObservable composable, or only wrap the components with Observer that needs to be observed:

<script setup lang="ts">
import { observable, runInAction } from 'mobx'
import { Observer } from 'mobx-vue-lite'
import AnotherComponent from './components/AnotherComponent.vue'

const data = observable({ name: 'John' })

const changeName = () => {
    runInAction(() => {
        data.name = 'Jane ' + Date.now()
    })
}
</script>

<template>
  <Observer>
    <div>Name: {{ data.name }}</div>
    <button @click="changeName">Change name</button>
    <div>Name: {{ data.name }}</div>
  </Observer>
  <AnotherComponent />
</template>
huybuidac commented 1 year ago

They are very simple components, if we binding an array to complex component, it will be reset and lost all state (input, checkbox, ...) each time array changed.

  <Observer>
    <div>Name: {{ data.name }}</div>
    <button @click="changeName">Change name</button>
    <div>Name: {{ data.name }}</div>
  </Observer>
wobsoriano commented 1 year ago

@huybuidac I agree. Do you have a solution in mind?

bdwenxi commented 8 months ago

render() { return h(this.$slots.default!, { key: this.key }) } forceUpdate` causes key change, which will cause all components wrapped in Observer Component to be re mounted and have their lifecycle restarted. Normally, the parent component will update the child components and they will also be re mounted instead of re-mounted. Can we consider replacing the prop injection of the key.

h(this.$slots.default!, { extraMobxVueKey: this.key }) like this.
@wobsoriano

wobsoriano commented 8 months ago

@bdwenxi I just released a new version with that request. It passed the tests, which is interesting. Would you mind explaining it to me? Why does using a different key fix it? Thank you!

Looks like this fixes the issue. Can anyone confirm?

wobsoriano commented 8 months ago

Feel free to reopen!