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)

  1. It is updated and mounted without a life cycle, but without a life cycle function
  2. No this(component instance)
  3. 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:

  1. React elements: Usually created through JSX
  2. Arrays or fragments: Enable the Render method to return multiple elements
  3. Portals: The ability to render child nodes into different DOM subtrees
  4. String or numeric types: They are rendered as text nodes in the DOM
  5. 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