preface

Last year, I wrote a Web music App and published a series of articles introducing the development process. At that time, creation-React-App was used as an official scaffolding project. React-scripts was version 1.x, while React version 16.2.0. Create-react-app was released in Version 2.0 last October, and React was updated to 16.7.0 in December

The technology iteration in the front-end field is so fast that it is often joked not to update, I can’t move, I can’t finish learning

Do the front end is ready to learn at any time, otherwise it will be eliminated the ﹏⊙∥∣°

All developers need to keep learning, whether it’s the front end or the back end, but the front end takes longer to learn new technologies than the back end. As a Java native, I know exactly what is on the way

Updated to introduce

create-react-app

Create-react-app has been updated to version 2.x, mainly with updates to many of the tools it relies on. These tools have been released with new features and performance improvements, such as babel7, WebPack4, babel7, and WebPack4. Which ones are optimized and you can look them up. The create-react-app updates are listed below

  • Added Sass preprocessor, CSS modular support
  • Updated to Babel7
  • Updated to webpack4
  • The new preset – env

More updates can be found here

react16.3

Since the previous version was Act16.2, we have to start with 16.3 to get to Act16.7

New lifecycle functions, context API, createRef API, and forwardRef API The two new life cycle functions getDerivedStateFromProps and getSnapshotBeforeUpdate replace componentWillMount. ComponentWillReceiveProps and componentWillUpdate, the purpose is to support the error boundaries and the upcoming async rendering mode (asynchronous rendering). When async Rendering mode is used, initial rendering is interrupted, and the interrupted behavior of error processing may cause memory leaks. And use componentWillMount componentWillReceiveProps and componentWillUpdate would increase the risk of such problems

In previous versions, there were two ways to get a DOM or component, either by giving a ref a name and using refs.name or reactdom.finddomNode (name), or by using the ref callback and giving ref a callback function. In the beginning, I used the first method, but later I changed to ref callback. Now the official recommendation is not to use the ref callback method, because the first method has several disadvantages, and it is a bit troublesome to use ref callback, so the official provides a new operation is createRef API

The forwardRef API allows you to use a function component and pass a ref to a child component to get the DOM from the subcomponent

Read more here

react16.6

I really liked the update, and Code Splitting was officially supported as well as Vue

Use Code Splitting in React. Take the trouble of writing a lazy load component yourself, and use a third-party library instead. React.lazy and Suspense are now officially added to support Code Splitting

import React, {lazy, Suspense} from 'react'; const LazyComponent = lazy(() => import('./LazyComponent')); function MyComponent() { return ( <Suspense fallback={<div>Loading... </div>}> <LazyComponent /> </Suspense> ); }Copy the code

React.lazy and Suspense does not currently support server rendering. Server rendering is officially recommended to use Loadable Components

ShouldComponentUpdate (); shouldComponentUpdate (); shouldComponentUpdate (); shouldComponentUpdate (); ShouldComponentUpdate (); shouldComponentUpdate (); shouldComponentUpdate (); There is no such functionality for function components. In this release, the react. memo has been added to give function components the same functionality as the react. PureComponent

16.3 added the Context API. When using context, you need to use Consumer like the following

const ThemeContext = React.createContext('light'); . Class MyComponent extends React.Component {render() {return (< themecontext.consumer > {theme => /* Use context */} </ThemeContext.Consumer> ); }}Copy the code

You can now use the more convenient Static contextType

const ThemeContext = React.createContext('light'); . class MyComponent extends React.Component { render() { let value = this.context; /* Use context */}} myComponent. contextType = ThemeContext;Copy the code

Read more here

upgrade

The upgrade is based on this source code

Before you start, adjust the component directory. Use conventional directory names to store the corresponding components. Create a new views directory, move components from components to Views, and then move components from common to Components

Modify the configuration

Now to upgrade, upgrade React-Scripts to 2.1.3 and React to 16.7.0

NPM install --save --save-exact [email protected]Copy the code
NPM install [email protected] [email protected]Copy the code

Wait a moment

Run NPM run start

The react-scripts2.x version was used to customize the script. The react-scripts2.x configuration has changed a lot, so the original script from the definition can not be used. In addition, finding ways to modify the configuration takes too much time. If you’re familiar with WebPack configuration and run eject to extract the configuration file, or look for a third party customize-cra, you’ll have to learn more about configuration methods. If the author doesn’t maintain it, react-Scripts will get major updates. It can’t adapt to the new version in time. Here I choose brute force and extract the configuration file

