As the saying goes, “laziness is a programmer’s virtue.” In today’s world of front-end engineering, “Ctrl+C” and “Ctrl+V” codes, while pleasant to use, are like crematoria if they need to be changed. How to “lazy” efficiency, is worth thinking about the problem. Reducing code copy, increasing packaging and reuse capabilities, and achieving maintainable, reusable code are undoubtedly the highest levels of what I would call “laziness.” In view of the fact that the author used React mostly before, I gradually used a lot of Vue for development after entering Ele. me, so I take this opportunity to talk about various component-based reuse and implementation methods in React and Vue.

Mixin Mixin mode

One of the most primitive forms of reuse is mixins. Reuse between components through injection by encapsulating common logic as a Mixin. “Ps: This method is not only used for components, but is also popular in various CSS preprocessors.”

In React, components created using React. CreateClass () can use Mixin mode. ES6 does not support Mixin mode.

//mixin const mixinPart = { mixinFunc() { console.log('this mixin! '); return 'this mixin! '; }}; const Contacts = React.createClass({ mixins: [mixinPart], render() { return ( <div>{this.mixinFunc()}</div> ); }}); // => 'this mixin! ';Copy the code

In Vue, the logic is similar:

//mixin const mixinPart = { created() { console.log('this mixin! '); return 'this mixin! '; }}; const Component = Vue.extend({ mixins: [mixinPart] }); const component = new Component(); // => "this mixin!"Copy the code

The Mixin pattern gives components common abstraction and reuse capabilities, but on the other hand has a number of limitations. Since mixins are intrusive, modifying mixins is equivalent to modifying the original component. Secondly, in the mixing process, the mutual covering and merging of the same key-value object and function can easily lead to various accidents. Therefore, some understanding of the Mixin internal implementation is essential. This flexibility makes mixins difficult to maintain in large projects.

High order component

The concept of high-order Component was first put forward by the React community. Referring to the high-order function in the function formula, it proposed to reuse a Component by passing in a Component and returning a new Component after operation.

React is very easy to use, as described in the official blog:

const HOC = (WrappedComponent) => { const HOC_Component = (props) => { return ( <React.Fragment> <WrappedComponent {... props} name="WrappedComponent" /> <div>This comes from HOC Component</div> </React.Fragment> ); }; HOC_Component.displayName = 'HOC_Component'; return HOC_Component; } const Component = (props) => { return <div>This message comes from Component: {props.name}</div>; } const Result = HOC(Component); ReactDOM.render(<Result />, document.getElementById('root')); // => This message comes from Component: WrappedComponent // => This comes from HOC ComponentCopy the code

There is no official example of Vue, but in contrast to React, components in Vue are ultimately rendered as functions, but in the process are actually individual objects. Therefore, a higher-order component in a Vue should pass in an object and then pass out a corresponding object. We can simply implement the HOC function corresponding to the above example:

const HOC = (WrappedComponent) => {
    return {
        components: {
            'wrapped-component': WrappedComponent
        },
        template: `
          <div>
              <wrapped-component name="WrappedComponent" v-bind="$attrs" />
              <div>This comes from HOC Component</div>
            </div>
          `
    };
}
const Component = {
    props: ['name'],
    template: '<div>This message comes from Component: {{ name }}</div>'
};

new Vue(HOC(Component)).$mount('#root')
// => This message comes from Component: WrappedComponent
// => This comes from HOC Component
Copy the code

High-order components are widely used and can be divided into property proxy and reverse inheritance.

The property proxy is specific for higher-order components that can directly obtain external input parameters and then re-transmit them to contained components after completing changes according to requirements. In this example, we added a name attribute to the WrappedComponent based on the original props and a line of render information to the original render.

// Basic reverse inheritance const HOC = (WrappedComponent) => {return class extends WrappedComponent {render() {return super.render(); }}}Copy the code

