Home > Web Front-end > JS Tutorial > Redux without React

Redux without React

尊渡假赌尊渡假赌尊渡假赌
Release: 2025-02-17 11:09:13
Original
642 people have browsed it

Redux without React

This article was peer-reviewed by Vildan Softic. Thanks to all the peer reviewers at SitePoint for making SitePoint’s content perfect!

Redux without React 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>
Copy after login
Copy after login
Copy after login

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>
Copy after login
Copy after login
Copy after login

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:

Redux without React

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'
Copy after login
Copy after login
Copy after login

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
}
Copy after login
Copy after login
Copy after login

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?

Redux without React

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>
Copy after login
Copy after login
Copy after login
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>
Copy after login
Copy after login
Copy after login
"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'
Copy after login
Copy after login
Copy after login

Components

I decided to use two components:

presentation layer

and 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
}
Copy after login
Copy after login
Copy after login

Now, let me show you a clear separation of these two component types.

Update DOM

A 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>
Copy after login
Copy after login
Copy after login

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>
Copy after login
Copy after login
Copy after login

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'
Copy after login
Copy after login
Copy after login

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
}
Copy after login
Copy after login
Copy after login

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)
}
Copy after login

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!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template