Article outline:

  • React hooks in business
  • React hooks are used differently from react classes
  • Write your own components using TSX
  • Compile components into ES5 using WebPack + Node
  • Retrofit the project to Monorepo using LerNA
  • Publish components to NPM
  • Build gh-Pages automatically using Github Actions
Rokit component library official website

React hooks in business

React has once again revolutionized our understanding of UI-oriented programming from the standpoint of writing in the wake of hooks.

I chose to migrate the components and pages used in the background project from the Class component to the hooks component, while keeping the business stable.

There are plenty of articles on the web that can get you started on hooks, but here is a way to expand and share some solutions that help you do business development efficiently and write elegant code.

  • Immutablejs This is essential for writing high performance React code with hooks, which are used by the react+immutable mutable language.
  • Umi is a “framework” for scaffolding like NextJS, but that doesn’t stop Ali’s React Developer from building on it. I prefer umi/hooks to Ant Design as an open source project because it starts from hooks’ perspective, With antD, it can quickly build applications and save a lot of time to write relevant logic. The official website of this open source project uses PWA instead of SSR, so it cannot be searched on Baidu at present. Click the link to access or Google.

React hooks are used differently from react classes

When I was writing Angular and React, I often wondered why I was using class for development rather than the simpler function.

Hooks provide the answer, but Angular believes that class will survive.

In fact, such for Java and c # language, the class is the core of the logic reuse, back-end code, in a sense not front end is so complex that the front-end code inside a mixture of view logic and data, and data is often asynchronous, which makes the data related to logic is more difficult to reuse, so the front-end code reuse has been a difficult problem.

What good is class when you can’t reuse front-end pages with class? The answer may be to make life cycles easier to implement. (Although React provides mixin functionality, react does not recommend logical reuse using mixins. See here for details.)

Components/page after a life cycle, when can precisely control the load, when the destruction, it is very important, to the spa applications, although now the performance of the hardware devices are strong enough, but the developers always consider is, on the premise of guarantee function, improve the efficiency of the development at the same time, maximum limit save hardware resources.

However, for most of the components, actually not need life cycle, their role just for show, the most common scenario is likely to be waiting for the data request after the filling of data to a specified location, function, like this can simply use the handlebars, but handlebars this underwear can’t accomplish two-way data binding, Then there was EmberJS, but the problem remained — componentization. As browsers moved on and got rid of IE, React came along, Google reinvented Angular from AngularJS, and two years later, You learned from both. And into the thinking of Web development, open source VUE.

Technology is always in the service of business. “The second half of the Internet”, in fact, means that it is no longer a stage of learning more and using more. Nowadays, knowledge without refinement is useless, because what developers need to do is not only to develop a product within the construction period, but also to carry out multi-dimensional construction and optimization. Interaction level, design level, code level, operation level, user psychology level, market level, technical level…

What is needed now is comprehensive strength, but this can not be opportunistic, requires the developer in various areas of talent and a long period of learning accumulation.

In a sense, frameworks are developers, and developers are frameworks. So “the second half of the Internet”, the framework is the same comprehensive strength.

So developers and framework developers started thinking, how to prevent bad money after bad COINS, in the face of a copycat, follower, the optimal solution is the dimension reduction blow – from the creative point of view, constantly create new solutions to solve the present problems, is not only made the wheels, but also to make the past carriage on the engine, Make it a car.

Anyway, react’s “evolution” actually represents a trend — those who change live and those who stay die.

At the code level, class components are redundant, and the impact of this redundancy can be mind-boggling when hundreds or thousands of pages are added. In terms of logical reuse, class components are difficult to logically reuse and can only be componentized.

React provides components like pureComponent, but it doesn’t solve the fundamental problem. Stateless components are still loaded with useless properties and methods. For code cleanliness freaks, this is unbearable.

React code can be split as much as possible with hooks. In the past, a complex page or component would have needed 700 lines of code and atomic code separation would have been impossible due to class. Hooks abstract logic like useState from components to achieve maximum code granularity. Not only does it make code more readable and easier to track code logic, but after ditching the lifecycle, developers only need to focus on data logic. Hooks “zip” view logic into data logic in a very functional way, allowing developers to deal with less complex code. In a way, Lower the react threshold.

