Enter the world of React from Demo

Enter the world of React from Demo

In this Demo, we will use these:

  • React
  • React-Router
  • Jest

git repository: github.com/Alexlangl/r...

cra new project

First of all, we don t need to directly use webpack to build a project by ourselves for the time being, because it s too time-consuming, our goal is to be able to write

yarn create react-app react-demo --template typescript copy the code

You can directly copy my code snippet above to create a react+ts project called react-demo.

If you don't like to use yarn, you can also use npm as your package manager, the code is as follows:

npx create-react-app react- demo --template typescript copy the code

If your operation is consistent with mine, you should get the following directory structure

README.md package.json public favicon.ico index.html logo192.png logo512.png manifest.json robots.txt src App.css App.test.tsx App.tsx index.css index.tsx logo.svg react-app-env.d.ts reportWebVitals.ts setupTests.ts tsconfig.json yarn.lock Copy code

Startup project

Run the following command

yarn start Copy the code

If your computer happens to not have a higher version of webpack or babel-loader installed, it should be able to run normally and automatically open the following page:

If, unfortunately, it reported the following error:

I believe you are smart, you can guess how to solve this problem~

That's right, that's right, just go to/User/<yourName>/node_moudles and delete the dependency that is prompted

It should be noted that deleting only one will usually not work, prompting other dependencies to have such problems, you can also try to use this method to solve


Make some small changes

So far, the initialization of our project has been completed

In the next step, we will complete a small requirement, as follows:

Write a layout, there are two buttons in the layout, click these two buttons to switch the route

Next, we need to do the following things:

  • Simply configure the environment
  • Add route
  • Write the simplest layout

Environment configuration

In order to make our code more regular, we can use eslint+pritter to standardize our code

We need to install the following dependencies

yarn add prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react @ typescript-eslint/parser @ typescript-eslint/eslint-plugin eslint -D duplicated code

Then, we start to build the configuration file

.eslintrc.js
module .exports = { parser : '@typescript-eslint/parser' , extends : [ 'prettier' , ], parserOptions : { ecmaVersion : 2020 , the sourceType : 'Module1' , ecmaFeatures : { JSX : to true , }, }, settings : { react : { version : 'detect' , }, }, }; Copy code
.prettierrc
{ "semi" : false , "singleQuote" : false , "arrowParens" : "avoid" , "trailingComma" : "none" } Copy code

In addition, I also made a configuration for vscode and editor

.vscode/settings.json
{ "editor.formatOnSave" : true , "files.autoSave" : "onWindowChange" , "editor.codeActionsOnSave" : { "source.fixAll" : true } } Copy code
.editorconfig
root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf [*.md] trim_trailing_whitespace = false Copy code
pre-commit hook

At the same time, I also hope that when we submit the code, eslint can automatically execute and fix some problems that we may not have seen.

We need to install the following dependencies first

yarn add husky lint-staged -D duplicated code

Then we need to add such a piece of code in package.json:

"lint-staged" : { "*.+(ts|tsx|js| jsx )" : [ "eslint --fix" , "git add" ], "*.+(json|css|md)" : [ "prettier --write" , "git add" ] }, "husky" : { "hooks" : { "pre-commit" : "lint-staged" } } Copy code

At this point, the initial environment is almost configured, and you can start to write the code seriously~ Of course, as the follow-up code continues to increase, we must still do some updates, but this is for the future~

Add route

Here, we will use the version of react-router5.x

For react-web-side development, we need to install react-router-dom

At the same time, because we are using typescript, we also need to install @types/react-router

Do the following:

yarn add react-router-dom duplicated code
yarn add @ types/react-router -D duplicated code

Create three folders in the src directory

views
route
Layout

views

Under the views folder

views/pageA/index.tsx

import React from "react" export default function PageA () { return < div > this is pageA </div > } Copy code

views/pageB/index.tsx

import React from "react" export default function PageA () { return < div > this is pageB </div > } Copy code

At the same time, under Layout: Layout/Layout.tsx

import * as React from "react" import {Link} from "react-router-dom" const Layout = () => { return ( < div > < div > this is a layout Component </div > </div > ) } export default Layout Copy code

