mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
NOTE: This CL appears far larger than it actually is for two reasons: 1) Many files were moved around to use the Dart package directory structure. 2) Many .dart files had to have import paths updated. - Organize mojo/public/dart so that it uses standard Dart package layout - Organize mojo/dart/apptest so that it uses a standard Dart package layout - Organize sky/sdk so that it uses a standard Dart package layout - Create a mojo/testing package (used by unittests) - Introduce the 'dart_pkg' gn rule which populates gen/Config/dart-pkg - All internally vended Dart packages must have a corresponding dart_pkg rule - It is now possible to use dependency_overrides: in pubspec.yaml to mix internal and external package dependencies (enables analyzer, editor, webstorm usage for internal developers). - Package root for dart content handler ends with "packages/" - Imports of mojo package uris no longer need the "public/dart" - mojo/public/tools/dart_package.py is a clone of mojo/public/tools/gn/zip.py - Sky tests no longer run 'deploy_sdk' script. R=eseidel@chromium.org Review URL: https://codereview.chromium.org/1132063007
220 lines
8.1 KiB
Markdown
220 lines
8.1 KiB
Markdown
Sky Framework
|
|
=============
|
|
|
|
Effen is a functional-reactive framework for Sky which takes inspiration from
|
|
[React](http://facebook.github.io/react/). Effen is comprised of three main
|
|
parts: a virtual-dom and diffing engine, a component mechanism and a very early
|
|
set of widgets for use in creating applications.
|
|
|
|
The central idea is that you build your UI out of components. Components
|
|
describe what their view should look like given their current configuration &
|
|
state. The diffing engine ensures that the DOM looks how the component describes
|
|
by applying minimal diffs to transition it from one state to the next.
|
|
|
|
If you just want to dive into code, see the [stocks example](../../../../examples/stocks).
|
|
|
|
Hello World
|
|
-----------
|
|
|
|
To build an application, create a subclass of App and instantiate it.
|
|
|
|
```HTML
|
|
<script>
|
|
import 'hello_world.dart';
|
|
|
|
main() {
|
|
new HelloWorldApp();
|
|
}
|
|
</script>
|
|
```
|
|
|
|
```dart
|
|
// In hello_world.dart
|
|
import 'package:sky/framework/fn.dart';
|
|
|
|
class HelloWorldApp extends App {
|
|
UINode build() {
|
|
return new Text('Hello, world!');
|
|
}
|
|
}
|
|
```
|
|
|
|
An app is comprised of (and is, itself, a) components. A component's main job is
|
|
to implement `UINode 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, our
|
|
`HelloWorldApp`'s `build` method just returns a `Text` node which displays the
|
|
obligatory line of text.
|
|
|
|
Nodes
|
|
-----
|
|
|
|
A component's `build` method must return a single `UINode` which *may* have
|
|
children (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 comprised of any of these nodes and plus any other imported object which
|
|
extends `Component`.
|
|
|
|
How to structure you app
|
|
------------------------
|
|
|
|
If you're familiar with React, the basic idea is the same: Application data
|
|
flows *down* from components which have data to components & nodes which they
|
|
construct via construction parameters. Generally speaking, View-Model data (data
|
|
which is derived from *model* data, but exists only because the view needs it),
|
|
is computed during the course of `build` and is short-lived, being handed into
|
|
nodes & components as configuration data.
|
|
|
|
What does "data flowing down the tree" mean?
|
|
--------------------------------------------
|
|
|
|
Consider the case of a checkbox. (i.e. `widgets/checkbox.dart`). The `Checkbox`
|
|
constructor looks like this:
|
|
|
|
```dart
|
|
ValueChanged onChanged;
|
|
bool checked;
|
|
|
|
Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key);
|
|
```
|
|
|
|
What this means is that the `Checkbox` component *never* "owns" the state of
|
|
the checkbox. It's current state is handed into the `checked` parameter, and
|
|
when a click occurs, the checkbox invokes its `onChanged` callback with the
|
|
value it thinks it should be changed to -- but it never directly changes the
|
|
value itself. 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 databinding, 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.
|
|
|
|
Stateful vs. Stateless components
|
|
---------------------------------
|
|
|
|
All components have access to two kinds of state: (1) configuration data
|
|
(constructor arguments) and (2) private data (data they mutate themselves).
|
|
While react components have explicit property bags for these two 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 convention) be
|
|
reflected as public fields of the component and state should only be set on
|
|
private (with a leading underbar `_`) fields.
|
|
|
|
All (non-component) Effen nodes are stateless. 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...
|
|
|
|
A component can become stateful in two ways: (1) by passing `super(stateful:
|
|
true)` to its call to the superclass's constructor, or by calling
|
|
`setState(Function fn)`. The former is a way to have a component start its life
|
|
stateful, and the latter results in the component becoming statefull *as well
|
|
as* scheduling the component to re-build at the end of the current animation
|
|
frame.
|
|
|
|
What does it mean to be stateful? It means that the diffing mechanism retains
|
|
the 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 provided new configuration in form of different values for the constructor
|
|
parameters, but these values (public fields) will be copied (using reflection)
|
|
onto the retained instance whose privates fields are left unmodified.
|
|
|
|
Rendering
|
|
---------
|
|
|
|
At the end of each animation frame, all components (including the root `App`)
|
|
which 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 near the root of the tree) will build first because their building may
|
|
require rebuilding 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.
|
|
|
|
Keys
|
|
----
|
|
|
|
In order to efficiently apply changes to the DOM and to ensure that stateful
|
|
components are correctly identified, Effen requires that `no two nodes (except
|
|
Text) or components of the same type may exist as children of another element
|
|
without being distinguished by unique keys`. [`Text` is excused from this rule].
|
|
In many cases, nodes don't require a key because there is only one type amongst
|
|
its siblings -- but if there is more one, you must assign each a key. This is
|
|
why most nodes will take `({ Object key })` as an optional constructor
|
|
parameter. In development mode (i.e. when sky is built `Debug`) Effen will throw
|
|
an error if you forget to do this.
|
|
|
|
Event Handling
|
|
--------------
|
|
|
|
Events logically fire through the Effen node tree. If want to handle an event as
|
|
it bubbles from the target to the root, create an `EventListenerNode`. `EventListenerNode`
|
|
has named (typed) parameters for a small set of events that we've hit so far, as
|
|
well as a 'custom' argument which is a `Map<String, sky.EventListener>`. If
|
|
you'd like to add a type argument for an event, just post a patch.
|
|
|
|
```dart
|
|
class MyComp extends Component {
|
|
MyComp({
|
|
Object key
|
|
}) : super(key: key);
|
|
|
|
void _handleTap(sky.GestureEvent e) {
|
|
// do stuff
|
|
}
|
|
|
|
void _customEventCallback(sky.Event e) {
|
|
// do other stuff
|
|
}
|
|
|
|
UINode build() {
|
|
new EventListenerNode(
|
|
new Container(
|
|
children: // ...
|
|
),
|
|
onGestureTap: _handleTap,
|
|
custom: {
|
|
'myCustomEvent': _customEventCallback
|
|
}
|
|
);
|
|
}
|
|
|
|
_handleScroll(sky.Event e) {
|
|
setState(() {
|
|
// update the scroll position
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
Styling
|
|
-------
|
|
|
|
Styling is the part of Effen which is least designed and is likely to change.
|
|
There are three ways to specify styles:
|
|
|
|
* `Style` objects which are interned and can be applied to WrapperNodes via the
|
|
``style` constructor parameter. Use `Style` objects for styles which are
|
|
`*not* animated.
|
|
|
|
* An `inlineStyle` string which can be applied to Elements via the
|
|
`inlineStyle` constructor parameter. Use `inlineStyle` for styles which
|
|
*are* animated.
|
|
|
|
If you need to apply a Style to a Component or UINode which you didn't construct
|
|
(i.e. one that was handed into your constructor), you can wrap it in a
|
|
`StyleNode` which also takes a `Style` constructor in it's `style` constructor
|
|
parameter.
|
|
|
|
Animation
|
|
---------
|
|
|
|
Animation is still an area of exploration. Have a look at
|
|
[AnimatedComponent](components/animated_component.dart) and
|
|
[Drawer](components/drawer.dart) for an example of this this currently works.
|
|
|
|
Performance
|
|
-----------
|
|
|
|
It is a design goal that it should be *possible* to arrange that all "build"
|
|
cycles which happen during animations can complete in less than one milliesecond
|
|
on a Nexus 5.
|