The difference between elements and components in React

Start from the problem

I was asked this question:

If you want to implement a useTitlemethod, the specific usage examples are as follows:

functionHeader  (​) {
     const [ Title , changeTitle] = useTitle ();
     return (
         < div  onClick = {() => changeTitle('new title')}>
           < Title /> 
        </ div >
    )
}

But useTitlesomething went wrong when writing the code:

function  TitleComponent ( {title} ) {
     return  < div > {title} </ div >
}

function  useTitle () {
     const [title, changeTitle] = useState ( 'default title' );

    const  Element = React . createElement ( TitleComponent , {title});
     return [ Element . type , changeTitle];
}

This code directly reports an error and cannot even be rendered. If it were you, how should you modify this code?

Elements and Components

In fact, this is a very typical problem of how to distinguish and use elements and components.

element

Let’s first look at the introduction to React elements in the official React documentation :

Babel will translate JSX into a React.createElement()function call. The following two sample codes are completely equivalent:

const element = < h1  className = "greeting" > Hello, world! </ h1 > ;

const element = React . createElement (
   'h1' ,
  { className : 'greeting' },
   'Hello, world!' 
);

React.createElement()does some checks beforehand to help you write error-free code, but in reality it creates an object like this:

// Note: This is a simplified structure 
const element = {
   type : 'h1' ,
   props : {
     className : 'greeting' ,
     children : 'Hello, world!'
  }
};

These objects are called “React elements”. They describe what you want to see on the screen.

You see, the React element actually refers to the JSX code we write every day. It will be escaped by Babel into a function call. The final result is an object describing the DOM structure. Its data structure is essentially a JS object.

In JSX, we can embed expressions, such as:

const name = 'Josh Perez' ;
 const element = < h1 > Hello, {name} </ h1 > ;

So if we want to use a React element, we should use embedded expressions like this:

const name = < span > Josh Perez </ span > ;
 const element = < h1 > Hello, {name} </ h1 > ;

components

What about components? There are two types of components, function components and class components:

// Function component 
function  Welcome ( props ) {
   return  < h1 > Hello, {props.name} </ h1 > ;
}
// class component 
class  Welcome  extends  React.Component {
   render () {
     return  < h1 > Hello, {this.props.name} </ h1 > ;
  }
}

So how to use components?

const element = < Welcome  name = "Sara" /> ;

For components, we need to use a method similar to HTML tags to call, and Babel will translate it into a function call

const element = React . createElement ( Welcome , {
   name : "Sara" 
});

So you see, the component’s data structure is essentially a function or class. When you call it using an element tag, the function or class will be executed and eventually a React element will be returned.

How to solve the problem

Although these contents all come from the official React documentation, if you can clearly understand the difference between React elements and components, you can already solve the initial problem. There are at least two ways to solve it, one is to return React elements, and the other is to return React components

First we return the React element:

const root = ReactDOM . createRoot ( document . getElementById ( 'root' ));

functionHeader  (​) {
     const [ Title , changeTitle] = useTitle ();
     // Because the React element is returned here, we use {} to embed the expression 
    return (
         < div  onClick = {() => changeTitle('new title' )}>
          {Title}
        </div>​​
    )
}

function  TitleComponent ( {title} ) {
     return  < div > {title} </ div >
}

function  useTitle () {
     const [title, changeTitle] = useState ( 'default title' );

    // createElement returns the React element 
    const  Element = React . createElement ( TitleComponent , {title});
     return [ Element , changeTitle];
}

root.render ( < Header / > );

In the second one we return the React component:

const root = ReactDOM . createRoot ( document . getElementById ( 'root' ));

functionHeader  (​) {
     const [ Title , changeTitle] = useTitle ();
     // Because the React component is returned, we use the element tag to call 
    return (
         < div  onClick = {() => changeTitle('new title')}>
           < Title / 
        > </div>​
    )
}

