The React license issue made the React community nervous. Fortunately, the React team listened to the opinions of the community and changed the license to MIT. Regardless of the React license, React is an excellent visual library to learn from.

This project is not a large project, but it is still a large project standard to build a good code quality, high performance, high maintainability, modular application using popular front-end best practices. The project is based on React, Redux, 2048, and has used excellent open source tools in the past two years to improve code quality, including ESLint, Stylelint, Prettier, and Travis, Codecov, etc. Services such as continuous deployment to ensure code quality and improve development efficiency.

Project address, if you like github point a star to support it 😘

preview

The desktop




The mobile terminal




features

responsive

Adaptive desktop and mobile platform different resolution and size, support mobile platform browser touch operation. The GIF below simulates the display at different resolutions. This was done by changing the CSS units from PX to VW and REM, and scaling elements according to resolution. CSS media query to mobile browsers, adjust the location of some components, hide some unimportant components, make the page more compact.




Data persistence

The first problem is to serialize the state to localStorage by subscribing to redux status update through store.subscribe. The state is still the latest state even if it is refreshed, powered off, and the program crashes when opened again. The second problem is that with Chrome’s PWA technology, cached resource files can be accessed even when disconnected from the network.




Redux state

Redux is a predictable JS state management container, which can be used with the Redux DevTools extension to facilitate application state shuttle. Not only can you see the saved state of Redux, but you can also go back to the state at any time in the past. Just like the time machine, you can also see the state changes caused by each action of Redux being triggered.




Review system

With the github Issue API, you can leave a message by replying to an issue after logging in using your Github account. Comments support Markdown format, similar to github Issue experience.




PWA

Opening a page on a browser that supports PWA technology (like newer Chrome) will automatically ask you to add to the screen, just like installing a native app. Once an application is added, it can be operated offline like a native application or uninstalled. The following image shows how PWA is added to Chrome. Once it is added, the desktop will be filled with the added app, which will function as a native app even if all the web is turned off.




i18n

The application supports multiple languages and automatically ADAPTS to browser language Settings. Currently, it is detected that the browser supports Chinese. Otherwise, English is displayed by default. For more language support, edit the SRC /utils/i18n.js data object and add the corresponding language text.