Layout/index.ts

Export { default } from "./Layout" copy the code
route

We create a router.config.ts file to store our routing table route/router.config.ts

import Layout from "../Layout" import {lazy} from "react" const RouteConfig = [ { path : "/pages" , component : Layout, children : [ { path : "/pages/page-a" , component : lazy( () => import ( "../views/pageA" )) }, { path : "/pages/page-b" , component : lazy( () => import ( "../views/pageB" )) }, { path : "/pages" , redirect : "/pages/paeg-a" } ] }, { path : "/" , redirect : "/pages/page-a" } ] Export default RouteConfig copy the code

Then, we also need to build a RouteView component to render our route

route/RouteView.tsx

import React from "react" import {Redirect, Route, Switch} from "react-router-dom" export interface IRouteViewProps { path?: string redirect?: string component?: any children?: IRouteViewProps[] } const RouteView: React.FC<IRouteViewProps> = props => { return ( < Switch > {props.children && props.children.map((item, index) => { if (item.redirect) { return ( < Redirect key = {index} from = {item.path} to = {item.redirect} > </Redirect > ) } return ( < Route key = {index} path = {item.path} render = {(props: JSX.IntrinsicAttributes ) => { return ( item.component && ( < item.component children = {item.children} { ...props } > </item.component > ) ) }} > </Route > ) })} </Switch > ) } export default RouteView Copy code
App && Layout

Then we transform the App.tsx under our src

It should be noted that I am using

BrowserRouter

src/App.tsx

import React, {Suspense} from "react" import {BrowserRouter} from "react-router-dom" import RouteConfig from "./route/router.config" import RouteView from "./route/RouteView" import "./App.css" function App () { return ( < div className = "App" > < BrowserRouter > < Suspense fallback = { < div > loading... </div > }> < RouteView children = {RouteConfig} > </RouteView > </Suspense > </BrowserRouter > </div > ) } export default App Copy code

I also made a little change to the corresponding App.css

src/App.css

.App { text-align : center; } .App a { margin-left : 12px ; } Copy code

Finally, let us transform our Layout

Layout/Layout.tsx

import * as React from "react" import {Link} from "react-router-dom" import {History} from "history" import RouteView, {IRouteViewProps} from "../route/RouteView" interface ILayoutProps extends IRouteViewProps { history : History } const Layout = ( props: ILayoutProps ) => { return ( < div > < div > this is a layout Component </div > < Link to = "/pages/page-a" > page-A </Link > < Link to = "/pages/page-b" > page-B </Link > < RouteView { ...props }/> </div > ) } Export default Layout copy the code

The current directory structure is as follows:

. README.md package.json public favicon.ico index.html manifest.json robots.txt src App.css App.test.tsx App.tsx Layout Layout.tsx index.ts index.css index.tsx logo.svg react-app-env.d.ts reportWebVitals.ts route RouteView.tsx router.config.ts setupTests.ts views pageA index.tsx pageB index.tsx tsconfig.json yarn.lock Copy code

Run the code

carried out

yarn start
We will see the following effects:


2021.06.29, see you next week


2021.7.5
It s another Monday, because I was too lazy last week, so I ll write a simple button component this week.

demand

As before, let s first mention a simple requirement, this button component, we need it to do the following:

  1. We need to use less
  2. We need to use ts, the purpose is to detect the type
  3. Our button needs to be able to pass in some parameters, these parameters can change the style of the component to a certain extent
  4. The parameters we pass in can be mutually exclusive, and we hope that we can use ts to do this

Configure some environment-related things

Install craco & less

The main purpose of installing craco is that we need to rewrite some webpack configuration, but we don't want to directly eject and expose webpack directly.

Execute the following commands:

yarn add @craco/craco craco-less -D

After the installation is complete, create a new craco.config.js in the root directory

const CracoLessPlugin = require ( 'craco-less' ); module .exports = { plugins : [ { plugin : CracoLessPlugin, options : { lessLoaderOptions : { lessOptions : { javascriptEnabled : true , }, }, }, } ] }; Copy code

Write component

Create a new directory components/Button under src

create a new file

index.tsx
index.less

Let's first determine which attributes we need to pass in:

Attribute nameTypes ofMust passeffect
childrenstringtrueButton text
type"primary" | "warning" | "error"falseButton type, default is primary
ghostbooleanfalseWhether to turn on the ghost button
textbooleanfalseWhether to open the text button, mutually exclusive with ghost
hrefstringfalseClick on the jump link
onClickFunctionfalseCallback method executed when the button is clicked, mutually exclusive with href

We have made it clear that we need to have mutually exclusive properties. Let's write a ts mutual exclusion first.

type WitOutType<T, U> = {[P in Exclude<keyof T, keyof U>]?: never } type MutuallyExclusive<T, U> = T | U extends object ? (WitOutType<T, U> & U) | (WitOutType<U, T> & T) : T | U Copy code

In this way, we define our component, which can be written like this at the beginning:

import React from "react" import "./index.less" type WitOutType<T, U> = {[P in Exclude<keyof T, keyof U>]?: never } type MutuallyExclusive<T, U> = T | U extends object ? (WitOutType<T, U> & U) | (WitOutType<U, T> & T) : T | U interface ButtonProps { children : string type ?: type ?: "primary" | "warning" | "error" } const Button: React.FC< ButtonProps & MutuallyExclusive<{ ghost?: boolean }, {text?: boolean }> & MutuallyExclusive< {href?: string }, {onClick?: React.MouseEventHandler<HTMLElement>} > > = ( {children, type = "primary" , ghost, text, href, onClick} ) => { return < div > {children} </div > } export default Button Copy code

Use the calling component in pageA

import React from "react" import Button from "..//Button" export default function PageA () { return ( < div style = {{ marginTop: " 12px " }}> < Button > this is a button </Button > </div > ) } Copy code

Then we run our code, we expect the pageA page to render

this is a buton

ok, no problem, so we have the first attribute children

Next, we go to get the remaining properties

First of all, type can determine the type of component. For components of the same type, it may be of type ghost or of text type.

We can use string concatenation to determine the className. Different type combinations will correspond to different classNames.

We define two variables

buttonClass
typeDefaultClass

buttonClass
OK (ghost | text) + type + button

typeDefault
Determine the basic style of each type

Ever since, there is the following piece of code

let buttonClass, typeDefultClass ghost ? ((buttonClass = "ghost_" + type + "_button" ), (typeDefultClass = "ghost_type_button" )) : text ? ((buttonClass = "text_" + type + "_button" ), (typeDefultClass = "text_type_button" )) : ((buttonClass = type + "_button" ), (typeDefultClass = "default_type_button" )) Copy code

For convenience, we can directly determine the className by using an array .join('') method

const classNames = [ "button_default" , buttonClass, typeDefultClass] .join ( "" ) copy the code

button_default
Is our button basic style

It seems that we still have an href attribute that has not been resolved. To be simple, we directly judge that when we are in href, we render an a tag and add styles to it, which seems to be able to achieve our needs.

< A className = {classNames + " href_button "} the href = {} the href target = "_blank" > {children} </a > copy code

ok, then put together the code

import React from "react" import "./index.less" type WitOutType<T, U> = {[P in Exclude<keyof T, keyof U>]?: never } type MutuallyExclusive<T, U> = T | U extends object ? (WitOutType<T, U> & U) | (WitOutType<U, T> & T) : T | U interface ButtonProps { children : string type ?: "primary" | "warning" | "error" } const Button: React.FC< ButtonProps & MutuallyExclusive<{ ghost?: boolean }, {text?: boolean }> & MutuallyExclusive< {href?: string }, {onClick?: React.MouseEventHandler<HTMLElement>} > > = ( {children, type = "primary" , ghost, text, href, onClick} ) => { let buttonClass, typeDefultClass ghost ? ((buttonClass = "ghost_" + type + "_button" ), (typeDefultClass = "ghost_type_button" )) : text ? ((buttonClass = "text_" + type + "_button" ), (typeDefultClass = "text_type_button" )) : ((buttonClass = type + "_button" ), (typeDefultClass = "default_type_button" )) const classNames = [ "button_default" , buttonClass, typeDefultClass].join( "" ) if (href) { return ( < a className = {classNames + " href_button "} href = {href} target = "_blank" > {children} </a > ) } return ( < div className = {classNames} onClick = {onClick} > {children} </div > ) } export default Button Copy code

Add style

The code is there. We seem to have only defined our style name, but we haven t written any styles. Next, we will write our styles in index.less.

Define three types

primary
warning
error

3.less functions are defined

#buttonCss
#ghostButton
#textButton

@primary_color: #288efc ; @warning_color: #e6a23c ; @error_color: #f12c5d ; .button_default { display : inline-block; padding : 6px 12px ; border-radius : 6px ; cursor : pointer; transition : background-color 0.2s linear; } .default_type_button { color : #fff ; } .ghost_type_button { color : #333 ; } .href_button { text-decoration : none; } #buttonCss ( @defaultBgc , @hoverBgc ) { background-color : @defaultBgc ; & :hover , & :focus { background-color : @hoverBgc ; } } #ghostButton ( @color ) { border : 1px solid @color ; & :hover , & :focus { border : 1px solid @color ; background-color : rgba( @color , 0.1 ); color : @color ; } } #textButton ( @color ) { color : @color ; & :hover , & :focus { color : rgba( @color , 0.8 ); } } .primary_button { #buttonCss ( @primary_color , #67b4eb ); } .warning_button { #buttonCss ( @warning_color , #f5dab1 ); } .error_button { #buttonCss ( @error_color , #eb8686 ); } .ghost_primary_button { #ghostButton ( @primary_color ); } .ghost_warning_button { #ghostButton ( @warning_color ); } .ghost_error_button { #ghostButton ( @error_color ); } .text_primary_button { #textButton ( @primary_color ); } .text_warning_button { #textButton ( @warning_color ); } .text_error_button { #textButton ( @error_color ); } Copy code

ok, so our component seems to be finished, let's test it, or in our pageA, we expect a ghost button of warning type, click to output event

import React from "react" import Button from "../../components/Button" export default function PageA () { const handleClick = ( event: React.MouseEvent<HTMLElement> | undefined ) => { console .log(event) } return ( < div style = {{ marginTop: " 12px " }}> < Button type = "error" onClick = {handleClick} > Primary </Button > </div > ) } Copy code

Run the code and click the button~

ok, no problem

Add an alias

The function of our component has been basically realized, but every time the component is imported, it needs to pass

../../
To introduce like this

Now the code is small, it doesn t matter, if the amount of code goes up in the future, it will be very troublesome

We can solve it by configuring alias

Because we use craco, we can pass

craco-alias
To configure

We first execute the following command

yarn add -D craco-alias

Then create a new file in the root directory

tsconfig.extend.json

{ "compilerOptions" : { "baseUrl" : "./src" , "paths" : { "@components/*" : [ "./components/*" ] } } } Copy code

Then modify tsconfig.json

{ "extends" : "./tsconfig.extend.json" , ........ } Copy code

Finally, we modify craco.config.js

const CracoLessPlugin = require ( 'craco-less' ); const CracoAlias = require ( "craco-alias" ); module .exports = { plugins : [ ..... { plugin : CracoAlias, options : { source : "tsconfig" , baseUrl : "./src" , tsConfigPath : "./tsconfig.extend.json" } } ], }; Copy code

Then change the introduction of Button in pageA

.... import Button from "@components/Button" .... Copy code

Run the code, if nothing happens, it should be successful~

At this point, if your operation is consistent with mine, then your current directory result should be like this:

. README.md craco.config.js package.json public favicon.ico index.html manifest.json robots.txt src App.css App.test.tsx App.tsx Layout Layout.tsx index.ts components Button index.less index.tsx index.css index.tsx logo.svg react-app-env.d.ts reportWebVitals.ts route RouteView.tsx router.config.ts setupTests.ts utils useAsyncEffect.ts views pageA index.tsx pageB index.tsx tsconfig.extend.json tsconfig.json yarn.lock Copy code

2021.07.05, see you next week