Reactjs 管理相同React.js类层次结构的OOP软件设计模式是什么?
我正在对React项目进行更改,这将产生许多类似的类,它们排列在相同的类层次结构中。我想知道我的代码可以使用什么替代组织 该产品当前显示的屏幕显示在店面终端上,店员和客户都将与之交互(一起阅读合同、收集双方签名等)。引入的更改是允许两个监视器设置,职员的班长将控制工作流程,客户的班长将跟进并在必要时请求输入。仍支持单监视器模式 代码目前有一个主合同签署窗口,带有可视组件,如文档查看器、签名捕获面板、带有合同详细信息的面板等。我想当两个监视器功能完成时,我将拥有三个几乎相同的类层次结构(或者更准确地说,相同的对象组成)。具有相同后缀的组件具有相同的用途,看起来几乎相同,但具有不同的行为Reactjs 管理相同React.js类层次结构的OOP软件设计模式是什么?,reactjs,typescript,oop,design-patterns,Reactjs,Typescript,Oop,Design Patterns,我正在对React项目进行更改,这将产生许多类似的类,它们排列在相同的类层次结构中。我想知道我的代码可以使用什么替代组织 该产品当前显示的屏幕显示在店面终端上,店员和客户都将与之交互(一起阅读合同、收集双方签名等)。引入的更改是允许两个监视器设置,职员的班长将控制工作流程,客户的班长将跟进并在必要时请求输入。仍支持单监视器模式 代码目前有一个主合同签署窗口,带有可视组件,如文档查看器、签名捕获面板、带有合同详细信息的面板等。我想当两个监视器功能完成时,我将拥有三个几乎相同的类层次结构(或者更准确
# Proposed object compositions
MainWindow has a DocumentViewer, and a SignaturePanel, and a DetailsPanel
ClerkWindow has a ClerkDocumentViewer, and a ClerkSignaturePanel, and a ClerkDetailsPanel
CustomerWindow has a CustomerDocumentViewer, and a CustomerSignaturePanel, and a CustomerDetailsPanel
我担心这种模式的复杂性增长。当更多的组件添加到产品中时,我们将需要实现每个组件的三个版本;尽管可能性较小,但如果我们添加另一个“模式”,我们可能需要实现每个现有组件的新版本
此外,Main
组件和Clerk
组件非常相似,因为它们有很多控件供用户交互;但是“客户”组件的交互作用要小得多。我不确定如何在三个组件中的两个组件之间有效地共享代码。似乎很难正确组织的部分原因是因为这些是Typescript React组件,它们必须具有键入的状态和道具。如果要使用不同的DocumentViewer组件(例如)所有这些都继承自一个公共超类,我要么继承Customer组件中未使用的状态/道具,要么使用重复的代码管理Main和Clerk组件的公共状态/道具
我曾考虑过使用一个DocumentViewer(etc)类,该类具有一个模式属性,该属性控制组件在不同监视器设置上的行为,但组件最终将充满switch语句,这表明我需要类似上述的某种多态性
存在哪些设计模式来管理这些并行类层次结构?这些模式是否与React样式的状态/属性管理兼容?React/Typescript是否具有某种混合/模块功能,以允许将状态/属性有组织地共享给兄弟类的子集?如前所述,这是一个脱离主题的问题,一个它与架构风格有关,而不仅仅是精确的解决方案或修复代码问题,无论回答与否,我都有点犹豫
但是,它仍然与特定的技术和生态系统相关,因此对于较小的社区来说可能过于狭窄。考虑到这一点,我将列出一些事情,您可以研究并缩小确切的问题
将所有共享代码提取到“哑”组件中
“dumb”组件并不是对主题中的表示组件的精确引用,而是遵循了类似的想法。在这种情况下,它们对您传递的特定类型一无所知,但知道如何处理任何共享逻辑或交互
这是您已经描述为一种潜在解决方案的东西,但您担心这些组件中有一堆switch case语句。避免这些条件语句的方法有:
使传递给这些组件的数据尽可能相似或通用。只需说数据是一个具有id和签名对象的对象,就可以走很长的路
所有特定于数据类型的处理程序和操作都应该从最顶层的组件(即“包装器”/“容器”)传递到该组件层次结构对于这些共享组件。作为一个简单的示例,假设您有一个带有垃圾图标的要删除的项目列表。在容器组件中,您将实现从列表状态中删除项目的逻辑,但其他交互(如单击处理程序和警告模式)将位于共享组件层次结构中
如果任何交互是可选的,只需将这些属性设置为可选。在上面的示例中,如果删除是可选的,并且您没有传递delete属性,则共享组件知道不显示垃圾箱图标
如果您有具有100%共享逻辑的可选交互(例如,是否使用了警告模式),请使用布尔属性作为标志。当您构建组件时,使用大量标志并不理想,但这是许多提供复杂表示组件(如表)的React库的一个常见功能
如果您有特定的JSX元素或React组件需要在层次结构中一直呈现,那么没有什么可以阻止您将它们作为道具传递。记住,一切都是JavaScript中的一个对象。为了让同一个示例继续下去,这可以是警告模式的主体,如果该道具被传递,则可以覆盖该主体
现在来看TypeScript部分。组件只是类或函数,您可以以相同的方式使用或函数,甚至将该泛型变量提供给子组件状态和props。您可以更具描述性地使用该泛型变量,并说它扩展了一个抽象类,以确保类型安全和性能将有关共享结构的更多信息传递给共享组件
我将使用我在以下代码片段中滥用的示例,而不是您的用例,因为我不太确定您的组件和组件的实际实现的性质
<SharedWindow> // same for all objects => does not need a type variable
<DocumentViewer<ClerkType> onSignature={this.handleSignature}>
{/*
DocumentViewer contains the Signature component that needs onSignature
which represents various needed handlers on that component or DocumentViewer
*/}
<ClerkDetailsPanel/> // specific Details component
</DocumentViewer>
</SharedWindow>
export interface DocumentViewerProps<T> {
// ...
}
export interface SignaturePanelProps<T> {
// ...
}
export interface DetailsPanelProps<T> {
// ...
}
export interface WindowProps<T> {
DocumentViewer: ComponentType<DocumentViewerProps<T>>;
SignaturePanel: ComponentType<SignaturePanelProps<T>>;
DetailsPanel: ComponentType<DetailsPanelProps<T>>;
// ...
}
export const Window = <T>({DocumentViewer, SignaturePanel, DetailsPanel}: WindowProps<T>) => {
// do all shared window stuff
return (
<Screen>
{needsSignature && (
<SignaturePanel
// pass down all the right props, which depend on `T`
/>
)}
// do the same for <DocumentViewer /> and <DetailsPanel />
</Screen>
)
}
export interface Props<Settings> {
initialSettings: Settings;
RenderControls: ComponentType<{
state: Settings;
update(s: Partial<Settings>): void;
}>;
RenderContents: ComponentType<Settings & Size>;
toolPadding?: number | string | Padding;
}
export const Tool = <Settings extends {}>({initialSettings, RenderControls, RenderContents, toolPadding = 0}: Props<Settings>) => {
const [settings, update] = usePartialState<Settings>(initialSettings);
const [ref, dimensions] = useDimensions();
const {width = 0, height = 0, y = 0} = dimensions;
const padding = padAmounts({width, toolPadding});
const toolHeight = window.innerHeight - (Math.max(y, 0) + height + padding.top + padding.bottom);
const toolWidth = width - (padding.left + padding.right);
return (
<div>
<div ref={ref}>
<RenderControls
state={settings}
update={update}
/>
</div>
<div style={{
paddingLeft: padding.left,
paddingRight: padding.right,
paddingTop: padding.top,
//don't include the bottom as a failsafe to prevent unnecessary scrolling
}}>
<RenderContents
{...settings}
width={toolWidth}
height={toolHeight}
/>
</div>
</div>
)
}
export const HistogramTool = () => (
<Tool
initialSettings={{
breakpoints: 6,
group: randomGroup().name,
channel: getChannel("hsl.l"),
}}
RenderControls={HistogramControls}
RenderContents={GroupChannelHistogram}
toolPadding={"10%"}
/>
);