惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LangChain Blog
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
博客园_首页
T
Tailwind CSS Blog
P
Proofpoint News Feed
雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
IT之家
IT之家
V
Vulnerabilities – Threatpost
阮一峰的网络日志
阮一峰的网络日志
C
CERT Recently Published Vulnerability Notes
Attack and Defense Labs
Attack and Defense Labs
S
Schneier on Security
Security Archives - TechRepublic
Security Archives - TechRepublic
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
The Register - Security
The Register - Security
N
Netflix TechBlog - Medium
Hugging Face - Blog
Hugging Face - Blog
Project Zero
Project Zero
博客园 - 叶小钗
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
Latest news
Latest news
S
SegmentFault 最新的问题
C
Cyber Attacks, Cyber Crime and Cyber Security
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
L
LINUX DO - 最新话题
Google DeepMind News
Google DeepMind News
P
Privacy International News Feed
I
InfoQ
F
Fortinet All Blogs
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
T
Tenable Blog
B
Blog RSS Feed

remy sharp's l:inks

Deno Style Guide Easy 6502 by skilldrick Profiling React.js Performance BEM Naming Cheat Sheet by 9elements Visual 6502 Remix Elijah Manor (@elijahmanor) on X All – Tiny Helpers AST explorer Insecure How to Authenticate with Next.js and Auth0: A Guide for Every Deployment Model Revealed: quarter of all tweets about climate crisis produced by bots I Add 3-25 Seconds of Latency to Every Page I Visit GitHub - ericchiang/pup: Parsing HTML at the command line Browserling – Online cross-browser testing Error Handling with GraphQL and Apollo Schwerkraftprojektionsgerät How To Turn Off Catalina Update Notifications (Prompts & Badges) • macReports Maskable.app It’s 2020 and you’re in the future Emulators written in JavaScript | Frederic Cambus The Size of Space systemfontstack JavaScript isn’t always available and it’s not the user’s fault Programming Fonts - Test Drive Screen Size Map GitHub - Yonet/MixedRealityResources: Mixed Reality related resources The Lines of Code That Changed Everything Notes from the Internet Health Report 2019 CopyPalette Generative Artistry Profile a React App for Performance nanoSQL 2 Moving Your JavaScript Development To Bash On Windows — Smashing Magazine Startup Playbook How to Release a Custom React Component, Hook or Effect as an npm Package React Suite - Enterprise React UI Component Library Use VSCode Dimmer to Highlight Code when Teaching 3 simple rules for effectively handling dates and timezones Large collection of how to animate pixelart Accessible React component libraries mineral-ui.com Australian Government Design System Reakit – Toolkit for building accessible UIs Chakra UI Curl Cookbook The problem with tooltips and what to do instead GitHub - trimstray/the-book-of-secret-knowledge: A collection of inspiring lists, manuals, cheatsheets, blogs, hacks, one-liners, cli/web tools and more. It Form design: from zero to hero all in one blog post Brussels changes its mind AGAIN on .EU domains: Euro citizens in post-Brexit Britain can keep them after all Improve Your Email Campaigns with These Fantastic Tools Ethical Web Principles Old Typewriter Text Effect Bruce Lawson Google open sources standardized code in bid to become Mr Robots.txt Preferreds-color-scheme:你好,黑暗,我的老朋友  |  Articles  |  web.dev Free for Developers Startup idea checklist Developing a Robust Font Loading Strategy for CSS-Tricks—zachleat.com How I use Slack—alone—to get more done Upcoming proposals in JavaScript 🦄 VueJS is dead, long live VueJS! Bruce Lawson Answers for young people - Tim Berners-Lee Finally, an AI that can reliably catch and undo Photoshop airbrushing. Who made it? Er, Photoshop maker Adobe Qubyte Codes - How I schedule posts using GitHub Actions Games and Graphics in Popup URL bars Stack 58 bytes of css to look great nearly everywhere The reduce ({...spread}) anti-pattern - RichSnapp.com The Online Disassembler CSS Gradient – Generator, Maker, and Background Chrome DevTools  |  Chrome for Developers HB88 | Đăng Nhập HB88 Cá Cược Nhanh Chóng Không Bị Chăn #1 Introducing React Apollo 2.1 - Apollo GraphQL Blog Color Palettes Generator and Color Gradient Tool Clément Chastagnol ~ Moving efficiently in the CLI Testing Async Components · Issue #346 · enzymejs/enzyme Game Platforms recent news | Game Developer Professional Sound Effects - Royalty-Free SFX | Unlimited Downloads entr(1) Learn React From The Comfort Of Your Browser Axe Rules | Deque University Record and share your terminal sessions, the simple way Web Design Museum Powerful New Additions to the CSS Grid Inspector in Firefox Nightly – Mozilla Hacks - the Web developer blog Latency | Apex Software HackerNoon Signals and Sine Waves (Learn Web Audio from the Ground Up, Part 1) JavaScript Systems Music Vintage bits on cassettes Microsoft Design Legal Documents & Contract Templates | Simply Docs create-graphql-server — instantly scaffold a GraphQL server GraphQL and MongoDB — a quick example Using GraphQL with MongoDB Your First GraphQL Server Build a GraphQL server from scratch 架构标记测试工具 | Google 搜索中心  |  Search Central  |  Google for Developers Freesound
Practical Redux, Part 3: Project Planning and Setup
Mark Erikson · 2017-03-19 · via remy sharp's l:inks

