搜索
首页web前端js教程没有反应的redux

Redux without React

本文经Vildan Softic同行评审。感谢所有SitePoint的同行评审员,让SitePoint的内容尽善尽美!

Redux without React 我属于那种喜欢从零开始,并了解一切工作原理的开发者。虽然我知道这给自己带来了(不必要的)工作量,但这确实帮助我欣赏和理解特定框架、库或模块背后的机制。

最近,我又经历了这样的时刻,开始使用Redux和纯JavaScript开发一个Web应用程序。在本文中,我想概述我的应用程序结构,检查我早期(最终失败的)迭代,然后看看我最终选择的解决方案以及在此过程中学到的知识。

关键要点

  • Redux可以不依赖React,使用纯JavaScript管理应用程序状态,展示了其在不同UI层中进行状态管理的灵活性。
  • 正确初始化和管理Redux存储至关重要;应在应用程序入口点创建存储,并将其传递给组件,以避免循环依赖等问题。
  • 在仅使用Redux的设置中,组件可以类似于React的组件结构,分为表现层和容器层,这有助于分离关注点并明确角色定义。
  • 与React的虚拟DOM(根据状态更新自动处理UI更改)不同,在使用纯JavaScript和Redux时,需要手动更新DOM。
  • 建议实现用例驱动的存储,确保仅存储必要的数据,这可以通过避免不必要的状态持久化来提高性能和用户体验。

设置

您可能听说过流行的React.js和Redux组合,它使用最新的前端技术构建快速而强大的Web应用程序。

React是由Facebook创建的用于构建用户界面的基于组件的开源库。虽然React只是一个视图层(不是像Angular或Ember这样的完整框架),但Redux管理应用程序的状态。它充当可预测的状态容器,其中整个状态存储在一个单一的对象树中,并且只能通过发出所谓的action来更改。如果您完全不了解这个主题,我建议您阅读这篇文章。

对于本文的其余部分,不需要成为Redux专家,但至少对它的概念有一定的了解会有所帮助。

无React的Redux——从零开始的应用程序

Redux的优点在于它迫使您提前思考并尽早了解应用程序的设计。您开始定义实际应该存储什么,哪些数据可以并且应该更改,以及哪些组件可以访问存储。但由于Redux只关注状态,我发现自己有点困惑如何构建和连接应用程序的其余部分。React在引导您完成所有步骤方面做得很好,但如果没有它,就需要我弄清楚什么方法最有效。

所讨论的应用程序是一个针对移动设备优先的俄罗斯方块克隆,它有几个不同的视图。实际的游戏逻辑在Redux中完成,而离线功能由localStorage和自定义视图处理提供。存储库可以在GitHub上找到,尽管该应用程序仍在积极开发中,并且我是在开发过程中撰写这篇文章的。

定义应用程序架构

我决定采用在Redux和React项目中常见的文件夹结构。这是一个逻辑结构,适用于许多不同的设置。这个主题有很多变体,大多数项目都略有不同,但整体结构相同。

src/scripts/

<code>actions/
├── game.js
├── score.js
└── ...
components/
├── router.js
├── pageControls.js
├── canvas.js
└── ...
constants/
├── game.js
├── score.js
└── ...
reducers/
├── game.js
├── score.js
└── ...
store/
├── configureStore.js
├── connect.js
└── index.js
utils/
├── serviceWorker.js
├── localStorage.js
├── dom.js
└── ...
index.js
worker.js</code>

我的标记被分隔到另一个目录中,最终由单个index.html文件呈现。该结构类似于scripts/,以便在整个代码库中保持一致的架构。

src/markup/

<code>layouts/
└── default.html
partials/
├── back-button.html
└── meta.html
pages/
├── about.html
├── settings.html
└── ...
index.html</code>

管理和访问存储

要访问存储,需要创建一次并将其传递给应用程序的所有实例。大多数框架都使用某种依赖注入容器,因此我们作为框架的用户不必自己想出解决方案。但是,当我使用自己的解决方案时,如何才能让它对我的所有组件都可用呢?

