Article Contents:

  • What are Render Props
  • Application of Render Props
  • React Hooks
  • React Hooks
  • conclusion

What are Render Props

In short, a retooling uses the technique of Render Props whenever the value of a property in a component is a function. Let’s start with a simple example. If we were to implement a component called Render Props, we might start with something like this:

const Greeting = props= > (
    <div>
        <h1>{props.text}</h1>
    </div>
);

// Then use it this way
<Greeting text="Hello 🌰!" />
Copy the code

But what if you had to send an emoticon along with the greeting? It might go something like this:

const Greeting = props= > (
    <div>
        <h1>{props.text}</h1>
        <p>{props.emoji}</p>
    </div>
);
// how to use
<Greeting text="Hello 🌰!" emoji="😳" />
Copy the code

Adding links and implementing the logic of sending links inside the Greeting component clearly violates one of the six principles of software development, which is that every change must be made inside the component.

Open and close principle: close to modification, open to expansion.

There are, of course, Render Props, but before we do that, let’s look at a very simple summation function:

const sumOf = array= > {
    const sum = array.reduce((prev, current) = > {
        prev += current;
        return prev;
    }, 0);
    console.log(sum);
}
Copy the code

The function does a very simple job of summing up an array and printing it. However, if sum needs to be displayed through alert, do we need to modify it inside sumOf? It is similar to the above Greeting. Yes, the two functions have the same problem, that is, when the requirements change, they need to modify it inside the function.

For the second function, you can probably quickly figure out how to do it with a callback function:

const sumOf = (array, done) = > {
    const sum = array.reduce((prev, current) = > {
        prev += current;
        return prev;
    }, 0);
    done(sum);
}

sumOf([1.2.3], sum => {
    console.log(sum);
    // or
    alert(sum);
})
Copy the code

We will find that the callback function is perfect to solve the existing problems, each time we need to modify the sumOf function callback function, not need to modify inside sumOf.

The React Greeting function solves the same problem as the sumOf callback:

const Greeting = props= > {
    return props.render(props);
};
// how to use
<Greeting
    text="Hello 🌰!"
    emoji="😳"
    link="link here"
    render={(props) => (
    <div>
        <h1>{props.text}</h1>
        <p>{props.emoji}</p>
        <a href={props.link}></a>
    </div>)} ></Greeting>
Copy the code

Is the analogy to the previous sumOf very similar?

  • sumOfBy executing the callback functiondoneAnd thesumPassed into it, as long as it’s insumOfThis is obtained by passing a function in the second argument to the functionsumTo write a custom requirement
  • GreetingBy executing the callback functionprops.renderAnd thepropsPassed into it, as long as it’s inGreetingThe component’srenderProperty by passing in a functionpropsAnd return the UI you need

It is important to note that not only is a function passed in the render property called render Props, but any property can be called render Props if its value is a function. For example, in this example, it’s easier to use the render attribute name as children:

const Greeting = props => { return props.children(props); }; // how to use <Greeting text="Hello 🌰!" Emoji = "😳 link =" link "here" > {(props) = > (< div > < h1 > {props. Text} < / h1 > < p > {props. Emoji} < / p > < a href = {props. Link} > < / a > </div> )} </Greeting>Copy the code

This allows you to write functions directly inside the Greeting tag, which is much more intuitive than the render function.

So, the Render Props in React can be thought of as JavaScript callback functions.

Application of Render Props

The Render Props are similar to the issues that higher-order components are addressing in order to solve the problem of code reuse.

If you’re not familiar with higher-order components, take a look at the higher-order components in React and their application scenarios.

Components that simply implement an “on/off” function:

class Switch extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            on: props.initialState || false}; } toggle() {this.setState({
            on:!this.state.on,
        });
    }
    render() {
        return (
            <div>{this.props.children({
                on,
                toggle: this.toggle,
            })}</div>); }}// how to use
const App = (a)= > (
    <Switch initialState={false}>{({on, toggle}) => {
        <Button onClick={toggle}>Show Modal</Button>
        <Modal visible={on} onSure={toggle}></Modal>
    }}</Switch>
);
Copy the code

This is a simple component to reuse explicit and implicit Modal popover logic, such as to display OtherModal directly replace Modal, to achieve reuse of “switch” logic code.

