React + Redux在模块/域之间共享操作

时间:2019-12-02 13:11:00

标签: reactjs redux react-redux

假设您有一个包含2个模块的应用程序(以鸭子的方式拆分)。
一个是热切加载的Notification模块,该模块用于在成功或失败时显示通知。 另一个是Calculation,它会进行一些计算

- Notification
    - components
    - actions
    - ...
    - index.js
- Calculation
    - components
    - actions
    - ...
    - index.js

在许多体系结构文章中,他们建议您然后通过index.js文件将每个模块的动作创建者导出,这些文件作为模块的公共API。 例如,如果我想公开我的success模块的Notification动作创建者,那么我将从该模块中index.js文件中导出它。现在,我的其他模块可以导入这些动作创建者。

我喜欢在您的模块中使用面向公众的API的想法。 我发现这种工作方式的麻烦之处在于您随后将模块非常紧密地耦合到了redux库。因为如果我要切换到新的Notification模块,那么该模块也必须公开动作创建者,而后者是与redux相关的。

我的担忧有效吗?如果是这样,您能建议一个更好的(但还是惯用的)解决方案吗?

Angular 中,我将执行以下操作: 我将从Notification模块中公开一个单例服务,该服务充当此模块的面向公众的API。如果需要使用任何其他模块(例如Calculation)来使用Notification模块中的功能,则可以使用依赖项注入来注入服务并调用notificationService.addNotification('message')。然后,在该单例服务中,我将在我的dispatch上调用store方法。

Calculation模块不需要知道NotificationModule是否使用商店。只要面向公众的单例服务仍然公开Notification方法,我就可以轻松切换addNotification模块。通过反转依赖关系,我不需要更改使用Notification模块的每个模块。

感谢您的建议!

4 个答案:

答案 0 :(得分:3)

使用connect函数怎么样?这样您的组件

  1. 可以完全不用Redux的用户
  2. dispatch和其他类似的redux人员将隐藏在connect后面

这里是例子

export const MyComponent = ({ alertState, notificationsArray, SetAlert, AddNotification }) => {
  return <div>
    Alert state: {alertState.toString()}
    <button onClick={() => SetAlert(!alertState)}>Toggle alert</button>
    <div>
      Notifications: {notificationsArray.map(n => `${n}, `)}
      <button onClick={() => AddNotification()}>Add notification</button>
    </div>
  </div>
}

export default connect(state => ({ alertState: state.alert.alertState, notificationsArray: state.notifications.notificationsArray }), {...Alerts.actionCreators, ...notification.actionsCreators})(MyComponent)

请注意,在MyComponent中没有dispatch。因此,您可以通过这样做而在不使用Redux的情况下使用MyComponent

// Another file
import { MyComponent } from './MyComponent.js'

export const App = () => {
  return <MyComponent alertState={true} SetAlert={(alert) => console.log(alert)} notificationsArray={[ 'notification1', 'notification2' ]} AddNotification={() => {}} />
}

或者,如果要以连接方式使用它,请执行

// Some third file
import MyComponent from './MyComponent.js'  // Note, that now it is default import

