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

如何创建一个模块的多个实例? #430

Open
zybzzc opened this issue Sep 16, 2020 · 11 comments
Open

如何创建一个模块的多个实例? #430

zybzzc opened this issue Sep 16, 2020 · 11 comments

Comments

@zybzzc
Copy link

zybzzc commented Sep 16, 2020

以及能和vue devtools的vuex模块打通吗?

@Brooooooklyn
Copy link
Member

如何创建一个模块的多个实例

Sigi 可以这样做但是不鼓励这样做。一个模块的多个实例在使用上会造成很多概念不一致的问题。
如果你需要一个模块复用在两个不同的业务逻辑,可以在 state 层面做好区分。并通过 selector API 将 state map 到不同的组件中。你可以把 Sigi 的 Module 看作 redux 的 store。

以及能和vue devtools的vuex模块打通吗

后续会做这件事情

@Brooooooklyn
Copy link
Member

@zybzzc
Copy link
Author

zybzzc commented Sep 18, 2020

Sigi 可以这样做但是不鼓励这样做。一个模块的多个实例在使用上会造成很多概念不一致的问题。

假设我要在页面上做文件拷贝, 有一个 FolderExplorer 组件和对应的 folder-explorer.module 模块, 此时页面上的两个 FolderExplorer 需要的是 FolderExplorerModule 的两个实例, 这种情况使用 Sigi 该如何处理呢?

另外, Sigi 和自行封装 Vuex 的动态模块, 除了框架无关外还有哪些优势呢(除了RxJS本身带来的优势之外)?

@Brooooooklyn
Copy link
Member

假设我要在页面上做文件拷贝, 有一个 FolderExplorer 组件和对应的 folder-explorer.module 模块, 此时页面上的两个 FolderExplorer 需要的是 FolderExplorerModule 的两个实例, 这种情况使用 Sigi 该如何处理呢?

因为 Sigi 的 Module 上 EffectReducer 都是纯函数,所以这部分是可以共用的。你可以在 State 上对着两块业务进行划分:

state = {
  page1: FolderExploerDefaultState,
  page2: FolderExploerDefaultState,
}

然后在组件调用 dispatcher 的时候传入各自的 namespace

dispatcher.update({ page: 'page1', value: ..... })

然后在 Effect/Reducer 里面分别处理

@Effect()
update(payload$: Observable<{ page: 'page1' | 'page2', value ... }>) {
  return payload$.pipe(
    exhaustMap(({ page, value }) => {
      return this.fetch(...value).pipe(
        map((res) => this.getActions().updatePage({ page, res }))
      )
    })
  )
}

另外, Sigi 和自行封装 Vuex 的动态模块, 除了框架无关外还有哪些优势呢

除了 RxJS 以外,Sigi 在测试上有很大的优势,DI 可以让你在编写单元测试的时候 Mock 掉所有模块无关的逻辑,减少运行时间和测试复杂度。

@zybzzc
Copy link
Author

zybzzc commented Sep 18, 2020

state = {
  page1: FolderExploerDefaultState,
  page2: FolderExploerDefaultState,
}

不太明白这样做的意思, 感觉更复杂了.

其实本来 FolderExplorer 自己维护内部状态就行了, 但是我想复用其中的状态和逻辑, 所以我想到的是使用 Sigi 实现 folder-explorer.module 模块, 但是它是单例的.

或许这种场景应该用纯粹的 react hooks 或其它组件框架相关的技术, Sigi 只该用在需要状态共享的地方?

@Brooooooklyn
Copy link
Member

非单例模块在注入到其它模块的时候会有歧义。

不太明白这样做的意思, 感觉更复杂了.

其实就是 follow redux 的设计,redux 单一 store 也是通过在 store 里面加 namespace 来区分相同"形状"的不同模块

@zybzzc
Copy link
Author

zybzzc commented Sep 18, 2020

// App
<div>
     <SigiMultiFolderExplorer />  
     <SigiFolderExplorer />
</div>

