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

Introduce PureComponent to reduce `render` method calling #10

merged 3 commits into from Jul 22, 2019


Copy link

commented Jul 19, 2019

What is PureComponent?

PureComponent is a Component, but it caches render's result to skip render method calling if the attributes are same.
I got the idea from React's PureComponent.


class Hello < Ovto::PureComponent
  def render(name:)
    o 'h1', "Hello, #{compute_name_with_heavy_process(name)}"

  private def compute_name_with_heavy_process(name)

class MainComponent < Ovto::Component
  def render
    o 'div' do
      o Hello, name:
      o 'div', state.something

In this example, Hello#render has a heavy function. But it is only called when is changed because it uses PureComponent. It is not called when state.something is changed.
So it will be faster than without PureComponent.


PureComponent only supports component that does not use state.
So state method is not available for PureComponent.

PureComponent decides using cache by attributes changes.
If it is aware of state, it will be meaningless. Because state is always changed when render is called.

But I think we can support state with the manual specifying state, which is like shouldUpdateComponent of React.
For example

class C < Ovto::Component
  def render
    o 'h1',

  def should_update?(attr, next_attr, state, next_state)
    # List dependent states manually !=

But I do not like this idea. It needs listing all of the dependent states manually. If I forget a dependency, it will cause a mysterious bug.

And we can add the should_update? feature in the future if it is needed.
So I think this feature is not necessary for the first release of PureComponent.


I profiled my application that is based on Ovto with/without PureComponent.

It is a Number Place (a.k.a. Sudoku) application. And I profiled clicking a cell.
When clicking a cell, it calls render method only for the clicked cell if the cell component is a PureComponent.

With PureComponent

Without PureComponent

As you can see, the second peek is the render method calling. (The first peek is handling the click event.)
The render time is about 29ms (765ms ~ 794ms) without PureComponent, but it is about 5.5ms (692ms ~ 697.5ms).
It reduces 23.5ms, it is 5.3x faster.

I don't think 23.5ms is a meaningful difference. I cannot distinguish the difference.
But I think it will be a meaningful difference if the application will be larger, or the user uses a low-tech computer, such as a cheap smartphone.
If the difference is 4~5x, which is 100ms, I guess I will be annoyed by the delay.


This pull request will change the Component (NOT PureComponent) implementation.
It will cache child components instances by this change because component instance should remember the previous attributes.

By the way, the PureComponent implementation is super simple. See the code.

What do you think? If you accept this proposal, I'll do the following TODOs.

  • Write test
  • Write documentation

This comment has been minimized.

Copy link

commented Jul 20, 2019

This looks nice to have 👍 Thank you.


def do_render(args, state)
return @cache if args == @prev_props

This comment has been minimized.

Copy link

yhara Jul 20, 2019


How about using #equal? instead of #== ?

This comment has been minimized.

Copy link

pocke Jul 21, 2019

Author Contributor

#equal? does not work, because component receives different hash instance every time.

def render
  o Pure, {foo: bar} # `{foo: bar}` hash is created by every time of `render` calling.

By the way, I guess we can use equal? for all value of args. e.g.) args.all?{|k, v| v.equal? @prev_props[k]}.

This comment has been minimized.

Copy link

pocke Jul 21, 2019

Author Contributor

I tried benchmarking, but the naive implementation to use equal? is slower than ==.

note: The example in the previous comment has a bug because it does not check key equivalent.

require 'benchmark'

# Use Struct because there are enough seped difference between #equal? and #==.
obj =, :bar).new(1, 2)

a = { foo: 'bar', hoge: obj }
b = a.dup
c = { foo: 'bar', hoge: obj, additional: 'aaa' }

def use_eqeq(x, y)
 x == y

def use_equal(x, y)
 x.keys == y.keys && x.all?{|k, v| y[k] == v}

# Just test for use_equal method
p use_eqeq(a, b) # => true
p use_equal(a, b) # => true
p use_eqeq(a, c) # => false
p use_equal(a, c) # => false

N = 100000 do |x|'Struct#==')     {N.times{obj == obj}}'Struct#equal?') {N.times{obj.equal?(obj)}}'same ==')    {N.times{use_eqeq(a, b)}}'same equal?'){N.times{use_equal(a, b)}}'diff ==')    {N.times{use_eqeq(a, c)}}'diff equal?'){N.times{use_equal(a, c)}}
$ opal test.rb
                           user     system      total        real
Struct#==              0.218000   0.218000   0.872000 (  0.217753)
Struct#equal?          0.016000   0.016000   0.064000 (  0.015879)
same ==                0.253000   0.253000   1.012000 (  0.253861)
same equal?            0.498000   0.498000   1.992001 (  0.498554)
diff ==                0.017000   0.017000   0.068000 (  0.017099)
diff equal?            0.085000   0.085000   0.340000 (  0.084590)

I guess we can improve speed by implementing it with JavaScript, like Hash#==.
But I think it is over-engineering, so we can use Hash#==. It is enough fast.

pocke added some commits Jul 21, 2019


This comment has been minimized.

Copy link
Contributor Author

commented Jul 22, 2019

I wrote the test and documentation.

Thank you!

@yhara yhara merged commit d1c4c29 into yhara:master Jul 22, 2019

@pocke pocke deleted the pocke:pure-component branch Jul 22, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
None yet
2 participants
You can’t perform that action at this time.