React Best Practices

  • One component per file.
  • Use Stateless components as much as possible. That is, if you write a simple presentation component that does not require the component to store its state, or that does not require lifecycle methods or refs to manipulate the DOM, use Stateless components in the form of functions. Take the project Tips component as an example:

      import React from "react";
      import PropTypes from "prop-types";
      import styles from "./tips.scss";
    
      export default function Tips({ title, content }) {
        return (
          <div className={styles.tips}>
            <p className={styles.title}>{title}</p>
            <p className={styles.content}>{content}</p>
          </div>
        );
      }
    
      Tips.propTypes = {
        title: PropTypes.string.isRequired,
        content: PropTypes.string.isRequired
      };Copy the code
  • In contrast, if you need a component lifecycle method to optimize component performance (typically, override shouldComponentUpdate), need the component to store its state, or refs to manipulate the DOM, you need a stateful component. Es6 class inherits React.Component. Component examples:

      import React from "react";
      import PropTypes from "prop-types";
      import classnames from "classnames";
      import styles from "./cell.scss";
      import { isObjEqual } from ".. /.. /utils/helpers";
    
      export default class Cell extends React.Component {
        static propTypes = {
          value: PropTypes.number.isRequired
        };
    
        shouldComponentUpdate(nextProps, nextState) {
          return (
            !isObjEqual(nextProps, this.props) || ! isObjEqual(nextState,this.state)
          );
        }
    
        render() {
          const { props: { value } } = this;
    
          const color = `color-${value}`;
          return (
            <td>
              <div
                className={classnames([styles.cell,{[styles[color]]:!!!!!value})} >
                <div className={styles.number}>{value || null}</div>
              </div>
            </td>); }}Copy the code
  • The event binds the this method. Once you bind this to the constructor, you can use it as usual. Sample code for the ControlPanel component part:

    constructor(... args) {super(... args);this.handleMoveUp = this.handleMoveUp.bind(this);
      this.handleMoveDown = this.handleMoveDown.bind(this);
      this.handleMoveLeft = this.handleMoveLeft.bind(this);
      this.handleMoveRight = this.handleMoveRight.bind(this);
      this.handleKeyUp = this.handleKeyUp.bind(this);
      this.handleSpeakerClick = this.handleSpeakerClick.bind(this);
      this.handleUndo = this.handleUndo.bind(this);
    }Copy the code
  • Verify incoming prop using the propTypes attribute. You can verify the prop type and whether it is required. If a prop is not required, you must also fill in the default value of defaultProps. An example of some code for a Button with a stateless component:

      Button.propTypes = {
        children: PropTypes.oneOfType([PropTypes.node]),
        onClick: PropTypes.func,
        size: PropTypes.oneOf(["lg"."md"."sm"."xs"]),
        type: PropTypes.oneOf([
          "default"."primary"."warn"."danger"."success"."royal"
        ]).isRequired
      };
    
      Button.defaultProps = {
        children: "",
        onClick() {},
        size: "md"};Copy the code
  • Use HOC(higher-order Components) instead of mixin. Mixin is officially no longer recommended, and Redux’s Connect method is an application of HOC.
  • To improve application performance and avoid unnecessary view redrawing, use the required componentsshouldComponentUpdateMethods; In the componentRowExample:
    // If there are no cells in the row to refresh and no component's own state to refresh,
    // The component does not execute the render method,
    // Avoid rerendering every time another row is refreshed.
    shouldComponentUpdate(nextProps, nextState) {
      return (
        !isObjEqual(nextProps, this.props) || ! isObjEqual(nextState,this.state)
      );
    }Copy the code

The project structure

This project is based on Facebook’s create-React-app scaffolding, reject, modified to meet the project requirements.

Adjust the following

  • webpackaddscssSupport. The reason it doesn’t workCssInJSBecause these schemes are generally not perfect, but also to follow the principle of style and structure separation, SCSS is a more mature CSS preprocessor, community wheels are also more, it is very convenient to develop. SCSS/SASS is recommendedThe tutorial. addsass-loaderGo to the bottom of the SCSS rules.Configuration code
  • Enable the CSS Module support. In large projects, components need to be decouple as much as possible, but the global nature of CSS class names can easily lead to unexpected errors. After the CSS Module is enabled, all class names will eventually be filled with a small hash value, so that class names are somewhat unique and not likely to pollute the global code. Configuration code
  • Add stylelint support. Js code already has ESLint (but uses the more popular and stricter Airbnb rules) to check code, but style code also needs to be consistent, and validation rules generally have community best practices. Configuration code
  • Added static resource CDN support. As the project is deployed ingithub pageAccess speeds are not ideal at home, so minimizing the size of JS packages where possible is critical to page loading speed. Larger NPM packages such as ReactDOM can be detached from the package file and loaded using CDN, which can significantly reduce the size of the package file. (PS: The reason why CDN loading is relatively fast is that CDN providers have established cache servers all over the country, and it is much faster to obtain resources nearby than from Github. React and ReactDOM can be stripped out by adding the CDN to the HTML fileThe script tag, while adding in WebpackexternalsProperty that specifies the codeimportThe package is obtained directly from the global variable. The size of the packed JS file after stripping is reduced from 278KB to 164 KB.
  • Add a WebPack code compression plug-in. The default WebPack configuration directly outputs raw JS and CSS code, but with the addition of compression, the files are significantly smaller (js files go from 164KB to 49KB), which is significantly faster for mobile browsers. Configuration code
  • Add the webpack-bundle-Analyzer plug-in to optimize your code by analyzing project code by the percentage of each module package in the packaged file. For example, React and ReactDOM were stripped because analysis revealed that these two packages accounted for a large proportion.

File structure

  • SRC, most of the project source code is here, mainly the REACT component JS code and SCSS style code. Subdirectories containjestUnit test code, test code as close as possible to the source code, easy to write.
    • Assets, mainly store some global style code, icon SVG files, game sound MP3 files, pictures and so on;
    • The components, depositThe react dumb componentsEach component is contained in a directory that uses uppercase lettersindex.jsInside, at the same time, the directory contains SCSS files of the style used by the component. As far as possible, a directory contains all codes required by the component to avoid polluting other codes and improve component reuse.
    • Containers, storageThe react smart components, the directory structure andcomponentsSimilar, but because it is a SMART component, the component here can manipulate redux data without much concern for reusability.
    • Reducers, which redux contains functions that are purely functional to calculate state operations without side effects.
    • Utils, including comment component initialization, i18N multilingual files, mobile browser swipe detection and ServiceWorker registration, etc.
    • Render the React root component to the specified DOM node and register the ServiceWorker.
    • Store. Js, redux store initialization, whilestore.subscribeSubscribe to application status updates and save serialized state tolocalStorage.
  • Public, including the project’s HTML file, site Icon Favicon and PWA manifest file.
  • Config, which mainly includes various configuration files for Webpack.
  • Scripts, NPM startup scripts, start development mode, project packaging, running JEST unit tests, etc.
  • Build, the output directory of the packaged project.
  • Screenshots, README screenshots, README screenshots, README screenshots, README screenshots, README screenshots, README screenshots, README screenshots, README screenshots, README screenshots, README screenshots, README screenshots
  • . Editorconfig, a common editor configuration that unifies different editor/IDE code formats.
  • .eslintignore, a file or directory that esLint will ignore, similar to.gitignore
  • .travis. Yml, continuous integration script, which is automatically run by the test server to execute test cases and output code coverage after each code submission to Github, and then automatically deployed to Github page. All states are visible in the README badge within the project.
  • Package. json, basic project information and some configuration are stored here. Common contents include various dependency packages of the project, various startup scripts, project homepage, and so on. To reduce the number of files in the root project, the configurations for jest, Babel, ESLint, and stylelint are also written here. It should be noted that Husky was introduced into the project and lint-staged code formatting was automatically performed prettier before each code commit. Each time the code is pushed to Github, all the unit test cases will be executed.
  • Yarn. locl, lock file generated after yarn installs dependency packages for the first time. When you install dependency packages using YARN, Yarn automatically fixes the dependency packages of a project (including the parent packages of the dependency packages) to the specified version (including the URL and hash value of the dependency packages). In this way, all development environments use Yarn to manage projects. The packages installed on different machines and systems are the same. This avoids the pitfalls of previous NPM (loose version requirements, parent package version updates, etc.).

Technology stack

  • React builds the UI as components
  • Redux, managing application status
  • Babel, convert ES2017 + syntax to ES5 compatible syntax
  • Webpack, code hot loading, SCSS style file handling, component compilation packaging, etc
  • SCSS, mature CSS preprocessor (the CssInJS solution is not used because these solutions are generally not perfect, and the principle of style and structure separation should be followed)
  • Eslint, uses the popular Airbnb code specification to strictly restrict code style
  • Stylelint, SCSS code style check
  • Jest, Fb code testing framework, snapshot function is very convenient to test react component UI
  • Prettier, JS, and SCSS code formatting tools
  • PWA(Progressive Web Apps) enables Web applications on mobile platforms to be close to native applications with the help of browser service worker capabilities, such as offline use and receiving notification messages

Run & test & package

The configuration file uses es6+ syntax, so the node version must be larger than 6.10. Yarn is recommended to manage dependency packages. After the fork project, you can do the following.

  npm i -g yarn # installation of yarn
  git clone[email protected]:< your name >/ react-2048-game.gitcd React-2048-game
  yarn Install dependencies
  yarn start Start the debug mode, and automatically open the browser http://localhost:3000
  yarn test # Automatic test
  yarn build # Package codeCopy the code

Record on pit

  • After carefully comparing the CSS files compiled by Webpack, I found that all @keyframes names had hash values (that is, as common local CSS class names). The solution is to add a pseudo-class :global in front of @Keyframes and in the entire SCSS file. You can refer to fireworks’ SCSS file. This is not a perfect solution (CSS class names no longer have local features).
  • The CSS Module uses :global this is not a standard pseudo-class, so the stylelint needs to be configured to ignore this error. seepackage.json çš„ stylelint.rules.

The project addressIf you like, github points a star to support it at 😘