Render Props are more like inversion of control (IoC), it just defines the interface or data and passes it to you as function parameters. How you use the interface or data is up to you.

If you’re not familiar with inversion of control, check out what I wrote earlierIoC philosophy in the front end

Render Props VS HOC

As mentioned earlier, the Render Props are addressing similar issues as higher-order components, addressing code reuse issues. What are the differences between these issues?

HOC

Disadvantages:

  • Because higher-order components can be nested multiple times, and it is difficult to ensure that the property names in each higher-order component are different, properties can be easily overridden.
  • When using higher-order components, the higher-order component is a black box, and we have to see how to implement it to use it:

Advantages:

  • You can usecomposeMethod to merge multiple higher-order components and then use
// Don't use it this way
constEnhancedComponent = withRouter (connect (commentSelector) (WrappedComponent));// You can compose these higher-order components using a compose function
// Lodash, Redux, Ramda and other third-party libraries provide functions similar to 'compose'
constEnhance = compose(withRouter, connect(commentSelector));constEnhancedComponent = enhance (WrappedComponent);Copy the code
  • Easy to call (ES6 + decorator syntax)
@withData   
class App extends React.Component {}
Copy the code

Render Props

  • disadvantages
    • Nesting too deeply can also form a hell callback
  • advantages
    • Solves the disadvantages of HOC

Render Props and HOC are not an either-or relationship. After understanding their advantages and disadvantages, we can implement them in the right way in the right scenario.

React Hooks

React Hooks are a new feature in React 16.8 that allows functional components to have the same “life cycle” as class-style components, allowing them to implement React features in functional components.

The React team introduced Hooks for the same purpose as the higher-order components and Render Props mentioned earlier, for code reuse.

Before we get to React Hooks let’s take a look at the Switch example of Render Props:

class Switch extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            on: props.initialState || false}; } toggle() {this.setState({
            on:!this.state.on,
        });
    }
    render() {
        return (
            <div>{this.props.children({
                on,
                toggle: this.toggle,
            })}</div>); }}// how to use
const App = (a)= > (
    <Switch initialState={false}>{({on, toggle}) => {
        <Button onClick={toggle}>Show Modal</Button>
        <Modal visible={on} onSure={toggle}></Modal>
    }}</Switch>
);
// use hooks
const App = (a)= > {
    const [on, setOn] = useState(false);
    return (
        <div>
            <Button onClick={()= > setOn(true)}>Show Modal</Button>
            <Modal visible={on} onSure={()= > setOn(false)}></Modal>
        </div>
    );
}
Copy the code

By comparison, it is easy to see that the Hooks version uses a few simple apis (useState, setOn, ON) to kill 20 lines of code in the Switch class component, greatly reducing the amount of code.

Reduction in code is only one of the benefits of Hooks, but the more important purpose is state logic reuse. (Same goes for higher-order components and Render Props, state logic reuse is a more specific term for code reuse.)

Hooks have several advantages:

  • While Hooks are just as intended for code reuse as higher-order components and Render Props, higher-order components and Render Props are much simpler and do not create nested hell.
  • It’s easier to separate UI from state
  • You can reference another Hooks from within one
  • Resolve pain points of class components
    • thisError-prone to pointing
    • Splitting the logic between different declaration cycles makes the code difficult to understand and maintain
    • High cost of code reuse (high-order components tend to increase code volume)

React Hooks

React provides the following common hooks:

  • Basic hook:useState,useEffectuseContext
  • Additional hooks:useReducer,useCallback,useMemo,useRef,useImperativeHandle,useLayoutEffect,useDebugValue

useEffect

The useEffect hook does exactly what it says — to handle side effects such as subscriptions, data fetching, DOM manipulation, and so on. It works like componentDidMount, componentDidUpdate, and componentWillUnmount.

For example, if we want to listen for input from the input box, we can use useEffect as follows:

function Input() {
    const [text, setText] = useState(' ')
    function onChange(event) {
        setText(event.target.value)
    }
    useEffect((a)= > {
        // componentDidMount and componentDidUpdate are life cycle functions
        const input = document.querySelector('input')
        input.addEventListener('change', onChange);
        return (a)= > {
            // Like componentWillUnmount
            input.removeEventListener('change', onChange); }})return (
        <div>
            <input onInput={onChange} />
            <p>{text}</p>
        </div>)}Copy the code

