• How do you separate components?
  • By James K Nelson
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: undead25
  • Proofreader: Schrodinger’s cat, Germxu

How do you split components?

The React component grows over time. Fortunately, I realized this, or some of my application components would have been horrible.

But is this actually a problem? While it may seem odd to create lots of widgets that are only used once…

In a large React application, there’s nothing inherently wrong with having a large number of components. In fact, for state components, of course, we want them to be as small as possible.

The appearance of a bloated component

It doesn’t usually decompose very well in terms of states. If you have multiple actions acting on the same state, they all need to be in the same component. The more ways the state can be changed, the larger the component. In addition, if a component has actions that affect multiple state types, it will inevitably become very large.

But even if large components are unavoidable, they are still very bad to use. This is why you try to break down as many components as possible, following the principle of separation of concerns.

Easier said than done, of course.

Finding a separation of concerns is as much an art as a technique. But here are some common patterns you can follow…

Four types of components

In my experience, there are four types of components that can be split from larger components.

The view components

For more information about view components (or what some call presentation components), see Dan Abramov’s famous book, Presentation Components and Container Components.

The view component is the simplest component type. All they do is display information and send user input via callbacks. They are:

  • Distribute attributes to child elements.
  • Have callbacks that forward data from child elements to parent components.
  • Usually function components, but if they need to bind callbacks for performance, it could be classes.
  • Lifecycle methods are generally not used, except for performance tuning.
  • No state is stored directly, except for UI-centric states such as animation state.
  • Don’t use Refs or interact directly with the DOM (because a DOM change means a state change).
  • Do not modify the environment. They should not directly send actions to Redux’s store or call apis, etc.
  • Do not use React context.

You can break down some of the signs of the presentation component from the larger component:

  • There are DOM tags or styles.
  • There are repeating parts like list items.
  • Have content that “looks” like a box or area.
  • Parts of JSX rely only on a single object as input data.
  • There is a large presentation component with different areas.

Examples of showing components can be broken out from larger components:

  • A component that performs layout for multiple child elements.
  • Cards and list items can be split from the list.
  • Fields can be split out of the form (combining all updates into oneonChangeCalling back).
  • Tags can be split out of controls.

The control component

A control component is a component that stores state associated with part of the input, that is, tracking states that the user has initiated actions that have not yet generated valid values through the onChange callback. They are similar to presentation components, but:

  • State can be stored (when associated with partial input).
  • You can use refs and interact with DOM.
  • You can use a lifecycle approach.
  • There are usually no styles and no DOM tags.

There are some signs that you can break out control components from larger components:

  • Store part of the input in the state.
  • Interact with the DOM through refs.
  • Some parts look like native controls — buttons, form fields, etc.

Some examples of control components:

  • Date picker
  • The input prompt
  • switch

You will often find that many of your controls have the same behavior, but have different representations. In this case, it makes sense to split the presentation into view components and pass it in as a theme or view property.

You can see examples of connector functions in action in the React-DND library.

When you split the presentation component out of the control, you might find that passing separate ref functions and callbacks to the presentation component via props feels a bit wrong. In this case, it might help to pass the connector function, which clones the refs and callbacks into the passed element. Such as:

class MyControl extends React.Component {
  // Connector functions use react. cloneElement to handle events
  // and refs are added to the elements created by the presentation component.
  connectControl = (element) = > {
    return React.cloneElement(element, {
      ref: this.receiveRef,
      onClick: this.handleClick,
    })
  }

  render() {
    // You can pass the presentation component to the control via properties,
    // this allows the control to be themed with arbitrary tags and styles.
    return React.createElement(this.props.view, {
      connectControl: this.connectControl,
    })
  }

  handleClick = (e) = > { / *... * / }
  receiveRef = (node) = > { / *... * / }

  // ...
}

// Display components can wrap an element in 'connectControl',
// To add appropriate callbacks and 'ref' functions.
function ControlView({ connectControl }) {
  return connectControl(
    <div className='some-class'>
      control content goes here
    </div>)}Copy the code

You will find that the control components are usually quite large. They have to deal with the DOM, which is inseparable from state, which makes the separation of control components particularly useful; By limiting DOM interactions to control components, you can put any DOM-related miscellaneous in one place.

The controller

Once you have split the presentation and control code into separate components, most of the remaining code will be business logic. If there’s one thing I want you to remember after reading this article, it’s that business logic doesn’t need to be in the React component. It often makes sense to implement business logic in ordinary JavaScript functions and classes. For lack of a better name, I’ll call it a controller.

So there are only three types of React components. But there are still four types of components, because not every component is a React component.