function  TitleComponent ( {title} ) {
     return  < div > {title} </ div >
}

function  useTitle () {
     const [title, changeTitle] = useState ( 'default title' );

    //Here we build a function component 
    const  returnComponent = () => {
         return  < TitleComponent  title = {title} />
    }
    //Here we return the component directly 
    return [returnComponent, changeTitle];
}

root.render ( < Header / > );

Custom content

Sometimes we need to pass in custom content to the component.

For example, we have implemented a Modal component with an OK button and a Cancel button. However, in order to make the content displayed by Modal more flexible, we provide a props attribute. The user can customize a component and pass it in. What does the user provide? Modal As far as what is displayed, Modal is equivalent to a container, so how do we implement this function?

The first way to achieve

Here is the first way to do it:

function  Modal ( {content} ) {
   return (
     < div >
      {content}
      < button > OK </ button > 
      < button > Cancel </ button > 
    </ div >
  )
}

function  CustomContent ( {text} ) {
   return  < div > {text} </ div >
}

< Modal content={ < CustomContent  text = "content" /> } />

Based on the previous knowledge, we can know that contentthe attribute passed here is actually a React element, so the inside of the Modal component is {}rendered using .

The second way to achieve

But the first way does not always solve the needs. Sometimes, we may use the value inside the component.

For example, a countdown component Timerstill provides an attribute contentfor customizing the display style of the time. The time Timeris processed internally by the component, and the display style is completely customized by the user. In this case, we can choose to pass in a component:

function  Timer ( {content: Content} ) {
     const [time, changeTime] = useState ( '0' );

    useEffect ( () => {
         setTimeout ( () => {
             changeTime (( new  Date ). toLocaleTimeString ())
    }, 1000 )
    }, [time])

    return (
         < div > 
          < Content  time = {time} /> 
        </ div >
    )
}

function  CustomContent ( {time} ) {
     return  < div  style = {{border: ' 1px  solid # ccc '}}> {time} </ div >
}


< Timer content={ CustomContent } />

In this example, we can see that contentthe attribute passed in is a React component CustomContent, and the CustomContent component will be passed in the time attribute. It is based on this convention that we develop the CustomContent component.

Inside the Timer component, because the component is passed in, <Content time={time}/>rendering is used.

The third way to achieve

When faced with the need for the second implementation method, in addition to the above implementation method, there is also a render propstechnique called , which is more common than the second method. We still take the Timer component as an example:

function  Timer ( {renderContent} ) {
     const [time, changeTime] = useState ( '0' );

    useEffect ( () => {
         setTimeout ( () => {
             changeTime (( new  Date ). toLocaleTimeString ())
    }, 1000 )
    }, [time])

  //Here directly call the passed in renderContent function 
    return (
         < div >
          {renderContent(time)}
        </div>​​
    )
}

function  CustomContent ( {time} ) {
     return  < div  style = {{border: ' 1px  solid # ccc '}}> {time} </ div >
}

root.render ( < Timer renderContent = { (time) =>  {
    return < CustomContent  time = {time} /> 
}} /> );

Since we are passing in a function, we contentchanged the attribute name to renderContent, in fact, it can be called anything.

renderContentA function is passed in, which receives timeas a parameter and returns a React element. Internally Timer, we directly execute the renderContent function and pass in the internally processed time parameter, thus enabling users to customize rendering using the internal values ​​of the component. content.

One more thing, in addition to putting it in attributes, we can also put it in children, which is the same:

function  Timer ( {children} ) {
       // ... 
    return (
         < div >
          {children(time)}
        </div>​​
    )
}


<Timer>​​
  { ( time ) => {
     return  < CustomContent  time = {time} />
  }}
</ Timer >

We can choose the appropriate incoming method depending on the situation.

React series

Explain the React source code, the implementation mechanism behind React API, React best practices, the development and history of React, etc. It is expected that there will be about 50 articles, welcome to follow