我的第一次迭代失败了。我不知道为什么我认为这是一个好主意,但我将存储放在它自己的模块(scripts/store/index.js)中,然后可以由应用程序的其他部分导入。我最终后悔了,并很快处理了循环依赖。问题是,当组件尝试访问存储时,存储没有正确初始化。我制作了一个图表来演示我正在处理的依赖关系流程:

Redux without React

应用程序入口点正在初始化所有组件,然后通过直接或通过辅助函数(此处称为connect)内部使用存储。但是,由于存储不是显式创建的,而只是在其自身模块中的副作用,组件最终在存储创建之前就使用了存储。无法控制组件或辅助函数第一次调用存储的时间。这很混乱。

存储模块如下所示:

scripts/store/index.js (☓ bad)

import { createStore } from 'redux'
import reducers from '../reducers'

const store = createStore(reducers)

export default store
export { getItemList } from './connect'

如上所述,存储是作为副作用创建的,然后导出。辅助函数也需要存储。

scripts/store/connect.js (☓ bad)

import store from './'

export function getItemList () {
  return store.getState().items.all
}

这正是我的组件最终相互递归的时刻。辅助函数需要存储才能运行,并且同时从存储初始化文件中导出,以便使它们可以访问应用程序的其他部分。您看到这听起来有多乱吗?

解决方案

现在看来很明显的事情,我花了一段时间才理解。我通过将初始化移动到我的应用程序入口点(scripts/index.js),并将其传递给所有必需的组件来解决此问题。

同样,这与React实际使存储可访问的方式非常相似(查看源代码)。它们一起工作得如此之好是有原因的,为什么不学习它的概念呢?

Redux without React

应用程序入口点首先创建存储,然后将其传递给所有组件。然后,组件可以连接到存储并调度操作、订阅更改或获取特定数据。

让我们来看一下更改:

scripts/store/configureStore.js (✓ good)

<code>actions/
├── game.js
├── score.js
└── ...
components/
├── router.js
├── pageControls.js
├── canvas.js
└── ...
constants/
├── game.js
├── score.js
└── ...
reducers/
├── game.js
├── score.js
└── ...
store/
├── configureStore.js
├── connect.js
└── index.js
utils/
├── serviceWorker.js
├── localStorage.js
├── dom.js
└── ...
index.js
worker.js</code>

我保留了该模块,但改为了导出一个名为configureStore的函数,该函数在代码库中的其他地方创建存储。请注意,这只是基本概念;我还使用了Redux DevTools扩展程序并通过localStorage加载持久化状态。

scripts/store/connect.js (✓ good)

<code>layouts/
└── default.html
partials/
├── back-button.html
└── meta.html
pages/
├── about.html
├── settings.html
└── ...
index.html</code>

connect辅助函数基本上没有改变,但现在需要将存储作为参数传递。起初我犹豫是否要使用此解决方案,因为我认为“那么辅助函数有什么意义呢?”。现在我认为它们很好而且足够高级,使一切更易于阅读。

scripts/index.js

import { createStore } from 'redux'
import reducers from '../reducers'

const store = createStore(reducers)

export default store
export { getItemList } from './connect'

这是应用程序入口点。存储被创建,并传递给所有组件。PageControls为特定操作按钮添加全局事件侦听器,TetrisGame是实际的游戏组件。在将存储移到这里之前,它看起来基本相同,但没有将存储分别传递给所有模块。如前所述,组件可以通过我失败的连接方法访问存储。

组件

我决定使用两种组件:表现层容器组件。表现层组件除了纯DOM处理之外什么也不做;它们不知道存储。另一方面,容器组件可以调度操作或订阅更改。

Dan Abramov已经为React组件写了一篇很棒的文章,但这套方法也可以应用于任何其他组件架构。

不过,对我来说也有例外。有时组件非常小,只做一件事情。我不想将它们分成上述模式之一,所以我决定将它们混合使用。如果组件增长并获得更多逻辑,我将对其进行分离。

scripts/components/pageControls.js

import store from './'

export function getItemList () {
  return store.getState().items.all
}

上面的示例就是其中一个组件。它有一个元素列表(在本例中是所有具有data-action属性的元素),并根据属性内容在单击时调度操作。仅此而已。然后,其他模块可能会侦听存储中的更改并相应地更新自身。如前所述,如果组件还进行了DOM更新,我将对其进行分离。

现在,让我向您展示这两种组件类型的清晰分离。

更新DOM

在我开始该项目时,我遇到的一个更大的问题是如何实际更新DOM。React使用称为虚拟DOM的DOM的快速内存中表示来最大限度地减少DOM更新。

我实际上是在考虑做同样的事情,如果我的应用程序变得更大且DOM更繁重,我可能会切换到虚拟DOM,但就目前而言,我进行经典DOM操作,这与Redux配合得很好。

基本流程如下:

  • 初始化容器组件的新实例并传递存储以供内部使用
  • 组件订阅存储中的更改
  • 并使用不同的表现层组件在DOM中呈现更新

注意:对于JavaScript中与DOM相关的任何内容,我都是$符号前缀的粉丝。正如您可能猜到的那样,它取自jQuery的$。因此,纯表现层组件文件名以美元符号为前缀。

scripts/index.js

<code>actions/
├── game.js
├── score.js
└── ...
components/
├── router.js
├── pageControls.js
├── canvas.js
└── ...
constants/
├── game.js
├── score.js
└── ...
reducers/
├── game.js
├── score.js
└── ...
store/
├── configureStore.js
├── connect.js
└── index.js
utils/
├── serviceWorker.js
├── localStorage.js
├── dom.js
└── ...
index.js
worker.js</code>

这里没有什么花哨的东西。导入、创建和初始化容器组件ScoreObserver。它究竟做了什么?它更新所有与分数相关的视图元素:高分列表和游戏期间的当前分数信息。

scripts/components/scoreObserver/index.js

<code>layouts/
└── default.html
partials/
├── back-button.html
└── meta.html
pages/
├── about.html
├── settings.html
└── ...
index.html</code>

请记住,这是一个简单的组件;其他组件可能具有更复杂的逻辑和需要处理的事情。这里发生了什么?ScoreObserver组件保存对存储的内部引用,并创建新实例的两个表现层组件以供以后使用。init方法订阅存储更新,并在每次存储更改时更新$label组件——但前提是游戏实际上正在运行。

updateScoreBoard方法在其他地方使用。每次发生更改时更新列表是没有意义的,因为视图无论如何都是不活动的。还有一个路由组件,它在每次视图更改时更新或停用不同的组件。它的API大致如下所示:

import { createStore } from 'redux'
import reducers from '../reducers'

const store = createStore(reducers)

export default store
export { getItemList } from './connect'

注意:$(和$$)不是jQuery引用,而是document.querySelector的便捷实用程序快捷方式。

scripts/components/scoreObserver/$board.js

import store from './'

export function getItemList () {
  return store.getState().items.all
}

同样,这是一个基本示例和一个基本组件。updateBoard()方法获取一个数组,对其进行迭代,并将内容插入到分数列表中。

scripts/components/scoreObserver/$label.js

import { createStore } from 'redux'
import reducers from '../reducers'

export default function configureStore () {
  return createStore(reducers)
}

此组件与上面的ScoreBoard几乎完全相同,但只更新单个元素。

其他错误和建议

另一个重要点是实现用例驱动的存储。我认为只存储对应用程序必不可少的内容很重要。一开始,我几乎存储了一切:当前活动视图、游戏设置、分数、悬停效果、用户的呼吸模式等等。

虽然这可能与一个应用程序相关,但与另一个应用程序无关。存储当前视图并在重新加载时继续在完全相同的位置可能很好,但在我的情况下,这感觉像是糟糕的用户体验,而且比有用的更烦人。您也不想存储菜单或模态的切换,对吧?用户为什么要回到那个特定状态?在较大的Web应用程序中,这可能是有意义的。但在我的小型移动游戏重点游戏中,回到设置屏幕只是因为我从那里离开,这相当烦人。

结论

我已经使用和不使用React完成了Redux项目,我的主要收获是,应用程序设计中的巨大差异并非必要。React中使用的大多数方法实际上都可以适应任何其他视图处理设置。我花了一段时间才意识到这一点,因为我一开始认为我必须做不同的事情,但我最终发现这没有必要。

然而,不同的是您初始化模块、存储的方式,以及组件对整体应用程序状态的了解程度。概念保持不变,但实现和代码量完全适合您的需求。

Redux是一个很棒的工具,它有助于以更周到的方式构建您的应用程序。单独使用,没有任何视图库,一开始可能会非常棘手,但一旦您克服了最初的困惑,就没有任何东西可以阻止您了。

您如何看待我的方法?您是否独自使用Redux和不同的视图处理设置?我很乐意收到您的反馈并在评论中讨论它。


如果您想了解更多关于Redux的信息,请查看我们的课程《重写和测试Redux以解决设计问题》迷你课程。在本课程中,您将构建一个Redux应用程序,该应用程序通过websocket连接接收按主题组织的推文。为了让您了解即将发生的事情,请查看下面的免费课程。

加载播放器……关于无React的Redux的常见问题解答(FAQ)

使用Redux与React和不使用React的主要区别是什么?

Redux是一个适用于JavaScript应用程序的可预测状态容器,可以与任何UI层一起使用。使用Redux与React和不使用React之间的主要区别在于UI层与Redux存储交互的方式。当与React一起使用时,Redux可以利用React的基于组件的架构及其生命周期方法来自动处理状态更改时组件的更新。如果没有React,您需要手动订阅存储并在状态更改时处理UI的更新。

如何在没有React的情况下处理Redux中的异步操作?

Redux中的异步操作通常使用Redux Thunk或Redux Saga等中间件来处理。这些中间件允许您调度函数(thunk)或更复杂的异步操作(saga),而不是普通的对象。即使没有React,您仍然可以在Redux存储中使用这些中间件。您只需要在使用Redux的applyMiddleware函数创建存储时应用中间件即可。

我可以在没有React的情况下使用Redux DevTools吗?

是的,Redux DevTools不依赖于React,可以与任何使用Redux的UI层一起使用。您可以通过在创建Redux存储时将其添加为中间件来将Redux DevTools集成到您的应用程序中。这将允许您实时检查应用程序的状态和操作,即使没有React。

如何在我的UI组件连接到Redux存储而无需React?

如果没有React及其connect函数,您需要手动订阅Redux存储并在状态更改时更新UI组件。您可以使用store.subscribe方法订阅存储,该方法采用一个侦听器函数,该函数将在每次调度操作时被调用。在此侦听器函数中,您可以使用store.getState获取存储的当前状态并相应地更新UI组件。

我可以将Redux与其他库或框架(如Vue或Angular)一起使用吗?

是的,Redux不依赖于React,可以与任何UI层一起使用。对于其他库和框架(如Vue和Angular),都提供了提供与React的connect函数类似功能的绑定。这些绑定允许您轻松地将UI组件连接到Redux存储,并在状态更改时处理组件的更新。

如何在没有React的情况下测试我的Redux代码?

在没有React的情况下测试Redux代码类似于使用React测试它。您可以使用任何JavaScript测试框架(如Jest或Mocha)为您的action creators和reducers创建单元测试。对于测试异步操作,您可以使用模拟存储来模拟Redux存储。

如何在没有React的情况下处理Redux中的副作用?

Redux中的副作用通常使用Redux Thunk或Redux Saga等中间件来处理。这些中间件允许您调度具有副作用的函数或更复杂的异步操作,例如进行API调用。即使没有React,您仍然可以在Redux存储中使用这些中间件。

我可以将Redux与纯JavaScript一起使用吗?

是的,Redux可以与纯JavaScript一起使用。您可以创建一个Redux存储,向其调度操作,并仅使用纯JavaScript订阅状态中的更改。但是,如果没有像React这样的库或框架来处理UI的更新,您需要在状态更改时手动更新UI组件。

如何在没有React的情况下构建Redux代码?

Redux代码的结构并不取决于您是否使用React。您仍然可以遵循构建Redux代码的相同最佳实践,例如将操作、reducers和selectors分离到不同的文件或文件夹中,并以规范化和模块化的方式组织您的状态。

我可以在没有React的情况下使用Redux中间件吗?

是的,Redux中间件不依赖于React,可以与任何使用Redux的UI层一起使用。Redux中的中间件用于处理副作用和异步操作,等等。您可以使用Redux的applyMiddleware函数将中间件应用于您的Redux存储,无论您是否使用React。

以上是没有反应的redux的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
在JavaScript中替换字符串字符在JavaScript中替换字符串字符Mar 11, 2025 am 12:07 AM

JavaScript字符串替换方法详解及常见问题解答 本文将探讨两种在JavaScript中替换字符串字符的方法:在JavaScript代码内部替换和在网页HTML内部替换。 在JavaScript代码内部替换字符串 最直接的方法是使用replace()方法: str = str.replace("find","replace"); 该方法仅替换第一个匹配项。要替换所有匹配项,需使用正则表达式并添加全局标志g: str = str.replace(/fi

构建您自己的Ajax Web应用程序构建您自己的Ajax Web应用程序Mar 09, 2025 am 12:11 AM

因此,在这里,您准备好了解所有称为Ajax的东西。但是,到底是什么? AJAX一词是指用于创建动态,交互式Web内容的一系列宽松的技术。 Ajax一词,最初由Jesse J创造

如何创建和发布自己的JavaScript库?如何创建和发布自己的JavaScript库?Mar 18, 2025 pm 03:12 PM

文章讨论了创建,发布和维护JavaScript库,专注于计划,开发,测试,文档和促销策略。

如何在浏览器中优化JavaScript代码以进行性能?如何在浏览器中优化JavaScript代码以进行性能?Mar 18, 2025 pm 03:14 PM

本文讨论了在浏览器中优化JavaScript性能的策略,重点是减少执行时间并最大程度地减少对页面负载速度的影响。

如何使用浏览器开发人员工具有效调试JavaScript代码?如何使用浏览器开发人员工具有效调试JavaScript代码?Mar 18, 2025 pm 03:16 PM

本文讨论了使用浏览器开发人员工具的有效JavaScript调试,专注于设置断点,使用控制台和分析性能。

如何构建简单的jQuery滑块如何构建简单的jQuery滑块Mar 11, 2025 am 12:19 AM

本文将引导您使用jQuery库创建一个简单的图片轮播。我们将使用bxSlider库,它基于jQuery构建,并提供许多配置选项来设置轮播。 如今,图片轮播已成为网站必备功能——一图胜千言! 决定使用图片轮播后,下一个问题是如何创建它。首先,您需要收集高质量、高分辨率的图片。 接下来,您需要使用HTML和一些JavaScript代码来创建图片轮播。网络上有很多库可以帮助您以不同的方式创建轮播。我们将使用开源的bxSlider库。 bxSlider库支持响应式设计,因此使用此库构建的轮播可以适应任何

jQuery矩阵效果jQuery矩阵效果Mar 10, 2025 am 12:52 AM

将矩阵电影特效带入你的网页!这是一个基于著名电影《黑客帝国》的酷炫jQuery插件。该插件模拟了电影中经典的绿色字符特效,只需选择一张图片,插件就会将其转换为充满数字字符的矩阵风格画面。快来试试吧,非常有趣! 工作原理 插件将图片加载到画布上,读取像素和颜色值: data = ctx.getImageData(x, y, settings.grainSize, settings.grainSize).data 插件巧妙地读取图片的矩形区域,并利用jQuery计算每个区域的平均颜色。然后,使用

如何使用源地图调试缩小JavaScript代码?如何使用源地图调试缩小JavaScript代码?Mar 18, 2025 pm 03:17 PM

本文说明了如何使用源地图通过将其映射回原始代码来调试JAVASCRIPT。它讨论了启用源地图,设置断点以及使用Chrome DevTools和WebPack之类的工具。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。