Write your own components using TSX

npx create-react-app my-app --typescriptCopy the code
In programming, there is a concept called “defensive programming” :

Defensive programming is a careful, cautious approach to programming. To develop reliable software, we design each component of the system so that it “protects” itself as much as possible. We crushed the unrecorded assumptions by explicitly checking them in the code. This is an effort to prevent (or at least observe) our code from being called in a way that would exhibit erroneous behavior.
Typescript gives us a passive form of defensive programming, which I call “convergent mode.” Not only does it syntactically specify that coders need to code according to its specifications, but it also provides more advanced syntactic features for developers to use. This pattern of building a language on top of a language through a compiler has been adopted by Kotlin and typescript in recent years.

Unlike creating a new programming language, a compiler-dependent high-level language can tap directly into the ecology and community of the target language and grow by absorbing the community’s content, which is far more effective than creating a new language.

Use typescript in React as an example:

Image.tsx

import React, { useState, useEffect, useRef } from 'react'
import styles from './Image.less'interface IProps { width? : string, height? : string, borderRadius? : string,src: string, alt? : string, mode? :"scaleToFill" | "aspectFit" | "aspectFill" | "aspectFillWidth" | "aspectFillHeight" | "widthFix" | "top" | "bottom" | "center" | "left" | "right" | "topLeft" | "topRight" | "bottomLeft" | "bottomRight", lazy? : boolean, }const Index: React.FC<IProps> = (props) = > {
      const {
            width = '300px',
            height = '225px',
            borderRadius = '0px',
            src = ' ',
            alt = 'image',
            mode = 'aspectFill',
            lazy = false
      } = props

      const [state_img_style, setStateImgStyle] = useState({})
      const [state_img_src, setStateImgSrc] = useState(' ')

      const container_ref = useRef<HTMLDivElement>(null)
      const img_ref = useRef<HTMLImageElement>(null)

      useEffect((a)= > {
            let _width: number
            let _height: number

            const { current } = img_ref

            if (width && height && container_ref.current) {
                  if (width === '100%') {
                        _width = container_ref.current.offsetWidth
                  } else {
                        _width = Number(width.split(' ').filter(item= > Number.isSafeInteger(Number(item))).join(' '))}if (height === '100%') {
                        throw Error('height can`t be 100%! ')}else {
                        _height = Number(height.split(' ').filter(item= > Number.isSafeInteger(Number(item))).join(' '))}if (current) {
                        if (lazy) {
                              let io = new IntersectionObserver((e) = > {
                                    const _e = e[e.length - 1]

                                    if (_e.isIntersecting) {
                                          setStateImgSrc(src)

                                          io.unobserve(current)
                                    }
                              })

                              io.observe(current)
                        } else {
                              setStateImgSrc(src)
                        }

                        if (mode === 'aspectFill') {
                              current.onload = (a)= > {
                                    const { naturalWidth, naturalHeight } = current
                                    const ratio_natural = naturalWidth / naturalHeight
                                    const ratio_preset = _width / _height

                                    if (ratio_natural > ratio_preset) {
                                          setStateImgStyle({
                                                maxHeight: '100%'})}else {
                                          setStateImgStyle({
                                                maxWidth: '100%'
                                          })
                                    }
                              }
                        }
                  }
            }
      }, [height, lazy, mode, src, width])

      const style: object = {
            width: width,
            height: mode === 'widthFix' ? 'auto' : height,
            borderRadius: borderRadius,
            overflow: 'hidden'
      }

      return (
            <div
                  ref={container_ref}
                  className={` ${styles._local} `}style={{
                        width: '100'}} % >
                  <div
                        className={` ${mode} `}style={style}
                  >
                        <img
                              ref={img_ref}
                              className='img'
                              src={state_img_src}
                              alt={alt}
                              style={state_img_style}
                        />
                  </div>
            </div>
      )
}

export default React.memo(Index) Copy the code
One of the things I like most about typescript is that it determines whether the current value is likely to be null based on the type, and then forces you to write the judgment so that you don’t get code errors with undefined values.

We also need to write a type definition file to the React component:

