Contents
Start from the problem
I was asked this question:
If you want to implement a useTitle
method, the specific usage examples are as follows:
functionHeader () { const [ Title , changeTitle] = useTitle (); return ( < div onClick = {() => changeTitle('new title')}> < Title /> </ div > ) }
But useTitle
something 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 content
the 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 Timer
still provides an attribute content
for customizing the display style of the time. The time Timer
is 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 content
the 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 props
technique 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 content
changed the attribute name to renderContent
, in fact, it can be called anything.
renderContent
A function is passed in, which receives time
as 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