mobxjs / mobx-vue

🐉 Vue bindings for MobX
MIT License
475 stars 22 forks source link

[RFC] mobx-vue v3 #60

Closed iChenLei closed 2 years ago

iChenLei commented 3 years ago

Introduction

Since mobx v6 and vue v3 was released for a while, it's time to support mobx v6 and vue v3.

Current Feature Request

Vue 3 Compatibility #50 Module Browser Build #49 Can I use mobx-vue without decorated class properties? #47 Add Documentation re Optimizing Rendering #36 Nuxt support #26 #32

Deprecate vue-class-component

Thoughts about mobx-vue https://github.com/mobxjs/mobx-vue/issues/3#issuecomment-573934357

I'm kinda wondering what direction this project might take with a "new vue" on the horizon. Since vue seems to move away from classes and therefore decorators is there a happy path to using mobx with vue in 2020?

vue-class-component has their own problem https://github.com/vuejs/vue-class-component/issues/406

Naive proto design (only for vue3)

No.1 idea

export a vue plugin for vue3

import { createApp } from 'vue';
import MobxVuePlugin from 'mobx-vue';
import App from './App.vue';

createApp(App)
    .use(MobxVuePlugin, { name: 'MyObserver' /** optional Observer component name */ })
    .mount('#app');

Using Observer Component in your app

<template>
  <Observer>
    <div>name: {{data.name}}</div>
    <button @click="changeName">change</button>
  </Observer>
</template>

<script setup>
  import { runInAction, observable } from mobx;
  const data = observable({ name: "iChenLei" });
  const changeName = () => {
   runInAction(() => { data.name = "Kuitos" });
 };
</script>

No.2 idea

export a observer function to hack vue export default, no need to install as plugin, you can use it directly.

<template>
  <UserComponent />
  <ProfileComponent />
</template>

<script>
  import { observer } from 'mobx-vue';
  export default observer({
      setup() {
        // balabala
      }
  });
</script>

<!-- use @ decorator -->
<script>
  import { observer } from 'mobx-vue';
  @observer
  export default {
      setup() {
        // balabala
      }
  };
</script>

But it not easy to do this , we need hack the vue @internel (e.g. $, $options, $forceUpdate)

// vue 3 internal component properties map
const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
  $: i => i,
  $el: i => i.vnode.el,
  $data: i => i.data,
  $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
  $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
  $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
  $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
  $parent: i => getPublicInstance(i.parent),
  $root: i => getPublicInstance(i.root),
  $emit: i => i.emit,
  $options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
  $forceUpdate: i => () => queueJob(i.update),
  $nextTick: i => nextTick.bind(i.proxy!),
  $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
} as PublicPropertiesMap)

And when you use <script setup>, it's also diffcult to implement a useful observer function.(any suggestion for this welcome)

No.3 idea

add the template sugar like <template pug>, <template functional>.

<tempalte observer>
  <UserProfile />
<template>

It looks like best solution for mobx-vue and vue sfc? But it need mobx-vue maintainer to maintian different tools to support this feature. (e.g. mobx-vue-loader for webpack, rollup-plugin-mobx-vue for vite and rollup, ...etc). Not a reliable choice i think. I didn't see any api allow us to change the <template> behavior. It's parsed by @vue/compiler-sfc and no public api.

My opinion

Deprecate vue-class-component and make vue sfc as first class support.

Summary

Any idea and disscussion welcome, maybe you can design the final mobx-vue v3 api.

kuitos commented 3 years ago

vote for idea 2, but don't need to support decorator any more.

Cyberhan123 commented 3 years ago

vote for idea 2, but don't need to support decorator any more.

yes,idea 2 is good,and there some different idea,

<script>
  import { observer } from 'mobx-vue';
  export default observer({
      setup() {
        // balabala
      }
  });
</script>

<!-- use <script setup> -->
<script setup>
  import { observer } from 'mobx-vue';

export const state= observer({
 // balabala
})
//or
export default observer({
 // balabala
})
</script>
voyageh commented 3 years ago
import { createApp } from 'vue';
import store from './store';
import App from './App.vue';
createApp(App)
.use(store)
.mount('#app');

<!-- use <script > -->
 import { observer,inject } from 'mobx-vue'; 
 @Inject('store') 
 @observer
 export default defineComponent({
})
//or
export default observer({
   setup(props) {
      console.log(props) // {store:{}}
    }
 })
steven-sheehy commented 3 years ago

Please don't deprecate vue-class-component support. It's the only reason I use mobx-vue. If you want to support composition API in addition to classes that would be fine. You could release the composition support now and do another release later when vue-class-component finalizes v3 support.

iChenLei commented 3 years ago