index.d.ts

import React from "react"; interface IProps { width? : string, height? : string, borderRadius? : string, src: string, alt? : string, mode? :"scaleToFill" | "aspectFit" | "aspectFill" | "aspectFillWidth" | "aspectFillHeight" | "widthFix" | "top" | "bottom" | "center" | "left" | "right" | "topLeft" | "topRight" | "bottomLeft" | "bottomRight", lazy? : boolean }declare const Image: React.FC<IProps>;

export default Image;Copy the code
And specify the path via the types field of package.json:

_package.json

{
      "name": "rokit-image"."version": "1.2.0"."description": "image component of rokit,inspire by miniapp image component."."main": "index.js"."types": "index.d.ts"."scripts": {
            "test": "echo \"Error: no test specified\" && exit 1"
      },
      "repository": {
            "type": "git"."url": "git+https://github.com/MatrixAge/rokit.git"
      },
      "keywords": [
            "react"."component"."js"."rokit"."miniapp"."wxapp"]."author": "matrixage"."license": "MIT"."bugs": {
            "url": "https://github.com/MatrixAge/rokit/issues"
      },
      "homepage": "https://github.com/MatrixAge/rokit#readme"
}Copy the code
The only thing that bothers me a little is generics, which are rarely used, and should be used with caution.

Compile components into ES5 using WebPack + Node

Once the components are written, they need to be compiled into ES5 so they can be released to other React developers. I use Monorepo to manage components, so components need to be separated from each other once they are packaged. Here you need to manipulate the file with Node and perform the Webpack configuration yourself:

lib_build.js

const path = require('path')
const glob = require('glob')
const fs = require('fs-extra')
const webpack = require('webpack')
const config = require('.. /config/lib_webpack.config')
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages')
const { appComponents, appComponentsSource } = require('.. /config/paths')

const build = () => {
	fs.emptyDirSync(appComponents)

	console.log('building components... ')

	const compiler = webpack(config)

	return new Promise((resolve, reject) => {
		compiler.run((err, stats) => {
			let messages

			if (err) {
				if(! err.message) {return reject(err)
				}

				messages = formatWebpackMessages({
					errors: [ err.message ],
					warnings: []
				})
			}
			else {
				messages = formatWebpackMessages(stats.toJson({ all: false, warnings: true, errors: true}}))if (messages.errors.length) {
				if (messages.errors.length > 1) {
					messages.errors.length = 1
				}
				return reject(new Error(messages.errors.join('\n\n')))}return resolve({
				stats,
				warnings: messages.warnings
			})
		})
	})
}

const copy = () => {
	const copyFiles = (name, target_name) => {
		const files = glob.sync(path.join(appComponentsSource, `/**/${name}`))

		files.map((item) => {
			const array_package_path = item.split('/')
			const package_name = array_package_path[array_package_path.length - 2]

			fs.copyFileSync(item, `${appComponents}/${package_name}/${target_name}`)
		})
	}

	copyFiles('index.d.ts'.'index.d.ts')
	copyFiles('_package.json'.'package.json')
	copyFiles('readme.md'.'readme.md')
}

build()
	.then((res) => {
		if (res) {
			copy()

			console.log('components building success! ')
		}
	})
	.catch((err) => {
		console.log(err)
	})

Copy the code
Here used a glob library, used to batch operation files, very easy to use, highly recommended. Then there is the WebPack configuration:

lib_webpack.config.js

const path = require('path')
const glob = require('glob')
const isWsl = require('is-wsl')
const TerserPlugin = require('terser-webpack-plugin')
const safePostCssParser = require('postcss-safe-parser')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const { appComponents } = require('./paths')

process.env.BABEL_ENV = 'production'
process.env.NODE_ENV = 'production'

const getComponents = (path) => {
	const files = glob.sync(path)

	let entry = {}

	files.map((item) => {
		const array_package_path = item.split('/')
		const package_name = array_package_path[array_package_path.length - 2]

		entry[package_name] = item
	})

	return entry
}

module.exports = [
	{
		mode: 'production',
		entry: getComponents(path.resolve(__dirname, '.. /src/components/**/index.tsx')),
		target: 'web',
		module: {
			rules: [
				{
					test: /\.(ts|tsx)$/,
					use: [
						{
							loader: 'babel-loader'
						}
					],
					exclude: /node_modules/
				},
				{
					test: /\.(css|less)$/,
					use: [
						{ loader: 'style-loader' },
						{
							loader: 'css-loader',
							options: {
								modules: true
							}
						},
						{
							loader: 'less-loader',
							options: {
								modules: true
							}
						}
					]
				}
			]
		},
		resolve: {
			extensions: [ '.tsx'.'.ts'.'.js' ]
		},
		externals: {
			react: 'react'
		},
		output: {
			filename: '[name]/index.js',
			path: appComponents,
			libraryTarget: 'commonjs2'
		},
		optimization: {
			minimize: true,
			minimizer: [
				new TerserPlugin({
					terserOptions: {
						parse: {
							ecma: 8
						},
						compress: {
							ecma: 5,
							warnings: false,
							comparisons: false,
							inline: 2
						},
						mangle: {
							safari10: true
						},
						keep_classnames: false,
						keep_fnames: false,
						output: {
							ecma: 5,
							comments: false,
							ascii_only: true} }, parallel: ! isWsl, cache:true.sourceMap: false
				}),
				new OptimizeCSSAssetsPlugin({
					cssProcessorOptions: {
						parser: safePostCssParser,
						map: false}})]}}Copy the code
Here are two things that could go wrong:

One is output.target configuration: Currently there are commonJS, AMD, CMD and ES6 modules, and webPack has a different way of exporting these modular standards. See here for details.

The second is externals configuration: if react is already included in the component and is not excluded from the package, an error may be reported because react is introduced several times.

Retrofit the project to Monorepo using LerNA

The goal of the RoKit component library is to provide a single, usable component that can be installed as it is. You don’t need to use a family bucket like some UI frameworks do. Although WebPack has tree-shaking, family buckets are still too much of a mental burden, so I use Lerna to manage component publishing. Modified the WebPack configuration to import the compiled components into the Packages folder.

For both new and existing projects, execute lerna init and change the directory structure to the structure specified for LerNA to enjoy lerna publish. Here is the lerna.json configuration:

{
      "packages": [
            "packages/*"]."version": "independent"."npmClient": "yarn"."useWorkspaces": true."command": {
            "publish": {
                  "ignoreChanges": [
                        "ignored-file"."*.md"]},"bootstrap": {
                  "npmClientArgs": [
                        "--no-package-lock"]}}}Copy the code
Here is the rokit file directory structure:

Publish components to NPM

Baidu search NPM, go to NPM official website using email to register an account, the account name should be international, the best word itself with semantics, such as matrixage, so as to strengthen cognition, do not take some self-righteousass ass English name, I’ve verts, like vertical and verts before, and dropped them later on because I can’t remember why I called them that.

Once registered, go to the project directory, NPM login, enter the account password and use lerna publish to select the version number to publish to NPM, or go to the directory of the component you want to publish and run NPM publish –access=publish.

Oh, and write a readme that describes the goals of the project and how to use it.

Build gh-Pages automatically using Github Actions

This year github opened github Actions, which allows you to build and deploy directly from Github. You can write yML files (similar to Dockerfiles) to automate build and deploy, and you can reference other people’s actions directly and extend them. It’s also an extension of automation.

Create a. Github folder in the project directory, create a workflows folder in the. Github folder, and create a ci.yml file in the workflows folder as follows:

name: Deploy gh-pages
on:
  push:
    branches:
      - master
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@master

    - name: Build
      run: npm install && npm run build:site

    - name: Deploy
      uses: peaceiris/actions-gh-pages@v2
      env:
        ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
        PUBLISH_BRANCH: gh-pages
        PUBLISH_DIR: ./buildCopy the code
There is an ACTIONS_DEPLOY_KEY, see here how to use it. In addition, ruan yifong’s Github Actions entry script has some problems. After generating the file, the gh-Pages automatic construction process cannot be triggered. Therefore, after a bit of Google, we found a solution, which is the above code.

At this point, the development of a component to the construction of the release and site deployment of the process is finished, what do not understand the follow-up please see here.