Hexagonal六角形架构ReactJS的实现方式 - Janos Pasztor


ReactJS是前端开发的主力,但它在SOLID方面表现不佳我们可以通过采用经典方法来改变它吗?
在将ReactJS视为UI的现代JavaScript解决方案时,很多人似乎在组织代码时遇到问题。让我们深入了解一下,看看我们是否可以做得更好。
性子急的人可立即获取代码

什么是React?
自从我20年前开始进行Web开发以来,在JavaScript中编写前端代码一直是一次痛苦的经历。主要是由于浏览器不兼容,还因为没有可行的组件框架。果然,ExtJS和新的Sencha已经上市,但使用它比使用起来更痛苦。
然后React出现了,试图解决这些问题,React使用了JS的下一代版本,您可以将其 转换为适用于当前可用的浏览器。在React中,您可以创建可重用的组件,例如:

class MyBlogComponent {
    render() { 
        return <div className="blogentry">
          <div className=
"blogentry__title">{this.props.title}</div>
          ...
        </div>;
    }
}

然后你可以在其他组件中使用这些组件:

class FeaturedBlogPost {
    render() {
        return <MyBlogComponent title="Featured article" />;
    }
}

这些组件中的每一个都有两个部分:属性和状态。属性props是从外部传递的参数数据。在上面的例子中,“title”是一个属性。另一方面,状态是组件内部状态。内部状态的一个很好的例子是当你需要从组件中的服务器加载数据然后显示它时。
这些组件最终创建了所谓的虚拟DOM。这是一棵树,就像真正的DOM一样,但它更快,更快。事实上,如此快速,它可以为每个状态变化重新计算整个DOM。
然后,React会接受虚拟DOM并比较不同版本,以便找出真正DOM需要更新的部分。

FLUX/REDUX架构
正如您可能想象的那样,最初的实现并不是完全无瑕疵的。每个组件都需要维护自己的内部状态,当两个不同的组件使用相同的数据时,通常会出现不一致的情况,因为数据将在不同的时间从服务器加载。
试图解决这个问题,FLUX或REDUX架构诞生了。两者略有不同,但为了本文的目的,我们将它们视为一个。
核心思想是应用程序具有一个中央状态,可以将所有内容存储在应用程序中。当组件触发操作时,该操作将改变状态(通过操作创建器功能),这反过来将使用该全局状态更改组件的呈现内容。

当然,这提供了一种集中式架构,可以从单个组件中分离出状态处理,使其自然呈现。
但是,正如您可能已经猜到的那样,这种架构也有其平衡的问题。首先,它有一个集中 状态,基本上是一个存储应用程序中所有数据的巨型上帝对象。我的第二个问题集中在解决方案的非SOLID问题上。为了更改应用程序的一部分,您必须在应用程序中触摸(并测试)各种代码部分,并且数据结构的更改可能会产生广泛的后果。

六角形Hexagonal架构
对我来说非常好的一种架构是六边形/六角形,或者我喜欢称之为饼干cutter架构。这种架构利用的服务来进行操作,而实体entites作为结构化格式数据传输。
很长一段时间,我没有办法在Javascript中重新创建这个架构,因为它没有依赖注入或静态类型。然而,最近,我一直在使用Typescript和Blueprint作为React的组件框架,我设法找到了构建类似系统的方法。
首先,我们将从业务逻辑开始。例如,我们将构建一个AuthenticationService。与我之前描述后端架构的文章相反,存储状态的主要位置将是服务层。因此,身份验证服务有几个本地字段:

class AuthenticationService {
    private accessToken: IAccessToken|null = null;
    private account: IAccount|null = null;
}

这些实体可以定义为这样的简单接口:

interface IAccessToken {
    readonly id: string;
    readonly accountId: string;
    readonly expires: LocalDateTime;
}

当转换为JavaScript时,这些将由常规对象表示。由于我们正在构建服务,因此我们还需要一些依赖项,这些依赖项可以按预期通过构造函数注入:

class AuthenticationService {
    //...
    public constructor(
        readonly authenticationBackend: IAuthenticationBackendApi
    ) {
    }
}

最后,让我们添加身份验证方法:

class AuthenticationService {
    //...
    public authenticate = (username: string, password: string): Promise<boolean> => {
       
    };
}

正如您所看到的,我们正在使用promises,因为身份验证过程需要时间。promises完成后,身份验证过程已完成或失败。然后我们可以实现这样的登录表单:

class AuthenticationDialog
    extends React.Component<IAuthenticationDialogProps, IAuthenticationDialogState>
{
    render = () : JSX.Element => {
        return <form>
          <input ... />
          <input ... />
          <button onClick={this.onLogin} />
        </form>
    };

    private onLogin = () => {
        this.setState({
            loading: true
        });
        const self = this;
        this.props.authenticationService
            .authenticate(this.state.username,this.state.password)
            .catch(() => {
                self.setState({
                    loading: false
                });
                //Authentication process failure
            })
            .then((result: boolean) => {
                self.setState({
                    loading: false
                });
                if (result) {
                   
//Successful login
                } else {
                   
//Failed login
                }
            })
    };
}

处理事件
到目前为止,我们只对事件作出反应 那么你如何根据正在发生的事件更新React组件?让我们坚持使用身份验证示例。我们来看看我们的身份验证服务:

class AuthenticationService {
    //...
    registerAuthenticationChangeListener(listener: IAuthenticationChangeListener):void {
    }
    deregisterAuthenticationChangeListener(listener: IAuthenticationChangeListener):void {
    }
}

所以我们添加了这两个新函数,允许组件将自己注册为事件监听器。在组件中,您可以执行以下操作:

class RequireAuthenticated
    extends React.Component
    implements IAuthenticationChangeListener
{
    public state : IAuthenticationContextState = {
        isAuthenticated: false
    };

    public componentDidMount = (): void => {
        this.props.authenticationService.registerAuthenticationChangeListener(this);
        this.onAuthenticationChange();
    };

    public onAuthenticationChange = ():void => {
        this.setState({
            isAuthenticated: this.props.authenticationService.isAuthenticated()
        });
    };

    public render = () => {
        return this.state.isAuthenticated?this.props.children:[];
    }
}

如您所见,此组件仅在用户通过身份验证时显示其内容。当组件出现时,它会将自身注册到身份验证服务,以便在状态发生变化时得到通知。这将允许DOM树在状态发生变化时发生变化。

构造组件树
您可能会注意到身份验证对话框将身份验证服务作为prop。因此,当您想要创建此身份验证对话框时,您必须这样做:

const authService = new AuthenticationService();
const authDialog = <AuthenticationDialog authenticationService={authService} />

显然,这使得创建组件树的过程有点混乱。要解决这个问题,我们将使用工厂:

class AuthenticationDialogFactory {
    public constructor(
        readonly authenticationServiceFactory: AuthenticationServiceFactory
    ) {

    }

    public create = () : JSX.Element => {
        return <AuthenticationDialog
            authenticationService={this.authenticationServiceFactory.create()}
        />
    }
}

然后将在我们的index.jsx文件中使用此工厂来构建根应用程序。

获取代码
现在您已经很好地了解了正在发生的事情,您可以 从GitHub中获取代码
此外,您可以通过添加React.DI来扩展此解决方案。

结论
在我的试验期间,我发现编写和调试这样的代码非常容易。这是更多的字节,但它在调试东西时可以省去很多麻烦。但是,它不是唯一可行的解​​决方案。