background
Hooks has been very popular since it was launched. It has changed the way we write React code and helped us write more concise code.
Today's article is not about Hooks. Besides Hooks, there are many practical skills that can help us write concise and clear code.
Today, I've sorted out a few skills, some of which are also my practice in the company's projects. Now I'll sort them out and share them with you, hoping to inspire you.
text
1. Use string to define a React element
Take a simple example:
// We can assign a string 'div' to a variable, as follows: import React from 'react' const MyComponent = 'div' function App() { return ( <> <MyComponent> <h3>I am inside a {'<div />'} element</h3> </MyComponent> </> ) }
React internally calls React.createElement, which is used to generate the element.
In addition, you can explicitly define the component to determine the rendered content, such as:
// Define a MyComponent function MyComponent({ component: Component = 'div', name, age, email }) { return ( <Component> <h1>Hi {name} </h1> <> <h6>You are {age} years old</h6> <small>Your email is {email}</small> </> </Component> ) }
Applicable mode:
function App() { return ( <> <MyComponent component="div" name="KK" age={18} email="xxx@gmail.com"> </> ) }
In this way, you can also pass in a custom component, such as:
function Dashboard({ children }) { return ( <div style={{ padding: '25px 12px' }}> {children} </div> ) } function App() { return ( <> <MyComponent component={Dashboard} name="KK" age={18} email="xxx@gmail.com"> </> ) }
If you encounter a similar element or component, it can be abstracted in this way to simplify your code.
For example:
For example, we need to make a demand for packaging goods, which can be printed individually or in batches. For common points, we can write custom components:
import React from 'react' import withTranslate from '@components/withTranslate' import PackComponent from './PackComponent' import usePack, { check } from './usePack' let PackEditor = (props) => { const packRes = usePack(props) return ( <PackComponent {...packRes} /> ) } PackEditor = withTranslate(PackEditor) PackEditor.check = check export default PackEditor
In this way, it can be used flexibly and conveniently in different business modules.
2. Define error boundary
In Javascript, we use try/catch to catch possible exceptions and handle errors in catch For example:
function getFromLocalStorage(key, value) { try { const data = window.localStorage.get(key) return JSON.parse(data) } catch (error) { console.error } }
In this way, even if an error occurs, our application will not crash the white screen.
React is also Javascript in the final analysis, which is no different in essence, so there is no problem in using try/catch.
However, due to the reason of React implementation mechanism, Javascript errors inside components will destroy the internal state, and render will generate errors:
https://github.com/facebook/react/issues/4026
For the above reasons, the React team introduced Error Boundaries:
https://reactjs.org/docs/erro...
Error boundaries
, In fact, that is React Component, you can find a component to handle any error information it catches.When the component tree crashes, you can also display your customized UI,As a fallback.
see React Official examples:
https://reactjs.org/docs/erro...class ErrorBoundary extends React.Component { constructor(props) { super(props) this.state = { hasError: false } } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true } } componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service logErrorToMyService(error, errorInfo) } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1> } return this.props.children } }
Usage:
<ErrorBoundary> <MyWidget /> </ErrorBoundary>
Live Demo By Dan Abramov:
https://codepen.io/gaearon/pe...
3. High level components
Generally speaking, the so-called high-level component is that you throw a component in, add some properties or operations, and then throw it out.
Generally speaking, you can abstract some common components into a higher-level component, and then reuse them in different modules.
For example, in our system, there is a kind of button to add a border, which is used in many places. We abstract it out:
import React from 'react' // Higher order component const withBorder = (Component, customStyle) => { class WithBorder extends React.Component { render() { const style = { border: this.props.customStyle ? this.props.customStyle.border : '3px solid teal' } return <Component style={style} {...this.props} /> } } return WithBorder } function MyComponent({ style, ...rest }) { return ( <div style={style} {...rest}> <h2> This is my component and I am expecting some styles. </h2> </div> ) } export default withBorder(MyComponent, { border: '4px solid teal' })
The MyComponent component decorated with with border has the function of unified border. If you want to modify it later, you can process it in the middle layer, which is very convenient.
In my project, I also used some high-level components, for example:
PackEditor = withTranslate(PackEditor)
Our PackEditor is an enhanced component. What functions have been added?
As the name indicates, with translate adds a translation function. Let's see how this component is implemented:
import React from 'react' import { Provider } from 'react-redux' import { injectIntl } from 'react-intl' import { store } from '@redux/store' import { Intl } from './Locale' const withTranslate = BaseComponent => (props) => { // avoid create a new component on re-render const IntlComponent = React.useMemo(() => injectIntl( ({ intl, ...others }) => ( <BaseComponent intl={intl} translate={(id, values = {}) => { // Injection translation method if (!id) { return '' } return intl.formatMessage( typeof id === 'string' ? { id } : id, values ) }} {...others} /> ) ), []) IntlComponent.displayName = `withTranslate(${BaseComponent.displayName || 'BaseComponent'})` return ( <Provider store={store}> <Intl> <IntlComponent {...props} /> </Intl> </Provider> ) } export default withTranslate
It's easy to use:
const Editor = withTranslate(({ // ... translate, }) => { // ... return ( <> {translate('xxx')}} </> ) })
Very convenient.
4. Render props
Rrender prop is a kind of simple technology that uses a function of prop to share code between React components. Similar to HOC, it is a problem of logic reuse between components.
More specifically, Render prop is a function that tells components what to render.
Here is a simple example:
The following components track the mouse position in a Web application:
class Mouse extends React.Component { state = { x: 0, y: 0 }; handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> <p>The current mouse position is ({this.state.x}, {this.state.y})</p> </div> ); } } class MouseTracker extends React.Component { render() { return ( <> <h1>Mobile mouse!</h1> <Mouse /> </> ); } }
When the cursor moves on the screen, the component displays its (x, y) coordinates.
The question now is:
How do we reuse this behavior in another component?
To put it another way, if another component needs to know the mouse position, can we encapsulate this behavior so that it can be easily shared with other components??
Let's say the product wants a feature that presents a picture of a cat chasing a mouse on the screen.
We might use < cat mouse = {x, y}} prop to tell the component the coordinates of the mouse so that it knows where the picture should be on the screen.
class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} /> ); } }
This requirement is so simple that you may modify the Mouse component directly:
class Mouse extends React.Component { state = { x: 0, y: 0 }; handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> <Cat mouse={this.state} /> </div> ); } }
Bashi ~ simple and rough, finish the task in one minute.
But what if you want to add a dog next time?
The above example, although it can fulfill the requirements of cat chasing mouse, has not achieved the goal of truly encapsulating behavior in a reusable way.
When we want the mouse position to be used for different use cases, we must create a new component to present something specifically for the use case
This is also the origin of render prop:
We can provide a < mouse > component with the function prop, which can dynamically determine what needs to be rendered instead of hard coding < cat > into the < mouse > component
Modify the above code:
class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} /> ); } } class Mouse extends React.Component { state = { x: 0, y: 0 }; handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div> ); } } class MouseTracker extends React.Component { render() { return ( <div> <h1>Mobile mouse!</h1> <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); } }
Provides a render method to dynamically determine what needs to be rendered.
In fact, render prop is called render prop because of the mode. It is not necessary to use the mode with prop named render.
Any function prop that tells a component what to render can be technically called "render prop."
In addition, one interesting thing about render prop is that you can use regular components with render prop to implement most high-level components (HOCs).
For example, if you prefer to use with mouse house instead of the < mouse > component, you can easily create one using the regular < mouse > with render prop:
function withMouse(Component) { return class extends React.Component { render() { return ( <Mouse render={mouse => ( <Component {...this.props} mouse={mouse} /> )}/> ); } } }
It is also very simple and clear.
It should be noted that if you create a function in the defined render function, using render prop will offset the advantages of using React.PureComponent.
Because when comparing props shallowly, it always gets false, and in this case, each render will generate a new value for render prop.
class Mouse extends React.PureComponent { // Same code as above } class MouseTracker extends React.Component { render() { return ( <> <Mouse render={mouse => ( // That's not good. The value of 'render' prop will be different for each rendering. <Cat mouse={mouse} /> )}/> </> ); } }
In such an example, every time < mousetracker > renders, it will generate a new function as a prop of < mousetrender > and thus offset the effect of the < mouse > component inherited from React.PureComponent
To avoid this problem, sometimes you can define a prop as an instance method, like this:
class MouseTracker extends React.Component { renderTheCat(mouse) { return <Cat mouse={mouse} />; } render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse render={this.renderTheCat} /> </div> ); } }
5. Component performance
Performance optimization is an eternal theme. I won't go into details here. I will provide you with integration resources for reference:
- React.memo https://reactjs.org/docs/reac...
- React.useMemo https://flaviocopes.com/react...
- React.useCallback https://reactjs.org/docs/hook...
- React.PureComponent https://reactjs.org/docs/reac...
- Optimizing performance https://reactjs.org/docs/opti...
summary
These are the skills we often use. They are simple and practical. I want to share them with you. I hope they can bring you some help or inspiration. Thank you.
Last
If you think the content is helpful, you can pay attention to my public number "front-end e advanced", learn and communicate more.