@steven-sheehy Wow, I really don't know vue-class-component support is so important for you. I get your feedback and rethink vue-class-component support.

RobinRadic commented 3 years ago

I would really like to use mobx-vue & vue-class-component with vue3. The code is very clear and understandable for non-js programmers in the team.

Cyberhan123 commented 3 years ago

我真的很想将mobx-vue&vue-class-component与 vue3一起使用。代码对于团队中的非js程序员来说非常清晰易懂。

但是我感觉吧,vue3的类hook特性,使得store层变得不是特别刚需

songgnqing commented 3 years ago

vue3.0 不需要mobx,vue自带的reactivity可以完全替代掉mobx。

// store
export class BaseStore {
  constructor() {
    return reactive(this);
  }
}

// module like 
class UserStore extends BaseStore {
  name = ''
}

export const user = new UserStore()

// index
export class StoreCenter extends BaseStore {
  token = ''
  user = user
}

export const store = new StoreCenter();

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $store: StoreCenter
  }
}

export default {
  install: (app: App) => {
    app.config.globalProperties.$store = store
  }
}

在component中用computed来引入store的数据就可以了。

murraybauer commented 3 years ago

I vote for:

murraybauer commented 3 years ago

Also some food for thought of how mobx-jsx handels this:

LengYXin commented 3 years ago

别启用 类语法和装饰器吧。类语言可以很好地封装继承,装饰器是未来趋势,而且很好用

LengYXin commented 3 years ago

我真的很想将mobx-vue&vue-class-component与 vue3一起使用。代码对于团队中的非js程序员来说非常清晰易懂。

但是我感觉吧,vue3的类hook特性,使得store层变得不是特别刚需

我觉得也是。class 语法对于后端开发人员真的非常友好,我就是一个写过C# ,Java 后面转型js 的在搭建工程时会用后端开发模式把React,Vue设计成后端工程结构,后面后端开发人员都不需要懂React,Vue这些都能很好修改代码,专注业务

sergeydorovsky commented 3 years ago

I'm currently using MobX 6 with Vue 3. Here is the plugin I wrote (it's actually Quasar boot file): Works like a charm. Hope this helps.

import { ComponentPublicInstance } from '@vue/runtime-core'
import { Reaction } from 'mobx'
import { boot } from 'quasar/wrappers'

const reactionKey = '__mobx_reaction__'

interface MobxComponent extends ComponentPublicInstance {
  [reactionKey]: Reaction
}

function getComponentName(vm: ComponentPublicInstance) {
  // @ts-ignore
  return (vm.$options.name || vm.tag || '<observed-component>') as string
}

export default boot(({ app }) => {
  app.mixin({
    created(this: MobxComponent) {
      const name = getComponentName(this)
      const reaction = new Reaction(`${name}::mobx-reaction`, () => {
        if (this.$.isMounted) {
          this.$forceUpdate()
        }
      })

      this[reactionKey] = reaction
      // @ts-ignore
      const originalRender = this.$.render

      // @ts-ignore
      this.$.render = function(...args: any) {
        let renderResult: unknown

        reaction.track(() => {
          renderResult = originalRender.call(this, ...args)
        })

        return renderResult
      }
    },
    beforeUnmount(this: MobxComponent) {
      this[reactionKey].dispose()
    },
  })
})
wobsoriano commented 3 years ago

Hey just released a really simple mobx-react-lite inspired library for Vue 3

https://github.com/wobsoriano/mobx-vue-lite

iChenLei commented 3 years ago

@wobsoriano Are you interested in mobx-vue v3 design ? or do you want transfer your mobx-vue-lite to mobxjs org ? It looks like good (mobx-vue-lite), but no test case ci, so we can do it better. what's your opinion ?

wobsoriano commented 3 years ago

@wobsoriano Are you interested in mobx-vue v3 design ? or do you want transfer your mobx-vue-lite to mobxjs org ? It looks like good (mobx-vue-lite), but no test case ci, so we can do it better. what's your opinion ?

I'm fine moving it to mobxjs org

LengYXin commented 3 years ago

有一个很神奇的现象。就是在v3里面的 mobx 类 外面套一个 reactive 或者直接在 vue组件内 new 的话,这个mobx是正常工作的。

@Options({
  components: {},
})
export default class Home extends Vue {
  System=new SystemController();
}
//或者
config.globalProperties.System = reactive(new SystemController())

仓库地址:https://github.com/LengYXin/mamba/tree/dev/packages/micro

https://github.com/LengYXin/mamba/blob/dev/packages/micro/src/components/user.vue 还用微前端方式传递到React 夸框架也正常使用。

iChenLei commented 2 years ago

closed via https://github.com/mobxjs/mobx-vue-lite/issues/1