The useEffect hook is used to pass in a function as the first argument to useEffect so that you can do things that have side effects, such as manipulating the DOM.

If the useEffect function passed in returns a function that will be called when the component is about to be uninstalled, we can do some cleanup operations here, such as clearing timerID or unsubscribing previously published, etc. It may be intuitive to write:

useEffect(function didUpdate() {
    // do something effects
    return function unmount() {
         // cleaning up effects}})Copy the code

When useEffect is passed in only one argument, the useEffect function is executed after each render:

useEffect((a)= > {
    // render once
    console.log('useEffect');
})
Copy the code

When useEffect is passed in an array as the second argument, the incoming callback is executed only if the array’s value (dependency) changes, as in the following case:

Although React’s diff algorithm only updates the changed parts during DOM rendering, it does not recognize the changes in useEffect, so the developers need to use the second parameter to tell React which external variables are used.

useEffect((a)= > {
    document.title = title
}, [title])
Copy the code

Because the useEffect callback uses the external title variable inside, if you want to execute the callback only when the title value changes, you simply pass in an array in the second argument and write the internal dependent variables in the array. If the title value changes, The useEffect callback can be used internally to determine whether a callback needs to be executed based on the dependencies passed in.

So if you pass an empty array to the second useEffect argument, the useEffect callback will only be executed once after the first rendering:

useEffect((a)= > {
    // only executes once after the first render
    console.log('useEffect')}, [])Copy the code

useContext

React has a context concept that allows us to share state across components without passing layers of props. Here’s a simple example:

Redux uses the React Context feature to share data across components.

const ThemeContext = React.createContext();
function App() {
    const theme = {
        mode: 'dark'.backgroundColor: '# 333',}return (
        <ThemeContext.Provider value={theme}>
            <Display />
        </ThemeContext.Provider>
    )
}

function Display() {
    return (
        <ThemeContext.Consumer>
            {({backgroundColor}) => <div style={{backgroundColor}}>Hello Hooks.</div>}
        </ThemeContext.Consumer>
    )
}
Copy the code

Here is the useContext version:

function Display() {
    const { backgroundColor } = useContext(ThemeContext);
    return (<div style={{backgroundColor}}>Hello Hooks.</div>)}Copy the code

Nested Consumer:

function Header() {
    return (
        <CurrentUser.Consumer>
            {user =>
                <Notifications.Consumer>
                    {notifications =>
                        <header>
                            Hello {user.name}!
                            You have {notifications.length} notifications.
                        </header>
                    }
                </Notifications.Consumer>
            }
        </CurrentUser.Consumer>
    );
}
Copy the code

Pat flat with useContext:

function Header() {
    const user = useContext(CurrentUser)
    const notifications = useContext(Notifications)
    return (
        <header>
            Hello {user.name}!
            You have {notifications.length} notifications.
        </header>)}Copy the code

emm… This effect is somewhat similar to the feeling of modifying hell callbacks with async and await.

That’s the base Hooks for React. This article does not cover the Hooks provided by other authorities that are recommended to read the documentation if you are interested.

Points needing attention in use

As convenient as it is to us, we also need to follow some of their conventions, otherwise it will be counter-productive:

  • Avoid using Hooks in loops, conditional judgments, or nested functions.
  • Only function-defined components and Hooks can call Hooks. Avoid calling Hooks from class components or normal functions.
  • Not in theuseEffectThe use ofuseStateReact displays an error message.
  • Class components cannot be replaced or discarded, and there is no need to force class components to be modified. Both methods can coexist

conclusion

The core idea of Render Props is to pass some state inside the component by calling this.props. Children () (of course, any other value is the name of the function property). The internal state of the component is then obtained through the parameters of the function in the corresponding property outside the component, and the corresponding UI or logic is processed in that function. Hooks are kind of like lithographs of Render Props (see useContext earlier).

React code can be reused in three ways:

  • Render Props
  • Hooks
  • HOC

By comparison, Hooks approach has the greatest advantage and solves some of the pain points of the other two approaches, so it is recommended to use them.

Related reading:

  • High-order components in React and their application scenarios
  • IoC philosophy in the front end
  • (Part) Middle and senior front factory interview secrets, winter for your escort, direct factory

QianDuanXiaoZhuanLan