OLD | NEW |
1 Effen (fn) | 1 Effen (fn) |
2 === | 2 === |
3 | 3 |
4 Effen is a prototype of a functional-reactive framework for sky which takes insp
iration from [React](http://facebook.github.io/react/). The code as you see it h
ere is a first-draft, is unreviewed, untested and will probably catch your house
on fire. It is a proof of concept. | 4 Effen is a prototype of a functional-reactive framework for sky which takes insp
iration from [React](http://facebook.github.io/react/). The code as you see it h
ere is a first-draft, is unreviewed, untested and will probably catch your house
on fire. It is a proof of concept. |
5 | 5 |
6 Effen is comprised of three main parts: a virtual-dom and diffing engine, a comp
onent mechanism and a very early set of widgets for use in creating applications
. | 6 Effen is comprised of three main parts: a virtual-dom and diffing engine, a comp
onent mechanism and a very early set of widgets for use in creating applications
. |
7 | 7 |
8 The central idea is that you build your UI out of components. Components describ
e what their view should look like given their current configuration & state. Th
e diffing engine ensures that the DOM looks how the component describes. | 8 The central idea is that you build your UI out of components. Components describ
e what their view should look like given their current configuration & state. Th
e diffing engine ensures that the DOM looks how the component describes. |
9 | 9 |
10 If you just want to dive into code, see the `sky/examples/stocks-fn`. | 10 If you just want to dive into code, see the `sky/examples/stocks-fn`. |
(...skipping 16 matching lines...) Expand all Loading... |
27 new HelloWorldApp(); | 27 new HelloWorldApp(); |
28 } | 28 } |
29 </script> | 29 </script> |
30 ``` | 30 ``` |
31 | 31 |
32 ```JavaScript | 32 ```JavaScript |
33 // In helloworld.dart | 33 // In helloworld.dart |
34 import '../fn/lib/fn.dart'; | 34 import '../fn/lib/fn.dart'; |
35 | 35 |
36 class HelloWorldApp extends App { | 36 class HelloWorldApp extends App { |
37 Node render() { | 37 Node build() { |
38 return new Text('Hello, World!'); | 38 return new Text('Hello, World!'); |
39 } | 39 } |
40 } | 40 } |
41 ``` | 41 ``` |
42 An app is comprised of (and is, itself, a) components. A component's main job is
to implement `Node render()`. The idea here is that the `render` method describ
es the DOM of a component at any given point during its lifetime. In this case,
our `HelloWorldApp`'s `render` method just returns a `Text` node which displays
the obligatory line of text. | 42 An app is comprised of (and is, itself, a) components. A component's main job is
to implement `Node build()`. The idea here is that the `build` method describes
the DOM of a component at any given point during its lifetime. In this case, ou
r `HelloWorldApp`'s `build` method just returns a `Text` node which displays the
obligatory line of text. |
43 | 43 |
44 Nodes | 44 Nodes |
45 ----- | 45 ----- |
46 A component's `render` method must return a single `Node` which *may* have child
ren (and so on, forming a *subtree*). Effen comes with a few built-in nodes whic
h mirror the built-in nodes/elements of sky: `Text`, `Anchor` (`<a />`, `Image`
(`<img />`) and `Container` (`<div />`). `render` can return a tree of Nodes com
prised of any of these nodes and plus any other imported object which extends `C
omponent`. | 46 A component's `build` method must return a single `Node` which *may* have childr
en (and so on, forming a *subtree*). Effen comes with a few built-in nodes which
mirror the built-in nodes/elements of sky: `Text`, `Anchor` (`<a />`, `Image` (
`<img />`) and `Container` (`<div />`). `build` can return a tree of Nodes compr
ised of any of these nodes and plus any other imported object which extends `Com
ponent`. |
47 | 47 |
48 How to structure you app | 48 How to structure you app |
49 ------------------------ | 49 ------------------------ |
50 If you're familiar with React, the basic idea is the same: Application data flow
s *down* from components which have data to components & nodes which they constr
uct via construction parameters. Generally speaking, View-Model data (data which
is derived from *model* data, but exists only because the view needs it), is co
mputed during the course of `render` and is short-lived, being handed into nodes
& components as configuration data. | 50 If you're familiar with React, the basic idea is the same: Application data flow
s *down* from components which have data to components & nodes which they constr
uct via construction parameters. Generally speaking, View-Model data (data which
is derived from *model* data, but exists only because the view needs it), is co
mputed during the course of `build` and is short-lived, being handed into nodes
& components as configuration data. |
51 | 51 |
52 What does "data flowing down the tree" mean? | 52 What does "data flowing down the tree" mean? |
53 -------------------------------------------- | 53 -------------------------------------------- |
54 Consider the case of a checkbox. (i.e. `widgets/checkbox.dart`). The `Checkbox`
constructor looks like this: | 54 Consider the case of a checkbox. (i.e. `widgets/checkbox.dart`). The `Checkbox`
constructor looks like this: |
55 | 55 |
56 ```JavaScript | 56 ```JavaScript |
57 ValueChanged onChanged; | 57 ValueChanged onChanged; |
58 bool checked; | 58 bool checked; |
59 | 59 |
60 Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key); | 60 Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key); |
61 ``` | 61 ``` |
62 | 62 |
63 What this means is that the `Checkbox` component is *never* "owns" the state of
the checkbox. It's current state is handed into the `checked` parameter, and whe
n a click occurs, the checkbox invokes its `onChanged` callback with the value i
t thinks it should be changed to -- but it never directly changes the value itse
lf. This is a bit odd at first look, but if you think about it: a control isn't
very useful unless it gets its value out to someone and if you think about datab
inding, the same thing happens: databinding basically tells a control to *treat
some remote variable as its storage*. That's all that is happening here. In this
case, some owning component probably has a set of values which describe a form. | 63 What this means is that the `Checkbox` component is *never* "owns" the state of
the checkbox. It's current state is handed into the `checked` parameter, and whe
n a click occurs, the checkbox invokes its `onChanged` callback with the value i
t thinks it should be changed to -- but it never directly changes the value itse
lf. This is a bit odd at first look, but if you think about it: a control isn't
very useful unless it gets its value out to someone and if you think about datab
inding, the same thing happens: databinding basically tells a control to *treat
some remote variable as its storage*. That's all that is happening here. In this
case, some owning component probably has a set of values which describe a form. |
64 | 64 |
65 Stateful vs. Stateless components | 65 Stateful vs. Stateless components |
66 --------------------------------- | 66 --------------------------------- |
67 All components have access to two kinds of state: (1) data which is handing in f
rom their owner (the component which constructed them) and (2) data which they m
utate themselves. While react components have explicit property bags for these t
wo kinds of state (`this.prop` and `this.state`), Effen maps these ideas to the
public and private fields of the component. Constructor arguments should (by con
vention) be reflected as public fields of the component and state should only be
set on private (with a leading underbar `_`) fields. | 67 All components have access to two kinds of state: (1) data which is handing in f
rom their owner (the component which constructed them) and (2) data which they m
utate themselves. While react components have explicit property bags for these t
wo kinds of state (`this.prop` and `this.state`), Effen maps these ideas to the
public and private fields of the component. Constructor arguments should (by con
vention) be reflected as public fields of the component and state should only be
set on private (with a leading underbar `_`) fields. |
68 | 68 |
69 All nodes and most components should be stateless, never needing to mutate thems
elves and only reacting to data which is handed into them. Some components will
be stateful. This state will likely encapsulate transient states of the UI, such
as scroll position, animation state, uncommitted form values, etc... | 69 All nodes and most components should be stateless, never needing to mutate thems
elves and only reacting to data which is handed into them. Some components will
be stateful. This state will likely encapsulate transient states of the UI, such
as scroll position, animation state, uncommitted form values, etc... |
70 | 70 |
71 A component can become stateful in two ways: (1) by passing `super(stateful: tru
e)` to its call to the superclasses constructor, or by calling `setState(Functio
n fn)`. The former is a way to have a component start its life stateful, and the
later results in the component becoming statefull *as well as* scheduling the c
omponent to re-render at the end of the current animation frame. | 71 A component can become stateful in two ways: (1) by passing `super(stateful: tru
e)` to its call to the superclasses constructor, or by calling `setState(Functio
n fn)`. The former is a way to have a component start its life stateful, and the
later results in the component becoming statefull *as well as* scheduling the c
omponent to re-build at the end of the current animation frame. |
72 | 72 |
73 What does it mean to be stateful? It means that the diffing mechanism retains th
e specific *instance* of the component as long as the component which renders it
continues to require its presence. The component which constructed it may have
provided new configuration in form of different values for the constructor param
eters, but these values (public fields) will be copied (using reflection) onto t
he retained instance whose privates fields are left unmodified. | 73 What does it mean to be stateful? It means that the diffing mechanism retains th
e specific *instance* of the component as long as the component which builds it
continues to require its presence. The component which constructed it may have p
rovided new configuration in form of different values for the constructor parame
ters, but these values (public fields) will be copied (using reflection) onto th
e retained instance whose privates fields are left unmodified. |
74 | 74 |
75 Rendering | 75 Rendering |
76 --------- | 76 --------- |
77 At the end of each animation frame, all components (including the root `App`) wh
ich have `setState` on themselves will be re-rendered and the resulting changes
will be minimally applied to the DOM. Note that components of lower "order" (tho
se near the root of the tree) will render first because their rendering may requ
ire re-rendering of higher order (those near the leaves), thus avoiding the poss
ibility that a component which is dirty render more than once during a single cy
cle. | 77 At the end of each animation frame, all components (including the root `App`) wh
ich have `setState` on themselves will be rebuilt and the resulting changes will
be minimally applied to the DOM. Note that components of lower "order" (those n
ear the root of the tree) will build first because their building may require re
building of higher order (those near the leaves), thus avoiding the possibility
that a component which is dirty build more than once during a single cycle. |
78 | 78 |
79 Keys | 79 Keys |
80 ---- | 80 ---- |
81 In order to efficiently apply changes to the DOM and to ensure that stateful com
ponents are correctly identified, Effen requires that `no two nodes (except Text
) or components of the same type may exist as children of another element withou
t being distinguished by unique keys`. [`Text` is excused from this rule]. In ma
ny cases, nodes don't require a key because there is only one type amongst its s
iblings -- but if there is more one, you must assign each a key. This is why mos
t nodes will take `({ Object key })` as an optional constructor parameter. In de
velopment mode (i.e. when sky is built `Debug`) Effen will throw an error if you
forget to do this. | 81 In order to efficiently apply changes to the DOM and to ensure that stateful com
ponents are correctly identified, Effen requires that `no two nodes (except Text
) or components of the same type may exist as children of another element withou
t being distinguished by unique keys`. [`Text` is excused from this rule]. In ma
ny cases, nodes don't require a key because there is only one type amongst its s
iblings -- but if there is more one, you must assign each a key. This is why mos
t nodes will take `({ Object key })` as an optional constructor parameter. In de
velopment mode (i.e. when sky is built `Debug`) Effen will throw an error if you
forget to do this. |
82 | 82 |
83 Event Handling | 83 Event Handling |
84 -------------- | 84 -------------- |
85 To handle an event is to receive a callback. All elements, (e.g. `Container`, `A
nchor`, and `Image`) have optional named constructor arguments named `on*` whose
type is function that takes a single `sky.Event` as a parameter. To handle an e
vent, implement a callback on your component and pass it to the appropriate node
. If you need to expose the event callback to an owner component, just pipe it t
hrough your constructor arguments: | 85 To handle an event is to receive a callback. All elements, (e.g. `Container`, `A
nchor`, and `Image`) have optional named constructor arguments named `on*` whose
type is function that takes a single `sky.Event` as a parameter. To handle an e
vent, implement a callback on your component and pass it to the appropriate node
. If you need to expose the event callback to an owner component, just pipe it t
hrough your constructor arguments: |
86 | 86 |
87 ```JavaScript | 87 ```JavaScript |
88 class MyComp extends Component { | 88 class MyComp extends Component { |
89 MyComp({ | 89 MyComp({ |
90 Object key, | 90 Object key, |
91 sky.EventListener onClick // delegated handler | 91 sky.EventListener onClick // delegated handler |
92 }) : super(key: key); | 92 }) : super(key: key); |
93 | 93 |
94 Node render() { | 94 Node build() { |
95 return new Container( | 95 return new Container( |
96 onClick: onClick, | 96 onClick: onClick, |
97 onScrollStart: _handleScroll // direct handler | 97 onScrollStart: _handleScroll // direct handler |
98 ); | 98 ); |
99 } | 99 } |
100 | 100 |
101 _handleScroll(sky.Event e) { | 101 _handleScroll(sky.Event e) { |
102 setState(() { | 102 setState(() { |
103 // update the scroll position | 103 // update the scroll position |
104 }); | 104 }); |
105 } | 105 } |
106 } | 106 } |
107 ``` | 107 ``` |
108 | 108 |
109 *Note: Only a subset of the events defined in sky are currently exposed on Eleme
nt. If you need one which isn't present, feel free to post a patch which adds it
.* | 109 *Note: Only a subset of the events defined in sky are currently exposed on Eleme
nt. If you need one which isn't present, feel free to post a patch which adds it
.* |
110 | 110 |
111 Styling | 111 Styling |
112 ------- | 112 ------- |
113 Styling is the part of Effen which is least designed and is likely to change. At
the moment, there are two ways to apply style to an element: (1) by handing a `
Style` object to the `style` constructor parameter, or by passing a `String` to
the `inlineStyle` constructor parameter. Both take a string of CSS, but the cons
truction of a `Style` object presently causes a new `<style />` element to be cr
eated at the document level which can quickly be applied to components by Effen
setting their class -- while inlineStyle does what you would expect. | 113 Styling is the part of Effen which is least designed and is likely to change. At
the moment, there are two ways to apply style to an element: (1) by handing a `
Style` object to the `style` constructor parameter, or by passing a `String` to
the `inlineStyle` constructor parameter. Both take a string of CSS, but the cons
truction of a `Style` object presently causes a new `<style />` element to be cr
eated at the document level which can quickly be applied to components by Effen
setting their class -- while inlineStyle does what you would expect. |
114 | 114 |
115 `Style` objects are for most styling which is static and `inlineStyle`s are for
styling which is dynamic (e.g. `display: ` or `transform: translate*()` which ma
y change as a result of animating of transient UI state). | 115 `Style` objects are for most styling which is static and `inlineStyle`s are for
styling which is dynamic (e.g. `display: ` or `transform: translate*()` which ma
y change as a result of animating of transient UI state). |
116 | 116 |
117 Animation | 117 Animation |
118 --------- | 118 --------- |
119 Animation is still an area of exploration. The pattern which is presently used i
n the `stocks-fn` example is the following: Components which are animatable shou
ld contain within their implementation file an Animation object whose job it is
to react to events and control an animation by exposing one or more Dart `stream
`s of data. The `Animation` object is owned by the owner (or someone even higher
) and the stream is passed into the animating component via its constructor. The
first time the component renders, it listens on the stream and calls `setState`
on itself for each value which emerges from the stream [See the `drawer.dart` w
idget for an example]. | 119 Animation is still an area of exploration. The pattern which is presently used i
n the `stocks-fn` example is the following: Components which are animatable shou
ld contain within their implementation file an Animation object whose job it is
to react to events and control an animation by exposing one or more Dart `stream
`s of data. The `Animation` object is owned by the owner (or someone even higher
) and the stream is passed into the animating component via its constructor. The
first time the component builds, it listens on the stream and calls `setState`
on itself for each value which emerges from the stream [See the `drawer.dart` wi
dget for an example]. |
120 | 120 |
121 Performance | 121 Performance |
122 ----------- | 122 ----------- |
123 Isn't diffing the DOM expensive? This is kind of a subject question with a few a
nswers, but the biggest issue is what do you mean by "fast"? | 123 Isn't diffing the DOM expensive? This is kind of a subject question with a few a
nswers, but the biggest issue is what do you mean by "fast"? |
124 | 124 |
125 The stock answer is that diffing the DOM is fast because you compute the diff of
the current VDOM from the previous VDOM and only apply the diffs to the actual
DOM. The truth that this is fast, but not really fast enough to re-render everyt
hing on the screen for 60 or 120fps animations on a mobile device. | 125 The stock answer is that diffing the DOM is fast because you compute the diff of
the current VDOM from the previous VDOM and only apply the diffs to the actual
DOM. The truth that this is fast, but not really fast enough to rebuild everythi
ng on the screen for 60 or 120fps animations on a mobile device. |
126 | 126 |
127 The answer that many people don't get is that there are really two logical types
of renders: (1) When underlying model data changes: This generally requires han
ding in new data to the root component (in Effen, this means the `App` calling `
setState` on itself). (2) When user interaction updates a control or an animatio
n takes place. (1) is generally more expensive because it requires a full render
ing & diff, but tends to happen infrequently. (2) tends to happen frequently, bu
t at nodes which are near the leafs of the tree, so the number of nodes which mu
st be reconsiled is generally small. | 127 The answer that many people don't get is that there are really two logical types
of builds: (1) When underlying model data changes: This generally requires hand
ing in new data to the root component (in Effen, this means the `App` calling `s
etState` on itself). (2) When user interaction updates a control or an animation
takes place. (1) is generally more expensive because it requires a full buildin
g & diff, but tends to happen infrequently. (2) tends to happen frequently, but
at nodes which are near the leafs of the tree, so the number of nodes which must
be reconsiled is generally small. |
128 | 128 |
129 React provides a way to manually insist that a componet not re-render based on i
ts old and new state (and they encourage the use of immutable data structures be
cause discovering the data is the same can be accomplished with a reference comp
arison). A similar mechanism is in the works for Effen. | 129 React provides a way to manually insist that a componet not rebuild based on its
old and new state (and they encourage the use of immutable data structures beca
use discovering the data is the same can be accomplished with a reference compar
ison). A similar mechanism is in the works for Effen. |
130 | 130 |
131 Lastly, Effen does something unique: Because its diffing is component-wise, it c
an be smart about not forcing the re-render of components which are handed in as
*arguments* when only the component itself is dirty. For example, the `drawer.d
art` component knows how to animate out & back and expose a content pane -- but
it takes its content pane as an argument. When the animation mutates the inlineS
tyle of the `Drawer`'s `Container`, it must schedule itself for re-render -- but
-- because the content was handed in to its constructor, its configuration can'
t have changed and Effen doesn't require it to re-render. | 131 Lastly, Effen does something unique: Because its diffing is component-wise, it c
an be smart about not forcing the rebuild of components which are handed in as *
arguments* when only the component itself is dirty. For example, the `drawer.dar
t` component knows how to animate out & back and expose a content pane -- but it
takes its content pane as an argument. When the animation mutates the inlineSty
le of the `Drawer`'s `Container`, it must schedule itself for rebuild -- but --
because the content was handed in to its constructor, its configuration can't ha
ve changed and Effen doesn't require it to rebuild. |
132 | 132 |
133 It is a design goal that it should be *possible* to arrange that all "render" cy
cles which happen during animations can complete in less than one milliesecond o
n a Nexus 5. | 133 It is a design goal that it should be *possible* to arrange that all "build" cyc
les which happen during animations can complete in less than one milliesecond on
a Nexus 5. |
134 | 134 |
135 | 135 |
OLD | NEW |