// SigiFolderExplorer
import { useModule, useModuleState } from '@sigi/react'
import { FolderExplorerModule } from './folder-explorer.module'

export default ({ actived }) => {
 const [state, dispatcher] = useModule(FolderExplorerModule )
 return (
   <DummyFolderExplorer {...state} actived onChange={dispatcher.update} />
 )
}

// SigiMultiFolderExplorer
// SigiMultiFolderExplorer 中 item 自身的状态该由谁管理 ?
// 如果是 SigiMultiFolderExplorer 自身管理, 如何复用 SigiFolderExplorer  组件 ?
// 如果是 SigiFolderExplorer 管理, 如何让所有 SigiFolderExplorer 中的状态独立 ?
import { useModule, useModuleState } from '@sigi/react'
import SigiFolderExplorer from './SigiFolderExplorer '
import { MultiFolderExplorerState } from './multi-folder-explorer.module'

export default () => {
 const [state, dispatcher] = useModule(MultiFolderExplorerState)
 return (
   <div>
      copy files in {state.explorer1.currentPath} to {state.explorer2.currentPath}
     // <SigiFolderExplorer actived={state.actived === 'explorer1'} />
     // <SigiFolderExplorer actived={state.actived === 'explorer2'} />
     <DummyFolderExplorer {...state.explorer1} onChange={dispatcher.updateExplorer1} />
     <DummyFolderExplorer {...state.explorer2} onChange={dispatcher.updateExplorer2} />
   </div>
 )
}


//  folder-explorer.module
@Module("FolderExplorer")
export class FolderExplorerModule extends EffectModule<FolderExplorerState> {
 defaultState: FolderExplorerState= {
   currentPath: '',
   // ...
 };

 constructor(
   private readonly appService: AppService,
   private readonly appModule: AppModule,
   private readonly authModule: AuthModule,
   // ...
) {
   super();
 }
}

// multi-folder-explorer.module
@Module("MultiFolderExplorer")
export class MultiFolderExplorerModule extends EffectModule<MultiFolderExplorerState> {
 defaultState: MultiFolderExplorerState= {
   // 应该在这里管理 item 的状态 ?
   // 如果在这里管理, 则需要在这里重写一遍 folder-explorer.module 中 的 reducer/effect
   explorer1: FolderExploerDefaultState,
   explorer2: FolderExploerDefaultState,
   actived: 'explorer1',
   // ...
 };

 constructor(
   private readonly appService: AppService,
   private readonly appModule: AppModule,
   private readonly authModule: AuthModule,
   // ...
) {
   super();
 }
}

不知道这样能否表达清楚.

你上面提到的:

state = {
  page1: FolderExploerDefaultState,
  page2: FolderExploerDefaultState,
}

这种做法, 我没理解错的话就是要在 multi-folder-explorer.module 中重写一遍 folder-explorer.module 中 的 reducer/effect.

@Brooooooklyn
Copy link
Member

Brooooooklyn commented Sep 18, 2020

大概这样封装,FolderExplorerModule 存所有 explorer 的状态,Effect/Reducer 通过组件传进来的 Explorer 来决定更新哪一个状态,所以 Effect/Reducer 是完全复用的。Redux 在这种场景下也是一样的用法,复用 reducer/effect 就得在参数上区分更新哪一个部分的状态,渲染的时候通过 selector 选择渲染需要的那一小部分数据。

// App
<div>
     <SigiMultiFolderExplorer />  
     <SigiFolderExplorer />
</div>

// SigiFolderExplorer
import { useModule, useModuleState } from '@sigi/react'
import { FolderExplorerModule } from './folder-explorer.module'

export default ({ actived }) => {
 const [state, dispatcher] = useModule(FolderExplorerModule, {
    selector: (state) => state[actived],
    dependencies: [actived]
 })
 const update = useCallback((actived) => {
   (updated: FolderUpdated) => dispatcher.update({ explorer: actived, updated })
 }, [actived, dispatcher])
 return (
   <DummyFolderExplorer {...state} actived onChange={update} />
 )
}