This is a post in the Practical Redux series.


Initial steps for our sample application

Table of Contents 🔗︎

  • Planning the Sample Application
  • UI Mockups
  • Project Setup and Configuration
  • Final Thoughts
  • Further Information

Planning The Sample Application 🔗︎

As I mentioned in the series introduction, I've got a number of specific techniques and concepts that I'd like to talk about. I've had several requests to show some actual working examples of these techniques, so I've decided to try to build a small example application to demonstrate these ideas in a meaningful context.

I'm a big fan of the Battletech game universe, a game about big stompy robots with weapons that are used to fight wars a thousand years in the future. The game universe includes miniatures tabletop games, computer games, roleplaying games, fiction, and more.

There's a number of fan-built projects based in the Battletech universe. One of the most popular is Megamek, a cross-platform computer implementation of the Classic Battletech tabletop game. In addition, the Megamek authors have built a tool called MekHQ, a tool for managing an ongoing campaign for a fictional combat organization, such as a mercenary unit.

For this blog series, I'm planning to build a miniature version of the MekHQ application. I definitely do not intend to seriously rebuild all of MekHQ's functionality, but it should serve as a useful inspiration and justification for most of the techniques I want to show off.

On a related note: I'm absolutely making this up as I go along :) I've got some definite ideas for what I want to show off, but I'll be developing this sample app as I work on each post. That means I'll probably make some mistakes, need to change things, and iterate on the code. This will be a learning experience for everyone involved :)

Every project needs a good name. I haven't come up with a good name yet, so we'll call this "Project Mini-Mek" for now. (If anyone comes up with a better name, I'm listening!)

Project Features 🔗︎

MekHQ is a very complex and in-depth application. The "About" page describes it this way:

MekHQ is a Java program that allow users to manage units in-between actual games of MegaMek. It implements most of the rules in the "Maintenance, Repair, and Salvage" section of Strategic Operations, many of the rules and options from the Mercenary Field Manual, and various options that allow users to customize a mercenary, house, or clan campaign to their liking . Some of the features include:

  • Track XP and improve pilot skills and abilities
  • Integration with MegaMek and MegaMekLab
  • Planetary map and the plotting of jumpship travel
  • Organize a TO&E
  • Create and resolve missions and scenarios
  • Repair and salvage units
  • Equipment tracking
  • Financial tracking

For Project Mini-Mek, we're only going to deal with a couple of these, and at a very simple level. Here's a tentative list of features:

  • Load JSON data describing the pilots and Battlemechs in a combat force
  • For both pilots and Battlemechs:
    • Show a list of all items
    • Allow selection of an item in the list, and show details of the selected item
  • Edit the details for a pilot
  • Organize the pilots and their mechs into "lances" of four mechs, and the "lances" into a "company" of three lances.
  • Add and remove pilots and mechs from the force
  • Save the force back out to JSON

So, basically Yet Another CRUD APP, with some specific themes :) For now, I'm not planning to deal with a backend or any AJAX handling. I figure I'll start with static JSON being directly imported, and then maybe add file import/export later on.

UI Mockups 🔗︎

MekHQ: Pilot Listing 🔗︎

Let's start by looking at some screenshots of the original MekHQ UI. First, here's the screen that shows the list of pilots, and the details for the currently selected pilot:

MekHQ: Unit Table of Organization Tree 🔗︎

Next, the "Unit Table of Organization" section, showing the various sub-units in a tree structure, and again with a details section on the right:

This gives us a general idea of the UI layout we want: a tab bar across the top, with most of the tab panes containing a list of some kind on the left, and a details box for the currently selected item on the right.

Here's some rough mockups of what our UI might look like:

Project Mini-Mek: Unit Info 🔗︎

Project Mini-Mek: Pilot Listing 🔗︎

Project Mini-Mek: Mech Listing 🔗︎

Project Mini-Mek: Unit Table of Organization Tree 🔗︎

Project Setup and Configuration 🔗︎