export const AnotherComponent = () => {
  return <MyComponent />

现在请注意,我没有为MyComponent提供任何道具,它们将由connect提供。

您还可以将对connect的呼叫移至其他文件。因此MyComponent将完全独立于Redux。

您也没有义务将MyCompoent完全连接到Redux。您可以部分连接

export default connect (state => ({ alertState: state.alert.alertState }), Alerts.actionCreators)(MyComponent)

现在调用notifications时应提供AddNotificationMyComponent,因为它们不是从Redux提取的。

答案 1 :(得分:1)

我认为Calculation模块的惯用Redux方式 是用来分发动作的,而对该动作感兴趣的模块可以在其简化器中处理该动作。由于所有动作都传递给所有减速器,因此动作分配器与动作使用者之间的耦合度降低了。在这种情况下,Calculation模块不需要关心哪个组件,多少个组件,或者实际上是否有任何组件在监视该动作。 (尽管在大多数情况下,我发现我确实为该动作创建了一个动作产生者和一个消费者-在大多数情况下只是一个消费者-即使它们之间是松散耦合的,我也同时工作。 )

我认为从理论上讲,可以创建一个Notification单例,您可以从Calculation模块中调用该单例,这又将分派仅由Notification处理的动作模块本身。我对Angular的工作方式不太熟悉,但是似乎如果您调用的是Notification公开的函数,则会在组件之间建立紧密的耦合。如果您以后想将Notification组件切换为另一个组件,是否需要再次查看所有绑定?如果其他组件对Calculation成功感兴趣,该怎么办? Calculation模块是否还必须调用那些模块中从单例暴露的函数,从而引入更紧密的耦合?

与大多数事情一样,这两种方法似乎各有利弊。如果您采用Redux的做事方式,则组件之间的紧密耦合是“优点”之一,但如果您决定改行Redux以采用其他方法,则会以降低灵活性的代价为代价。

答案 2 :(得分:1)

您可能会错误地考虑/假设某些事情。

从某种意义上讲,动作/减少器等的组织和编写方式使这些特定的模块独立。因此,Notification是独立的,Calculation也是如此。两者的代码都在各自的文件夹中。 Calculation模块无需担心世界各地正在发生的事情。完成了Calculations个相关的工作,并调度了相关的动作或更新了reducer。现在,如果某个模块(例如Notification)想在Calculation成功时执行某项操作,则它可以侦听在其自己区域内的成功调度。
(此处请注意,我们无需在“计算”模块中进行任何更改即可使“通知”模块正常工作)。因此,两者都是解耦的。

What I find troublesome with that way of working is that you then very tightly couple the module to the redux library 是的,这是绝对正确的,但使用某些特定框架创建项目时不会发生这种情况。您使用这些库提供的语法和功能,但是使整个项目与该库紧密绑定,如果更改该库,则必须根据新库或指南重新编写很多代码(除非有一些智能编译器)。但是,这不会使模块耦合(至少在redux中如此)

This means that when I want to replace my Notification module with another Notification module that doesn't use redux, I'll have to refactor my whole app to not use the dispatch function anymore to create a success是的,因为底层库已更改我不是angular方面的专家,但是即使在您的angular项目中,如果您决定在Notification模块中使用其他内容,我肯定也必须在其中重写很多内容。围绕Calculation模块进行处理。

我认为您所说的通常会发生if there are very big projects written badly which led to origin of micro-services like architecture。以电子商务网站为例。最初,身份验证,搜索,结帐,付款(基本上是后端服务)等都是一起编写的,因此它们紧密地结合在一起。后来人们用它们创建了微服务,并且每个微服务都可以使用API​​相互通信。现在,可以更改每个服务和基础框架,而不会影响其他功能,但是标准API在那里。同样,在前端,您也可以拥有这样的东西,但是从本质上讲,这意味着您总共有单独的项目,需要进行通信,而不是同一项目中的模块。但是在Redux或Angular中也会有同样的问题。

编辑:评论后的讨论中有几点更新:

可以有微前端吗?

Yes, you can have micro-frontends,例如Notifications in ReactJs and AngularJs中的计算and use some public methods such as [ window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)或eventListeners,但会有优缺点

我发现的几篇文章:

现在流行吗?


我认为某些原因是:

  • 与后端frontend is more of UI/UX and has to look consistent in look and feel wise. So, achieving that might be a bit of issue
  • 相比
  • 网络延迟是一个大问题,需要下载很多内容。使用2或3个框架意味着您必须下载其他数据才能使该框架正常工作。例如。包括React和Angular库等。如果您看到了很多工作,就可以减少下载量,随着框架数量的增加,下载量会增加
  • 大多数网站没有很多页面。最多说10-12个不同的页面,因此,在一个框架中创建所有页面既简单又便宜。但是,如果项目规模很大,那么大公司就会分裂。在很大的项目中,a.domain.com在reactJs中,b.domain.com在angular中。但这通常发生在大型项目并且彼此完全分开的情况下。

是的,您可以拥有它,但这在很大程度上取决于因素,包括但不限于资源,价格,可用性等

如果要构建微前端,可以使用

  • window.postMessage
  • EventListeners
  • 使用库和window.postMessage API进行协调,将微型应用隔离到IFrame中。 IFrame共享其父窗口公开的API
  • 事件发射器(一个很好的库-https://github.com/chrisdavies/eev
  • 使用html5存储并听其进行更改(一般来说,任何可以让我们在dom /窗口内或周围播放的内容,因为这将是API层,可帮助我们在不同模块之间进行通信)

希望,我能为您澄清一下。如有任何疑问/困惑,请还原。

答案 3 :(得分:1)

要将状态模块与Redux派遣/操作范例解耦,您可以通过钩子公开“公共API”:

例如,假设您的模块有一个动作:

// notifications/actions.js
const createNotification = (data) => ({
  type: 'CREATE_NOTIFICATION',
  data
});

// ...

在您的模块中,定义一个钩子,该钩子返回一个分派动作的函数:

// notifications/hooks.js
import { useDispatch } from 'react-redux';
import { createNotification } from './actions';

function useCreateNotification() {
  const dispatch = useDispatch();
  return (data) => dispatch(createNotification(data))
}

// ...

现在,您的组件不必了解调度/动作。只需导入并使用钩子即可。

// components/MyComponent.js
import React from 'react';
import { useCreateNotification } from './notifications/hooks'

function MyComponent() {
  const createNotification = useCreateNotification();
  const handleClick = () => createNotification('foo');
  return (
    <button onClick={handleClick}>Create Notification</button>
  );
}

如果您需要公共API公开普通(非挂钩)函数,则可以通过以下更高阶函数来实现: 接受dispatch并返回一组函数。出于本示例的目的,这些功能将被称为“端点”。

// endpoints.js
import * as actions from './actions';

const createEndpoints = (dispatch) => {
  const createNotification = (data) => {
    dispatch(actions.createNotification(data))
  }

  // ...

  return {
    createNotification,
    // ...
  }
}

通过调度它来调用高阶函数:

// store.js
import { createStore } from 'redux';
import rootReducer from './reducer';
import { createEndpoints } from './notifications/endpoints';

export const store = createStore(rootReducer, {});

export const { 
  createNotification,
  // ...
} = createEndpoints(store.dispatch);

现在,您的UI不必了解调度,操作或挂钩;只需这样调用普通函数即可:

// MyComponent.js
import { createNotification } from './store'

function MyComponent() {
  const handleClick = () => createNotification('foo');

  return (
    <button onClick={handleClick}>Create Notification</button>
  );
}

使用这种方法,您在很大程度上与Redux实现脱钩了。您仍然需要依靠redux“分发”来使用该模块,但是现在您耦合到一个点(createEndpoints),而不是整个组件中的许多点。