Skip to content
This repository was archived by the owner on Oct 19, 2018. It is now read-only.

Commit 9472d8a

Browse files
catmandozetachang
authored andcommitted
refactored handling of tags and method_missing
# Conflicts: # .rubocop.yml # lib/react/api.rb # lib/react/component.rb # lib/react/top_level.rb # spec/react/component_spec.rb
1 parent fb8c151 commit 9472d8a

File tree

12 files changed

+493
-397
lines changed

12 files changed

+493
-397
lines changed

lib/react/api.rb

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,39 @@
11
require 'react/native_library'
22

33
module React
4+
# Provides the internal mechanisms to interface between reactrb and native components
5+
# the code will attempt to create a js component wrapper on any rb class that has a
6+
# render (or possibly _render_wrapper) method. The mapping between rb and js components
7+
# is kept in the @@component_classes hash.
8+
9+
# Also provides the mechanism to build react elements
10+
11+
# TOOO - the code to deal with components should be moved to a module that will be included
12+
# in a class which will then create the JS component for that class. That module will then
13+
# be included in React::Component, but can be used by any class wanting to become a react
14+
# component (but without other DSL characteristics.)
415
class API
516
@@component_classes = {}
617

718
def self.import_native_component(opal_class, native_class)
819
@@component_classes[opal_class] = native_class
920
end
1021

22+
def self.eval_native_react_component(name)
23+
component = `eval(name)`
24+
raise "#{name} is not defined" if `#{component} === undefined`
25+
unless `#{component}.prototype !== undefined && !!#{component}.prototype.isReactComponent`
26+
raise 'does not appear to be a native react component'
27+
end
28+
component
29+
end
30+
31+
def self.native_react_component?(name)
32+
eval_native_react_component(name)
33+
rescue
34+
nil
35+
end
36+
1137
def self.create_native_react_class(type)
1238
raise "Provided class should define `render` method" if !(type.method_defined? :render)
1339
render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
@@ -74,7 +100,7 @@ def self.create_element(type, properties = {}, &block)
74100
params << @@component_classes[type]
75101
elsif type.kind_of?(Class)
76102
params << create_native_react_class(type)
77-
elsif React.html_tag?(type)
103+
elsif React::Component::Tags::HTML_TAGS.include?(type)
78104
params << type
79105
elsif type.is_a? String
80106
return React::Element.new(type)
@@ -108,8 +134,6 @@ def self.convert_props(properties)
108134
props["className"] = value
109135
elsif ["style", "dangerously_set_inner_HTML"].include? key
110136
props[lower_camelize(key)] = value.to_n
111-
elsif React::HASH_ATTRIBUTES.include?(key) && value.is_a?(Hash)
112-
value.each { |k, v| props["#{key}-#{k.tr('_', '-')}"] = v.to_n }
113137
else
114138
props[React.html_attr?(lower_camelize(key)) ? lower_camelize(key) : key] = value
115139
end

lib/react/component.rb

Lines changed: 25 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
require 'react/ext/string'
22
require 'react/ext/hash'
33
require 'active_support/core_ext/class/attribute'
4-
require 'active_support/core_ext/module/introspection'
54
require 'react/callbacks'
6-
require 'react/children'
75
require 'react/rendering_context'
86
require 'react/observable'
97
require 'react/state'
@@ -17,6 +15,8 @@ module Component
1715
def self.included(base)
1816
base.include(API)
1917
base.include(Callbacks)
18+
base.include(Tags)
19+
base.include(DslInstanceMethods)
2020
base.class_eval do
2121
class_attribute :initial_state
2222
define_callback :before_mount
@@ -27,28 +27,6 @@ def self.included(base)
2727
define_callback :before_unmount
2828
end
2929
base.extend(ClassMethods)
30-
31-
if base.name
32-
parent = base.name.split("::").inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }[-2]
33-
class << parent
34-
def method_missing(n, *args, &block)
35-
name = n
36-
if name =~ /_as_node$/
37-
node_only = true
38-
name = name.gsub(/_as_node$/, "")
39-
end
40-
begin
41-
name = const_get(name)
42-
rescue Exception
43-
name = nil
44-
end
45-
unless name && name.method_defined?(:render)
46-
return super
47-
end
48-
React::RenderingContext.build_or_render(node_only, name, *args, &block)
49-
end
50-
end
51-
end
5230
end
5331

5432
def initialize(native_element)
@@ -59,30 +37,9 @@ def render
5937
raise "no render defined"
6038
end unless method_defined?(:render)
6139

