Note: This guide can be used alongside the React tutorial from Bilibili Shangguigu~
Video Portal
Chapter 1: Introduction to React#
Introduction:#
React is a JavaScript library for building user interfaces, open-sourced by Facebook for global developers to use.
Features of React:
- Declarative coding
- Component-based coding
- Native applications for mobile can be written using React Native
- Efficient (Diff algorithm + virtual DOM, minimizing page repaint time)
- etc.
Here is a basic React page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- Prepare a container -->
<div id="app"></div>
<!-- Core library -->
<script src="../react/react.development.js"></script>
<!-- DOM library -->
<script src="../react/react-dom.development.js"></script>
<!-- Babel -->
<script src="../react/babel.min.js"></script>
<!-- code here -->
<!-- Babel -->
<script type="text/babel">
// JSX code
// Create virtual DOM
const VDOM = (
<h1>
<span>Hello, React</span>
</h1>
); /* No quotes needed here, JSX specific syntax */
// Render virtual DOM to the page (deprecated)
ReactDOM.render(VDOM, app);
</script>
</body>
</html>
Virtual DOM and Real DOM#
- About Virtual DOM
-
- Essentially an object of type Object
-
- Virtual DOM is relatively lightweight
-
JSX Syntax Rules#
When writing HTML elements in JSX files, the following points must be followed:
- When defining virtual DOM, no quotes are needed.
- When mixing JS expressions in tags, use { }.
- In JSX, the class attribute of DOM must be replaced with className.
- Inline styles should be written in the form of style={{key}}.
- A virtual DOM can only have one root tag.
- Tags must be closed.
- If it starts with a lowercase letter, the tag will be converted to the same named element in HTML; if there is no corresponding element in HTML, an error will be reported.
- If it starts with an uppercase letter, it is treated as a React Component; if it cannot be found, an error will be reported.
const myId = "kanoKano";
const myData = "AbCd";
// 1. Create virtual DOM
const VDOM = (
<div>
<h2 className="yellow" id={myId.toLowerCase()}>
<span style={{ color: "red", fontSize: "30px" }}>
{myData.toLowerCase()}
</span>
</h2>
<h2 className="yellow" id={myId.toLowerCase()}>
<span style={{ color: "red", fontSize: "30px" }}>
{myData.toLowerCase()}
</span>
</h2>
<Hello></Hello>
</div>
);
// 2. Render DOM
ReactDOM.render(VDOM, document.querySelector("#app"));
Iterating Data#
In React, if the data to be rendered is an array, you can use automatic iteration:
Note: Automatic iteration requires a key as a unique value.
const data = [{ name: "Angular" }, { name: "React" }, { name: "Vue" }];
// for
// Automatic iteration requires a key as a unique value
const VDOM = (
<div>
<h1>Frontend JS Framework List</h1>
<ul>
{data.map((item, index) => (
<li key={index}>{item.name}</li>
))}
</ul>
</div>
);
// Render
ReactDOM.render(VDOM, document.querySelector("#app"));
Components and Modules#
Similar to Vue, React can also achieve component-based programming and module extraction.
Functional Components#
Functional components are suitable for defining simple components.
// 1. Create functional component (the first letter of the function needs to be uppercase)
function Demo() {
return <h2>I am a component defined by a function (suitable for defining simple components)</h2>;
}
// Render component to the page
ReactDOM.render(<Demo />, document.querySelector("#app"));
It is important to note that when rendering a component, you cannot directly write the function name; you need to fill it in as a component tag <Demo />
. After the above code is compiled by Babel, this
in Demo
will point to undefined
because Babel automatically enables strict mode.
After executing ReactDOM.render, the following happens:
- React parses the component tag and finds the corresponding component.
- It discovers that the component is defined using a function, then calls that function, converts the returned virtual DOM to a real DOM, and then presents it on the page.
Class Components#
Creating a class component:
// Create class component
class MyComponent extends React.Component {
render() {
console.log("this in render", this);
return <h2>I am a component defined by a class [suitable for defining complex components]</h2>;
}
}
// 2. Render component to the page
ReactDOM.render(<MyComponent />, document.getElementById("app"));
What happens after executing ReactDOM.render(<MyComponent/>)
?
- React parses the component tag and finds the
MyComponent
component. - It discovers that the component is defined using a class, then creates an instance of that class and calls the method on the prototype through that instance.
- It converts the virtual DOM returned by
render
into a real DOM and then presents it on the page.
About this
in render:
render
is placed on the prototype object ofMyComponent
for instances to use.- Who is
this
in render? It is: the instance object of theMyComponent
component.
The component instance object has three commonly used properties:
context
props
refs
state
Below, I will introduce the above properties one by one.
Three Core Properties of Component Instances#
State#
State is the ultimate property of the component object, and its value is an object (which can contain multiple key-value pairs).
Components are referred to as "state machines," and the corresponding page display is updated by updating the component's state (re-rendering the component).
However, it is important to note:
- The
this
in the render method of the component is the component instance object. - The
this
in the custom methods of the component isundefined
. Solutions:- Use the
bind()
method of the function object to specify the direction ofthis
. - Use arrow functions to ignore the current level's
this
.
- Use the
- State data cannot be directly modified or updated.
Below is a simple example of using state:
// JSX code
class Weather extends React.Component {
// Pass props
constructor(props) {
super(props);
// Initialize state
this.state = {
isHot: false,
wind: "Strong Wind",
};
// Using bind can also change the direction of this, directly mounting the function to the instance, but this is a bit memory-consuming (in the case of a large number of new objects)
this.demo = this.demo.bind(this);
}
render() {
return (
<h1 onClick={this.demo}>
Today's weather is {this.state.isHot ? "very hot" : "not hot"}
</h1>
);
}
// Using arrow function, this direction is correct, because as a callback for onClick, plus strict mode, this would be lost
// demo = () => {
// console.log(this);
// // This change has no effect
// this.state.isHot = true;
// console.log("I have been clicked");
// };
demo() {
console.log(this);
// Direct assignment is ineffective; the setState method must be used (similar to Flutter, setState is mounted on the prototype of React.Component)
// this.state.isHot = true;
// Use setState to update the DOM (object as a parameter, the parameter will be merged into the instance's state)
this.setState({
isHot: !this.state.isHot
});
console.log("I have been clicked");
};
}
ReactDOM.render(<Weather />, document.querySelector("#app"));
The above simply implements a state toggle demo. We encountered the problem of this
direction. The direction of this
can be changed using the bind
method and mounting the fixed method to the instance, but doing so has a slight problem:
- Every time a new method is added, it needs to be bound in the constructor, which is very inconvenient.
- Using
bind
can also change the direction ofthis
, directly mounting the function to the instance, which consumes memory.
So we summarize the above errors to get the standard way of writing state:
class Weather extends React.Component {
constructor(props) {
super(props);
}
state = {
isHot: false,
wind: "Strong Wind",
};
demo = () => {
/*
this.setState({
isHot: !this.state.isHot,
});
*/
// Arrow function writing
this.setState((oldState) => {
return {
isHot: !oldState.isHot,
};
});
};
render() {
return (
<h1 onClick={this.demo}>
Today's weather is {this.state.isHot ? "very hot" : "not hot"}
</h1>
);
}
}
ReactDOM.render(<Weather />, document.querySelector("#app"));
Props#
- In Vue, props are similar to parameters defined on components, used for value transmission/communication between parent and child components, and the same goes for React, with slight differences.
Props are properties that every component object will have, and all attributes of the component tag are stored in props.
Here is an example of passing values through props:
class Person extends React.Component {
render() {
return (
<ul>
<li>Name: {this.props.name}</li>
<li>Gender: Female</li>
<li>Age: {this.props.age}</li>
</ul>
);
}
}
// Render
ReactDOM.render(
<Person name="kano" age="20" />,
document.getElementById("app")
);
Note: In React, props are read-only, which is the same as in Vue.
In addition, there are many ways to pass values through props. You can use {value}
as a prop parameter, or you can directly use the object spread operator supported by Babel + JSX to unpack the object (shallow copy) as the props passed in:
let kano = {
name: "kanokano",
age: 18,
};
// Note: This is not destructuring, nor is it spreading; this is object spreading, which can only be used in component tags
ReactDOM.render(<Person {...kano} />, document.getElementById("app"));
Restricting props
Sometimes we need to restrict the type of props passed in to ensure the type correctness of the results passed in.
At this time, we can use the prop-types package:
npm i prop-types --save
Usage
class Person extends React.Component {
render() {
return (
<ul>
<li>Name: {this.props.name}</li>
<li>Gender: {this.props.sex}</li>
<li>Age: {this.props.age}</li>
</ul>
);
}
// Must be a static property
static propTypes = {
// Remember to import the package before use
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
speak: PropTypes.func, // Function type writing
};
static defaultProps = {
sex: "Male",
};
}
let kano = {
name: "kanokano",
age: 18,
// sex: "Female",
};
ReactDOM.render(<Person {...kano} />, document.getElementById("app"));
Using props in functional components
In React, functional components actually have a default parameter
props
that we can use directly.
// Create component
function Person(props) {
console.log(props);
return (
<ul>
<li>Name: {props.name}</li>
<li>Gender: {props.sex}</li>
<li>Age: {props.age}</li>
</ul>
);
}
// Add restrictions to attributes
Person.propTypes = {
// Remember to import the package before use
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
speak: PropTypes.func, // Function type writing
};
// Default values for parameters not passed in
Person.defaultProps = {
sex: "Male",
};
let kano = {
name: "kanokano",
age: 18,
// sex: "Female",
};
ReactDOM.render(<Person {...kano} />, document.getElementById("app"));
Refs#
React's refs are actually the same as Vue's $refs, but it seems that higher versions of React have optimized this feature, converting it to a ref writing similar to Vue 3 Using refs to manipulate the DOM - React.
Let's take a look at the common usage of refs and refs:
String form of ref (deprecated)#
class Demo extends React.Component {
// Display data from the left input box
showData = () => {
console.log(this.refs["input1"]);
alert(this.refs["input1"].value);
};
// Display data from the right input box
showData1 = () => {
alert(this.refs["input2"].value);
};
render() {
// ref (this has been deprecated in later versions)
return (
<div>
<input ref="input1" type="text" placeholder="Click the button to prompt data" />
<button onClick={this.showData}>Click me to prompt the left data</button>
<p>
<input
ref="input2"
onBlur={this.showData1}
type="text"
placeholder="Prompt data when losing focus"
/>
</p>
</div>
);
}
}
ReactDOM.render(<Demo />, document.querySelector("#app"));
Inline function form of ref#
class Demo extends React.Component {
// Display data from the left input box (ref callback has mounted the node on the instance, no need for refs)
showData = () => {
console.log(this["input1"]);
alert(this["input1"].value);
};
render() {
return (
<div>
<input
ref={el => this.input1 = el}
type="text"
placeholder="Click the button to prompt data"
/>
</div>
);
}
}
Note: If the ref callback is defined in an inline function, this function will be executed twice during the update process. The first time the parameter passed in is null, and the second time the parameter passed in is the DOM element because a new function instance (after all, it is an anonymous function) is created every time rendering occurs. React will clear the old ref and set a new ref.
This problem can be solved by placing the callback inside the class, but this problem is trivial; perfectionists can try the method below.
class Demo extends React.Component {
state = { isHot: true };
show = () => {
const { input1 } = this;
alert(input1.value);
};
changeWeather = () => {
// Updating will trigger the ref callback twice
// @ null
// @ <input type="text" placeholder="Input data">
const { isHot } = this.state;
this.setState({ isHot: !isHot });
};
saveDOM = (el) => {
this.input1 = el;
console.log("@", el);
};
render() {
return (
<div>
<p>Today's weather is {this.state.isHot ? "hot" : "cool"}</p>
{/* Placing the callback inside the class can solve the problem of triggering the callback twice */}
<input ref={this.saveDOM} type="text" placeholder="Input data" />
<button onClick={this.show}>Click me to prompt data</button>
<button onClick={this.changeWeather}>Click me to change the weather</button>
</div>
);
}
}
Using createRef (recommended)#
In addition to the above methods, we can also use createRef to bind and manipulate the DOM.
Calling createRef returns a container that can store the node identified by ref (a property can only bind one node).
class Demo extends React.Component {
// Calling createRef returns a container that can store the node identified by ref (a property can only store one node)
myRef = React.createRef();
myRef1 = React.createRef();
show = () => {
const { myRef } = this;
console.log(myRef.current);
alert(myRef.current.value);
};
show1 = () => {
const { myRef1 } = this;
console.log(myRef1.current);
alert(myRef.current.value);
};
render() {
return (
<div>
<input ref={this.myRef} type="text" />
<input ref={this.myRef1} onBlur={this.show1} type="text" />
<button onClick={this.show}>Click me to prompt data</button>
</div>
);
}
}
Event Handling#
We already know that we can specify event handling functions through onXxx attributes (note the naming rules).
(React uses synthetic events instead of native DOM events for better compatibility).
Event binding in React is similar to Vue, and it will automatically pass an event parameter to the event handling function.
Note: Events in React are handled through event delegation (delegated to the outermost element of the component).
class Demo extends React.Component {
// Automatically pass the event e
show2 = (e) => {
e.target.value = "blurred"
console.log(e.target);
};
render() {
return (
<div>
<input onBlur={this.show2} type="text" />
</div>
);
}
}
Controlled and Uncontrolled Components#
The difference between controlled and uncontrolled components:
- Controlled components maintain state to save the DOM's state and data, and when used, you can directly take from the state. Uncontrolled components do not save state.
- Controlled components can easily achieve two-way data binding similar to Vue, while uncontrolled components are difficult to achieve.
Uncontrolled component:
class Login extends React.Component {
handleSubmit = (e) => {
// Here e is the form event submission object rewritten by React
e.preventDefault()
console.log(e.currentTarget);
alert(`Username: ${this.username.value} Password: ${this.password.value}`);
};
render() {
return (
<form action="https://kanokano.cn" onSubmit={this.handleSubmit}>
Username:
<input ref={(e) => (this.username = e)} type="text" />
Password:
<input ref={(e) => (this.password = e)} type="password" />
<input type="submit" value="Submit" />
</form>
);
}
}
Controlled component:
class Login extends React.Component {
state = {
username: "",
password: "",
};
saveUsername = (e) => {
this.setState({
username: e.target.value,
});
};
savePassword = (e) => {
this.setState({
password: e.target.value,
});
};
handleSubmit = (e) => {
// Here e is the form event submission object rewritten by React
e.preventDefault();
console.log(e.currentTarget);
alert(`Username: ${this.state.username} Password: ${this.state.password}`);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
Username:
<input type="text" onChange={this.saveUsername} />
Password:
<input type="password" onChange={this.savePassword} />
<input type="submit" value="Submit" />
</form>
);
}
}
Of course, the above example of controlled components can be further optimized (function currying) to cope with the need to bind a large number of nodes.
...
// Save form data to state
saveFromData = (prop) => {
// Function currying
return (e) => {
console.log(e.target.value);
this.setState({
// Use brackets to indicate string property names
[prop]: e.target.value,
});
};
};
render() {
return (
<form onSubmit={this.handleSubmit}>
Username:
<input type="text" onChange={this.saveFromData("username")} />
Password:
<input type="password" onChange={this.saveFromData("password")} />
<input type="submit" value="Submit" />
</form>
);
}
...
Component Lifecycle (Old)#
Like Vue, React also has the concept of component lifecycle, which can be roughly divided into the following three stages:
-
Initialization stage: triggered by
ReactDOM.render()
-- initial renderingconstructor()
componentWillMount()
render()
componentDidMount()
-
Update stage: triggered by
this.setState()
inside the component or by the parent component's rendershouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
-
Unmounting component: triggered by
ReactDOM.unmountComponentAtNode()
componentWillUnmount()
Code demonstration:
The
componentWillReceiveProps
below needs special attention: The first time the component receives props when the page opens will not trigger this hook.
// Parent component
class B extends React.Component {
state = { name: "kano" };
change = () => {
this.setState({
name: "kanokano",
});
};
render() {
return (
<div>
<div>B</div>
<button onClick={this.change}>Change Car</button>
<Count Bname={this.state.name} />
</div>
);
}
}
// Create component (child component)
class Count extends React.Component {
constructor(props) {
console.log("Count-constructor");
super(props);
// Initialize state
this.state = { count: 0 };
}
// Button callback
add = () => {
const { count } = this.state;
this.setState({ count: count + 1 });
};
// Unmount button callback
unmount = () => {
ReactDOM.unmountComponentAtNode(document.getElementById("app"));
};
// B is about to receive props (the first time receiving props will not call this hook)
componentWillReceiveProps() {
console.log("B--componentWillReceiveProps");
}
// Force update button callback (can update the component forcibly without changing the state)
force = () => {
// Force update regardless of the return value of shouldComponentUpdate
this.forceUpdate();
};
// Component is about to mount hook
componentWillMount() {
console.log("Count-componentWillMount");
}
// Mounted
componentDidMount() {
console.log("Count-componentDidMount");
}
// Component is about to unmount hook
componentWillUnmount() {
console.log("Count-componentWillUnmount");
}
// Whether the component needs to update, will return true (execute update) or false (do not execute update)
shouldComponentUpdate() {
console.log("Count-shouldComponentUpdate");
return true;
}
// Component is about to update hook
componentWillUpdate() {
console.log("Count-componentWillUpdate");
}
// Component updated hook, accepts two parameters, one is the previous props, the other is the previous state
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, prevState);
}
render() {
console.log("Count-render");
const { count } = this.state;
return (
<div>
<h2>Current sum is {count}</h2>
<h2>Value from parent component: {this.props.Bname}</h2>
<button onClick={this.unmount}>BOOM</button>
<button onClick={this.force}>
Force update, without changing any data in the state
</button>
<button onClick={this.add}>Click me +1</button>
</div>
);
}
}
// Render component
ReactDOM.render(<B />, document.getElementById("app"));
Component Lifecycle (New)#
- Initialization stage: triggered by
ReactDOM.render()
-- initial renderingconstructor()
getDerivedStateFromProps()
render()
componentDidMount()
- Update stage: triggered by
this.setState()
inside the component or by the parent component's rendergetDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
- Unmounting component: triggered by
ReactDOM.unmountComponentAtNode()
componentWillUnmount()
The new version of React components has added two new hooks:
getDerivedStateFromProps
andgetSnapshotBeforeUpdate
, marginalizingcomponentWillReceiveProps
,componentWillMount
, andcomponentWillUpdate
. The reason is that these three hooks are not very meaningful but are often misused by developers, so the React development team intends to marginalize and deprecate these three hooks in the future.
If you really want to use these three hooks in the new version, please prefix them withUNSAFE_
.
getDerivedStateFromProps#
This is a preprocessing hook added in the new version of React, which can return the props passed to the component as state properties.
This hook also has a state parameter, which indicates the current existing state of the instance component.
getDerivedStateFromProps can return null or an object
- When the return value is null, it means the hook does nothing and continues to execute the lifecycle.
- When the return value is an object, the returned object will be used as the state property in the subsequent lifecycle.
The following code can use the props passed to the component as state, indicating that the value of state depends on props at any time (can be used, but unnecessary, as this operation can be implemented in the constructor).
static getDerivedStateFromProps(props) {
return props;
}
...
ReactDOM.render(<B />, document.getElementById("app"));
The following code can control the value of count to be within 0-5.
static getDerivedStateFromProps(props, state) {
console.log("getDerivedStateFromProps");
if (state.count > 5 && state.count < 0) {
return { count: 0 };
}
return null;
}
getSnapshotBeforeUpdate#
This hook executes before the component updates (render), and we can do some operations inside, such as getting the scroll position of the element before the update, and this hook must return a value (as long as it is not undefined).
// Get the previous snapshot before updating (must be used with componentDidUpdate)
getSnapshotBeforeUpdate() {
console.log("getSnapshotBeforeUpdate");
return "snapshotkanokano";
}
// Component updated hook, accepts three parameters: the previous props, the previous state, and the snapshot value
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(prevProps, prevState, snapshot);
}
The following example simulates a news list, and continuously updates elements at the top of the list.
The functionality implemented: Updating the list does not affect the user's scrolling and preview experience.
Principle: The scroll height increases with the number of items.
Formula: Scroll position = current list height - previous height.
class NewList extends React.Component {
state = { newsArr: [] };
componentDidMount() {
setInterval(() => {
// Get the original state
const { newsArr } = this.state;
// Simulate a news item
const news = "News" + (newsArr.length + 1);
// Update
this.setState({ newsArr: [news, ...newsArr] });
}, 500);
}
getSnapshotBeforeUpdate() {
// Get the height of the content area
return this.refs.list.scrollHeight;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// The scroll height increases with the number of items, so we can "fix" the scrolling page
// Scroll position = current list height - previous height
this.refs.list.scrollTop += this.refs.list.scrollHeight - snapshot;
console.log(snapshot);
}
render() {
return (
<div className="list" ref="list">
{this.state.newsArr.map((item, index) => (
<div className="news" key={index}>
{item}
</div>
))}
</div>
);
}
}
Effect:
Chapter 2: React Engineering Development#
React Scaffolding#
Scaffolding, as the name suggests, is a tool used to help programmers quickly create a template project based on a specific library.
A scaffolding includes:
- All necessary configurations (syntax checking, JSX compilation, devServer, etc.)
- Required dependencies
- An example DEMO
Like Vue, React also has a related scaffolding tool called create-react-app
.
Installation method:
npm i create-react-app -g && create-react-app myapp
The above installation method is not recommended; here we recommend using npx to quickly create a React instance:
npx create-react-app myapp
Start the project:
npm start
The directory tree after initialization is as follows:
│ .gitignore -- git ignore file
│ package-lock.json -- npm package description file after fixing version numbers
│ package.json -- npm package description file
│ README.md
│
├─public -- directory for storing static resources
│ favicon.ico -- site icon
│ index.html -- homepage
│ logo192.png
│ logo512.png
│ manifest.json -- app configuration file (information about the icon added to the desktop through the browser https://developers.google.com/web/fundamentals/web-app-manifest/)
│ robots.txt -- controls search engine crawler rules
│
└─src
App.css -- App component styles
App.js -- App component's js file
App.test.js -- for unit testing
index.css -- global styles
index.js -- entry file
logo.svg
reportWebVitals.js -- for page performance testing
setupTests.js -- for overall application testing
The above is the default generated Demo of React, but in the early stages of learning, to simplify, the directory structure we usually use is as follows:
│ .gitignore
│ package-lock.json
│ package.json
│ README.md
│
├─public -- directory for storing static resources
│ favicon.ico -- site icon
│ index.html -- homepage
│
└─src
│ App.jsx
│ index.js
│
└─components
└─Hello
index.jsx
index.module.css
In React, components are usually named with the .jsx extension, located in
components/componentName/index.jsx
.
Style files and component files are placed in the same directory but with the module.css extension, allowing for modular CSS (less) during import, enabling the use of CSS (less) classes in the form ofvariableName.css property
.
Below is an example of writing a Hello component:
import React, { Component } from "react";
// Change the css file to a module.css file to avoid style conflicts
import hello from "./index.module.css";
export class Hello extends Component {
render() {
return (
<div>
<h4 className={hello.title}>Hello React</h4>
</div>
);
}
}
Writing the App component:
import { Hello } from "./components/Hello";
// The App component can generally be written as a functional component
function App() {
return (
<div>
<p>I am the App component</p>
<Hello />
</div>
);
}
export default App;
Writing the entry file (index.js):
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
Writing index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Note: %PUBLIC_URL%
is a property in process.env
, the react scaffolding can read the current project's public directory path, which is then read and replaced by react-scripts
.
Component-based Coding Process#
A complete component-based coding process can be roughly divided into the following points:
- Split components: break down the interface, extract components.
- Implement static components: use components to achieve static page effects.
- Implement dynamic components
- Dynamically display initialization data
- Data type
- Data name
- Data storage location
- Interaction (starting from listening to data)
- Dynamically display initialization data
Component-based Coding: ToDoList Case#
Case download: https://kanokano.cn/wp-content/uploads/2023/05/ToDoList.zip
Chapter 3: Sending AJAX Requests with React#
Like Vue, React does not include code for sending AJAX requests by default; you need to build it yourself or use third-party libraries (fetch or axios).
Using Axios#
Using axios in React is not much different from using it in Vue. First, you need to install axios:
npm i axios --save
Example code:
import React, { Component } from "react";
import axios from "axios";
class App extends Component {
getStudentData = () => {
axios.get("http://localhost:3000/v1/students").then(
(response) => {
console.log("data:", response.data);
},
(error) => {
console.log("err:", error);
}
);
};
getCarData = () => {
axios.get("http://localhost:3000/v2/cars").then(
(response) => {
console.log("data:", response.data);
},
(error) => {
console.log("err:", error);
}
);
};
render() {
return (
<div>
<button onClick={this.getStudentData}>Click me to get student data</button>
<button onClick={this.getCarData}>Click me to get car data</button>
</div>
);
}
}
export default App;
Configuring a Proxy Server#
The purpose of configuring a proxy server is to solve the browser's cross-origin request issue.
Configuration method: Directly create a setupProxy.js
file in the src directory:
const { createProxyMiddleware } = require("http-proxy-middleware");
// Create proxy middleware
module.exports = (app) => {
// Multiple proxy servers can be configured
app.use(
createProxyMiddleware("/v1", {
target: "http://localhost:5000",
changeOrigin: true,
pathRewrite: { "^/v1": "" },
})
);
app.use(
createProxyMiddleware("/v2", {
target: "http://localhost:5001",
changeOrigin: true,
pathRewrite: { "^/v2": "" },
})
);
};
There is also a simple method suitable for configuring a single proxy server:
Add the following configuration to package.json
"proxy":"http://xxxxx:xxx"
The advantage of this method is that it is simple to configure, and all requests that are not present in the front end will go through the proxy.
The downside is that it cannot configure multiple proxies, and if there are requests in the front end, it cannot manually specify requests to the back end, making it inconvenient for management and control.
Using PubSub to Optimize Existing Code#
From the previous examples, we can see that whenever parameters need to be passed between components, especially when passing parameters between sibling components, we usually have to rely on the parent component as an intermediate node for passing values. This is not very elegant and will increase unnecessary code, so we can utilize the publish-subscribe pattern from design patterns to solve this problem.
The PubSub plugin just happens to utilize the publish-subscribe pattern, and we can install and use it directly:
npm i pubsub-js --save
Using PubSub#
Subscription and Unsubscription:
import PubSub from "pubsub-js";
...
componentDidMount() {
this.token = PubSub.subscribe("onSearch", (msg, data) => {
this.setState({
list: data,
});
});
this.token1 = PubSub.subscribe("toggleLoading", (msg, flag) => {
this.setState({ loading: flag });
});
}
// Remember to unsubscribe before destroying the component
componentWillUnmount(){
PubSub.unsubscribe(this.token)
PubSub.unsubscribe(this.token1)
}
...
Publishing:
import PubSub from "pubsub-js";
search = async () => {
// Destructure continuously
const {
current: { value },
} = this.keyWord;
// Send request
try {
PubSub.publish("toggleLoading", true);
const res = await axios.get(
`https://api.github.com/search/users?q=${value}`
);
const list = res.data.items || [];
// Update data
PubSub.publish("toggleLoading", false);
PubSub.publish("onSearch", list);
} catch (err) {
console.log(err.message);
PubSub.publish("toggleLoading", false);
}
};
Using Fetch#
Fetch, as a new AJAX solution, naturally has its own advantages, such as native support for promises, adhering to the principle of separation of concerns.
Directly using the simplest case:
try {
const res = await fetch(`https://api.github.com/search/users?q=${value}`);
if (res.status === 200) {
const list = (await res.json()).items || [];
}
} catch (err) {
console.log(err.message);
}
As you can see, using native fetch can also be as elegant as axios or other second-hand encapsulated XHR. My evaluation is to recommend using it more; it’s 3202 now, and compatibility is not an issue.
Chapter 4: React Router#
Since most front-end development nowadays is single-page applications (SPA), routing will naturally be used.
Routing can be implemented using hash or history; hash has higher compatibility but is not elegant, while history is more modern and friendly...
We will not elaborate on the concept of routing here; let's get straight to the point.
Using React Router 5#
Note: Here we use the old version of react-router (v5).
Installation:
npm i react-router-dom@5
Using router:
index.js
....
// Import router
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
// Directly wrap the entire App with BrowserRouter or HashRouter
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
....
App.jsx
import React, { Component } from "react";
import { Link, Route } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";
class App extends Component {
render() {
return (
...
{/* The way to switch links in React */}
<Link to="/about" className="list-group-item active">
About
</Link>
<Link to="/home" className="list-group-item ">
Home
</Link>
<div>
{/* Of course, the component can be imported separately; here for convenience, we use function components instead */}
<Route path="/about" component={()=><h2>About</h2>} />
<Route path="/home" component={()=><h2>Home</h2>} />
</div>
...
);
}
}
export default App;
The above links will not switch the highlighted state when switching, so we can change to NavLink (don't forget to import):
{/* Use NavLink, clicking on it will add the class specified in activeClassName */}
<NavLink activeClassName="active" to="/about" className="list-group-item">
About
</NavLink>
<NavLink activeClassName="active" to="/home" className="list-group-item ">
Home
</NavLink>
After looking at the above example, we can analyze that the basic use of routing can include these four steps:
- First, perform interface partition layout.
- Change the a tag to Link tag.
- Use Route tags in the specified area and perform route matching.
- Wrap the root component with
<BrowserRouter />
or<HashRouter />
.
Of course, the above example does not perform component segmentation. Generally, we need to split static components into the src/components folder and route components into the src/pages folder.
Note that when rendering route components, several props parameters will be passed inside:
{
history: {
action: "PUSH",
block: ƒ block(prompt),
createHref: ƒ createHref(location),
go: ƒ go(n),
goBack: ƒ goBack(),
goForward: ƒ goForward(),
length: 45,
listen: ƒ listen(listener),
location: {
pathname: '/home',
search: '',
hash: '',
state: undefined,
key: 'faavc8'
}
push: ƒ push(path, state)
replace: ƒ replace(path, state)
}
location: {
hash: ""
key: "faavc8"
pathname: "/home"
search: ""
state: undefined
}
match: {
path: '/home',
url: '/home',
isExact: true,
params: {…}
}
staticContext: undefined
}
Don't rush; the meaning of these parameters will be analyzed one by one later.
Double Encapsulation of NavLink#
After using the NavLink component, we find that the number of parameters passed is still relatively large. How can we optimize this?
<NavLink activeClassName="active" to="/home" className="list-group-item ">
Home
</NavLink>
The answer is to encapsulate it twice!
MyNavLink.jsx
import React, { Component } from "react";
import { NavLink } from "react-router-dom";
class MyNavLink extends Component {
render() {
// props.children can get the content passed in
const { to, children } = this.props;
console.log(this.props);
return (
<NavLink
activeClassName="active"
to={to}
// You can also directly spread (children can also be written in the tag attributes)
// {...this.props}
className="list-group-item "
>
{children}
</NavLink>
);
}
}
export default MyNavLink;
App.jsx
{/* Encapsulate NavLink */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
The above uses a property that has not been mentioned before: children
.
Children are what is passed between the tags of the component. The above passed in are the characters About and Home, so children contain About and Home.
If a component is passed in, then children will contain a component.
In short, whatever is passed between the tags is what is contained in children.
The children property can be written as an attribute in the tag body or placed between the tags.
Switch Component#
Normally, one path corresponds to one component.
Sometimes we write many route components, and when the component is parsed from top to bottom, what happens if there are multiple same-path routes?
The following three routes mix the same-path routes. When entering the /home
path, both components will be parsed:
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={()=><p>FAKE</p>} />
At this point, we can introduce the Switch component. import {Switch} from 'react-router-dom'
After adding the Switch component, only the nearest one (Home) will match the same-path route from top to bottom; the excess will not be matched:
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={()=><p>FAKE</p>} />
</Switch>
Switch can ensure the uniqueness of the route component.
Fuzzy Matching of Routes#
By default, routes will perform prefix fuzzy matching:
{/* Encapsulate NavLink */}
<MyNavLink to="/about">About</MyNavLink>
{/* Can jump to home route */}
<MyNavLink to="/home/home1">Home</MyNavLink>
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</Switch>
After enabling strict mode, the route will perform strict matching:
{/* Encapsulate NavLink */}
<MyNavLink to="/about">About</MyNavLink>
{/* After enabling strict mode, cannot jump to home route */}
<MyNavLink to="/home/home1">Home</MyNavLink>
<Switch>
{/* Enabling strict mode */}
<Route exact path="/about" component={About} />
<Route exact path="/home" component={Home} />
</Switch>
Note: Generally, there is no need to enable strict matching for routes, especially when there are query parameters following the path.
Moreover, enabling strict matching will also prevent matching secondary routes.
Usage of Redirect#
Generally, if a user requests a non-existent route, React will not match anything and display a blank page, which is not elegant. At this time, we can use the Redirect component.
The redirect component is generally written at the very bottom of all route registrations. When all routes cannot be matched, it will redirect to the route specified by Redirect.
Specific writing (remember to import):
<Switch>
<Route exact path="/about" component={About} />
<Route exact path="/home" component={Home} />
{/* Redirect to home if nothing is matched */}
<Redirect to="/home" />
</Switch>
Nested Routes#
In React, nested routes are relatively simple to implement. You just need to distinguish the parent-child relationship between components and pay attention to the following two points:
- When registering child routes, write the parent route's path value.
- Route matching is performed in the order of registered routes.
- The above route path writing is indeed quite redundant, so in the later v6 version, improvements were made, which will be detailed in the following chapters.
Without further ado, let's go directly to the case:
Parent route setup:
/src/pages/Home/index.jsx
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
import MyNavLink from "../../components/MyNavLink";
import Message from "./Message";
import News from "./News";
class Home extends Component {
render() {
console.log("Props rendered in Home:", this.props);
return (
<div>
<h3>I am the content of Home</h3>
<ul className="nav nav-tabs">
<li>
{/* The writing of secondary routes */}
<MyNavLink to="/home/news" className="list-group-item active">
News
</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message" className="list-group-item">
Message
</MyNavLink>
</li>
</ul>
{/* Register routes */}
<Switch>
<Route path="/home/news" component={News}></Route>
<Route path="/home/message" component={Message}></Route>
</Switch>
</div>
);
}
}
export default Home;
Child route setup (only showing the Message component, the News component is similar):
/src/pages/Home/Message/index.jsx
import React, { Component } from "react";
class Message extends Component {
render() {
return (
<div>
<ul>
<li>
<a href="/">message001</a>
</li>
<li>
<a href="/">message002</a>
</li>
<li>
<a href="/">message003</a>
</li>
</ul>
</div>
);
}
}
export default Message;
Passing Parameters to Route Components#
Passing params Parameters to Route Components#
Passing params parameters through routes is actually very simple. You just need to add a placeholder
:xxx
when registering the route.
- First, carry parameters in the route link (
/home/message/detail/${obj.id}/${obj.title}
) - Declare parameters when registering the route (
xxx/:id/:title
) - In the target route, you need to receive parameters (
props.match.params
)
Example:
/src/pages/Home/Message/index.jsx
import React, { Component } from "react";
import Detail from "./Detail";
import { Link, Route } from "react-router-dom";
const data = [
{ id: "01", title: "Message 1" },
{ id: "02", title: "Message 2" },
];
class Message extends Component {
render() {
return (
<div>
<ul>
{data.map((obj) => {
return (
<li key={obj.id}>
{/* Pass params parameters to route components */}
<Link to={`/home/message/detail/${obj.id}/${obj.title}`}>
{obj.title}
</Link>
</li>
);
})}
</ul>
<hr />
{/* Declare to accept params parameters */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
</div>
);
}
}
export default Message;
/src/pages/Home/Message/Detail/index.jsx
import React, { Component } from "react";
const data = [
{ id: "01", content: "hahaha" },
{ id: "02", content: "kanokano.cn" },
];
class Detail extends Component {
render() {
// The passed params
console.log(this.props.match.params);
const { params } = this.props.match;
const content = data.find((item) => {
return item.id === params.id;
});
return (
<ul>
<li>ID: {params.id}</li>
<li>Title: {params.title}</li>
<li>Content: {content.content}</li>
</ul>
);
}
}
export default Detail;
Passing Search Parameters to Route Components#
Passing search parameters, also known as query parameters, typically looks like this:
http://xxx.com?id=02&title=%E6%B6%88%E6%81%AF2
The string after the ?
is the search (query) parameter.
Passing search parameters between route components is very simple; the steps are as follows:
- Carry search parameters in the route link:
to={
/home/message/detail/?id=${obj.id}&title=${obj.title}}
- In the target route component, use
props.location.search
to view the passed search string. - In the target route component, use the
parse()
method from the query-string plugin to convert theurlencoded
form of the search string into an entity object.
Note that the query-string plugin needs to be installed manually: npm i query-string
.
Specific code:
Source route component:
//...
const data = [
{ id: "01", title: "Message 1" },
{ id: "02", title: "Message 2" },
];
//...
{/* Pass search (query) parameters to route components */}
<Link to={`/home/message/detail/?id=${obj.id}&title=${obj.title}`}>
{obj.title}
</Link>
{/* No need to declare search (query) parameters */}
<Route path="/home/message/detail" component={Detail} />
//...
Target route component:
//....
// npm i query-string
import qs from "query-string";
render() {
// The passed query
console.log("query", this.props.location);
const { search } = this.props.location;
// querystring needs to be converted to an object
let out = qs.parse(search);
console.log(out);
const content = data.find((item) => {
return item.id === out.id;
});
return (
<ul>
<li>ID: {out.id}</li>
<li>Title: {out.title}</li>
<li>Content: {content.content}</li>
</ul>
);
}
//.....
Passing State Parameters to Route Components#
This method does not change with the change of the address bar, making the information less prone to be tampered with.
Source route component:
{/* Pass state parameters to route components */}
<Link
to={{
pathname: "/home/message/detail/",
state: { id: obj.id, title: obj.title },
}}
>
{obj.title}
</Link>
{/* No need to declare state parameters */}
<Route path="/home/message/detail" component={Detail} />
Using useLocation
to accept state parameters:
render() {
// The passed state
console.log("state", this.props);
// Since state is stored in history, sharing the page will not retain state
const { id, title } = this.props.location.state || {};
const content =
data.find((item) => {
return item.id === id;
}) || {};
return (
<ul>
<li>ID: {id}</li>
<li>Title: {title}</li>
<li>Content: {content.content}</li>
</ul>
);
}
Question: The state parameter does not appear in the browser's address bar; how does it persist after refreshing?
Answer: Because state is maintained bywindow.history
, and the history helps us record the state object. If you switch to HashRouter, the state will not be retained, and refreshing will lose the state content. Therefore, state is more suitable for one-time data transmission scenarios.
Replace and Push#
Because history is stored in a stack format, the default is push mode.
Sometimes we do not want to directly return to the previous history; we can add the replace
attribute to the corresponding Link, which will enable replace mode.
<Link replace to="/home/kano/detail">{obj.title}</Link>
When we successively enter home, kano, and then click the link to enter detail, clicking the browser's back button will not return to the kano route but directly back to the home route because the replace mode has replaced the current path in history.
The above is the role of the link in replace mode.
Programmatic Route Navigation#
Previously, we used component-maintained route navigation. Programmatic route navigation uses methods in history to achieve custom route jumps.
// Define a show method, method can be customized
show = (method, id, title) => {
// Replace jump (programmatic route navigation)
// params
this.props.history[method](`/home/message/detail/${id}/${title}`);
// search
// this.props.history[method](`/home/message/detail/?id=${id}&title=${title}`);
// state
// this.props.history[method]("/home/message/detail/", { id, title });
};
//.....
<Link to={`/home/message/detail/${obj.id}/${obj.title}`}>
{obj.title}
</Link>
<button onClick={() => this.show("push", obj.id, obj.title)}>push view</button>
<button onClick={() => this.show("replace", obj.id, obj.title)}>replace view</button>
//.....
{/* Declare to accept params parameters */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
{/* No need to declare search (query) parameters */}
{/* <Route path="/home/message/detail" component={Detail} /> */}
{/* No need to declare state parameters */}
{/* <Route path="/home/message/detail" component={Detail} /> */}
<button onClick={() => this.props.history.goForward()}>Forward</button>
<button onClick={() => this.props.history.goBack()}>Back</button>
<button onClick={() => this.props.history.go(-2)}>Back 2 steps</button>
The above uses the history.push and replace methods, which can pass params, query, and state parameters, and even use history to operate forward and backward.
Using Router Methods in General Components#
We know that only route components, that is, components navigated to by the Route component, have the history method in their props. But if we need to use routing methods in general components, how should we do it? At this point, we need to use withRouter.
import React, { Component } from "react";
// To use router methods in general components, you need to import withRouter
import { withRouter } from "react-router-dom";
class Header extends Component {
render() {
return (
<div className="row">
<div className="col-offset-2 col-8">
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={() => this.props.history.goForward()}>Forward</button>
<button onClick={() => this.props.history.goBack()}>Back</button>
<button onClick={() => this.props.history.go(-2)}>Back 2</button>
</div>
</div>
<hr />
</div>
);
}
}
// General component with router methods
export default withRouter(Header);
As above, you just need to import withRouter, and then when exporting the component, use withRouter to wrap the component class, and you can use history in props.
Summary:
- withRouter can process a general component, allowing it to have the API unique to route components.
- The return value of withRouter is a new component.
Differences Between BrowserRouter and HashRouter#
- The underlying principles are different:
- BrowserRouter uses the H5 history API, which is not compatible with IE9 and below (IE is dead).
- HashRouter uses the URL hash value.
- The URL presentation is different:
- The path in BrowserRouter does not have a #, while the path in HashRouter has a #.
- The effect of refreshing on route state parameters:
- BrowserRouter has no effect, as state is stored in the history object.
- HashRouter will cause the loss of state parameters after refreshing.
- HashRouter can be used in environments with compatibility issues or incorrect paths; normally, BrowserRouter is sufficient.
EX Chapter: AntDesign#
Compared to the ElementUI component library in the domestic ecosystem of Vue, React also has a corresponding component library called AntDesign.
Installation#
$ npm install antd --save
$ yarn add antd
Usage#
import { Button, Space, DatePicker } from "antd";
// Icons
import { WechatFilled, SearchOutlined } from "@ant-design/icons";
const onChange = (date, dateString) => {
console.log(date, dateString);
};
const App = () => (
<Space wrap>
<DatePicker onChange={onChange} />
<WechatFilled spin />
<WechatFilled />
<Button type="primary">Primary Button</Button>
<Button type="primary" icon={<SearchOutlined />} />
<Button>Default Button</Button>
<Button type="dashed">Dashed Button</Button>
<Button type="text">Text Button</Button>
<Button type="link">Link Button</Button>
</Space>
);
export default App;
After that, I will not elaborate on the use of the AntD component library; for details, just click the documentation to view: https://ant.design/components/
On-demand Loading (antd 3.x)#
The new version of antd (4.x +) has default support for on-demand loading (based on ES module tree shaking), no additional configuration is required. However, if you encounter problems, you can follow the configuration method below (FROM: Ant Design 3.x).
1. Use react-app-rewired#
Import react-app-rewired and modify the start configuration in package.json. Due to the new react-app-rewired@2.x version, you also need to install customize-cra.
$ yarn add react-app-rewired customize-cra
/* package.json */
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
Then create a config-overrides.js
file in the root directory to modify the default configuration.
module.exports = function override(config, env) {
// do stuff with the webpack config...
return config;
};
2. Use babel-plugin-import for on-demand imports#
babel-plugin-import is a Babel plugin used for on-demand loading of component code and styles (principle). Now we try to install it and modify the config-overrides.js
file.
$ yarn add babel-plugin-import
+ const { override, fixBabelImports } = require('customize-cra');
- module.exports = function override(config, env) {
- // do stuff with the webpack config...
- return config;
- };
+ module.exports = override(
+ fixBabelImports('import', {
+ libraryName: 'antd',
+ libraryDirectory: 'es',
+ style: 'css',
+ }),
+ );
Then remove the @import '~antd/dist/antd.css';
style code added in src/App.css
, and import the module in the following format.
// src/App.js
import React, { Component } from 'react';
- import Button from 'antd/es/button';
+ import { Button } from 'antd';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<Button type="primary">Button</Button>
</div>
);
}
}
export default App;
Finally, restart yarn start
to access the page. The js and css code of the antd component will be loaded on demand, and you will not see such warning messages in the console.
import DatePicker from 'antd/es/date-picker'; // Load JS
import 'antd/es/dte-picker/style/css'; // Load CSS
// import 'antd/es/date-picker/style'; // Load LESS
Custom Theme (antd 5.x)#
For custom themes in 5.x, see: Customize Theme - Ant Design 5
For custom themes in 3.x, see: Customize Theme - Ant Design 3
Chapter 5: Redux#
Documentation: https://www.redux.org.cn
Redux is a JavaScript library specifically for state management (not a React plugin library).
It can be used in projects using React, Angular, Vue, etc., but is often used in conjunction with React (perhaps the name is more fitting).
Purpose: Centrally manage the shared state of multiple components in a React application.
When to use Redux
- When the state of a certain component needs to be accessed by other components (i.e., shared).
- When an action needs to change the state of another component (communication).
- General principle: Use as needed according to business requirements.
Workflow#
It looks quite similar to Vuex (State Action Mutation), and indeed, the three core objects of Redux are explained as follows:
- Action
- An object representing an action, containing two properties:
- type: the identifier property, a string value, unique, and a necessary property.
- data: the data property, with any type of value, an optional property.
- Example:
{type:'ADD_STUDENT', data:{name:'tom', age:18}}
- An object representing an action, containing two properties:
- Reducer
- Used to initialize and process state.
- A pure function that produces a new state based on the old
state
andaction
.
- Store
- An object that links state, action, and reducer together.
- How to obtain this object?
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
- Functions of this object:
getState()
: get state.dispatch(action)
: dispatch action, triggering reducer calls to produce new state.subscribe(listener)
: register a listener that automatically calls when a new state is produced.
Installation and Usage#
npn i redux
The specific usage can be illustrated through the following redux summation case.
A Simple Redux Example#
Count component: components/Count/index.jsx
import React, { Component } from "react";
// Store
import store from "../../redux/store";
// Actions
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction,
} from "../../redux/count_action";
class Count extends Component {
// Addition
increment = () => {
const { value } = this.selectedNumber;
store.dispatch(createIncrementAction(+value));
};
// Subtraction
decrement = () => {
const { value } = this.selectedNumber;
store.dispatch(createDecrementAction(+value));
};
// Odd addition
incrementOdd = () => {
const { value } = this.selectedNumber;
store.getState() % 2 !== 0 && store.dispatch(createIncrementAction(+value));
};
// Asynchronous addition
incrementAsync = () => {
const { value } = this.selectedNumber;
// Delay function is more convenient to manage when written in Action
store.dispatch(createIncrementAsyncAction(+value, 500));
};
// Mounted in index.js for unified monitoring, more convenient
// componentDidMount() {
// // Monitor changes in redux state, if changed, call render
// store.subscribe(() => {
// this.forceUpdate();
// // or use setState to update
// // this.setState({})
// });
// }
render() {
return (
<div>
<h1>Current sum is: {store.getState()}</h1>
<select
ref={(c) => (this.selectedNumber = c)}
style={{ width: 50, textAlign: "center" }}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment} style={{ margin: "0 5px" }}>
+
</button>
<button onClick={this.decrement} style={{ margin: "0 5px" }}>
-
</button>
<button onClick={this.incrementOdd} style={{ margin: "0 5px" }}>
Odd +
</button>
<button onClick={this.incrementAsync} style={{ margin: "0 5px" }}>
Async +
</button>
</div>
);
}
}
export default Count;
In the src
directory, create a redux
folder, then create store.js
, count_reducer.js
, which will serve as store
and reducer
.
Note: createStore is deprecated, but you can use legacy_createStore to create the store object.
Store main file: store.js
Here we need to use the thunk middleware, installation method:
npm i redux-thunk
, the reason will be mentioned below.
// Deprecated, create store object
import { legacy_createStore as createStore, applyMiddleware } from "redux";
import countReducer from "./count_reducer";
// redux-thunk used to support asynchronous actions
import thunk from "redux-thunk";
// Use store and apply thunk middleware
const store = createStore(countReducer, applyMiddleware(thunk));
export default store;
Constant identifier file: src/redux/constant.js
This module is used to define the constant values of the type in the action object. Since the variable has built-in intelligent prompts, it can prevent accidental typos.
// This module is used to define the constant values of the type in the action object, as the exported variable has built-in intelligent prompts, it can prevent accidental typos.
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
Reducers: src/redux/reducers/count.js
A reducer is a function, equivalent to Vuex's mutations, that accepts the previous state and action, returning the processed state.
In general, the responsibility of a reducer is to initialize and process state.
Note: If the initState saves a reference type of data (like an array), when combining prevState, we need to create a new array to replace the previous array (using destructuring or creating a new array is fine), see the example below:
// A reducer is a function, equivalent to Vuex's mutations
// Two parameters: the previous state and the action object
import { INCREMENT, DECREMENT } from "./constant";
const initState = 0; // Initialize state
export default function countReducer(prevState = initState, action) {
// Extract type and data from action
const { type, data } = action;
console.log(action);
switch (type) {
case INCREMENT:
return prevState + data;
case DECREMENT:
return prevState - data;
default:
return prevState;
}
}
//....
// Add a state
const initState = [{ id: "001", name: "tom", age: 18 }];
export default function personReducer(prevState = initState, action) {
const { type, data } = action;
switch (type) {
case ADD_PERSON:
// Here you must return a new array, so that it will trigger render to redraw the interface
// Before render, shallow comparison is triggered
return [data, ...prevState];
// The following approach will not trigger page redraw
// prevState.unshift(data)
// return prevState
default:
return prevState;
}
}
Actions: src/redux/actions/count.js
Actions in redux are divided into synchronous and asynchronous. Synchronous actions are usually an object:
{type:xxx,data:data}
.
Asynchronous actions are usually a function, but the native redux dispatch cannot accept a function as a parameter, so we will use a middleware:redux-thunk
.
This way, the returned function will automatically pass the dispatch parameter of the triggered component instance for us to use.
// Generate action objects for the count component
import { INCREMENT, DECREMENT } from "./constant";
export const createIncrementAction = (data) => ({
type: INCREMENT,
data,
});
export const createDecrementAction = (data) => ({
type: DECREMENT,
data,
});
// Asynchronous action
// Need to install npm i redux-thunk (converter) so that dispatch can accept asynchronous functions
// The returned function will have a dispatch parameter for us to use
export const createIncrementAsyncAction = (data, time) => (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data));
}, time);
};
**The above is a simple example of using redux to manage summation. We