let’s do it

Run NPM run eject

The scripts directory already exists in the project (the script that was written before the custom configuration), delete it, run it again, wait a little while, and after executing it add a lot of dependencies to package.json, and some postCSS, Babel, and ESLint configurations

wait

The scripts in package.json have not been updated, refer to scripts after other NPM run eject and modify it as follows

"scripts": {
  "start": "npm run dev",
  "dev": "node scripts/start.js",
  "build": "node scripts/build.js"
}
Copy the code

After eject, develop dependencies into Dependencies, then put development dependencies into devDependencies and remove jest dependencies

Run NPM run dev

If you want to add a browserslist configuration, type Y and press Enter

Module not found: Can't resolve '@/api/config'
Copy the code

Aliases @ and stylus are not configured at this point

Open webpack.config.js in the config directory, find the alias under the Config resolve node, and add the alias

config/webpack.config.js

module.exports = function(webpackEnv) { ... return { ... resolve: { ... alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', '@': path.join(__dirname, '.. ', "src") }, } } ... }Copy the code

Regarding aliases, using aliases reduces webpack packaging time, but is ide – or tool-unfriendly, makes jumping impossible, and makes viewing code very inconvenient. If you can tolerate it, configure it. If you can’t tolerate import, write the relative path. Alias is used here to demonstrate, and the final source code does not use alias

Then stylus, officially only supports SASS, possibly sASS is used by many people, you at least support a few more ≡(▔﹏▔

For styled compoents, styled JSX, and CSS modules, there are many solutions to this problem. The first two are simply unorthodox. CSS Modules do not subvert the original CSS, but also support CSS processors, independent of the framework, and can be used not only in React but also in VUE. To enable CSS modules in webpack, simply give the MODULES option to CSS-loader. In some projects CSS files use CSS modules and others don’t. Resct-scripts does this

config/webpack.config.js

. // style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; // This is the production and development configuration. // It is focused on developer experience, fast rebuilds, and a minimal bundle. module.exports = function(webpackEnv) { ... return { ... module: { strictExportPresence: true, rules: [ ..., { test: cssRegex, exclude: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction && shouldUseSourceMap, }), sideEffects: true, }, // Adds support for CSS Modules (https://github.com/css-modules/css-modules) // using the extension .module.css { test: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent, }), }, // Opt-in support for SASS (using .scss or .sass extensions). // By default we support SASS Modules with the // extensions .module.scss or .module.sass { test: sassRegex, exclude: sassModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, }, 'sass-loader' ), sideEffects: true, }, // Adds support for CSS Modules, but using SASS // using the extension .module.scss or .module.sass { test: sassModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent, }, 'sass-loader' ), }, ... ] }}}Copy the code

The above configuration, getStyleLoaders is a function of a return to style loader configuration, according to the different configuration of the incoming parameter, in the rules, in order to. CSS or. (SCSS | sass) end use conventional loader, To moduels. CSS or module. (SCSS | sass) end will enable CSS moduels. When you want to use CSS modules, add a. Module to the file name before the suffix. In React, the style file naming protocol is the same as the component file name and the component and style are placed in the same directory. So the style file will be named design-list.module. CSS, and when put together, it will look like this

Why is there such a long tail

How to get rid of this long tail without affecting the use of CSS modules, we use rule-oneof and rule-resourcequery in the Webpack configuration

Add stylus configuration in webpack.config.js

config/webpack.config.js

. // style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; const stylusRegex = /\.(styl|stylus)$/; // This is the production and development configuration. // It is focused on developer experience, fast rebuilds, and a minimal bundle. module.exports = function(webpackEnv) { ... return { ... module: { strictExportPresence: true, rules: [ ..., // Adds support for CSS Modules, but using SASS // using the extension .module.scss or .module.sass { test: sassModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent, }, 'sass-loader' ), }, { test: stylusRegex, oneOf: [ { // Match *.styl?module resourceQuery: /module/, use: getStyleLoaders( { camelCase: true, importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent, }, 'stylus-loader' ) }, { use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, }, 'stylus-loader' ) } ] }, ... ] }}}Copy the code

OneOf is used to take oneOf the first matched rules, resourceQuery is used to match import style from ‘xxx.styl? ‘module’, so you need to use CSS and then you add’ module’? CamelCase: true is a configuration option in CSS-Loader that enables camel naming. Using CSS moduels requires passing objects. Attribute to get the compiled style name. If the style name is delimited by a dash, you need to use a property selector such as style[‘ CSs-name ‘]

At this point, the page style is normal, but CSS modules are not used yet. Then you need to change all of your CSS to CSS Modules, which is a tedious process. Take the Recommend component for example

To import the style

import style from "./recommend.styl? module"Copy the code

Get the style from the Style object

class Recommend extends React.Component { ... render() { return ( <div className="music-recommend"> <Scroll refresh={this.state.refreshScroll} onScroll={(e) => { /* Check if lazy-loaded components appear in the view, and if so, load the component */ forceCheck(); }}> <div> <div className="slider-container"> <div className="swiper-wrapper"> { this.state.sliderList.map(slider => { return ( <div className="swiper-slide" key={slider.id}> <div className="slider-nav" OnClick ={this.tolink (slider.linkurl)}> <img SRC ={slider.picurl} width="100%" height="100%" Alt =" "/> </div> </div>); }) } </div> <div className="swiper-pagination"></div> </div> <div className={style.albumContainer} style={this.state.loading === true ? { display: "none" } : {}}> <h1 className={' ${style.title} skin-recommend-title '}> </h1> <div className={style.albumList}> {Albums} </div> </div> </div> </Scroll> ... </div> ); }}Copy the code

Some of these are plugin fixed sample names, some are used for skin switching fixed sample names, these can not use CSS modules, in this case use :global(), to represent the global style, CSS-loader does not handle the style name, such as

:global(.music-recommend)
  width: 100%
  height: 100%
  :global(.slider-container)
    height: 160px
    position: relative
    :global(.slider-nav)
      display: block
      width: 100%
      height: 100%
    :global(.swiper-pagination-bullet-active)
      background-color: #DDDDDD
Copy the code

Because of joining ESLint, the following warning appears

./src/components/recommend/Recommend.js
  Line 131:  The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md  jsx-a11y/anchor-is-valid
./src/components/singer/SingerList.js
  Line 153:  The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md  jsx-a11y/anchor-is-valid
  Line 159:  The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md  jsx-a11y/anchor-is-valid
Copy the code

This rule states that the a tag must specify a valid href. Replace the a tag with something else

ref

As mentioned earlier, act16.3 added the createRef API, so replace the ref callback with this new API. Take the Album component for example

Initialize with react.createref () in constructor

src/views/album/Album.js

class Album extends React.Component { constructor(props) { super(props); // React 16.3 or higher this. AlbumBgRef = React. CreateRef (); this.albumContainerRef = React.createRef(); this.albumFixedBgRef = React.createRef(); this.playButtonWrapperRef = React.createRef(); this.musicalNoteRef = React.createRef(); }... }Copy the code

Use ref to specify the initialized value

render() { ... return ( <CSSTransition in={this.state.show} timeout={300} classNames="translate"> <div className="music-album"> <Header  title={album.name}></Header> <div style={{ position: "relative" }}> <div ref={this.albumBgRef} className={style.albumImg} style={imgStyle}> <div className={style.filter}></div> </div> <div ref={this.albumFixedBgRef} className={style.albumImg + " " + style.fixed} style={imgStyle}> <div className={style.filter}></div> </div> <div className={style.playWrapper} ref={this.playButtonWrapperRef}> <div className={style.playButton} onClick={this.playAll}> <i className="icon-play"></i> <span> Play all </span> </div> </div> <div ref={this.albumContainerRef} className={style.albumContainer}> <div className={style.albumScroll} style={this.state.loading === true ? { display: "none" } : {}}> <Scroll refresh={this.state.refreshScroll} onScroll={this.scroll}> <div className={`${style.albumWrapper} skin-detail-wrapper`}> ... </div> </Scroll> </div> <Loading title= show={this.state.loading} /> </div> <MusicalNote ref={this.musicalNoteRef}/> </div> </CSSTransition> ); }Copy the code

Get the DOM or component instance via the current property,

scroll = ({ y }) => {
  let albumBgDOM = this.albumBgRef.current;
  let albumFixedBgDOM = this.albumFixedBgRef.current;
  let playButtonWrapperDOM = this.playButtonWrapperRef.current;
  if (y < 0) {
    if (Math.abs(y) + 55 > albumBgDOM.offsetHeight) {
      albumFixedBgDOM.style.display = "block";
    } else {
      albumFixedBgDOM.style.display = "none";
    }
  } else {
    let transform = `scale(${1 + y * 0.004}, ${1 + y * 0.004})`;
    albumBgDOM.style.webkitTransform = transform;
    albumBgDOM.style.transform = transform;
    playButtonWrapperDOM.style.marginTop = `${y}px`;
  }
}
Copy the code
selectSong(song) {
  return (e) => {
    this.props.setSongs([song]);
    this.props.changeCurrentSong(song);
    this.musicalNoteRef.current.startAnimation({
      x: e.nativeEvent.clientX,
      y: e.nativeEvent.clientY
    });
  };
}
Copy the code

When ref is used on an HTML tag, current is a reference to a DOM element, and when ref is used on a component, current is a mounted instance of the component. Current points to a DOM element or component instance when the component is mounted, is assigned null when the component is unmounted, and ref is updated before the component is updated

Code Splitting

Code Splitting can reduce the size of JS files, speed up file transfer, and load them on demand. Now React provides react. lazy and Suspense to support Code Splitting

Before, routes were directly written in components. Now, routes are separated and configured in configuration files for centralized management

Add the router directory to the SRC directory and create router.js

import React, { lazy, Suspense } from "react" let RecommendComponent = lazy(() => import(".. /views/recommend/Recommend")); const Recommend = (props) => { return ( <Suspense fallback={null}> <RecommendComponent {... props} /> </Suspense> ) } let AlbumComponent = lazy(() => import(".. /containers/Album")); const Album = (props) => { return ( <Suspense fallback={null}> <AlbumComponent {... props} /> </Suspense> ) } ... const router = [ { path: "/recommend", component: Recommend, routes: [ { path: "/recommend/:id", component: Album } ] }, ... ] ; export default routerCopy the code

Component layers wrapped in Suspense after using the lazy method need to be wrapped in Suspense and specify fallback. Fallback is rendered when the component’s corresponding resource is downloaded. In this case, null is not rendered. In the official example, only one Suspense is used in the outer layer of Route. See here, there are child routes. If you use Suspense in the outermost layer, the rendering fallback of the parent Route view component will replace the content of the parent component during lazy loading, resulting in the loss of the content of the parent component. There is a blinking process in between, so it is best to wrap up each routing view component in Suspense. You need to manually pass props to the lazy loading component to get match, history, and so on from the React-Router

Suspense parts have repetitive code, let’s change it with higher-order components

const withSuspense = (Component) => { return (props) => ( <Suspense fallback={null}> <Component {... props} /> </Suspense> ); } const Recommend = withSuspense(lazy(() => import(".. /views/recommend/Recommend"))); const Album = withSuspense(lazy(() => import(".. /containers/Album"))); const router = [ { path: "/recommend", component: Recommend, routes: [ { path: "/recommend/:id", component: Album } ] }, ... ] ;Copy the code

Next, use these configurations


Just call the renderRoutes method and pass in the route configuration

Note: The Route configuration must use several fixed properties, most of which are the same as the props for the Route component

Install react-router-config. The react-router version is the same as the react-router-config version

NPM install [email protected]Copy the code

src/views/App.js

import { renderRoutes } from "react-router-config" import router from ".. /router" class App extends React.Component { ... render() { return ( <Router> ... <div className={style.musicView}> {/* Switch component used to select the nearest route, <Switch> <Redirect from="/" to="/recommend" /> {/* Render Route */} { renderRoutes(router) } </Switch> </div> </Router> ); }}Copy the code

Redirect Is used for redirection. You need to put it first; otherwise, it does not take effect.

Then use the child routing configuration in the Recommend component

src/views/recommend/Recommend.js

import { renderRoutes } from "react-router-config" class Recommend extends React.Component { render() { let { route } = this.props; return ( <div className="music-recommend"> ... <Loading title=" Loading..." show={this.state.loading} /> { renderRoutes(route.routes) } </div> ); }}Copy the code

When renderRoutes is called, the routing configuration for the current level is passed to Route, and then the child routing configuration is retrieved via route.routes, as is the case for both pusher and child routes

RenderRoutes can be found here

There are other component routes that need to be modified, all in this way

preview

Dxx.github. IO/Mango -music

Qr code:

The source code

Github

If you feel good, please give a Star, thank you ~