Reverse inheritance, because it inherits from WrappedComponent, can retrieve state, render, and other component data to intervene in component rendering and state. Reverse inheritance, though less common in everyday use, is certainly a bright spot for higher-order components in my opinion (there seems to be no alternative yet). For example, Vue has keep-alive as a component cache, while React has no such function. Applying the principle of data => View, a common alternative implementation is to save state and then restore state when needed. In this case, Reverse inheritance is a great tool.

const withStateCached = (WrappedComponent) => { return class extends WrappedComponent { static getDerivedStateFromProps(nextProps, State) {componentDidMount() {render() {return super.render(); }}}Copy the code

In the author’s actual project, the more advanced components as a component factories or the application of the decorator pattern, such as a basic form elements of high order component packaging for many times, adding paging, toolbars, and other functions, form conforms to the specific needs of the business of new components, achieve the goal of component reuse. Of course, higher-order components are not omnipotent. First of all, they have a higher degree of business coupling and are more suitable for encapsulating some commonly used components in daily business. The second most important disadvantage is that the Props value generated internally is fixed and easily overwritten by the external input value. As in the example, when a name attribute value is also passed in from the outside, different overrides can result in errors depending on how the component is written.

Due to space constraints, this is only a cursory introduction to some basic uses. For a more in-depth understanding, please refer to this article.

Render property/function subcomponents

To solve the problems of higher-order components, a new scheme of “Render Props” was proposed. In this scheme, a function called render is passed in as the Props parameter. After the internal processing is completed, the required component information and data are transmitted as the parameters of Render, so as to achieve a more flexible reuse logic.

const RenderProps = ({ render, ... props }) => render(props, 'RenderPropComponent'); const Component = () => ( <RenderProps render={(originProps, componentName) => (<div>From {componentName}</div>)} /> ); ReactDOM.render(<Component />, document.getElementById('root')); // => From RenderPropComponentCopy the code

In this example, we pass in the Props and a new name attribute via the Render function, and in practice, rename the name to componentName, thus avoiding the drawbacks of higher-order components.

Based on this concept, React extends the concept of function sub-components and uses children as functions, which further implements the concept that everything is a component. Meanwhile, in [email protected] version, the implementation of the new API of FB official Context also adopts the method of function sub-component.

const RenderProps = ({ children, ... props }) => children(props, name = 'RenderPropComponent'); const Component = () => (<RenderProps> {(originProps, componentName) => (<div>From {componentName}</div>)} </RenderProps>); ReactDOM.render(<Component />, document.getElementById('root')); // => From RenderPropComponentCopy the code

In versions after [email protected], the concept of slot-scope is also a bit of a render property.

const RenderProps = { template: `<div><slot v-bind="{ name: 'RenderPropComponent' }"></slot></div>` }; const vm = new Vue({ el: '#root', components: { 'render-props': RenderProps }, template: ` <render-props> <template slot-scope="{ name }"> <div>From Component</div> From {{ name }} </template> </render-props> `}); // => From Component // => From RenderPropComponentCopy the code

Component injection

The concept of Component Injection is similar to the render property, which is used to transfer a function property similar to Render. The difference is that Component Injection uses the function as a stateless Component in React instead of the original function.

const RenderProps = ({ Render, ... props }) => <Render {... props} name='RenderPropComponent' />; const Component = () => (<RenderProps Render={({ name }) => (<div>From {name}</div>)} />); ReactDOM.render(<Component />, document.getElementById('root')); // => From RenderPropComponentCopy the code

In contrast to render properties, component injection visually displays the embedded component structure in devTool’s component tree. On the other hand, because all properties are packaged as props, the flexibility of rendering properties with multiple parameters is lost.

conclusion

There are so many ways to reuse components today that I don’t think there is a single best practice. The best way to build reusable and maintainable components is to use different solutions based on specific scenarios and personal preferences and stay away from “Ctrl+C” or “Ctrl+V” programmers.