We're going to set up the project using the excellent Create-React-App tool. I'm also going to be using the new Yarn package manager, partly because it's supposed to be faster, and partly because this gives me a chance to try it out. Also, as an FYI, I'm writing all this on Windows, but I don't expect any meaningful OS/platform issues to pop up as we go. Finally, I don't necessarily intend to show every last command I've typed or bit of code I've written as we go on - the real focus is intended to be on using Redux itself, not using Git or an editor.

The project is available on Github, at https://github.com/markerikson/project-minimek. I plan on updating it as I publish each post, and hope to show both some of the nitty-gritty "WIP" commits as well as cleaner "final result" commits. Also, when I show code snippets or refer to source files in the repo, I'll usually try to link to that file on Github at the specific version where the changes were made.

All right, let's do this!

Creating the Project 🔗︎

Follow the instructions for installing the create-react-app and yarn tools on your system. Once that's done, we can create our project:

create-react-app project-minimek

This is a good time to initialize a Git repo for the initial sample files and project config files. After the project is created, we want to set up a Yarn lockfile to nail down the specific dependency versions we're using. create-react-app already installed everything it needs (using NPM, although Yarn support is planned), but we need to run yarn to make sure we're ready for adding more dependencies:

cd project-minimek
yarn

There's a bunch more file work done as Yarn figures out what dependencies you have, and prepares its lockfile, yarn.lock. That should be committed to Git as well.

Dependencies 🔗︎

Time to pull in our initial list of dependencies. Here's what we're going to add:

  • Redux: because without it, this would be a really short blog series
  • React-Redux: ditto
  • Redux-Thunk: the most common and simple addon for side effects and complex dispatching logic
  • Reselect: needed for memoized "selector" functions when retrieving data from our state
  • Redux-ORM: a handy abstraction layer for managing relational data in our state
  • Lodash: every useful function you can think of, and a bunch more you didn't even know existed
  • cuid: We'll probably be generating IDs at some point, so this will fill that need

We can add them all in at once:

yarn add redux react-redux redux-thunk reselect redux-orm lodash cuid

And then commit our package.json and yarn.lock again.

Initial Redux Configuration 🔗︎

We need to set up the initial Redux store and reducers, make the store available to our component tree, and make sure that it's being used. Here's what my initial Redux configuration looks like:

store/configureStore.js

import {createStore, applyMiddleware, compose} from "redux";

import thunk from "redux-thunk";

import rootReducer from "../reducers/rootReducer";

export default function configureStore(preloadedState) {
    const middlewares = [thunk];
    const middlewareEnhancer = applyMiddleware(...middlewares);

    const storeEnhancers = [middlewareEnhancer];
    
    const composedEnhancer = compose(...storeEnhancers);

    const store = createStore(
        rootReducer,
        preloadedState,
        composedEnhancer
    );

    return store;
}

I like to keep my store setup logic in a configureStore function that can be further improved as time goes on. I also try to keep the setup for each piece of the configuration process separate, so it's easy to follow what's going on.

reducers/testReducer.js

const initialState = {
    data : 42
};

export default function testReducer(state = initialState, action) {
    return state;
}

reducers/rootReducer.js

import {combineReducers} from "redux";

import testReducer from "./testReducer";

const rootReducer = combineReducers({
    test : testReducer,
});

export default rootReducer;

Our initial reducers just pass along some test data so we can verify things are working.

We then create a store instance and use it in the rendering process:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from "react-redux";

import App from './App';
import './index.css';

import configureStore from "./store/configureStore";
const store = configureStore();

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);

Finally, we add a simple test component, connect it to Redux, and render it in our App component to verify that everything's hooked up properly:

SampleComponent.jsx

import React, {Component} from "react";
import {connect} from "react-redux";

const mapState = state => ({
    data : state.test.data
});

class SampleComponent extends Component {
    render() {
        const {data} = this.props;
        
        return (
            <div>
                Data from Redux: {data}
            </div>
        );
    }
}

export default connect(mapState)(SampleComponent);

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

import SampleComponent from "./SampleComponent";

class App extends Component {
    render() {
        return (
            <div className="App">
                <div className="App-header">
                    <img src={logo} className="App-logo" alt="logo"/>
                    <h2>Project Mini-Mek</h2>
                </div>
                <SampleComponent />
            </div>
        );
    }
}

export default App;

We should now see the text "Data from Redux: 42" in our page, right below the header bar.

Adding the Redux DevTools Extension 🔗︎

One of the original reasons for Redux's creation was the goal of "time-travel debugging": the ability to see the list of dispatched actions, view the contents of an action, see what parts of the state were changed after the action was dispatched, view the overall application state after that dispatch, and step back and forth between dispatched actions. Dan Abramov wrote the original Redux DevTools toolset, which showed that information with the UI as a component within the page.