Not every car is a Toyota (but at least in Tokyo most of them are).

Controllers generally follow a similar pattern. They are:

  • Store a state.
  • There are actions that alter that state and may cause side effects.
  • There may be ways to subscribe to state changes that are not directly caused by actions.
  • You can accept configurations of similar properties, or subscribe to the state of a global controller.
  • Does not rely on any React API.
  • There is no interaction with the DOM and no styles.

There are some signs that you can split controllers out of your components:

  • A component has many states that are independent of part of its input.
  • State is used to store information received from the server.
  • Reference global state, such as drag-and-drop or navigation state.

Some examples of controllers:

  • A store of Redux or Flux.
  • A JavaScript class with MobX observable.
  • A normal JavaScript class that contains methods and instance variables.
  • An event emitter.

Some controllers are global; They are completely independent of your React application. Redux’s Stores are a good example of a global controller. But not all controllers need to be global, and not all states need to be placed in separate controllers or stores.

By splitting the controller code for forms and lists into separate classes, you can instantiate these classes in container components as needed.

Container components

The container component is the glue that connects the controller to the presentation and control components. They are more flexible than other types of components. But still tend to follow a few patterns, which:

  • Stores controller instances in component state.
  • Render state by presenting components and controlling components.
  • Use the lifecycle method to subscribe to controller status updates.
  • No DOM tags or styles are used (the possible exception is some unstyled divs).
  • Usually made by people like ReduxconnectHigher order functions like this are generated.
  • Global controllers (such as Redux’s Store) can be accessed from context.

Although sometimes you can split container components out of other containers, this is rare. Instead, it’s best to focus on splitting the controller, the presentation component, and the control component, and make everything else your container component.

Some examples of container components:

  • aAppcomponent
  • From the storyconnectComponent returned.
  • By MobXobserverComponent returned.
  • The react to the router<Link>Component (because it uses context and affects the environment).

Component files

What do you call a component that is not a view, control, controller, or container? You just call it a component! Simple, isn’t it?

Once you break out a component, the question becomes where do I put it? To be honest, the answer depends largely on personal preference, but there is one rule that I think is important:

If the split component is used in only one parent, it will be in the same file as the parent.

This is to split components as easily as possible. Creating files is cumbersome and can interrupt your train of thought. If you try to put each component in a different file, you’ll quickly ask yourself “Do I really need a new component?” Therefore, place related components in the same file.

Of course, once you find a place to reuse the component, you may want to move it to a separate file. This makes it a sweet annoyance which file to put it in.

What about performance?

Breaking up a large component into controllers, presentation components, and control components increases the amount of code that needs to be run. That may slow things down a little bit, but not much.

The story

The only time I encountered performance issues due to using too many components — I rendered 5000 grid cells per frame, with multiple nested components per cell.

The thing about React performance is that even if your application has significant latency, the problem is definitely not due to too many components.

So you can use as many components as you want.

If not split…

I’ve covered a lot of rules in this article, so you might be surprised to hear that I don’t really like strict rules. They are usually wrong, at least in some cases. So it’s important to be clear:

“Can” split does not mean “must” split.

Assuming that your goal is to make your code easier to understand and maintain, this still leaves the question: what is easier to understand? What is easy to maintain? The answer often depends on who’s asking, which is why refactoring is as much an art as a technology.

As a concrete example, consider the design of this component:


      
<html>
  <head>
    <title>I'm in a React app!</title>
  </head>
  <body>
    <div id="app"></div>

    <script src="https://unpkg.com/[email protected]/dist/react.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/react-dom.js"></script>
    <script>
      // Write JavaScript here
    </script>
  </body>
</html>Copy the code
class List extends React.Component {
  renderItem(item, i) {
    return (
      <li key={item.id}>
        {item.name}
      </li>
    )
  }

  render() {
    return (
      <ul>
        {this.props.items.map(this.renderItem)}
      </ul>
    )
  }
}

ReactDOM.render(
  <List items={[
    { id: 'a', name: 'Item 1'}, {id: 'b', name: 'Item 2'}}] / >,
  document.getElementById('app')
)Copy the code

While it is entirely possible to split the renderItem into a separate component, what is the actual benefit of doing so? Probably not. In fact, using the renderItem method might be easier to understand in a file with multiple different components.

Remember: The four types of components are a pattern you can use when you feel they make sense. They are not hard and fast rules. If you’re not sure if something needs to be split, don’t, because the world won’t end even if some components are more bloated than others.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, React, front-end, back-end, product, design and other fields. If you want to see more high-quality translation, please continue to pay attention to the Project, official Weibo, Zhihu column.