Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic rendering #2

Open
Tracked by #1
jgaskins opened this issue Sep 10, 2016 · 0 comments
Open
Tracked by #1

Basic rendering #2

jgaskins opened this issue Sep 10, 2016 · 0 comments
Labels

Comments

@jgaskins
Copy link
Member

jgaskins commented Sep 10, 2016

To render content to the screen, you need a component. For the most part, components are just objects that respond to render with a virtual-DOM node. The easiest way to generate these is by including the Clearwater::Component mixin.

class MyComponent
  include Clearwater::Component

  def render
    div('Hello, world!')
  end
end

The render method calls div with some text. When this component is rendered into your document, it will be the same as if you'd rendered this HTML: <div>Hello, world!</div>.

In order to render a component, you need an application object with an instance of this component:

app = Clearwater::Application.new(component: MyComponent.new)

Once you have that, you simply invoke the app with call. When your application's state changes, you simply use the render method to update the UI to match. If, for example, your app displays the current time, this means the current time is part of your application state, so you may want to update it every second or every minute:

class Clock
  include Clearwater::Component

  def render
    div(Time.now.strftime('%I:%M:%S%P')) # "3:53:12pm"
  end
end

app = Clearwater::Application.new(component: Clock.new)
app.call

# Re-render every second
Bowser.window.interval(1) { app.render }

Complex contents

In these examples, the only content inside of our elements was a single string. In real apps, you'll never have just one element with one string, but instead one container element with many child elements that may also have their own child elements.

Child elements are as easy as replacing the string with another element:

div(span('Hello, world'))

This places a span element within the div. If you want to supply multiple child elements, you can also pass an array:

div([
  span('Hello, '),
  span('World!'),
])

You can also replace some of these method calls with other component instances:

class Greeting
  include Clearwater::Component

  def render
    div([
      Hello.new('World')
    ])
  end
end

# Using a Struct to remove boilerplate of constructor and attr_reader
Hello = Struct.new(:name) do
  def render
    span("Hello, #{name}!")
  end
end

Rendering lifecycle

The lifecycle is rarely necessary to keep in mind, but in case you need to track down a bug, it can be helpful. A render occurs like this:

  • Call app.render
  • App delays the render until the end of the current animation frame (or until the next if currently in an animation-frame callback) to coalesce further render calls
  • Rendering begins
  • VDOM generation begins
    • HTML tag methods return VDOM nodes directly
    • If a render-able object is encountered during VDOM generation …
      • If it is a CachedRender component, it gets wrapped in a delayed render
      • Otherwise, its render method is called and its return value replaces it in the VDOM tree
  • VDOM reconciliation begins
    • Typical VDOM diff and patching occurs for VDOM nodes
      • New DOM nodes are created based on the corresponding VDOM nodes for any VDOM nodes that were not in the previous render
      • Existing VDOM nodes have their corresponding DOM nodes updated (recursively) to match their structure
      • DOM nodes whose VDOM nodes have been removed are also removed from the DOM
    • If a CachedRender delayed render is encountered, its should_render? method is invoked with the previous instance of itself
      • If it returns a truthy value, a new VDOM node will be generated from the component's render method and the diff/patch process will go down that tree
      • If it returns false or nil, that DOM subtree will not be touched
  • Browser recalculates styles
  • Browser recalculates layouts for DOM subtrees whose dimensions changed based on those styles
  • Browser paints the updated DOM to the screen
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant