React
React is based on the following concepts:
- component
JSX
Virtual DOM
Data Flow
React. Js is not a framework, it’s just a library. It only provides uI-level solutions. In the actual project, it can not solve all our problems, we need to combine other libraries, such as Redux, React-Router, etc., to help provide a complete solution.
React browser development environment
<! DOCTYPEhtml>
<html>
<head>
<meta charset="UTF-8" />
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<! Type ="text/ Babel "-->
<script type="text/babel">
Create a virtual DOM
const vDom = <h1>Hello,React</h1>
// 2. Render the virtual DOM to the page
ReactDOM.render(vDom, document.getElementById('root'))
</script>
</body>
</html>
Copy the code
React, React -dom, and Babel are three libraries that must be loaded first.
react.js
:React
Core library.react-dom.js
: be responsible forWeb
Of the pageDOM
Operation.babel.js
Will:JSX
Grammar toJavaScript
Syntax.
Introduction to Virtual DOM
A real page corresponds to a DOM tree. In the traditional page development model, each time a page needs to be updated, you manually manipulate the DOM to do so. DOM manipulation is very expensive. And the code that manipulates the DOM becomes unmaintainable.
React converts a real DOM tree into a JavaScript object tree, also known as a Virtual DOM. The diagram below:
After each data update, the Virtual DOM is recalculated and compared with the Virtual DOM generated last time, and the changed part is updated in batches. VirtualDOM not only improves React performance, but its biggest benefit is that it can be integrated on other platforms (for example, React-Native is a native control rendered based on the VirtualDOM).
Therefore, when the Virtual DOM is output, whether to output the Web DOM, Android control or iOS control depends on the platform.
Iv. Introduction to JSX
Hello,React
<body>
<div id="root"></div>
<script type="text/babel">
Create a virtual DOM
const element = React.createElement('h1', { id: 'title' }, React.createElement('span'.null.'Hello,React! '))
// 2. Render the virtual DOM to the page
ReactDOM.render(element, document.getElementById('root'))
</script>
</body>
Copy the code
React.createElement builds a JavaScript object that describes HTML structure information, including tag names, attributes, and child elements. An object structure similar to the following:
{
type: 'h1'.props: {
id: 'title'.children: {
type: 'span'.props: {
children: 'Hello,React'}}}}Copy the code
Creating the virtual DOM with react. createElement is difficult enough when the tag structure is not too complex. It doesn’t have the brevity of writing structures in HTML. To solve this problem, the JSX syntax was born. If we were to reexpress the above elements using JSX syntax, we would simply write:
<body>
<div id="root"></div>
<script type="text/babel">
Create a virtual DOM
const element = (
<h1 id="title">
<span>Hello,React</span>
</h1>
)
// 2. Render the virtual DOM to the page
ReactDOM.render(element, document.getElementById('root'))
</script>
</body>
Copy the code
JSX adds HTML syntax directly to JavaScript code, making it more intuitive and easier to maintain. Converted to pure JavaScript by the compiler and executed by the browser. JSX has been compiled into pure JavaScript at the product packaging stage.
JSX is a syntactic extension of the JavaScript language that looks like HTML but is not HTML. JSX is a third-party standard that can be applied to any set of frameworks. Compiling JSX syntax is possible using Babel’s JSX compiler.
4.1. HTML attributes in JSX
React differs from HTML in a number of attributes. For example, class is rewritten as className:
ReactDOM.render(<div className="foo">Hello</div>.document.getElementById('root'))
Copy the code
4.2. JavaScript expressions in JSX
When JSX encounters an HTML tag (beginning with <), it parses it with HTML rules. When you encounter a block of code (beginning with {), you parse it with JavaScript rules. That is, JSX uses expressions wrapped in curly braces {} :
<body>
<div id="root"></div>
<script type="text/babel">
const names = ['zhangsan'.'lisi'.'wangwu']
const title = 'title'
ReactDOM.render(
<div>
<h1>{title}</h1>
<div>
{names.map((name, key) => {
return <div key={key}>Hello,{name}</div>
})}
</div>
</div>.document.getElementById('root'))</script>
</body>
Copy the code
JSX can insert JavaScript variables directly into templates. If the variable is an array, all members of the array are expanded.
const names = [<div key="1">zhangsan</div>.<div key="2">lisi</div>]
ReactDOM.render(
<div>
{names}
</div>.document.getElementById('root'))Copy the code
Any expression can be embedded in {} of JSX. {{}} returns an object with an object literal inside {}. For example, inline styles should be written in the form of {{key:value}} :
<span style={{ color: 'red' }}>Hello,React</span>
Copy the code
4.3 comments in JSX
Using comments in JSX is also very simple, just using JavaScript, the only thing to note is that using comments in the child element of a component is wrapped in {}.
const element = (
/* Multi-line comments */
// Single-line comment
<h1 id="title">{/* Node comments */}<span>Hello,React</span>
</h1>
)
ReactDOM.render(element, document.getElementById('root'))
Copy the code
4.4 HTML escape in JSX
React escapes all strings to be displayed in the DOM to prevent XSS. So any HTML format will be escaped:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { title: '< h1 > title < / h1 >'}}render() {
return <div>{this.state.title}</div>
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
Expression inserts do not render a
to the page, but render it as text:
AngerouslySetInnerHTML React provides an alternative to the innerHTML provided by the browser DOM. DangerouslySetInnerHTML passes in an object, The __html attribute value of this object is equivalent to innerHTML of the element:
render() {
return <div dangerouslySetInnerHTML={{ __html: this.state.title}} ></div>
}
Copy the code
4.5. Custom HTML attributes in JSX
If an attribute used in JSX does not exist in the HTML specification, it is ignored. If you want to use custom attributes, you can use the data- prefix. The accessibility attribute prefix aria- is also supported.
ReactDOM.render(<div data-attr="abc">content</div>.document.getElementById('root'))
Copy the code
4.6. Fragment Labels
React.Fragment represents an empty tag. It allows multiple elements to be returned in the Render () method without creating additional DOM elements. Fragment can only accept key attributes:
import React from 'react'
class App extends React.Component {
render() {
return (
<React.Fragment key={1}>
<p>App</p>
</React.Fragment>)}}Copy the code
Fragment the Fragment syntax is <>, but the Fragment cannot use key:
<>
<p>App</p>
</>
Copy the code
Five, componentization
React allows code to be wrapped as components. Component can be a class Component or a function Component.
Components have three core concepts. The React component is basically composed of components’ external property states (PROPS), internal property states (States), and lifecycle methods. The diagram below:
The HTML structure generated by the component can only have a single root node. In React, data flows one way from top to bottom, from parent to child components.
There are two different methods for building React components: ES6 class and stateless function.
Use of React. CreateClass is not recommended after [email protected].
5.1. Write components in class mode
class Hello extends React.Component {
constructor(props) {
super(props)
}
render() {
return (<h1>Hello,React</h1>)
}
}
ReactDOM.render(<Hello />.document.getElementById('root'))
Copy the code
5.2. Stateless Components
A pure function can be used to define a stateless function, which has no state and no life cycle and simply receives props rendering to generate a DOM structure. Stateless components are very simple and have low overhead. For example, using a function definition:
function Person(props) {
return <h1>Hello, {props.name}</h1>
}
ReactDOM.render(<Person name="Zhang" />.document.getElementById('root'))
Copy the code
Sixth, the state
State is the current state (data) of the component. Once the state (data) changes, the component automatically calls Render to rerender the UI, which is triggered by the this.setState method.
Instead of modifying state directly, use the setState() method. If you change state directly, the component does not refire the Render method:
// Error, will not trigger render() to rerender
this.state.count = this.state.count + 1
Copy the code
6.1 introduction to setState() method
The setState() method, which takes an object or function as an argument to update state.
- Object as a parameter is passed in only the parts that need to be updated,
React
Shallow merge is automatically performed without passing in the entire object:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0.name: 'Joe'}}handleClick() {
// Change only count
this.setState({ count: this.state.count + 1})}render() {
return <div onClick={this.handleClick.bind(this)}>{this.state.count}</div>
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
- Function as an argument
prevState
andprops
Two parameters,state
The value of is returned by the object:
prevState
A:state
.props
: Updates when appliedprops
.
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}handleClick(e) {
this.setState((prevState, props) = > {
// return first state
return { count: this.state.count + 1}})this.setState((prevState, props) = > {
// The first state is the state of the previous one
console.log(prevState) / / 1
return { count: this.state.count + 1}})}render() {
return <div onClick={this.handleClick.bind(this)}>{this.state.count}</div>
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
6.2 setState() asynchronous update
The setState() method updates state asynchronously. React doesn’t change state right away. Instead, the object is placed on an update queue, and the new state is extracted from the queue and merged into state, which triggers the component update:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}handleClick(e) {
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) / / 0
}
render() {
return <div onClick={this.handleClick.bind(this)}>{this.state.count}</div>
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
When the handleClick event is triggered, the this.state.count output is 0.
One solution is that the setState(updater, [callback]) method can also pass in a callback function that will be called once setState() is done and the component is redrawn.
handleClick(e) {
this.setState({ count: this.state.count + 1 }, () = > {
console.log(this.state.count) / / 1})}Copy the code
6.3 setState() shallow merge
React.js may merge multiple setState() state changes into a single state change for performance reasons. So don’t rely on the current setState() to calculate the next State. A counter like this:
class AddCount extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}handleClick() {
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1})}render() {
return (
<div>{/* count outputs 1 */}<h2>{this.state.count}</h2>
<button onClick={this.handleClick.bind(this)}>add</button>
</div>)}}Copy the code
When the handleClick event is triggered, the page outputs 1. Although the setState() method is called twice. This is because when setState() is called to modify the component state, the update of the component state is actually a shallow merge, equivalent to:
Object.assign(
previousState,
{count: state.count + 1},
{count: state.count + 1},...).Copy the code
So use a function as the setState() argument if subsequent operations depend on the result of the previous setState(). React will pass the result of the last setState() to this function, which can operate on the last correct result, and return an object to update the state:
handleClick () {
this.setState({ count: this.state.count + 1 })
this.setState((prevState) = > {
/ / 1
console.log(prevState.count)
return { count: prevState.count + 1}}}})Copy the code
Pass the value updated by the last setState() operation to the next setState() to display count correctly.
6.4, State is Immutable
React officially recommends treating state as an Immutable object. All states contained in state should be Immutable. When a state in state changes, we should recreate the state object instead of directly modifying the original state.
When adding a name to name, use the concat method of the array or the ES6 extension operator:
const names = ['Joe']
/ / 1
this.setState(prevState= > ({
names: prevState.names.concat(['bill'])}))/ / 2
this.setState(prevState= > ({
names: [...prevState.names,'bill']}))Copy the code
Do not use methods like push, POP, Shift, unshift, splice, etc., to modify the state of an array type, because these methods modify the original array, while concat, slice, etc., return a new array.
If we have a state of the Object type person, to not change the original Object, we can use object. assign or extend the Object property:
const person = { age: 30 }
/ / 1
function updatePerson (person) {
return Object.assign({}, person, { age: 20})}/ / 2
function updatePerson (person) {
return{... person,age:20}}Copy the code
To create a new state object, avoid methods that directly modify the original object, and instead use methods that return a new object.
6.5. Modify deeply nested objects
Because setState() merges only the first level of an object’s attributes. If you want to modify a property within a multi-layer nested object, deconstruct it layer by layer as follows:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { person: { city: { cityName: 'Beijing'}}}}handleClick() {
this.setState((prevState) = > {
return {
person: {
...prevState.person,
city: {
...prevState.person.city,
cityName: 'Shanghai'}}}})}render() {
return (
<div>
<h2>{this.state.person.city.cityName}</h2>
<button onClick={this.handleClick.bind(this)}>Modify the city</button>
</div>)}}Copy the code
As you can see from the code above, as the state object structure gets deeper, changing the innermost state child node becomes more cumbersome to write.
We can come up with a simple solution to make a deep copy of a new object and then directly change the properties of the new object, such as using cloneDeep with LoDash:
handleClick() {
this.setState((prevState) = > {
const newState = _.cloneDeep(prevState)
newState.person.city.cityName = 'Shanghai'
return newState
})
}
Copy the code
However, this scheme has significant performance problems. No matter which property of the object you want to update, make a deep copy of the entire object each time. Deep copy can cause performance problems when objects are particularly large.
Another solution is to use Immutable libraries such as immer.js to simplify development. It avoids deep-copying all attributes and only modifies the target attributes.
/ / installation
npm i immer
Copy the code
When we call IMmer’s API Produce, the IMmer will temporarily store our target object internally. And expose a draft so we can make changes on the draft and then go back. Here’s a simple use:
import { produce } from 'immer'
handleClick() {
this.setState((prevState) = > {
return produce(prevState, (draftState) = > {
draftState.person.city.cityName = 'Shanghai'})})}Copy the code
6.6 Principles of setState(
-
Object mode can be used if the new state does not depend on the previous state.
-
If the new state depends on the way the function is used in the previous state.
-
If you need to get the latest state data in setState(), you can get it in the second callback function.
Seven, props
Components are independent and reusable. A component may be used in different places. The requirements for this component may vary in different scenarios, so pass in different configuration items for the same component.
The React props can do this. Each component can accept a props argument, which is an object that contains all the configurations for the component:
class Person extends React.Component {
render() {
const { name, age } = this.props
return (
<ul>
<li>{name}</li>
<li>{age}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="Zhang" age={20} />.document.getElementById('root'))
Copy the code
Component parameters are obtained internally by means of this.props. When using a component, all properties are used as the keys of the props object. We can also specify a default value using the static property of defaultProps:
class Person extends React.Component {
// Specify a default value
static defaultProps = {
name: 'bill'.age: 30
}
render() {
const { name, age } = this.props
return (
<ul>
<li>{name}</li>
<li>{age}</li>
</ul>
)
}
}
ReactDOM.render(<Person age={20} />.document.getElementById('root'))
Copy the code
If no name attribute is passed in, the default custom attribute value is used:
7.1. Checking the tag attribute type of props
Sometimes we need to restrict the type of props passed in. Starting with Act16, type checking was removed and split into another library. Need separate installation:
npm install prop-types
Copy the code
Or directly into the browser:
<script src="https://unpkg.com/[email protected]/prop-types.js"></script>
Copy the code
Here’s a simple little example:
class Person extends React.Component {
// Check the attributes
static propTypes = {
name: PropTypes.string.isRequired, // Name must be passed and is a string
getName: PropTypes.func // getName is a function because 'function' is the keyword, so use 'func'
}
render() {
const { name, age } = this.props
return (
<ul>
<li>{name}</li>
<li>{age}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="Zhang" age={20} />.document.getElementById('root'))
Copy the code
7.2. Props cannot be changed
Once passed in, props cannot be modified inside the component. However, the parent component can pass in new props by actively modifying the state re-rendering. As follows:
/ / child component
function Child(props) {
return <div>{props.name}</div>
}
/ / the parent component
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { name: 'Joe'}}// Change the name to render again
changeName() {
this.setState({ name: 'bill'})}render() {
return (
<div>
<Child name={this.state.name} />
<button onClick={this.changeName.bind(this)}>Modify the name</button>
</div>
)
}
}
ReactDOM.render(<Parent />.document.getElementById('root'))
Copy the code
Eight, Ref
React doesn’t fully satisfy all DOM manipulation requirements, and sometimes we still need to work with DOM. For example, the DOM API of input.focus() is invoked to automatically focus to an input field after entering the page. React provides several ways to get the DOM nodes of mounted elements.
Don’t abuse refs. For example, use it to manipulate the UI the traditional way: find the DOM -> update the DOM.
8.1. The format is a string
Specify a name by setting a ref attribute above the DOM element, and then access the corresponding DOM element through this.refs.name.
class MyComponent extends React.Component {
// Click to get focus
handleClick() {
this.refs.textInput.focus()
}
render() {
return (
<div>
<input ref="textInput" />
<button onClick={this.handleClick.bind(this)}>Click me</button>
</div>
)
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
It has been officially deprecated because of some problems with string refs. It is outdated and may be removed in a future release.
8.2. Callback form
The ref attribute sets this to a callback function that receives the current DOM element:
class MyComponent extends React.Component {
handleClick() {
this.textInput.focus()
}
render() {
return (
<div>
<input ref={(element)= > (this.textInput = element)} />
<button onClick={this.handleClick.bind(this)}>Click me</button>
</div>
)
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
If the ref callback is defined as an inline function, it is executed twice during the update process, passing in the argument NULL the first time, and then the argument DOM element the second time. This is because a new function instance is created each time it renders, so React clears the old ref and sets the new one. This can be avoided by defining the ref callback function as a class binding function, but in most cases it is irrelevant.
8.3. Form createRef()
The React. CreateRef () API, introduced in React 16.3, can also be used to retrieve DOM elements by calling createRef() and returning a container that can store the node identified by ref. The container can only hold one element node:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
handleClick() {
this.myRef.current.focus()
}
render() {
return (
<div>
<input ref={this.myRef} />
<button onClick={this.handleClick.bind(this)}>Click me</button>
</div>
)
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
Ref gets the component instance
A Ref can retrieve not only DOM elements, but also instances of child components. Here are three different ways to get child component instances:
import React, { createRef } from 'react'
/ / child component
class Child extends React.Component {
handleChild() {
console.log('Child Component')}render() {
return <div>The Child components</div>}}/ / the parent component
class Father extends React.Component {
constructor(props) {
super(props)
this.childRef3 = createRef()
}
componentDidMount() {
this.refs.childRef1.handleChild() //Child Component
this.childRef2.handleChild() //Child Component
this.childRef3.current.handleChild() //Child Component
}
render() {
return (
<div>
<Child ref="childRef1" />
<Child ref={(element)= > (this.childRef2 = element)} />
<Child ref={this.childRef3} />
</div>)}}Copy the code
Ix. Event handling
React binds events in a similar way to HTML, but with a camel name. Here is a small example of a sum of numbers:
class AddCount extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}handleClick(e) {
this.setState({ count: this.state.count + 1})}render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick.bind(this)}>Click me</button>
</div>
)
}
}
ReactDOM.render(<AddCount />.document.getElementById('root'))
Copy the code
Note that you explicitly call bind(this) to bind the event function context to the component instance.
Collect form data
React processing forms can be managed by managed and uncontrolled components. The following two methods are introduced respectively.
10.1 controlled Components
In React, form elements are maintained through the component’s state property. And calls setState() to update the data based on user input. The form input elements that React controls their values in this way are called controlled components. Here is an example of changing a user name:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { userName: 'init userName'}}// Change the user name
changeUsername(e) {
this.setState({ userName: e.target.value })
}
/ / submit
handleSubmit(e) {
e.preventDefault()
const { userName } = this.state
console.log('Your username is:${userName}`)}render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text" value={this.state.userName} name="username" onChange={this.changeUsername.bind(this)} />
<button type="submit">submit</button>
</form>
)
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
In React, data flows in one direction. The data for the form comes from the component’s state and is passed in through props, which is also known as one-way data binding. We then write the new form data back to the component’s state via the onChange event handler, completing two-way data binding.
React-controlled components update state
- By starting with
state
To set the default values for the form. - Called whenever the value of the form changes
onChange
Event handler. - Get the latest value from the event handler and use
setState()
updatestate
. setState()
Triggers a re-rendering of the view to complete the update of the form component values.
In most cases, we still use controlled components to process form data.
10.2 Uncontrolled Components
An uncontrolled component is an input class element that does not maintain data through state and uses ref to retrieve data from a DOM node:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.userNameNode = React.createRef()
}
handleSubmit(e) {
e.preventDefault()
console.log('Your username is:The ${this.userNameNode.current.value}`)}render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text" name="username" ref={this.userNameNode} />
<button type="submit">submit</button>
</form>
)
}
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
Component life cycle
Each component in React contains component lifecycle methods that are executed at specific phases of the runtime.
Component lifecycles fall into four categories:
- Mount (initialization) phase.
- Update phase.
- Destruction phase.
- Error phase.
Some lifecycle methods have been deprecated or modified due to React version issues. The component lifecycle is described in the following sections based on the version.
React Lifecycle before React 16.3
To give you an overview of the old life cycle, take a look:
Mount the stage
When a component instance is created and inserted into the DOM, the component lifecycle is invoked in the following order:
constructor()
Constructor call.componentWillMount()
: called before the component is about to be mounted.render()
: Initializes rendering.componentDidMount()
: After the component is mounted (insertDOM
Tree) immediately called.
class LifeCycle extends React.Component {
constructor(props) {
super(props)
console.log('1. construct')}componentWillMount() {
console.log('2. componentWillMount')}render() {
console.log('3. render')
return <div>React Old life cycle</div>
}
componentDidMount() {
console.log('4. componentDidMount')
}
}
ReactDOM.render(<LifeCycle />.document.getElementById('root'))
Copy the code
The component mount phase is output as follows:
1. construct
2. componentWillMount
3. render
4. componentDidMount
Copy the code
Update the stage
The update phase of a component is the process by which setState() causes React to re-render the component and apply its changes to DOM elements. React also provides a series of lifecycle functions that allow you to perform operations during component updates. Updates are triggered when a component’s state changes. Component updates are called in the lifecycle order as follows:
shouldComponentUpdate()
: whenprops
orstate
Called when changes occur.componentWillUpdate()
: called before the component begins rerendering.render()
: Re-render after update.componentDidUpdate()
: Components are re-rendered and changed to realDOM
Call later.
Note that shouldComponentUpdate() uses this method to control whether or not the component is rerendered. The default returns true, meaning the component is rerendered each time it changes. If this function returns false, the component will not be rerendered. This life cycle is useful for React performance tuning (more on that later).
class LifeCycle extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}handleClick() {
this.setState({ count: this.state.count + 1})}shouldComponentUpdate() {
console.log('1. shouldComponentUpdate')
return true
}
componentWillUpdate() {
console.log('2. componentWillUpdate')}render() {
console.log('3. render')
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick.bind(this)}>Click me</button>
</div>)}componentDidUpdate() {
console.log('4. componentDidUpdate')
}
}
ReactDOM.render(<LifeCycle />.document.getElementById('root'))
Copy the code
When the handleClick click event is triggered, the output is as follows:
1. shouldComponentUpdate
2. componentWillUpdate
3. render
4. componentDidUpdate
Copy the code
forceUpdate()
We can also re-render components using forceUpdate(). Forcing updates skips shouldComponentUpdate(). ForceUpdate () should be avoided in general.
Updates to props changes
If you have a father and son components, pass an argument by props, the child component lifecycle update will add a componentWillReceiveProps () life cycle. This is called before the child component receives the new props from the parent component.
ComponentWillReceiveProps (nextProps) receives a parameter, the parameter is updated props objects:
/ / the parent component
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { name: 'Joe'}}changeName() {
this.setState({ name: 'bill'})}render() {
return (
<div>
<Child name={this.state.name} />
<button onClick={this.changeName.bind(this)}>Modify the name</button>
</div>)}}/ / child component
class Child extends React.Component {
componentWillReceiveProps() {
console.log('1. componentWillReceiveProps')}shouldComponentUpdate() {
console.log('2. shouldComponentUpdate')
return true
}
componentWillUpdate() {
console.log('3. componentWillUpdate')}render() {
console.log('4. render')
return <div>I am a child component and I receive {this.props. Name}</div>
}
componentDidUpdate() {
console.log('5. componentDidUpdate')
}
}
ReactDOM.render(<Parent />.document.getElementById('root'))
Copy the code
When a click event is triggered, it is printed as follows:
1. componentWillReceiveProps
2. shouldComponentUpdate
3. componentWillUpdate
4. render
5. componentDidUpdate
Copy the code
Initialize the transfer props will not perform when componentWillReceiveProps (). This method is called if the parent causes the component to be re-rendered, even if the props have not changed.
Unloading phase
The following component lifecycle is invoked when a component is removed from the DOM:
componentWillUnmount()
: called directly before the component is uninstalled and destroyed.
class LifeCycle extends React.Component {
handleClick() {
/ / unloading
ReactDOM.unmountComponentAtNode(document.getElementById('root'))}render() {
return <button onClick={this.handleClick.bind(this)}>Component uninstall</button>
}
componentWillUnmount() {
console.log('1. componentWillUnmount')}}const App = () = > <LifeCycle />
export default App
Copy the code
When the click event handleClick is triggered, it prints:
1. componentWillUnmount
Copy the code
React v16.3 New life cycle
To get an overview of the new life cycle, start with an image:
The new version of the lifecycle has three methods renamed:
componentWillMount()
Change toUNSAFE_componentWillMount()
componentWillReceiveProps()
Change toUNSAFE_componentWillReceiveProps()
componentWillUpdate
Change toUNSAFE_componentWillUpdate()
The UNSAFE_ prefix must be added after the new version, otherwise the three health hooks may not be available.
The new version adds two static method lifecycles:
static getDerivedStateFromProps()
static getSnapshotBeforeUpdate()
static getDerivedStateFromProps()
GetDerivedStateFromProps () means to get state from props and map the passed props to state.
GetDerivedStateFromProps () is called both during the initial mount and during subsequent updates.
State must be initialized when using getDerivedStateFromProps(). It should return an object to update state, and if null is returned, nothing is updated.
GetDerivedStateFromProps (nextProps,nextState) accepts two arguments:
nextProps
: the latestprops
nextState
: the lateststate
This function is not affected when null is returned:
class LifeCycle extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}static getDerivedStateFromProps(props, state) {
return null
}
render() {
return <div>{this.state.count}</div>
}
}
ReactDOM.render(<LifeCycle />.document.getElementById('root'))
Copy the code
When returning props as a state object, the value of the state in the component depends on the value of props at any time:
class LifeCycle extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}// The state value in the component depends on props, the setting value is invalid
handleClick() {
this.setState({ count: this.state.count + 1})}static getDerivedStateFromProps(props, state) {
return props // Return props:{count: 100}
}
render() {
return <div onClick={this.handleClick.bind(this)}>Count: {this. State. The count}</div>
}
}
ReactDOM.render(<LifeCycle count={100} />.document.getElementById('root'))
Copy the code
Count ={100} is passed as props, which is returned by getDerivedStateFromProps instead of this. State ={count: 0} :
static getSnapshotBeforeUpdate()
GetSnapshotBeforeUpdate () is called before the last render output (committed to the DOM node) to get the snapshot before the update.
Must return a Snapshot value(any value) or NULL. The returned value is passed as an argument to the componentDidUpdate() hook function.
You must also define the componentDidUpdate() hook function when using getSnapshotBeforeUpdate().
The componentDidUpdate(prevProps, prevState, snapShotValue) function accepts three arguments:
prevProps
A:props
prevState
A:state
snapShotValue
:getSnapshotBeforeUpdate()
Snapshot value returned.
The Snapshot value returned by getSnapshotBeforeUpdate() is received by the componentDidUpdate() method:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}handleClick(e) {
this.setState({ count: this.state.count + 1})}// Take the snapshot before the update
getSnapshotBeforeUpdate() {
return 'Joe'
}
componentDidUpdate(prevProps, prevState, snapShotValue) {
console.log(snapShotValue) / / zhang SAN
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick.bind(this)}>Click me</button>
</div>
)
}
}
ReactDOM.render(<MyComponent count={100} />.document.getElementById('root'))
Copy the code
You can use getSnapshotBeforeUpdate() to implement a case where the current position is always positioned while the list height continues to increase:
class News extends React.Component {
constructor(props) {
super(props)
this.state = { news: []}this.newWrapRef = React.createRef()
}
componentDidMount() {
// Generates one news per second
setInterval(() = > {
const { news } = this.state
const oneNew = 'news' + news.length
this.setState({ news: [oneNew, ...news] })
}, 1000)}render() {
return (
<ul ref={this.newWrapRef} style={{ height: '180px', border: '1px solid red', overflow: 'auto' }}>
{this.state.news.map((item, index) => {
return (
<li key={index} style={{ height: '30px' }}>
{item}
</li>)})}</ul>)}getSnapshotBeforeUpdate() {
// Get the container's scrollHeight before rerendering
return this.newWrapRef.current.scrollHeight
}
componentDidUpdate(prevProps, prevState, snapshotValue) {
// The new scrollHeight minus the previous scrollHeight
const newScrollHeight = this.newWrapRef.current.scrollHeight - snapshotValue
// Reset the latest scrollTop
this.newWrapRef.current.scrollTop += newScrollHeight
}
}
ReactDOM.render(<News count={100} />.document.getElementById('root'))
Copy the code
Mount the stage
When a component instance is created and inserted into the DOM, the component lifecycle is invoked in the following order:
constructor()
Constructor call.getDerivedStateFromProps()
: called during initial mount and subsequent updates.render()
: Initializes rendering.componentDidMount()
: After the component is mounted (insertDOM
Tree) immediately called.
class LifeCyCle extends React.Component {
constructor(props) {
super(props)
this.state = {}
console.log('1. constructor')}static getDerivedStateFromProps() {
console.log('2. getDerivedStateFromProps')
return null // There must be a return value
}
render() {
console.log('3. render')
return <div>React New life cycle</div>
}
componentDidMount() {
console.log('4. componentDidMount')
}
}
ReactDOM.render(<LifeCyCle />.document.getElementById('root'))
Copy the code
The component mount phase is output as follows:
1. constructor
2. getDerivedStateFromProps
3. render
4. componentDidMount
Copy the code
Update the stage
Component updates are called in the lifecycle order as follows:
getDerivedStateFromProps()
: is called during the initial mount and subsequent updates.shouldComponentUpdate()
: whenprops
orstate
Called when changes occur.render()
Render after status update.getSnapshotBeforeUpdate()
: called before the last render output (committed to the DOM node).componentDidUpdate()
: Components are re-rendered and changed to realDOM
Call later.
class LifeCycle extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0}}handleClick() {
this.setState({ count: this.state.count + 1})}static getDerivedStateFromProps() {
console.log('1. getDerivedStateFromProps')
return null
}
shouldComponentUpdate() {
console.log('2. shouldComponentUpdate')
return true
}
render() {
console.log('3. render')
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick.bind(this)}>Click me</button>
</div>)}getSnapshotBeforeUpdate() {
console.log('4. getSnapshotBeforeUpdate')
return null
}
componentDidUpdate() {
console.log('5. componentDidUpdate')
}
}
ReactDOM.render(<LifeCycle />.document.getElementById('root'))
Copy the code
When the click event is triggered, handleClick prints the following:
1. getDerivedStateFromProps
2. shouldComponentUpdate
3. render
4. getSnapshotBeforeUpdate
5. componentDidUpdate
Copy the code
Unloading phase
The life cycle of the destruction phase of the new and old versions has not changed.
12. False boundaries
JavaScript errors in part of the UI should not crash the entire app, but React 16 introduces a new concept called Error Boundaries. Error boundaries are a React component that can be used to catch descendant component errors and render alternate pages. It can only catch errors that occur in the life cycle of subsequent components.
If the child component gets an error, the parent component needs to render the standby UI with getDerivedStateFromError() and print the error message with componentDidCatch(). Here is an example of a parent-child component:
import React from 'react'
/ / child component
class Child extends React.Component {
constructor(props) {
super(props)
this.state = { userList: 'abc'}}render() {
return (
<div>
<h2>I am the Child component</h2>} {this.state.userlist.map ((item) => {return<span key={item.id}>{item.name}</span>
})}
</div>)}}/ / the parent component
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false}}// If an error occurs in a child component, a call to getDerivedStateFromError is raised with an error message in the argument
static getDerivedStateFromError(error) {
console.log(error)
return { hasError: error } // An error message needs to be returned
}
componentDidCatch() {
// Error logs can be reported to the server
}
render() {
return (
<div>
<h2>I'm the Parent component</h2>} {this.state.haserror?<span>There is an error</span> : <Child />}
</div>)}}Copy the code
PureComponent Optimization
When the parent component’s data is modified and re-rendered, the child component will also re-render without using any of the parent’s data:
import React from 'react'
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { name: 'Joe'}}handleChangeName(e) {
this.setState({ name: 'bill'})}render() {
console.log('Parent render')
return (
<div>
<button onClick={this.handleChangeName.bind(this)}>Modify the name</button>
<Child />
</div>)}}class Child extends React.Component {
render() {
console.log('Child render')
return <div>I am the Child component</div>}}Copy the code
Child Render is output when the handleChangeName() method is triggered by clicking on the button. Any data that is not used by the child component is rerendered.
One other thing to note is that the component is also re-rendered when this.setState({}) is called with no value.
ShouldComponentUpdate () we can use shouldComponentUpdate() to optimize, with the parent comparing state and the child comparing props to determine whether to re-render:
import React from 'react'
/ / the parent component
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { name: 'Joe'}}handleChangeName(e) {
this.setState({ name: 'bill'})}/ * * *@param {*} NextProps Latest props *@param {*} NextState The latest state */
shouldComponentUpdate(nextProps, nextState) {
// State's name attribute does not need to be rerendered if it does not change
return! (this.state.name === nextState.name)
}
render() {
console.log('Parent render')
return (
<div>
<button onClick={this.handleChangeName.bind(this)}>Modify the name</button>
<Child name={this.state.name} />
</div>)}}/ / child component
class Child extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// The name property of props does not need to be re-rendered if it does not change
return! (this.props.name === nextProps.name)
}
render() {
console.log('Child render')
return <div>I am Child component: {this.props. Name}</div>}}Copy the code
As you can see from the above code, if the object has multiple attributes, you also need to compare each attribute. This is a very cumbersome way to write a project. React provides the React.PureComponent. The react. PureComponent implements shouldComponentUpdate as a shallow comparison between prop and state.
import React from 'react'
class Parent extends React.PureComponent {
constructor(props) {
super(props)
this.state = { name: 'Joe'}}handleChangeName(e) {
this.setState({ name: 'bill'})}render() {
console.log('Parent render')
return (
<div>
<button onClick={this.handleChangeName.bind(this)}>Modify the name</button>
<Child name={this.state.name} />
</div>)}}class Child extends React.PureComponent {
render() {
console.log('Child render')
return <div>I am Child component: {this.props. Name}</div>}}Copy the code
ShouldComponentUpdate () in react.pureComponent is only a superficial comparison of objects.
If the object contains complex data structures, it is possible to generate false comparisons because you cannot examine the underlying differences.
Use React.PureComponent only when props and state are simple, or call forceUpdate() when the underlying data structure changes to ensure that the component is updated properly.
Don’t modify the data directly, regenerate it.
14, Render Props
A component with Render Prop takes a function that returns the React element and implements its own rendering logic within the component by calling this function.
If you want to write something to a component tag, you need to get the tag body from the component props. Children.
import React from 'react'
/ / the parent component
class Parent extends React.Component {
render() {
return (
<div>
<p>I'm the Parent component</p>
<Child>Hello!</Child>
</div>)}}/ / child component
class Child extends React.Component {
render() {
return (
<div>
<p>I am the Child component</p>
{this.props.children}
</div>)}}Copy the code
The Child component will print Hello:
Let’s say there is a requirement for nested components within components to form parent-child relationships like the following:
class App extends React.Component {
render() {
return (
<B>
<C></C>
</B>)}}Copy the code
To pass the state data of B to C, we can use this.props. Render to call our Render function:
import React from 'react'
class App extends React.Component {
render() {
return (
<div>
<p>I'm an App component</p>{/* Write the render callback and receive the data */}<B render={(name)= > <C name={name} />} / ></div>)}}/ / child component
class B extends React.Component {
constructor(props) {
super(props)
this.state = { name: 'Joe'}}render() {
return (
<div>
<p>I'm component B</p>{/* Call the render() method of props and pass the data */} {this.props. Render (this.state.name)}</div>)}}/ / sun components
class C extends React.Component {
render() {
return (
<div>
<p>I am C component, and I get name from B component: {this.props. Name}</p>
</div>)}}Copy the code
C Props = Props (); C Props = Props ();
Render Props are similar to Vue’s slot technology.
React Scaffolding
Create-react-app, similar to vue-cli for Vue:
npm install -g create-react-app
// Create a new project with create-react-app
create-react-app my-react-app
Copy the code
After the installation is successful, NPM start or YARN start can start the project.
Package. json Install the React dependencies as follows:
As you can see from package.json dependencies, the scaffolding tool installs dependencies required by React by default. Here’s how these key core dependencies work:
react
Is:React
Core library.react-dom
: be responsible forWeb
Of the pageDOM
Operation.react-scripts
: Generates all dependencies for the project. For example,babel
.css-loader
.webpack
And so on from development to packaging front-end engineering needsreact-scripts
It’s all done for us.
Xvi. TodoList function
Here we can create a new project using the create-react-app scaffolding todo a todoList function:
Since we haven’t touched on state management yet, we can pass data between sibling components and implement functionality via parent components. First, define the following four components:
Header
Components.Item
Component that represents each item in the list.List
List components.Footer
Components.
App.js
/* app.css */
#root {
display: flex;
justify-content: center;
}
.todo-container {
margin-top: 20px;
min-height: 240px;
min-width: 420px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 20px;
box-sizing: border-box;
}
Copy the code
// App.js
import React from 'react'
import Header from './components/Header'
import List from './components/List'
import './app.css'
import Footer from './components/footer'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
list: [{ id: 1.name: 'JS'.isChecked: false}}}]/ / add a todo
addTodo(todo) {
const newList = [todo, ...this.state.list]
this.setState({ list: newList })
console.log(todo)
}
/ / modify todo
editTodo(item, isChecked) {
const newTodo = this.state.list.map((el) = > {
return el.id === item.id ? { ...el, isChecked } : el
})
this.setState({ list: newTodo })
}
/ / delete todo
deleteTodo(item) {
const newTodo = this.state.list.filter((el) = >el.id ! == item.id)this.setState({ list: newTodo })
}
// Delete all selections
deleteChecked() {
const newTodos = this.state.list.filter((el) = >! el.isChecked)this.setState({ list: newTodos })
}
// Delete all elements
deleteCheckedAll() {
this.setState({ list: []})}// Select all and reverse
changeTodoAll(isChecked) {
const newTodos = this.state.list.map((el) = > {
return { ...el, isChecked }
})
this.setState({ list: newTodos })
}
render() {
return (
<div className="todo-container">
<Header addTodo={this.addTodo.bind(this)} />
<List list={this.state.list} editTodo={this.editTodo.bind(this)} deleteTodo={this.deleteTodo.bind(this)} />
<Footer list={this.state.list} deleteChecked={this.deleteChecked.bind(this)} deleteCheckedAll={this.deleteCheckedAll.bind(this)} changeTodoAll={this.changeTodoAll.bind(this)} />
</div>)}}export default App
Copy the code
The Header component
/* components/Header/index.css */
.header {
margin-bottom: 20px;
}
.header input {
box-sizing: border-box;
padding: 8px;
width: 100%;
border: 1px solid #ccc;
}
.header input:focus {
outline: none;
border-color: #409eff;
}
Copy the code
// components/Header/index.jsx
import React from 'react'
import './index.css'
export default class Header extends React.Component {
// Enter to add elements
handleKeyUp(e) {
if(e.keyCode ! = =13) return
const val = e.target.value
if (val.trim() === ' ') return
const todo = {
id: Math.random(), // Random numbers are simply used here and are not recommended in actual development
name: val,
isChecked: false
}
// Call the function passed by the parent component props to add data
this.props.addTodo(todo)
e.target.value = ' '
}
render() {
return (
<div className="header">{/* Input box */}<input onKeyUp={this.handleKeyUp.bind(this)} type="text" autoComplete="off" name="myInput" placeholder="Please enter the task name and press Enter to confirm." />
</div>)}}Copy the code
The List component
/* components/List/index.css */
.list-container {
border: 1px solid #ccc;
padding: 20px;
}
Copy the code
// components/List/index.jsx
import React from 'react'
import Item from '.. /Item'
import './index.css'
export default class List extends React.Component {
render() {
const { list, editTodo, deleteTodo } = this.props
return (
<div className="list-container">{list.map((item) => {/* Uses the item component */ return<Item key={item.id} item={item} editTodo={editTodo} deleteTodo={deleteTodo} />
})}
</div>)}}Copy the code
The Item component
// components/Item/index.jsx
import React from 'react'
export default class Item extends React.Component {
constructor(props) {
super(props)
this.state = {
isShowBtn: false}}// Mouse over the display
handleMouseEnter(e) {
this.setState({ isShowBtn: true})}// Mouse remove hide
handleMouseLeave(e) {
this.setState({ isShowBtn: false})}handleChange(e, item) {
// Notify the parent component to change the status
this.props.editTodo(item, e.target.checked)
}
/ / delete
handleDelete(e, item) {
this.props.deleteTodo(item)
}
render() {
const { item } = this.props
const { isShowBtn } = this.state
return (
<div
onMouseEnter={this.handleMouseEnter.bind(this)}
onMouseLeave={this.handleMouseLeave.bind(this)}
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0'}} >
<div>
<input type="checkbox" id={item.id} checked={item.isChecked} onChange={(e)= > this.handleChange.call(this, e, item)} />
<label style={{ paddingLeft: '10px'}}htmlFor={item.id}>
{item.name}
</label>
</div>
<button onClick={(e)= >this.handleDelete.call(this, e, item)} style={{ display: isShowBtn ? 'Block' : 'None'}}> Delete</button>
</div>)}}Copy the code
Footer component
// components/Footer/index.jsx
import React from 'react'
export default class Footer extends React.Component {
constructor(props) {
super(props)
this.state = {}
}
handleChange(e) {
// Notify the parent element of the modification
this.props.changeTodoAll(e.target.checked)
}
render() {
const { list, deleteChecked, deleteCheckedAll } = this.props
// The selected list
const checkeds = list.filter((item) = > item.isChecked)
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: '20px' }}>
<div>
<input type="checkbox" checked={checkeds.length= = =list.length && list.length! = =0} onChange={this.handleChange.bind(this)} name="all" />
<span>Completed {checkeds.length} / all {list.length}</span>
</div>
<div>
<button onClick={deleteChecked} style={{ marginRight: '5px' }}>Delete all selected</button>
<button onClick={deleteCheckedAll}>Delete all</button>
</div>
</div>)}}Copy the code
17, Redux
Redux is a library for state management (not the official React library). It stores the entire application state in the Store. A component can dispatch actions to a store. Other components can refresh their views by subscribing to states in the store:
The whole Redux can be considered as a restaurant, with React Components as a table of guests, Action Creators as a server, Store as a lobby manager, and Reducers as a back chef in charge of cooking. The guest orders food to the waiter, the waiter notifies the lobby manager, manager Tang notifies the back kitchen to cook, and sends the food to the table through the lobby manager:
action
Action represents the action object. It contains two attributes:
type
: Identifies an attribute. The value is a string and unique.data
: Data to be transmitted.
store
Store stores state (sets of data). And link state, Action and Reducer together. There is only one Store object for the entire application.
reducer
Reducer is used to initialize state and process state. When processing states, a new state pure function is generated based on the old state and action.
Installation:
npm install --save redux
Copy the code
Here is a small example of addition and subtraction:
Create redux/store.js first:
// redux/store.js
// createStore is introduced to create the core store object in Redux
import { createStore } from 'redux'
// Define a constant value of type type
export const INCREMENT = 'increment' // 加1
export const DECREMENT = 'decrement' / / minus 1
/** * create action */
export const actions = {
increment(data) {
return { type: INCREMENT, data }
},
decrement(data) {
return { type: DECREMENT, data }
}
}
/** * Create reducer *@param {*} PreState Indicates the previous state *@param {*} Action Action object *@returns* /
function countReducer(preState = 0, action) {
/** * Get type and data * type from the action object. The default initialization is similar to @@redux/ initc.q. 3. O.K.G a random value */
const { type, data } = action
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
// Create a store and pass in the corresponding countReducer
const store = createStore(countReducer)
export default store
Copy the code
Then use it in app.js:
import React from 'react'
// Introduce store and Action
import store, { actions } from './redux/store'
class App extends React.Component {
/** * Redux does not render the page by default after calling an action with dispatch to change the state. It listens for state changes with store.subscribe() */
componentDidMount() {
// Listen for state changes in redux, and call setState() to re-render the page whenever it changes
store.subscribe(() = > {
this.setState({})
})
}
increment() {
store.dispatch(actions.increment(1))}decrement() {
store.dispatch(actions.decrement(1))}render() {
return (
<div>
<h1>{store.getState()}</h1>
<button onClick={this.increment.bind(this)}>Add 1</button>
<button onClick={this.decrement.bind(this)}>Minus 1</button>
</div>)}}export default App
Copy the code
17.1. Asynchronous Action
Actions can return functions as well as objects. Synchronous actions for objects and asynchronous actions for functions.
If you want to use asynchronous methods in your Actions. Redux-thunk middleware is required:
/ / installation
npm install -S redux-thunk
Copy the code
After installation, use the applymiddleware() middleware using Redux as the second parameter to createStore() :
// Introduce react-thunk to support asynchronous actions
import thunk from 'redux-thunk'
// Pass the corresponding countReducer and redux-thunk
const store = createStore(countReducer, applyMiddleware(thunk))
Copy the code
Here is a small example of asynchronous addition and subtraction:
Create redux/store.js first:
// createStore is introduced to create the core store object in Redux
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
// Define a constant value of type type
export const INCREMENT = 'increment' // 加1
export const DECREMENT = 'decrement' / / minus 1
/** * create action */
export const actions = {
increment(data) {
return { type: INCREMENT, data }
},
decrement(data) {
return { type: DECREMENT, data }
},
incrementAsync(data) {
return (dispatch) = > {
setTimeout(() = > {
dispatch(this.increment(data)) // Synchronous actions are usually called in asynchronous actions
}, 500)}},decrementAsync(data) {
return (dispatch) = > {
setTimeout(() = > {
dispatch(this.decrement(data)) // Synchronous actions are usually called in asynchronous actions
}, 500)}}}/** * Create reducer *@param {*} PreState Indicates the previous state *@param {*} Action Action object *@returns* /
function countReducer(preState = 0, action) {
/** * Get type and data * type from the action object. The default initialization is similar to @@redux/ initc.q. 3. O.K.G a random value */
const { type, data } = action
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
// Create a store and pass in countReducer and applyMiddleware
const store = createStore(countReducer, applyMiddleware(thunk))
export default store
Copy the code
Then use it in app.js:
import React from 'react'
// Introduce store and Action
import store, { actions } from './redux/store'
class App extends React.Component {
/** * Redux does not render the page by default after calling an action with dispatch to change the state. It listens for state changes with store.subscribe() */
componentDidMount() {
// Listen for state changes in redux, and call setState() to re-render the page whenever it changes
store.subscribe(() = > {
this.setState({})
})
}
incrementAsync() {
store.dispatch(actions.incrementAsync(1))}decrementAsync() {
store.dispatch(actions.decrementAsync(1))}render() {
return (
<div>
<h1>{store.getState()}</h1>
<button onClick={this.incrementAsync.bind(this)}>Asynchronous plus 1</button>
<button onClick={this.decrementAsync.bind(this)}>Asynchronous minus 1</button>
</div>)}}export default App
Copy the code
Eighteen, react – story
React redux allows you to use Redux in React.
React-redux automatically rerenders pages by listening for changes in data, instead of using store. Subscribe () to listen for changes in data and manually coding to render pages. Install first:
# If you use npm:
npm install react-redux
# Or if you use Yarn:
yarn add react-redux
Copy the code
- All of the
UI
Components should wrap a container component, and they are parent-child. - Container components are true and
redux
Interactive and can be used freelyredux
theapi
. - You cannot use any in UI components
redux
theapi
. - Container components pass to components
redux
The saved state and the method used to manipulate the state, throughprops
Pass.
The connect() method is used to connect UI components to redux.
connect(mapStateToProps? , mapDispatchToProps? , mergeProps? , options?) Four parameters are received. Only the first two parameters are described here:
-
MapStateToProps: Function, representing the state saved in redux, returns an object in which the key is the key passed to the UI component props, and the value is the value passed to the UI component props.
-
MapDispatchToProps: the value of the Function | Object, indicates the operating status in a redux method, returns an Object, the key Object is passed to the UI components props key, the value is passed to the UI component value of props.
-
MergeProps: Value is Function.
-
Options: The value is Object.
Here’s a quick example of adding and subtracting using react-redux:
Create redux/store.js first:
// createStore is introduced to create the core store object in Redux
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
// Define a constant value of type type
export const INCREMENT = 'increment' // 加1
export const DECREMENT = 'decrement' / / minus 1
/** * create action */
export const actions = {
increment(data) {
return { type: INCREMENT, data }
},
decrement(data) {
return { type: DECREMENT, data }
}
}
/** * Create reducer *@param {*} PreState Indicates the previous state *@param {*} Action Action object *@returns* /
function countReducer(preState = 0, action) {
/** * Get type and data * type from the action object. The default initialization is similar to @@redux/ initc.q. 3. O.K.G a random value */
const { type, data } = action
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
const store = createStore(countReducer, applyMiddleware(thunk))
export default store
Copy the code
Create a new Count container component containers/ count.jsx:
// Introduce the UI component of Count
import CountUI from '.. /components/Count'
/ / into actions
import { actions } from '.. /redux/store'
// Introduce connect to connect UI components to redux
import { connect } from 'react-redux'
/ * * *@param {*} State: State saved by redux. React-redux automatically passes */
const mapStateToProps = function (state) {
return { count: state }
}
/ * * *@param {*} Dispatch redux's dispatch method, which is automatically passed */ by react-redux
const mapDispatchToProps = function (dispatch) {
return {
incr: (data) = > dispatch(actions.increment(data)),
decr: (data) = > dispatch(actions.decrement(data))
}
}
// Create and expose a Count container component with connect()()
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
Copy the code
Note that the short for mapDispatchToProps can also pass in an object, and react-redux will automatically call dispatch() for us:
const mapDispatchToProps = {
incr: actions.increment,
decr: actions.decrement
}
Copy the code
Then create a new UI component components/ count.jsx:
import React from 'react'
// Introduce store and Action
class Count extends React.Component {
componentDidMount() {
console.log(this.props) / / {store: {... }, count: 0, incr: ƒ, decr: ƒ}
}
increment() {
console.log(this.props)
this.props.incr(1)}decrement() {
this.props.decr(1)}render() {
return (
<div>
<h1>{this.props.count}</h1>
<button onClick={this.increment.bind(this)}>Add 1</button>
<button onClick={this.decrement.bind(this)}>Minus 1</button>
</div>)}}export default Count
Copy the code
In app.js, the store needs to be passed to the container component via props:
import React from 'react'
/ / into the store
import store from './redux/store'
// Introduce the Count container
import Count from './containers/Count'
class App extends React.Component {
render() {
// Pass store to the container component
return <Count store={store} />}}export default App
Copy the code
By using React-Redux, we write all the code that interacts with the Redux state into the container component, associating them with connect(), and finally the UI component only needs to be invoked via props.
18.1, the Provider
A Provider enables all components to receive a store and bind to the component as props.
If we want to use components many times, each component passes the store, as follows:
import store from './redux/store'
class App extends React.Component {
render() {
return (
<div>
<Count store={store} />
<Count store={store} />
<Count store={store} />
<Count store={store} />
<Count store={store} />
<Count store={store} />
<Count store={store} />
<Count store={store} />
<Count store={store} />
</div>)}}Copy the code
You can pass store to the Provider, and store is automatically passed to the container component:
import { Provider } from 'react-redux'
import store from './redux/store'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
</React.StrictMode>.document.getElementById('root'))Copy the code
The container component does not need to pass the store manually:
import store from './redux/store'
class App extends React.Component {
render() {
return (
<div>
<Count />
<Count />
<Count />
<Count />
<Count />
<Count />
<Count />
<Count />
</div>)}}Copy the code
18.2. Integrate UI components and container components
If there are 100 UI components in the project that need container components, then we need to create 100 container components. This will multiply the file. We can combine UI components and container components in one file:
// Container/Count.jsx
import { actions } from '.. /redux/store'
import { connect } from 'react-redux'
import React from 'react'
/ / UI components
class Count extends React.Component {
componentDidMount() {
console.log(this.props) / / {store: {... }, count: 0, incr: ƒ, decr: ƒ}
}
increment() {
console.log(this.props)
this.props.incr(1)}decrement() {
this.props.decr(1)}render() {
return (
<div>
<h1>{this.props.count}</h1>
<button onClick={this.increment.bind(this)}>Add 1</button>
<button onClick={this.decrement.bind(this)}>Minus 1</button>
</div>)}}// Export the container component
export default connect(
// mapStateToProps
(state) = > {
return { count: state }
},
// mapDispatchToProps
{
incr: actions.increment,
decr: actions.decrement
}
)(Count)
Copy the code
18.3. Data sharing between components
The redux features introduced earlier are relatively simple. The data in redux is just a single basic data type. The actual development will use a lot of data, we need to merge the redux data, we need to store the object {}. You can use the combineReducers() method to implement the merge:
/ / introduce combineReducers
import { combineReducers } from 'redux'
/** ** Summarize all reducers by using combineReducers() and merge the results returned by reducer * into a state object. {a: aReducer,b: bReducer} */
const allReducer = combineReducers({
a: aReducer,
b: bReducer
})
Copy the code
We also need a clearer division of functionality, as follows:
├ ─ ─ redux ├ ─ ─ the actions// All actions└ ─ ─ personAction. Js// Customize the Person Action└ ─ ─ bookAction. Js// Customize the book action├ ─ ─ reducers// All reducers└ ─ ─ index. JsCompile all reducer files└ ─ ─ countReducer. Js// Customize the person reducer└ ─ ─ bookReducer. Js// Customize the book reducer├ ─ ─ constant. Js/ / constant├ ─ ─ store. Js// store
Copy the code
Here is an example of how the Person and Book components implement that one Person can have more than one Book:
Write a constant
/** * type Constant value */
export const ADD_PERSON = 'addPerson' // Add a person
export const ADD_BOOK = 'addBook' // Add a book
Copy the code
Write the Action:
// redux/actions/bookAction
import { ADD_BOOK } from '.. /constant'
export const addBookAction = (data) = > {
return { type: ADD_BOOK, data }
}
Copy the code
// redux/actions/PersonAction
import { ADD_PERSON } from '.. /constant'
export const addPersonAction = (data) = > {
return { type: ADD_PERSON, data }
}
Copy the code
Write the reducer:
// redux/reducers/bookReducer
import { ADD_BOOK } from '.. /constant.js'
export default function bookReducer(preState = [], action) {
const { type, data } = action
switch (type) {
case ADD_BOOK:
return [data, ...preState]
default:
return preState
}
}
Copy the code
// redux/reducers/personReducer
import { ADD_PERSON } from '.. /constant'
// Initialize state
const initState = [{ id: '001'.name: 'Joe' }]
function personReducer(preState = initState, { type, data }) {
switch (type) {
case ADD_PERSON:
return [data, ...preState]
default:
return preState
}
}
export default personReducer
Copy the code
// redux/reducers/index.js
Compile all reducer */
import { combineReducers } from 'redux'
/ / introduce bookReducer
import bookReducer from './bookReducer'
/ / introduce personReducer
import personReducer from './personReducer'
/** ** Summarize all reducers by using combineReducers() and merge the results returned by reducer * into a state object. For example: {a: XXX,b: XXX} */
const allReducer = combineReducers({
storeBook: bookReducer,
storePerson: personReducer
})
export default allReducer
Copy the code
Write the store:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import allReducer from '.. /redux/reducers'
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
export default store
Copy the code
Write the Person and Book components:
import React from 'react'
import { connect } from 'react-redux'
import { addBookAction } from '.. /redux/actions/bookAction'
class Book extends React.Component {
handleKeyUp(e) {
if(e.keyCode ! = =13) return
const val = e.target.value
if(! val.trim() ===' ') return
this.props.addBookAction({ id: Date.now(), name: val })
e.target.value = ' '
}
render() {
return (
<div>
<h1>I'm the Book component</h1>
<input type="text" name="book" placeholder="Add a book" onKeyUp={this.handleKeyUp.bind(this)} />
</div>)}}export default connect(
(state) = > {
return { storeBook: state.storeBook }
},
{
addBookAction: addBookAction
}
)(Book)
Copy the code
import React from 'react'
import { connect } from 'react-redux'
import { addPersonAction } from '.. /redux/actions/personAction'
class Person extends React.Component {
render() {
return (
<div style={{ borderBottom: '1px solid #ccc' }}>
<h1>I'm the Person component</h1>{this.props.storeperson [0].name} {this.props.storebook.map (({id, name}) => () {this.props.storeperson [0].name}<span key={id}>{name},</span>
))}
</div>)}}export default connect(
(state) = > {
return { storeBook: state.storeBook, storePerson: state.storePerson }
},
{
addPersonAction: addPersonAction
}
)(Person)
Copy the code
Finally use it in app.js:
import React from 'react'
import Book from './components/Book'
import Person from './components/Person'
class App extends React.Component {
render() {
return (
<div>
<Person />
<Book />
</div>)}}export default App
Copy the code
18.4,reducer
Pure functions
The Reducer function of redux must be a pure function. As long as it is the same input (argument), it must get the same output (return).
- Parameter data cannot be overwritten.
- There are no side effects, such as network requests.
- You can’t use
Date.now()
orMath.random()
Such impure methods.
Because the only way to compare whether all the properties in two javascript objects are exactly the same is to do a deep comparison. However, deep comparisons can be very costly in real applications and require a lot of comparisons, so an effective solution is to make a rule that the developer should return a new object regardless of any changes, and the developer should return the old object if there are no changes. This is why Redux designed reducer as a pure function.
18.5 redux developer tools
The Redux developer tool requires not only installing the Chrome plugin, but also configuring it in your project:
-
Install the Chrome plug-in Redux DevTools
-
Redux-devtools-extension: redux-devtools-extension:
/ / installation
npm install redux-devtools-extension
Copy the code
- And then in the project
store.js
In the configuration:
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools( applyMiddleware(thunk) ))
Copy the code
In the 19th, Hook,
Hook is a new feature in React 16.8. It can use state and other React features without writing a class. Some commonly used hooks will be introduced below.
19.1, useState ()
State hooks allow function components to have State and read and write State data. Grammar:
const [xxx, setXXX] = React.useState(initValue)
Copy the code
InitValue is cached internally when it is initialized for the first time. The return value is an array of two elements, the first being the current value of the internal state and the second being the function that updates the state value.
SetXXX () can be written in two ways:
setxxx(newValue)
: Parameter is non-functional value, directly specify the new state value, internal overwrite the original state value.setxxx(value=> newvalue)
The: argument is a function that receives the previous status value and returns the new status value, internally overwriting the original status value.
import React, { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
function addCount() {
// The first way
setCount(count + 1)
// The second way
setCount((count) = > {
return count + 1})}return (
<div>
<p>Count is: {count}</p>
<button onClick={addCount}>Click on the + 2</button>
</div>)}Copy the code
19.2, Effect ()
Effect Hooks can use lifecycle Hook functions in function components. UseEffect Hook can be regarded as a combination of componentDidMount, componentDidUpdate and componentWillUnmount.
Effect() passes in a callback function that executes after the first render and after each update:
import { useEffect } from 'react'
// componentDidMount and componentDidUpdate
useEffect(() = > {
console.log('success')})Copy the code
Effect() can also be passed a second argument. It is an array specifying the dependencies to listen for. Rerender only when dependencies change. Default does not transmit means listens on all. An empty array [] means none is listened on.
const [count, setCount] = useState(0)
const [name, setName] = useState('Joe')
ComponentDidMount () and componentDidUpdate()
useEffect(() = > {
// TODO
}) // Do not transmit to monitor all
// componentDidMount()
useEffect(() = > {
// TODO
}, []) // [] Does not listen
// it is executed at initialization and when count changes
useEffect(() = > {
// TODO
}, [count]) // only listen on count
Copy the code
Effect() can return a function that is executed before the component is unloaded. Equivalent to the componentWillUnmount hook function:
import { useEffect } from 'react'
import ReactDOM from 'react-dom'
function Demo() {
useEffect(() = > {
return function () {
console.log('Execute before component uninstallation')}})/ / unloading
function handleUnmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))}return (
<div>
<button onClick={handleUnmount}>Uninstall the component</button>
</div>)}Copy the code
When you click uninstall the component, the function returned by Effect() is executed.
Here is an example of a timer:
import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
// Timer component
function Counter() {
const [count, setCount] = useState(0)
useEffect(() = > {
let timer = setInterval(() = > {
setCount((count) = > count + 1)},1000)
return () = > {
clearInterval(timer) // Clear timers before uninstalling components}}, [])/ / unloading
function handleUnmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))}return (
<div>
<p>The value of count is {count}</p>
<button onClick={handleUnmount}>Uninstall the component</button>
</div>)}Copy the code
One thing to note about using Effect() ⚠ is that if there are multiple side effects, multiple useeffects () should be called instead of being written together:
import { useEffect, useState } from 'react'
function App() {
const [timerA, setTimerA] = useState(0)
const [timerB, setTimerB] = useState(0)
useEffect(() = > {
setInterval(() = > setTimerA((timerA) = > timerA + 1), 1000)
}, [])
useEffect(() = > {
setInterval(() = > setTimerB((timerB) = > timerB + 1), 2000)}, [])return (
<div>
{timerA},{timerB}
</div>)}Copy the code
19.3, useRef ()
Using String Ref, Callback Ref, or Create Ref in a function component throws an error. Because the function component has no instance, it cannot be used in the function component and useRef() is required.
What useRef does:
- To obtain
DOM
Element node. - Gets an instance of the child component.
- Storage of shared data between render cycles.
Get the DOM element node:
import { useEffect, useRef } from 'react'
function App() {
let inputRef = useRef()
useEffect(() = > {
console.log(inputRef.current) // <input type="text">
}, [])
return <input type="text" ref={inputRef} />
}
Copy the code
Because a function component has no instance, if you want to get an instance of a subcomponent using ref, the subcomponent group is written as a class component. Get instances of child components:
import React, { useEffect, useRef } from 'react'
function App() {
let childRef = useRef()
useEffect(() = > {
childRef.current.handleChild() // Child Component
}, [])
return <Child ref={childRef} />
}
class Child extends React.Component {
handleChild() {
console.log('Child Component')}render() {
return <div>The Child components</div>}}Copy the code
Storage of shared data between render cycles:
import React, { useEffect, useRef } from 'react'
function App() {
const timerRef = useRef()
useEffect(() = > {
let timerId = setInterval(() = > {
// TODO
})
timerRef.current = timerId
return () = > {
clearInterval(timerRef.current)
}
}, [])
return <div>App.js</div>
}
Copy the code
The timer ID is stored in the useRef. The timer ID is available not only in useEffect, but also in the entire component function.
19.4, useContext ()
CreateContext () is a communication mode between components commonly used for grandparent component communication.
Suppose there are three components, A, B, and C, which are group grandchildren. A represents the parent component, B represents the child component, and C represents the grandchild component. The data of A component is transmitted to C component:
Use createContext() to pass cross-level component data:
import React from 'react'
// Create a Context container object outside the component
const MyContext = React.createContext()
/ / the parent component
class A extends React.Component {
constructor(props) {
super(props)
this.state = { username: 'Joe'}}render() {
return (
<>
<div>I'm component A</div>{/* Wrap the child component with myContext.provider and pass the data through value */}<MyContext.Provider value={{ username: this.state.username}} >
<B />
</MyContext.Provider>
</>)}}/ / child component
class B extends React.Component {
render() {
return (
<>
<div>I'm component B</div>
<C />
</>)}}/ / sun components
class C extends React.Component {
static contextType = MyContext // Declare to receive the context
render() {
const { username } = this.context / / get the context
return <div>I am component C and THE name I get from component A is {username}.</div>}}Copy the code
If the C component is a function component, use Consume to get the value, and the value in the callback is the value of the context:
function C() {
return <MyContext.Consumer>{(value) => <div>I am component C, and the name I get from component A is: {value.username}</div>}</MyContext.Consumer>
}
Copy the code
C components can also use useContext() to get values:
function C() {
const { username } = useContext(MyContext)
return <div>I am component C and THE name I get from component A is {username}.</div>
}
Copy the code
UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer > in the class component.
19.5, useReducer
UseReducer is an alternative to useState. It is used in a similar way to Redux:
const [state, dispatch] = useReducer(reducer, initialArg, init);
Copy the code
UseReducer () can be passed three arguments:
-
The first reducer parameter is used in much the same way as the Reducer function in Redux: (state, action) => newState.
-
The second parameter, initialArg, is the initial value of the state.
-
The third parameter init is an optional function for Lazy Initialization, which returns the state after Initialization.
Here is an example of a counter:
import { useReducer } from 'react'
/ / Reducer function
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
throw new Error()}}function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<>
<p>Count: {state.count}</p>
<button onClick={()= > dispatch({ type: 'decrement' })}>-</button>
<button onClick={()= > dispatch({ type: 'increment' })}>+</button>
</>)}export default Counter
Copy the code
Use useReducer() in a variety of scenarios. Here are a few examples:
-
State is an array or object.
-
State changes can be complex, often requiring many states to be changed in a single operation.
-
Complex business logic, UI and business can be separated.
-
Different properties are bundled together and must be managed using a single State Object.
If there is a login function, use useState() and useReduer() respectively:
/ / useState () method
import { useState, useReducer } from 'react'
// Login component
function LoginUI() {
const [name, setName] = useState(' ') / / user name
const [pwd, setPwd] = useState(' ') / / password
const [isLoading, setIsLoading] = useState(false) // Whether to display loading, sending a request
const [error, setError] = useState(' ') // Error message
const [isLoggedIn, setIsLoggedIn] = useState(false) // Whether to log in
async function LoginPage() {
setError(' ')
setIsLoading(true)
try {
await loginService({ name, pwd })
setIsLoggedIn(true)
setIsLoading(false)}catch (err) {
// Login failure: Error information is displayed, the user name and password are cleared, and the loading flag is cleared
setError(error.message)
setName(' ')
setPwd(' ')
setIsLoading(false)}}// Login interface
function loginService() {
// ...
}
return <h2>Login Page</h2>
}
Copy the code
As the code above uses useState(), you can see that as the requirements become more complex, more states are defined, more setState() calls are made, and it is easy to set errors or omissions, and maintainability is poor. Here’s how to use useReducer() :
/ / useReducer () method
import { useReducer } from 'react'
const initState = { name: ' '.pwd: ' '.error: ' '.isLoading: false.isLoggedIn: false }
function loginReducer(state, action) {
switch (action.type) {
case 'login':
return { ...state, isLoading: true.error: ' ' }
case 'success':
return { ...state, isLoggedIn: true.isLoading: false }
case 'error':
return { ...state, error: action.payload.error, name: ' '.pwd: ' '.isLoading: false }
default:
return state
}
}
// Login component
function LoginPage() {
const [state, dispatch] = useReducer(loginReducer, initState)
const { name, pwd, error } = state
async function Login() {
dispatch({ type: 'login' })
try {
await loginService({ name, pwd })
dispatch({ type: 'success'})}catch (err) {
// Login failure: Error information is displayed, the user name and password are cleared, and the loading flag is cleared
dispatch({
type: 'error'.payload: { error: error.message }
})
}
}
// Login interface
function loginService() {
// ...
}
return <h2>Login Page</h2>
}
export default Login
Copy the code
reference
Github.com/xzlaptt/Rea…
zhuanlan.zhihu.com/p/146773995
Segmentfault.com/a/119000002…
www.bilibili.com/video/BV1wy…
www.ruanyifeng.com/blog/2020/0…
Juejin. Cn/post / 684490…