Skip to content

Rendering from the top down

Jamie Gaskins edited this page Jul 1, 2016 · 2 revisions

Clearwater renders your entire app from the top down and only when you tell it to. This differs from how React renders on state changes — because React has no concept of an "app", you explicitly tell it to render a component tree to a DOM node, but you call this.setState to update the component's state and re-render.

In Clearwater, however, there is an "app" concept: the Clearwater::Application. Application state shouldn't be in many of your components; only your root component and routing targets should maintain any state at all and pass them into the ephemeral components as brand-new objects.

One of the things people primarily bring up when discussing top-down rendering is that it's inefficient. It's entirely possible that Clearwater could track the DOM node where each component gets rendered, but that has its own performance costs. Instead, Clearwater apps can use component render caching to get around this.

The CachedRender Mixin

Re-rendering absolutely everything every time the app renders is the surest way to have the freshest view of your app state, but is frequently unnecessary. The virtual DOM has to generate new virtual nodes, then compare them to the previous version, and make the necessary changes to the rendered DOM. If your component can be sure it has no changes between then and now, it can reuse the previous rendered virtual nodes and save some CPU time.

class TodoList
  include Clearwater::Component
  include Clearwater::CachedRender

  attr_reader :todos

  def initialize(todos)
    @todos = todos
  end

  def should_render?(previous)
    todos != previous.todos
  end

  def render
    ul(todos.map { |todo| TodoListItem.new(todo) })
  end
end

Notice the should_render? method. We check the todos array against the one in the previously rendered TodoList component. We return true if anything has changed, signaling that the component should go ahead and rerender. Returning a falsy value tells the component that it can safely reuse the previously rendered value. Returning the exact same virtual DOM tree (the exact same object in memory, not just an equivalent tree) between renders triggers an optimization in the diffing algorithm that causes it not to descend into the tree, which can save precious time in rerendering your app.

Mutability Warning

If you depend on mutable data at all in your components, you will want to dup it. For example, if you append a new todo to the list rather than creating a new array with the new todo added to the end, you should set @todos = todos.dup in TodoList#initialize. Similarly, you'll want to dup the individual tasks in the TodoListItem component so that when you compare it you won't be comparing the model with itself.

Clone this wiki locally