Since then, Mihail Diordiev has built the Redux DevTools Extension, a browser extension that bundles together the core Redux DevTools logic and several community-built data visualizers as a browser extension, and adds a bunch of useful functionality on top of that. Connecting the DevTools Extension to your store requires a few extra checks, so there's now a package available that encapsulates the process needed to hook up the DevTools Extension to your store.

We'll add the helper package with yarn add redux-devtools-extension, then make a couple small changes to the store setup logic:

store/configureStore.js

- import {createStore, applyMiddleware, compose} from "redux";
+ import {createStore, applyMiddleware} from "redux";
+ import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';


- const composedEnhancer = compose(...storeEnhancers);
+ const composedEnhancer = composeWithDevTools(...storeEnhancers);

This should add the DevTools enhancer to our store, but only when we're in development mode. With that installed, the Redux DevTools browser extension can now view the contents of our store and the history of the dispatched actions.

Component Hot Reloading 🔗︎

Out of the box, create-react-app will watch your files on disk, and whenever you save changes, it will recompile the application and reload the entire page. That's nice, but we can actually set up some improvements on that. In particular, the Webpack build tool used by create-react-app supports a feature known as Hot Module Replacement, or HMR, which can hot-swap the newly compiled versions of files into your already-open application. That lets us see our changes faster. This works great with a React app, and even better in combination with Redux, since we can reload our component tree but still keep the same application state as before.

We'll also want to add the redbox-react package, which we can use to render an error message and a stack trace if something goes wrong. Once that's installed, we need to update index.js to rework the top-level rendering logic:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from "react-redux";

import './index.css';

import configureStore from "./store/configureStore";
const store = configureStore();

// Save a reference to the root element for reuse
const rootEl = document.getElementById("root");

// Create a reusable render method that we can call more than once
let render = () => {
    // Dynamically import our main App component, and render it
    const App = require("./App").default;
    
    ReactDOM.render(
        <Provider store={store}>
            <App />
        </Provider>,
        rootEl
    );
};

if(module.hot) {
    // Support hot reloading of components
    // and display an overlay for runtime errors
    const renderApp = render;
    const renderError = (error) => {
        const RedBox = require("redbox-react").default;
        ReactDOM.render(
            <RedBox error={error} />,
            rootEl,
        );
    };

    // In development, we wrap the rendering function to catch errors,
    // and if something breaks, log the error and render it to the screen
    render = () => {
        try {
            renderApp();
        }
        catch(error) {
            console.error(error);
            renderError(error);
        }
    };

    // Whenever the App component file or one of its dependencies
    // is changed, re-import the updated component and re-render it
    module.hot.accept("./App", () => {
        setTimeout(render);
    });
}

render();

With this in place, editing one of our components (such as changing the text in SampleComponent.jsx) should now just reload the component tree, rather than reloading the entire page. If there's an error, we should see it displayed on the screen and logged in the console. (To try it out, you can add throw new Error("Oops!") to SampleComponent.render() and see what happens.)

Reducer Hot Reloading 🔗︎

Finally, we can also configure our project to hot-reload our reducers as well. Right now, if we edit the initial state in our testReducer.js from data : 42 to data : 123, we'll see the whole page reload, just like it did for component edits before we added the hot reloading. We need to listen for updates to the root reducer file, re-import it, and then replace the old reducer in the store with the new one. The updated configureStore.js looks like this:

store/configureStore.js

import {createStore, applyMiddleware} from "redux";
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';

import thunk from "redux-thunk";

import rootReducer from "../reducers/rootReducer";

export default function configureStore(preloadedState) {
    const middlewares = [thunk];
    const middlewareEnhancer = applyMiddleware(...middlewares);

    const storeEnhancers = [middlewareEnhancer];
    
    const composedEnhancer = composeWithDevTools(...storeEnhancers);

    const store = createStore(
        rootReducer,
        preloadedState,
        composedEnhancer
    );

    if(process.env.NODE_ENV !== "production") {
        if(module.hot) {
            module.hot.accept("../reducers/rootReducer", () =>{
                const newRootReducer = require("../reducers/rootReducer").default;
                store.replaceReducer(newRootReducer)
            });
        }
    }

    return store;
}

Final Thoughts 🔗︎

At this point, we know what kind of app we want to build, and have a decent idea what it should do and what it should look like (or at least enough of an idea to get us started). We also have an initial project set up with our basic dependencies, and some nice tweaks to the development workflow.

Next time, we'll at setting up some initial UI layout, and possibly tackle some of the initial data modeling as well.

If you've got questions, comments, or suggestions, please let me know! Leave me a comment here, file an issue in the repo, or ping me on Twitter or Reactiflux.

Further Information 🔗︎


This is a post in the Practical Redux series. Other posts in this series: