This article was peer-reviewed by Vildan Softic. Thanks to all the peer reviewers at SitePoint for making SitePoint’s content perfect!
I am the kind of developer who likes to start from scratch and understand how everything works. While I know this brings myself an (unnecessary) workload, it does help me appreciate and understand the mechanisms behind a specific framework, library, or module.
Recently, I have experienced this moment again and started developing a web application using Redux and pure JavaScript. In this article, I want to outline my application structure, examine my early (finally failed) iterations, and then look at the solution I finally chose and what I learned along the way.
Key Points
- Redux can use pure JavaScript to manage application state without relying on React, demonstrating its flexibility in state management in different UI layers.
- It is crucial to properly initialize and manage Redux storage; storage should be created at the application entry point and passed to the component to avoid issues such as circular dependencies.
- In Redux-only settings, components can be similar to React's component structure, divided into presentation and container layers, which helps separate concerns and clarify role definitions.
- Unlike React's virtual DOM (which automatically handles UI changes based on status updates), when using pure JavaScript and Redux, the DOM needs to be updated manually.
- It is recommended to implement use case-driven storage to ensure that only necessary data is stored, which can improve performance and user experience by avoiding unnecessary state persistence.
Settings
You may have heard of the popular React.js and Redux combination that uses the latest front-end technology to build fast and powerful web applications.
React is a component-based open source library created by Facebook for building user interfaces. While React is just a view layer ( is not a complete framework like Angular or Ember), Redux manages the state of the application. It acts as a predictable state container where the entire state is stored in a single object tree and can only be changed by issuing a so-called action. If you don't understand this topic at all, I suggest you read this article.
For the rest of this article, you don't need to be a Redux expert, but at least it will be helpful to have a certain understanding of its concept.
React-free Redux – Application from scratch
The advantage of Redux is that it forces you to think ahead and understand the design of your application as early as possible. You start defining what should actually be stored, what data can and should be changed, and which components can access the storage. But since Redux only focuses on state, I find myself a little confused about how to build and connect the rest of the application. React does a great job of guiding you through all the steps, but without it, I need to figure out what works best.The application in question is a mobile-first Tetris clone with several different views. The actual game logic is done in Redux, while offline functionality is provided by localStorage and custom view processing. The repository can be found on GitHub, although the app is still under active development and I wrote this article during development.
Define application architecture
I decided to adopt the folder structure that is common in Redux and React projects. This is a logical structure that works for many different settings. There are many variations of this topic, most projects are slightly different, but the overall structure is the same.
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>
My tags are separated into another directory and end up being rendered by a single index.html file. This structure is similar to scripts/ to maintain a consistent architecture throughout the code base.
src/markup/
<code>layouts/ └── default.html partials/ ├── back-button.html └── meta.html pages/ ├── about.html ├── settings.html └── ... index.html</code>
Manage and access storage
To access the storage, you need to create and pass it to all instances of the application once. Most frameworks use some kind of dependency injection container, so we as framework users don't have to come up with solutions on their own. But when I use my own solution, how can I make it available for all my components?
My first iteration failed. I don't know why I think this is a good idea, but I put the storage in its own module (scripts/store/index.js) which can then be imported by the rest of the application. I ended up regretting it and quickly dealt with circular dependencies. The problem is that when the component tries to access the storage, the storage is not initialized correctly. I made a chart to demonstrate the dependency flow I'm working on:
Application entry point is initializing all components and then using the storage internally either directly or through helper functions (here referred to as connect). However, since the storage is not created explicitly, but is just a side effect in its own module, the component ends up using the storage before it is created. There is no control over the time when a component or helper function is first called to store. This is very confusing.
Storage module is as follows:
scripts/store/index.js (☓ bad)
import { createStore } from 'redux' import reducers from '../reducers' const store = createStore(reducers) export default store export { getItemList } from './connect'
As mentioned above, the storage is created as a side effect and then exported. Helper functions also need to be stored.
scripts/store/connect.js (☓ bad)
import store from './' export function getItemList () { return store.getState().items.all }
This is exactly the moment when my components end up recursing to each other. Helper functions require storage to run and are exported from the storage initialization file at the same time so that they can access other parts of the application. Do you see how messy this sounds?
Solution
It seems obvious now, and it took me a while to understand. I solved this by moving the initialization to my application entry point (scripts/index.js) and passing it to all the required components.
Again, this is very similar to how React actually makes storage accessible (see the source code). There is a reason they work so well together, why not learn its concept?
Application entry point first creates the storage and then passes it to all components. The component can then connect to the storage and schedule operations, subscribe to changes, or get specific data. Let's look at the changes:
scripts/store/configureStore.js
(✓ good)I kept the module, but instead exported a function called configureStore which creates storage elsewhere in the code base.
<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>Note that this is just a basic concept; I also used the Redux DevTools extension and loaded the persistent state via localStorage.
scripts/store/connect.js
(✓ good)connect helper function has basically not changed, but now the storage needs to be passed as a parameter. At first I hesitated to use this solution because I thought
<code>layouts/ └── default.html partials/ ├── back-button.html └── meta.html pages/ ├── about.html ├── settings.html └── ... index.html</code>"So what does helper function mean?"
. Now I think they are good and advanced enough to make everything easier to read. scripts/index.js
This is the application entry point. The store is created and passed to all components. PageControls adds a global event listener for specific action buttons, and TetrisGame is the actual game component. It looks basically the same before moving the storage here, but does not pass the storage separately to all modules. As mentioned earlier, the component can access the storage via my failed connection method.
import { createStore } from 'redux' import reducers from '../reducers' const store = createStore(reducers) export default store export { getItemList } from './connect'Components
I decided to use two components:
presentation layerand container component. Presentational components do nothing but pure DOM processing; they don't know storage. On the other hand, the container component can schedule actions or subscribe to changes. Dan Abramov has written a great article for React components, but this set of methods can also be applied to any other component architecture.
However, there are exceptions for me. Sometimes the components are very small and do only one thing. I don't want to split them into one of the above patterns, so I decided to mix them. If the component grows and gets more logic, I'll separate it.
scripts/components/pageControls.js
The above example is one of the components. It has a list of elements (in this case all elements with data-action attributes) and schedules actions when clicked based on the attribute content. That's all. Other modules may then listen for changes in storage and update themselves accordingly. As mentioned before, if the component also has a DOM update, I will separate it.
import store from './' export function getItemList () { return store.getState().items.all }
Now, let me show you a clear separation of these two component types.
Update DOMA bigger problem I had when I started the project was how to actually update the DOM. React uses a fast in-memory representation of a DOM called a virtual DOM to minimize DOM updates.
I'm actually thinking about doing the same thing, if my application gets bigger and DOM is more tedious I might switch to a virtual DOM, but for now I'm doing classicDOM operation, which works well with Redux.
The basic process is as follows:
- Initialize a new instance of the container component and pass the storage for internal use
- Changes in component subscription storage
- and render updates in DOM using different presentation layer components
Note: I am a fan of the $ symbol prefix for anything related to DOM in JavaScript. As you might guess, it is taken from jQuery's $. Therefore, the pure presentation component file name is prefixed with the dollar sign.
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>
Nothing fancy here. Import, create, and initialize container component ScoreObserver. What exactly does it do? It updates all the view elements related to scores: high score list and current score information during the game.
scripts/components/scoreObserver/index.js
<code>layouts/ └── default.html partials/ ├── back-button.html └── meta.html pages/ ├── about.html ├── settings.html └── ... index.html</code>
Remember, this is a simple component; other components may have more complex logic and things to deal with. What's going on here? The ScoreObserver component saves internal references to the storage and creates two presentation-level components of the new instance for later use. The init method subscribes to storage updates and updates the $label component every time the storage changes - but only if the game is actually running.
updateScoreBoard method is used elsewhere. It doesn't make sense to update the list every time a change occurs, because the view is inactive anyway. There is also a routing component that updates or deactivates a different component every time the view changes. Its API is roughly as follows:
import { createStore } from 'redux' import reducers from '../reducers' const store = createStore(reducers) export default store export { getItemList } from './connect'
Note: $(and $$) is not a jQuery reference, but a convenient utility shortcut to document.querySelector.
scripts/components/scoreObserver/$board.js
import store from './' export function getItemList () { return store.getState().items.all }
Again, this is a basic example and a basic component. The updateBoard() method takes an array, iterates over it, and inserts the content into the score list.
scripts/components/scoreObserver/$label.js
import { createStore } from 'redux' import reducers from '../reducers' export default function configureStore () { return createStore(reducers) }
This component is almost exactly the same as the ScoreBoard above, but only updates a single element.
Other errors and suggestions
Another important point is to implement use case-driven storage. I think it's important to just store content that is essential to the application. At the beginning, I almost stored everything: the current active view, game settings, scores, hover effects, user's breathing mode and so on.
While this may be related to one application, it has nothing to do with another. It might be nice to store the current view and continue in the exact same place when reloaded, but in my case it felt like a bad user experience and more annoying than useful. You don't want to store menus or modal switches, right? Why do users need to return to that specific state? In larger web applications, this may make sense. But in my small mobile game focus game, going back to the settings screen just because I left from there, which is pretty annoying.
Conclusion
I have done my Redux project with and without React, and my main takeaway is that the huge difference in application design is not necessary. Most of the methods used in React can actually fit any other view processing settings. It took me a while to realize this because I thought at first I had to do something different, but I ended up finding it wasn't necessary. However, what is different is how you initialize modules, how you store them, and how well the components understand the overall application state. The concept remains the same, but the implementation and code volume are perfect for your needs.
Redux is a great tool that helps build your application in a more thoughtful way. Using it alone without any view gallery can be very tricky at first, but once you overcome the initial confusion, nothing can stop you. What do you think of my method? Are you using Redux and different views to handle settings alone? I'd love to hear from you and discuss it in the comments.
If you want to learn more about Redux, check out our course "Rewriting and Testing Redux to Solve Design Issues" mini course. In this course, you will build a Redux application that receives tweets organized by topic through a websocket connection. To give you an idea of what’s going to happen, check out the free course below.
Loading the player…FAQ on React-free Redux (FAQ) What is the main difference between using Redux and React and not using React?
Redux is a predictable state container for JavaScript applications that can be used with any UI layer. The main difference between using Redux vs. React and not using React is how the UI layer interacts with Redux storage. When used with React, Redux can leverage React's component-based architecture and its lifecycle approach to automatically handle updates of components when state changes are made. Without React, you need to manually subscribe to the storage and handle updates to the UI when the status changes.
How to handle asynchronous operations in Redux without React?
Asynchronous operations in Redux are usually handled using middleware such as Redux Thunk or Redux Saga. These middleware allows you to schedule functions (thunks) or more complex asynchronous operations (sagas), rather than ordinary objects. Even without React, you can still use these middleware in Redux storage. You just need to apply middleware when creating storage using Redux's applyMiddleware function.
Can I use Redux DevTools without React?
Yes, Redux DevTools does not depend on React and can be used with any UI layer that uses Redux. You can integrate Redux DevTools into your application by adding it as a middleware when creating Redux storage. This will allow you to check the status and actions of your application in real time, even without React.
How to connect to Redux storage in my UI component without React?
Without React and its connect function, you need to manually subscribe to the Redux storage and update the UI components when the state changes. You can subscribe to the store using the store.subscribe method, which takes a listener function that will be called every time the operation is scheduled. In this listener function, you can use store.getState to get the current state of the storage and update the UI components accordingly.
Can I use Redux with other libraries or frameworks like Vue or Angular?
Yes, Redux does not rely on React and can be used with any UI layer. For other libraries and frameworks such as Vue and Angular, bindings are provided that provide similar functionality to React's connect function. These bindings allow you to easily connect UI components to Redux storage and handle updates of components when state changes.
How to test my Redux code without React?
Testing Redux code without React is similar to testing it with React. You can create unit tests for your action creators and reducers using any JavaScript testing framework like Jest or Mocha. For testing asynchronous operations, you can use mock storage to simulate Redux storage.
How to deal with side effects in Redux without React?
Side effects in Redux are usually handled using middleware such as Redux Thunk or Redux Saga. These middleware allows you to schedule functions with side effects or more complex asynchronous operations, such as making API calls. Even without React, you can still use these middleware in Redux storage.
Can I use Redux with pure JavaScript?
Yes, Redux can be used with pure JavaScript. You can create a Redux store, schedule actions to it, and subscribe to changes in the state using only pure JavaScript. However, if there is no library or framework like React to handle updates to the UI, you need to manually update the UI components when the state changes.
How to build Redux code without React?
The structure of the Redux code does not depend on whether you use React. You can still follow the same best practices for building Redux code, such as separating operations, reducers, and selectors into different files or folders and organizing your state in a normalized and modular way.
Can I use Redux middleware without React?
Yes, Redux middleware does not rely on React and can be used with any UI layer that uses Redux. Middleware in Redux is used to handle side effects and asynchronous operations, and so on. You can use Redux's apply Middleware to your Redux storage using Redux's applyMiddleware function, whether you use React or not.
The above is the detailed content of Redux without React. For more information, please follow other related articles on the PHP Chinese website!

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript's applications in the real world include server-side programming, mobile application development and Internet of Things control: 1. Server-side programming is realized through Node.js, suitable for high concurrent request processing. 2. Mobile application development is carried out through ReactNative and supports cross-platform deployment. 3. Used for IoT device control through Johnny-Five library, suitable for hardware interaction.

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

Python is more suitable for data science and machine learning, while JavaScript is more suitable for front-end and full-stack development. 1. Python is known for its concise syntax and rich library ecosystem, and is suitable for data analysis and web development. 2. JavaScript is the core of front-end development. Node.js supports server-side programming and is suitable for full-stack development.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

MinGW - Minimalist GNU for Windows
This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

EditPlus Chinese cracked version
Small size, syntax highlighting, does not support code prompt function

SublimeText3 Linux new version
SublimeText3 Linux latest version

SublimeText3 Chinese version
Chinese version, very easy to use