62-
def children
63-
Children.new(`#{@native}.props.children`)
64-
end
65-
66-
def params
67-
@params ||= self.class.props_wrapper.new(self)
68-
end
69-
70-
def props
71-
Hash.new(`#{@native}.props`)
72-
end
73-
74-
def refs
75-
Hash.new(`#{@native}.refs`)
76-
end
77-
78-
def state
79-
@state_wrapper ||= StateWrapper.new(@native, self)
80-
end
81-
8240
def update_react_js_state(object, name, value)
8341
if object
84-
set_state({"***_state_updated_at-***" => Time.now.to_f,
85-
"#{object.class.to_s+'.' unless object == self}#{name}" => value})
42+
set_state({"***_state_updated_at-***" => Time.now.to_f, "#{object.class.to_s+'.' unless object == self}#{name}" => value})
8643
else
8744
set_state({name => value})
8845
end rescue nil
@@ -94,11 +51,10 @@ def emit(event_name, *args)
9451

9552
def component_will_mount
9653
IsomorphicHelpers.load_context(true) if IsomorphicHelpers.on_opal_client?
54+
@props_wrapper = self.class.props_wrapper.new(Hash.new(`#{@native}.props`))
9755
set_state! initial_state if initial_state
9856
State.initialize_states(self, initial_state)
99-
State.set_state_context_to(self) do
100-
self.run_callback(:before_mount)
101-
end
57+
State.set_state_context_to(self) { self.run_callback(:before_mount) }
10258
rescue Exception => e
10359
self.class.process_exception(e, self)
10460
end
@@ -113,23 +69,26 @@ def component_did_mount
11369
end
11470

11571
def component_will_receive_props(next_props)
116-
# need to rethink how this works in opal-react, or if its actually that
117-
# useful within the react.rb environment for now we are just using it to
118-
# clear processed_params
119-
State.set_state_context_to(self) do
120-
self.run_callback(:before_receive_props, next_props)
121-
end
72+
# need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
73+
# for now we are just using it to clear processed_params
74+
State.set_state_context_to(self) { self.run_callback(:before_receive_props, Hash.new(next_props)) }
12275
rescue Exception => e
12376
self.class.process_exception(e, self)
12477
end
12578

79+
def props_changed?(next_props)
80+
return true unless props.keys.sort == next_props.keys.sort
81+
props.detect { |k, v| `#{next_props[k]} != #{params[k]}`}
82+
end
83+
12684
def should_component_update?(next_props, next_state)
12785
State.set_state_context_to(self) do
86+
next_props = Hash.new(next_props)
12887
if self.respond_to?(:needs_update?)
129-
!!self.needs_update?(next_props, next_state)
88+
!!self.needs_update?(next_props, Hash.new(next_state))
13089
elsif false # switch to true to force updates per standard react
13190
true
132-
elsif props != next_props
91+
elsif props_changed? next_props
13392
true
13493
elsif `!next_state != !#{@native}.state`
13594
true
@@ -144,16 +103,15 @@ def should_component_update?(next_props, next_state)
144103
end
145104

146105
def component_will_update(next_props, next_state)
147-
State.set_state_context_to(self) do
148-
self.run_callback(:before_update, next_props, next_state)
149-
end
106+
State.set_state_context_to(self) { self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state)) }
107+
@props_wrapper = self.class.props_wrapper.new(Hash.new(next_props), @props_wrapper)
150108
rescue Exception => e
151109
self.class.process_exception(e, self)
152110
end
153111

154112
def component_did_update(prev_props, prev_state)
155113
State.set_state_context_to(self) do
156-
self.run_callback(:after_update, prev_props, prev_state)
114+
self.run_callback(:after_update, Hash.new(prev_props), Hash.new(prev_state))
157115
State.update_states_to_observe
158116
end
159117
rescue Exception => e
@@ -169,47 +127,14 @@ def component_will_unmount
169127
self.class.process_exception(e, self)
170128
end
171129

172-
def p(*args, &block)
173-
if block || args.count == 0 || (args.count == 1 && args.first.is_a?(Hash))
174-
_p_tag(*args, &block)
175-
else
176-
Kernel.p(*args)
177-
end
178-
end
179-
180-
def component?(name)
181-
name_list = name.split("::")
182-
[self.class, *self.class.parents].each do |scope|
183-
component = name_list.inject(scope) do |scope, class_name|
184-
scope.const_get(class_name)
185-
end rescue nil
186-
return component if component && component.method_defined?(:render)
187-
end
188-
nil
189-
end
190-
191-
def method_missing(n, *args, &block)
192-
# TODO deprecate and remove - done so that params shadow tags, no longer
193-
# needed
194-
return props[n] if props.key?(n)
195-
name = n
196-
if name =~ /_as_node$/
197-
node_only = true
198-
name = name.gsub(/_as_node$/, "")
199-
end
200-
unless (React.html_tag?(name) || name == 'present' || name == '_p_tag' || (name = component?(name, self)))
201-
return super
202-
end
203-
204-
if name == "present"
205-
name = args.shift
206-
end
130+
attr_reader :waiting_on_resources
207131

