开发大型React应用程序的实用指南。 第2部分:状态管理,路由

今天,我们发布了材料翻译的第二部分,该部分致力于大型React应用程序的开发。 在这里,我们将讨论管理应用程序状态,路由和接口开发。



第1部分: 开发大规模React应用程序的实用指南。 计划,操作,数据源和API

第2部分: 开发大规模React应用程序的实用指南。 第2部分:状态管理,路由


应用程序状态管理,Redux集成,路由组织


在这里,我们将讨论如何扩展Redux的功能,以便能够有序地在应用程序中执行复杂的操作。 如果这些机制实施不当,则可能会违反设计存储库时使用的模式。

JavaScript 生成器函数可以解决与异步编程相关的许多问题。 事实是,可以根据程序员的要求启动和停止这些功能。 Redux-saga中间件使用此概念来管理应用程序的问题方面。 特别是,我们正在谈论解决这样的问题,这些问题在纯函数形式下无法借助减速器来解决。

pure解决纯功能无法解决的任务


请考虑以下情形。 提供给您的工作是为在房地产市场上工作的公司设计的应用程序。 客户希望获得一个新的,更好的网站。 您可以使用REST API,拥有使用Zapier准备的所有页面的布局,并概述了应用程序计划。 但是随后出现了一个严重的问题。

客户公司长期以来一直使用某种内容管理系统(CMS)。 公司的员工非常了解此系统,因此客户不想切换到新的CMS,只是为了更轻松地在公司博客上撰写新帖子。 另外,您还需要将现有出版物从博客复制到新站点,这也可能导致问题。

好处是,客户端使用的CMS具有便捷的API,您可以通过该API访问博客中的出版物。 但是,如果您创建了使用此API的代理,则情况会变得复杂,因为位于某个服务器上,在该服务器上根本没有显示您所需的数据。

这是一个问题的示例,可能会污染应用程序代码,因为这里您必须在项目机制中使用与用于下载博客文章的新API一起使用。 您可以使用redux-saga处理这种情况。
看下图。 这就是我们的应用程序和API交互的方式。 我们使用redux-saga在后台下载出版物。


使用Redux和Redux-Saga存储的应用图

在这里,组件调度GET.BLOGS操作。 该应用程序使用redux-saga,因此该请求将被拦截。 之后,生成器功能将在后台从数据存储区下载数据并更新Redux支持的应用程序状态。

