假设您有一个包含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
模块的每个模块。
感谢您的建议!
答案 0 :(得分:3)
使用connect
函数怎么样?这样您的组件
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
时应提供AddNotification
和MyComponent
,因为它们不是从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
是的,您可以拥有它,但这在很大程度上取决于因素,包括但不限于资源,价格,可用性等
希望,我能为您澄清一下。如有任何疑问/困惑,请还原。
答案 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),而不是整个组件中的许多点。