208-
if name == "_p_tag"
209-
name = "p"
132+
def _render_wrapper
133+
State.set_state_context_to(self) do
134+
React::RenderingContext.render(nil) {render || ""}.tap { |element| @waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources }
210135
end
211-
212-
React::RenderingContext.build_or_render(node_only, name, *args, &block)
136+
rescue Exception => e
137+
self.class.process_exception(e, self)
213138
end
214139

215140
def watch(value, &on_change)
@@ -220,18 +145,5 @@ def define_state(*args, &block)
220145
State.initialize_states(self, self.class.define_state(*args, &block))
221146
end
222147

223-
attr_reader :waiting_on_resources
224-
225-
def _render_wrapper
226-
State.set_state_context_to(self) do
227-
React::RenderingContext.render(nil) {render || ""}.tap do |element|
228-
if element.respond_to?(:waiting_on_resources)
229-
@waiting_on_resources = element.waiting_on_resources
230-
end
231-
end
232-
end
233-
rescue Exception => e
234-
self.class.process_exception(e, self)
235-
end
236148
end
237149
end

lib/react/component/class_methods.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@ module React
22
module Component
33
module ClassMethods
44
def backtrace(*args)
5-
@backtrace_off = (args[0] == :off)
5+
@dont_catch_exceptions = (args[0] == :none)
6+
@backtrace_off = @dont_catch_exceptions || (args[0] == :off)
67
end
78

89
def process_exception(e, component, reraise = nil)
910
message = ["Exception raised while rendering #{component}"]
10-
if e.backtrace && e.backtrace.length > 1 && !@backtrace_off # seems like e.backtrace is empty in safari???
11+
if e.backtrace && e.backtrace.length > 1 && !@backtrace_off
1112
message << " #{e.backtrace[0]}"
1213
message += e.backtrace[1..-1].collect { |line| line }
1314
else
1415
message[0] += ": #{e.message}"
1516
end
1617
message = message.join("\n")
1718
`console.error(message)`
18-
raise e if reraise
19+
raise e if reraise || @dont_catch_exceptions
1920
end
2021

2122
def deprecation_warning(message)
@@ -180,9 +181,14 @@ def export_component(opts = {})
180181
Native(`window`)[first_name] = add_item_to_tree(Native(`window`)[first_name], [React::API.create_native_react_class(self)] + export_name[1..-1].reverse).to_n
181182
end
182183

183-
def imports(native_component_name)
184-
React::API.import_native_component(native_component_name, self)
184+
def imports(component_name)
185+
React::API.import_native_component(self,
186+
React::API.eval_native_react_component(component_name))
185187
render {} # define a dummy render method - will never be called...
188+
rescue Exception => e # rubocop:disable Lint/RescueException : we need to catch everything!
189+
raise "#{self} cannot import '#{component_name}': #{e.message}."
190+
# rubocop:enable Lint/RescueException
191+
ensure
186192
self
187193
end
188194

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
module React
2+
module Component
3+
module DslInstanceMethods
4+
def children
5+
if `#{@native}.props.children==undefined`
6+
nodes = []
7+
else
8+
nodes = [`#{@native}.props.children`].flatten
9+
end
10+
class << nodes
11+
include Enumerable
12+
13+
def to_n
14+
self
15+
end
16+
17+
def each(&block)
18+
if block_given?
19+
%x{
20+
React.Children.forEach(#{self.to_n}, function(context){
21+
#{yield React::Element.new(`context`)}
22+
})
23+
}
24+
nil
25+
else
26+
Enumerator.new(`React.Children.count(#{self.to_n})`) do |y|
27+
%x{
28+
React.Children.forEach(#{self.to_n}, function(context){
29+
#{y << Element.new(`context`)}
30+
})
31+
}
32+
end
33+
end
34+
end
35+
end
36+
37+
nodes
38+
end
39+
40+
def params
41+
@props_wrapper
42+
end
43+
44+
def props
45+
Hash.new(`#{@native}.props`)
46+
end
47+
48+
def refs
49+
Hash.new(`#{@native}.refs`)
50+
end
51+
52+
def state
53+
@state_wrapper ||= StateWrapper.new(@native, self)
54+
end
55+
end
56+
end
57+
end

lib/react/component/props_wrapper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
module React
22
module Component
3+
4+
def deprecated_params_method(name, *args, &block)
5+
self.class.deprecation_warning "Direct access to param `#{name}`. Use `params.#{name}` instead."
6+
params.send(name, *args, &block)
7+
end
8+
39
class PropsWrapper
410
attr_reader :component
511

0 commit comments

Comments
 (0)