Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
Any one person’s logical ability to process information is limited, so when faced with a very complex problem, it is unlikely that we can deal with a lot of content at once.
If you take a complex problem, break it down into manageable pieces, and put it all together, you’ll find that the bigger problem will be solved
The current modular and componentization of the front end is based on the idea of divide and conquer
What is componentized development
If we put all of the processing logic on a page together, it would become very complex to process, and not conducive to subsequent management and extension
However, if we talk about a page divided into a small function block, each function block to complete their own part of the independent function, then the whole page management and maintenance becomes very easy.
Componentized application
- We split a complete page into many components
- Each component is used to implement a functional block of the page
- Each component can be subdivided
- The components themselves can be reused in multiple places
Componentization is the core idea of React. The App itself is a component
Componentization provides an abstraction that allows us to develop individual reusable components to construct our applications
Any application is abstracted into a tree of components
React Component Classification
-
Depending on how components are defined, they can be divided into: Functional Component (Functional Component) and Class Component (Class Component)
-
Components can be divided into Stateless Component and Stateful Component according to whether there are Stateful components to be maintained.
-
Components can be divided into Presentational components and Container Components based on their respective responsibilities.
Functional components, stateless components, and presentation components focus on the presentation of the UI
Class components, stateful components, and container components focus on data logic
Of course, there are many other ways to divide components: asynchronous components, higher-order components, etc
Class components
- Component names start with uppercase characters (whether class or function components)
- The class component needs to inherit from react.component
- Class components must implement the Render function; the Render () method is the only one that must be implemented in a class component
- Constructor is optional, and we usually initialize some data in constructor
- This. State maintains data inside our component, and when that data changes, the UI needs to change as well
import { Component } from 'react'
export default class App extends Component{
render() {
return <h2>Simplest class component</h2>}}Copy the code
Function component
A function component is a function defined using function that returns the same interior as the render function in the class component
A function component can be thought of as a special representation of the Render function in a class component
So function components (in this case function components that don’t use React Hook)
- It is updated and mounted without a life cycle, but without a life cycle function
- No this(component instance)
- No internal state
export default function App() {
return (
<h2>I am the simplest functional component</h2>)}Copy the code
The return value of render
When render is called, it checks for changes to this.props and this.state and returns one of the following types:
- React elements: Usually created through JSX
- Arrays or fragments: Enable the Render method to return multiple elements
- Portals: The ability to render child nodes into different DOM subtrees
- String or numeric types: They are rendered as text nodes in the DOM
- Boolean type or NULL: Render nothing
Life cycle function
Many things have a whole process from creation to destruction. This process is called the life cycle
React internally calls back to certain functions implemented within our component to tell us what phase we are in. These functions are called lifecycle functions
We can write our own logic code in these callback functions to accomplish our required functions
Life cycle is an abstract concept. The whole process of life cycle is divided into many stages:
- Mount, the process by which a component is first rendered in the DOM tree
- Update the rendering process when the state of the component changes
- Unmount, the process by which a component is removed from the DOM tree
Constructor
If state is not initialized or method binding is not performed, there is no need to implement a constructor for the React component
Constructor usually does only two things:
- Initialize the internal state by assigning the this.state object
- Bind the event instance (this), which fixes the this reference in the event
componentDidMount
ComponentDidMount () is called immediately after the component is mounted (inserted into the DOM tree)
So where do you normally operate in componentDidMount?
- Dom-dependent operations can be performed here
- Network request occurred
- Add some subscriptions here (need to unsubscribe at componentWillUnmount [event listener])
componentDidUpdate
ComponentDidUpdate () is called immediately after the update, not the first rendering
- This is where you can manipulate the DOM when the component is updated
- Compare the changes of the old and new props and perform the corresponding operations
componentWillUnmount
ComponentWillUnmount () is called directly before the component is unmounted and destroyed
- This method performs necessary cleanup actions, such as clearing timers, canceling network requests, or clearing subscriptions created in componentDidMount()
In addition to the more common life cycle functions mentioned above, there are also some less common life cycle functions, which can be viewed here
Intercomponent communication
If we had an application that put all of its logic in one component, that component would become very bloated and difficult to maintain
So the core idea of componentization should be to break up components into smaller components
These components are then combined and nested together to form our application
So communication between components becomes very important
The father the son
Class components
import { Component } from 'react'
class Cpn extends Component {
// All the props passed in are stored in the props object of the current component
// The following is the default constructor for the dispatch class, so leave the constructor out
// We can still use the props object to get the data passed from the parent class to the subclass
/* constructor(props) { super(props) } */
render() {
return (<div>
<h2>{ this.props.name }</h2>
<h2>{ this.props.age }</h2>
</div>)}}export default class App extends Component {
render() {
return (
<div>
<Cpn name="Klaus" age="23" />
</div>)}}Copy the code
Function component
import { Component } from 'react'
// The props passed by the parent component are passed directly as arguments to the function component
function Cpn(props) {
return (<div>
<h2>{ props.name }</h2>
<h2>{ props.age }</h2>
<h2>{ props.friends.join(',') }</h2>
</div>)}export default class App extends Component {
render() {
return (
<div>
<Cpn name="Klaus" age={23} friends={['Alex', 'Jhon']} / >
</div>)}}Copy the code
Attribute check
There are times when we might want to validate data passed to child components, especially for large projects
Prop-types is used to verify the prop types of React functions
Prop-types this library was originally part of React,
But as of React V15.5, React.PropTypes has moved into another package :prop-types
The specific way to verify and use can be viewed by clicking here
Class components
import { Component } from 'react'
// React scaffolding is not installed separately
// The first letter of the object's name is uppercase when it is inserted into the library
import PropTypes from 'prop-types'
class Cpn extends Component {
// There are two ways to set type validation in a class component
Cpn.propTypes = {} --> not recommended
// Method 2: Static attribute
// Note: the initial letter of the props type validation is lowercase
static propTypes = {
name: PropTypes.string.isRequired, The value of the name attribute is a string and is mandatory
age: PropTypes.number,
friends: PropTypes.array
}
// Set the default value of props
static defaultProps = {
// If both the default value and isRequired exist, the default value is set first, and then the value is passed to the child component
// If the default value and isRequired exist together, isRequired loses its meaning
name: 'Steven'.age: 23.friends: ['Alice']}render() {
return (<div>
<h2>{ this.props.name }</h2>
<h2>{ this.props.age }</h2>
<h2>{ this.props.friends.join(',') }</h2>
</div>)}}export default class App extends Component {
render() {
return (
<div>
<Cpn name="Klaus" age={23} friends={['Alex', 'Jhon']} / >{/* Use default values */}<Cpn />
</div>)}}Copy the code
Function component
import { Component } from 'react'
import PropTypes from 'prop-types'
function Cpn(props) {
return (<div>
<h2>{ props.name }</h2>
<h2>{ props.age }</h2>
<h2>{ props.friends.join(',') }</h2>
</div>)}// Function components have no corresponding static methods and static properties
// So you need to mount the verification rules and default values on the manual function component
Cpn.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
friends: PropTypes.array
}
Cpn.defaultProps = {
age: 23.friends: ['Alice']}export default class App extends Component {
render() {
return (
<div>
<Cpn name="Klaus" age={23} friends={['Alex', 'Jhon']} / >{/* Use default values */}<Cpn />
</div>)}}Copy the code
Child the parent
In some cases, we also need a child component to pass a message to its parent:
- This is done with custom events in VUE
- React also passes messages through props, but the parent component passes a callback function to the child component, which is called in the child component
import { Component } from 'react'
class Btn extends Component {
render() {
return (
<>{/* React uses a reference to trigger the event function passed by the parent component */<button onClick={ this.props.increment} >+ 1</button>
</>)}}export default class App extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0}}render() {
return (
<div>
<h2>{ this.state.counter }</h2>{/* We need to fix this in the parent component because this method is called in the child component, but uses the state defined in the parent component, so this must be the parent component of the state */}<Btn increment={() = > this.increment() }/>
</div>)}increment() {
this.setState({
counter: this.state.counter + 1}}})Copy the code
Phase case
Requirement: Implement a simple Tab component
Cpn/index.css
.tabs {
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
}
.tab.active {
color: red;
}
.tab span {
padding: 5px 8px;
}
.tab.active span {
border-bottom: 3px solid #ff0000;
}
Copy the code
Cpn/index.js
import { Component } from 'react'
import PropTypes from 'prop-types';
import './index.css'
export default class Tabs extends Component {
static propTypes = {
tabs: PropTypes.array.isRequired,
active: PropTypes.string,
changeActive: PropTypes.func
}
static defaultProps = {
active: 'one'
}
render() {
const { tabs, active, changeActive } = this.props
return (
<div className="tabs">
{
tabs.map(item =>
<div
key={item}
className={'tab'+ (item= = =active ? 'active' :"')}onClick= {()= > changeActive(item)}
>
<span>{ item }</span>
</div>)}</div>)}}Copy the code
App.js
import { Component } from 'react'
import Tabs from './Tabs'
export default class App extends Component {
constructor() {
super(a)// The data that needs to be rerendered in the UI needs to be placed in the state object only if it needs to be used in the UI and the data changes
// Use class fields to define the rest of the data as instance attributes of the class
this.tabs = ['one'.'two'.'three']
this.state = {
active: 'one'}}render() {
return (
<div>
<Tabs
active={this.state.active}
tabs={this.tabs}
changeActive={active= > this.changeActive(active)}
/>
<h2>{ this.state.active }</h2>
</div>)}changeActive(active) {
this.setState({
active
})
}
}
Copy the code
Simulation of slot.
React doesn’t provide slot functionality in Vue, but JSX is essentially an object, so we can emulate slots to use it
The default slot
App.js
import { Component } from 'react'
import Cpn from './Cpn'
export default class App extends Component {
render() {
return (
<Cpn>{/* This will be placed in the props. Children property of Cpn as children */}<span>default slot</span>
</Cpn>)}}Copy the code
Cpn.js
import { Component } from 'react'
export default class Cpn extends Component {
render() {
return (
<div>{/* If there is more than one value passed in as an array (the value must be evaluated by an index), the socket can be loaded with props. If the value of props. Children is an array, there is a requirement for the order in which the slots are passed in. */} {this.props. Children} {this.props. Children}</div>)}}Copy the code
A named slot
App.js
import { Component } from 'react'
import Cpn from './Cpn'
export default class App extends Component {
constructor() {
super(a)this.leftSlot = <h2>left item</h2>
this.centerSlot = <h2>center item</h2>
this.rightSlot = <h2>right item</h2>
}
render() {
return (
<Cpn
leftSlot={ this.leftSlot }
centerSlot={ this.centerSlot }
rightSlot={ this.rightSlot} / >)}}Copy the code
Cpn.js
import { Component } from 'react'
export default class Cpn extends Component {
render() {
return (
<div>
<div className="left">{ this.props.leftSlot }</div>
<div className="center">{ this.props.centerSlot }</div>
<div className="right">{ this.props.rightSlot }</div>
</div>)}}Copy the code
Communication across parent and child components
In a real-world scenario, we might encounter communication between parent and grandchild components
There are several ways to do this at this point
Props is passed layer by layer
import { Component } from 'react'
function ProfileHeader(props) {
return (
<div>
<h2>nickname: { props.nickname }</h2>
<h2>level: { props.level }</h2>
</div>)}function Profile(props) {
return (
<div>
<ProfileHeader {. props} / >
<h2>content</h2>
</div>)}export default class App extends Component {
constructor() {
super(a)this.state = {
nickname: 'Klaus'.level: 6}}render() {
return (
<div>{/* Spread attributes are syntheticcandies provided by JSX to pass deconstructed attributes directly as components props */}<Profile {. this.state} / >
</div>)}}Copy the code
At this time, the intermediate component Profile obviously does not use the nickname and level props
However, because of the need for props to pass layer by layer, Profiles still need to pass corresponding properties
React provides context (similar to provide and inject in Vue)
Context basic usage
Context provides a way to share such values between components without explicitly passing props layer by layer through the component tree
Function component
import React,{ Component } from 'react'
1. Create context
// React.createcontext () parameters are default values
The return value of the React. CreateContext argument is itself a React component
const userContext = React.createContext({
nickname: 'default name'.level: -1
})
function ProfileHeader() {
return (
<userContext.Consumer>Use context data in the consumer */} {info => (<div>
<h2>nickname: { info.nickname }</h2>
<h2>level: { info.level }</h2>
</div>)}</userContext.Consumer>)}function Profile(props) {
return <ProfileHeader />
}
export default class App extends Component {
constructor() {
super(a)this.state = {
nickname: 'Klaus'.level: 6}}render() {
return (
<div>{/* 2. Provide the data that needs to be passed by using the context. The value that needs to be passed is stored in the value attribute.<userContext.Provider value={this.state}>
<Profile />
</userContext.Provider>
<hr />{/* Use default values */}<Profile />
</div>)}}Copy the code
Class components
import React, { Component } from 'react'
const userContext = React.createContext({
nickname: 'default name'.level: -1
})
class ProfileHeader extends Component {
// There is one property in the class component, context
// By default, its value is an empty object because it does not know which CTX value it needs to use
// So the class has a static contextType property
// Used to specify the context property, which CTX value should be used
static contextType = userContext
render() {
const { nickname, level } = this.context
return (
<div>
<h2>nickname: { nickname }</h2>
<h2>level: { level }</h2>
</div>)}}class Profile extends Component {
render() {
return <ProfileHeader />}}export default class App extends Component {
constructor() {
super(a)this.state = {
nickname: 'Klaus'.level: 6}}render() {
return({/* Provide data */}
<userContext.Provider value={this.state}>
<Profile />
</userContext.Provider>
)
}
}
Copy the code
Multiple contexts coexist
Function component
import React,{ Component } from 'react'
const userContext = React.createContext({
nickname: 'default name'.level: -1
})
const themeContext = React.createContext({
color: 'red'
})
function ProfileHeader() {
return (
<userContext.Consumer>
{
info => (
<themeContext.Consumer>
{
theme => (
<div style={{color: theme.color}} >
<h2>nickname: { info.nickname }</h2>
<h2>level: { info.level }</h2>
</div>)}</themeContext.Consumer>)}</userContext.Consumer>)}function Profile(props) {
return <ProfileHeader />
}
export default class App extends Component {
constructor() {
super(a)this.state = {
nickname: 'Klaus'.level: 6}}render() {
return (
<div>
<userContext.Provider value={this.state}>
<themeContext.Provider value={{color: 'blue'}} >
<Profile />
</themeContext.Provider>
</userContext.Provider>
</div>)}}Copy the code
Class components
import React, { Component } from 'react'
const userContext = React.createContext({
nickname: 'default name'.level: -1
})
const themeContext = React.createContext({
color: 'red'
})
class ProfileHeader extends Component {
static contextType = userContext
render() {
const { nickname, level } = this.context
return (
<themeContext.Consumer>
{
theme => (
<div style={{ color: theme.color}} >
<h2>nickname: { nickname }</h2>
<h2>level: { level }</h2>
</div>)}</themeContext.Consumer>)}}class Profile extends Component {
render() {
return <ProfileHeader />}}export default class App extends Component {
constructor() {
super(a)this.state = {
nickname: 'Klaus'.level: 6}}render() {
return (
<userContext.Provider value={this.state}>
<themeContext.Provider value={{ color: 'blue' }}>
<Profile />
</themeContext.Provider>
</userContext.Provider>)}}Copy the code
At this point, it can be found that if we need to use multiple contexts for data sharing, it is easy to have the problem of too deep a hierarchy
It is recommended to use redux instead of context for data sharing