这是一个示例,用于描述如何加载出版物(这些函数称为“传奇”)。 Sagas可以在其他情况下使用。 例如,组织用户数据的存储(例如,可以是令牌),因为这是纯功能不适合的任务的另一个示例。

 ... function* fetchPosts(action) { if (action.type === WP_POSTS.LIST.REQUESTED) {   try {     const response = yield call(wpGet, {       model: WP_POSTS.MODEL,       contentType: APPLICATION_JSON,       query: action.payload.query,     });     if (response.error) {       yield put({         type: WP_POSTS.LIST.FAILED,         payload: response.error.response.data.msg,       });       return;         yield put({       type: WP_POSTS.LIST.SUCCESS,       payload: {         posts: response.data,         total: response.headers['x-wp-total'],         query: action.payload.query,       },       view: action.view,     });   } catch (e) {     yield put({ type: WP_POSTS.LIST.FAILED, payload: e.message });  ... 

这里提出的传奇要求采取WP_POSTS.LIST.REQUESTED类的WP_POSTS.LIST.REQUESTED 。 通过接收此类操作,它将从API加载数据。 她在收到数据后发送另一个操作WP_POSTS.LIST.SUCCESS 。 其处理导致使用适当的reducer更新存储库。

of减速机介绍


开发大型应用程序时,不可能预先计划所有必需型号的设备。 此外,随着应用程序大小的增加,使用引入减速器的技术有助于节省大量的工时。 此技术使开发人员无需重写整个存储库即可向系统添加新的reducer。

有些旨在创建动态Redux存储库。 但是,我更喜欢引入减速器的机制,因为它为开发人员提供了一定程度的灵活性。 例如,现有应用程序可以配备有此机制,而无需认真重组应用程序。

归约器的引入是代码分离的一种形式。 React开发人员社区热烈地欢迎这项技术。 我将使用这段代码来演示reducer的实现机制的外观和功能。

首先,让我们看一下它与Redux的集成:

 ... const withConnect = connect( mapStateToProps, mapDispatchToProps, ); const withReducer = injectReducer({ key: BLOG_VIEW, reducer: blogReducer, }); class BlogPage extends React.Component {  ... } export default compose( withReducer, withConnect, )(BlogPage); 

此代码是包含应用程序组件的BlogPage.js文件的一部分。

在这里,我们在export命令中不使用connect函数,而是使用compose函数。 这是Redux库的功能之一,可让您组合多个功能。 传递给compose的功能列表必须从右到左或从下到上读取。

您可以从Redux 文档中了解到compose函数允许您创建深层嵌套函数的转换。 在这种情况下,程序员无需使用非常长的结构。 这些构造看起来像是代码行,代表对某些函数的调用,并将对其他函数的调用结果作为参数传递给它们。 文档指出, compose功能应谨慎使用。

组合中最右边的函数可以带有多个参数,但是只能将一个参数传递给其后的函数。 结果,通过调用使用compose产生的函数,我们将其从原始函数中获取的东西传递给了它,这在所有其他函数的右边。 这就是为什么我们将compose函数作为最后一个参数传递给withConnect函数。 结果,可以像connect功能一样使用compose功能。

▍路由和Redux


有许多工具可用于解决应用程序中的路由问题。 但是,在本节中,我们将重点介绍react-router-dom库。 我们将扩展其功能,使其可以与Redux一起使用。

通常,React路由器的用法是这样的:根组件封装在BrowserRouter标记中,子容器封装在withRouter()方法中并导出( 这里是一个示例)。

通过这种方法,子组件通过props机制接收一个history对象,其中包含一些特定于当前用户会话的属性。 此对象中有一些方法可用于控制导航。

此路由选项可能导致大型应用程序出现问题。 这是因为它们没有集中的history对象。 此外,未使用<Route>呈现的组件不能与history对象一起使用。 这是使用<Route>的示例:

 <Route path="/" exact component={HomePage} /> 

为了解决这个问题,我们将使用connected-react-router库,该库将允许我们使用dispatch方法建立路由。 将该库集成到项目中将需要进行一些修改。 特别是,有必要创建专门为路线设计的新的减速器(这是显而易见的),并且还需要向系统中添加一些辅助机制。

配置完成后,可以通过Redux使用新的路由系统。 因此,可以通过发送操作来实现应用程序中的导航。

为了利用组件中的connected-react-router库的功能,我们仅将dispatch方法映射到存储库,并根据应用程序的需要执行此操作。 这是一个代码示例,演示了connected-react-router库的使用(为了使此代码正常工作,您需要将系统的其余部分配置为使用connected-react-router)。

 import { push } from 'connected-react-router' ... const mapDispatchToProps = dispatch => ({  goTo: payload => {    dispatch(push(payload.path));  }, }); class DemoComponent extends React.Component {  render() {    return (      <Child        onClick={          () => {            this.props.goTo({ path: `/gallery/`});                      />    } ... 

在这里, goTo方法调度一个操作,该操作将所需的URL goTo浏览器历史记录堆栈中。 以前, goTo方法已goTo到存储库。 因此,此方法将传递给props对象中的DemoComponent

动态用户界面和不断增长的应用程序的需求


随着时间的流逝,尽管存在用于应用程序的适当后端和高质量的客户端部件,但用户界面的某些元素开始严重影响用户的工作。 这是由于这些组件的不合理实现,乍看之下似乎很简单。 在本节中,我们将讨论有关创建一些小部件的建议。 随着应用程序的增长,它们的正确实现变得更加复杂。

▍延迟加载和React.Suspense


关于JavaScript的异步特性,最好的部分是它利用了浏览器的全部潜能。 也许真正的好处是,开始某个过程不需要等待上一个任务的完成。 但是,开发人员无法影响网络以及网站功能所需的各种材料的加载速度。

网络子系统通常被认为是不可靠且容易出错。

为了使他的应用程序尽可能高质量,开发人员可以对其进行许多检查,并使其成功通过。 但是仍然有些事情,例如网络连接的状态或服务器响应时间,开发人员无法影响。

但是,软件的创建者并未试图用“这不是我的事”之类的短语来证明应用程序的低质量工作是合理的。 他们找到了解决网络问题的有趣方法。

前端应用程序的某些部分,您可能需要显示一些备份材料(例如,加载速度比真实材料快得多)。 这将使用户免于考虑加载页面的“抽动”,或更糟的是,不必考虑此类图标。


用户最好不要看到这样的东西。

React Suspense技术使您可以应对此类问题。 例如,它允许您在数据加载期间显示某个指示器。 尽管也可以通过将isLoaded属性设置为true来手动完成此操作,但使用Suspense API可使代码更简洁。

在这里,您可以观看有关Suspense的精彩视频,其中Jared Palmer向观众介绍了该技术,并通过一个实际应用程序的示例展示了其某些功能。

这是不使用Suspense的应用程序的工作方式。


不使用暂停功能的应用程序

为组件提供Suspense支持比使用整个应用程序范围的isLoaded要容易得多。 首先,将父App容器放入React.StrictMode 。 我们确保在应用程序中使用的React模块中,不会有过时的模块。

 <React.Suspense fallback={<Spinner size="large" />}>  <ArtistDetails id={this.props.id}/>  <ArtistTopTracks />  <ArtistAlbums id={this.props.id}/> </React.Suspense> 

在加载主要内容期间,包装在React.Suspense标记中的组件将加载并显示fallback属性中指定的内容。 我们必须努力确保fallback属性中使用的组件的体积尽可能小,并且排列尽可能简单。


使用暂挂的应用程序

▍自适应组件


在大型前端应用程序中,重复模式的表现很常见。 同时,在工作开始时,这几乎是完全不明显的。 没有什么可做的,但是您一定已经遇到了。

例如,应用程序中有两个模型。 其中一个用于描述赛道,第二个用于描述汽车。 汽车列表页面使用正方形元素。 它们每个都包含图像和简短描述。

跟踪列表使用类似的元素。 他们的主要特点是,除了路线的图像和描述之外,他们还有一个很小的区域,指示在此路线上进行比赛的观众是否可以购买东西。


用于描述汽车的元素和用于描述轨道的元素

这两个组件的样式略有不同(它们具有不同的背景颜色)。 描述路线的组件包含有关它所描述的现实世界对象的一些其他信息,而象征汽车的组件则没有此类信息。 此示例仅显示两个模型。 在大型应用程序中,可以键入许多类似的模型,仅在小事情上有所不同。

为这些实体中的每一个创建单独的独立组件与常识相反。

程序员可以省去编写几乎完全重复的代码片段的需要。 这可以通过开发自适应组件来完成。 在工作过程中,它们考虑了加载它们的环境。 考虑某个应用程序的搜索栏。


搜索栏

它会在许多页面上使用。 同时,将对其外观和在不同页面上的工作顺序进行细微更改。 例如,在项目的主页上,它会比其他页面上的稍大。 为了解决此问题,您可以创建一个单个组件,该组件将根据传输给它的属性进行显示。

 static propTypes = {  open: PropTypes.bool.isRequired,  setOpen: PropTypes.func.isRequired,  goTo: PropTypes.func.isRequired, }; 

使用此技术,可以在呈现此类元素时控制HTML类的使用,从而可以影响它们的外观。

自适应组件可以找到应用的另一种有趣情况是将某些材料拆分为页面的机制。 导航栏可能出现在应用程序的每个页面上。 每个页面上此面板的实例将与其他页面上的实例几乎完全相同。


分页面板

假设某个应用程序需要类似的面板。 开发该应用程序时,开发人员应遵守及时明确的要求。 在这种情况下,用于将材料拆分为页面的自适应组件仅需要传递几个属性。 这是URL和每页元素的数量。

总结


如今,React生态系统已经变得如此成熟,以至于几乎没有人会在应用程序开发的任何阶段都需要“发明一辆自行车”。 尽管这会影响开发人员,但会导致这样一个事实,即很难为每个特定项目准确选择适合的项目。

每个项目的范围和功能都是唯一的。 开发React应用程序没有单一的方法或通用的规则。 因此,在开始开发之前,正确规划它很重要。

在计划时,很容易理解为项目直接创建了哪些工具,以及哪些工具显然不适合他,对于他来说太大了。 例如,一个由2-3页组成并且对某些API进行很少请求的应用程序不需要类似于我们所讨论的数据存储。 我准备在这些考虑因素上走得更远,并说在小型项目中,您不需要使用Redux。
在应用程序的规划阶段,在绘制页面布局时,很容易看到在这些页面上使用了许多类似的组件。 如果您尝试重用此类组件的代码或努力编写通用组件,则将有助于节省大量时间和精力。

最后,我想指出数据是任何应用程序的核心。 React应用程序也不例外。 随着应用程序规模的扩大,处理的数据量也随之增加,出现了用于处理这些数据的其他软件机制。 如果应用程序的设计不当,类似这样的事情很容易“粉碎”程序员,使他们充满复杂而混乱的任务。 如果在规划过程中预先确定了使用数据仓库的问题,如果事先考虑了操作,减速器,下垂的工作顺序,那么在应用程序上的工作会容易得多。

亲爱的读者们! 如果您知道创建大型React应用程序时表现良好的任何库或开发方法,请共享它们。

Source: https://habr.com/ru/post/zh-CN458498/


All Articles