import { useModule, useModuleState } from '@sigi/react'
import SigiFolderExplorer from './SigiFolderExplorer '
import { MultiFolderExplorerState } from './multi-folder-explorer.module'
import { FolderExplorerModule } from './folder-explorer.module'

export default () => {
 const [state, dispatcher] = useModule(MultiFolderExplorerState)
 const [folderState, folderDispatcher] = useModule(FolderExplorerModule)
  
 const updateExplorer = useCallback((explorer: string) => {
    return (changed: FolderChanged) => folderDispatcher.updateExplorer({ explorer, changed })
 }, [folderDispatcher])

 return (
   <div>
      copy files in {state.explorer1.currentPath} to {state.explorer2.currentPath}
     <DummyFolderExplorer {...folderState.explorer1} onChange={updateExplorer(explorer1)} />
     <DummyFolderExplorer {...folderState.explorer2} onChange={updateExplorer(explorer2)} />
   </div>
 )
}


//  folder-explorer.module
@Module("FolderExplorer")
export class FolderExplorerModule extends EffectModule<FolderExplorerState> {
 defaultState: { [index: string]: FolderExplorerState }= {};

 constructor(
   private readonly appService: AppService,
   private readonly appModule: AppModule,
   private readonly authModule: AuthModule,
   // ...
) {
   super();
 }
}

// multi-folder-explorer.module
@Module("MultiFolderExplorer")
export class MultiFolderExplorerModule extends EffectModule<MultiFolderExplorerState> {
 defaultState: MultiFolderExplorerState= {
   actived: 'explorer1',
   // ...
 };

 constructor(
   private readonly appService: AppService,
   private readonly appModule: AppModule,
   private readonly authModule: AuthModule,
   // ...
) {
   super();
 }
}

@zybzzc
Copy link
Author

zybzzc commented Sep 18, 2020

我知道这样可以解决, 但是这样的话 FolderExplorerModule 的 defaultState: { [index: string]: FolderExplorerState } 实际上已经包含多个 FolderExplorer 的状态了, 因为要考虑在 SigiFolderExplorer 和 SigiMultiFolderExplorer 中复用, 但 defaultState: FolderExplorerState 才更符合直觉. 或者直接全部状态丢到一 MultiFolderExplorerState 中管理.

@Brooooooklyn
Copy link
Member

defaultState: FolderExplorerState 才更符合直觉

是的, 这个是 Sigi 在设计的时候做的取舍。我们用 Class 形式只是因为 DI,但是 Class 会带来额外的组合问题,比如你这个场景其实是 state 树上的多个叶子与同一组 reducer/effect 组合的问题。以前传统的 redux 做法中,reducer/effect 全是纯函数,所以比较好组合。但是 Class 里面就不太容易做这个事情。

但本质上代码量并没有增多,因为无论是 redux 还是 vuex,这种形状 { [index]: State } 的状态树都是存在的,只要出现这种状态树想要复用相同的 reducer/effect 就需要额外的传参数。 只不过说 Class 在这里会让使用者出现一种幻觉,那就是 Class 要是能多实例就可以在这种场景下自动隔离这些差异。但本质上多实例会让 Sigi 无法构建 state 树 (多实例与 root 的关系是什么?用什么区分?),而且对 reducer/effect 也不是复用而是拷贝了一份,这会让程序更加难以理解。

@zybzzc
Copy link
Author

zybzzc commented Sep 28, 2020

以及能和vue devtools的vuex模块打通吗?

请问大致方案会是什么呢, 我现在用的是注册动态 vuex 模块. 订阅 state$ 来维护 vuex 模块里的 state, 然后订阅 action$ 来触发一个假的 commit mutation 或 dispatch action. 我不确定这样手动维护的 vuex 状态在 vue devtools 中与 sigi 模块的实际状态会不会有出入(暂时看来是没问题的), 而且还侵入到了 @sigi/core, 因为要标记 vuex 的 mutations 和 actions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants