├── 03 - Animations └── TBD ├── 02 - Web Components └── TBD ├── 06 - Embedded Queries └── TBD ├── 04 - Layout ├── prototype │ ├── index.html │ └── index.js ├── 03 - Usage.js ├── 01 - Primitives.js ├── 02 - Layout Components.js └── 04 - Inline Styles.md ├── 01 - Core ├── 04 - Modules.js ├── 07 - Imperative Bridge.js ├── 03 - Stateless Functions.js ├── 02 - Mixins.js ├── 01 - Classes.js ├── 08 - Transferring Props.js ├── 06 - Refs.js └── 05 - Elements.js ├── 07 - Returning State ├── 02 - Module Pattern.js ├── 05 - Async Sequence.js ├── 03 - Default Props and Initial State.js ├── 01 - Stateful Functions.js └── 04 - Callback Chaining.js ├── 08 - Types ├── 01 - Elements.js └── 02 - DOM Elements.js ├── 09 - Reduce State └── 01 - Declarative Component Module.js ├── 05 - Workers ├── 02 - Nested Components.js └── 01 - Serializable Elements.js └── README.md /03 - Animations/TBD: -------------------------------------------------------------------------------- 1 | This section is not yet complete. 2 | 3 | Please come back later or send a pull request with your own ideas. -------------------------------------------------------------------------------- /02 - Web Components/TBD: -------------------------------------------------------------------------------- 1 | This section is not yet complete. 2 | 3 | Please come back later or send a pull request with your own ideas. -------------------------------------------------------------------------------- /06 - Embedded Queries/TBD: -------------------------------------------------------------------------------- 1 | This section is not yet complete. 2 | 3 | Please come back later or send a pull request with your own ideas. -------------------------------------------------------------------------------- /04 - Layout/prototype/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Layout Experiment 5 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /01 - Core/04 - Modules.js: -------------------------------------------------------------------------------- 1 | // Through out the other examples named exports are used. 2 | 3 | export class Button extends Component { 4 | 5 | } 6 | // This means that components are imported using the following syntax: 7 | 8 | import { Button } from "Button"; 9 | 10 | // and can be aliases like this: 11 | 12 | import { Button as MySpecialButton } from "Button"; 13 | 14 | // Another alternative is to export defaults. It's unclear which of these two 15 | // paradigms will win for Component modules. If default exports win popular 16 | // mindshare, we might encourage a different syntax for exports: 17 | 18 | export default class Button extends Component { 19 | // ... 20 | } 21 | 22 | // To import a default export you would simply leave out the curly braces. 23 | 24 | import MySpecialButton from "Button"; 25 | -------------------------------------------------------------------------------- /07 - Returning State/02 - Module Pattern.js: -------------------------------------------------------------------------------- 1 | // The module pattern is similar to stateful functions. Except, instead of 2 | // exporting a single function, it exports multiple functions that can be used 3 | // to implement a component. The render function is required. 4 | 5 | interface P { 6 | width: number; 7 | onClick: function; 8 | } 9 | 10 | function handleClick(props, state, event) { 11 | if (event.button === 1) { 12 | return { count: 0 }; 13 | } 14 | if (props.onClick) { 15 | props.onClick(); 16 | } 17 | return { count: state ? state.count + 1 : 0 }; 18 | } 19 | 20 | export function componentDidMount(props, state) { 21 | console.log('mounted'); 22 | } 23 | 24 | export function render(props : P, state) { 25 | return ( 26 |
27 | Clicked {state ? state.count : 0} times 28 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /01 - Core/07 - Imperative Bridge.js: -------------------------------------------------------------------------------- 1 | import { Button } from "button"; 2 | import { Renderer } from "react-dom"; 3 | 4 | // Currently setProps() provides a convenient hook on rendered components 5 | // that are top level. This magically turns props into state instead of being 6 | // modeled outside the component. This undermines the integrity of props 7 | // in a component tree. Instead, we want to add a wrapper that saves these 8 | // values as its own internal state. You can imperatively update the 9 | // props using setters on the renderer instances. These gets flushed down to 10 | // the underlying component class. 11 | 12 | var button = new Renderer( 35 | 36 | /** 37 | * PLAIN JS 38 | * __DEV__ MODE 39 | * 40 | * This helper function ensures that your static children don't get the key 41 | * warning. It creates an element for you with the current owner/context. 42 | * The props object is cloned and key/ref moved onto the element. 43 | */ 44 | 45 | var _Button = React.createFactory(Button); 46 | var _span = React.createFactory('span'); 47 | 48 | _Button({ foo: bar, key: 'mybutton', ref: myButtonRef }, 49 | _span(null, a), 50 | _span(null, b) 51 | ) 52 | 53 | /** 54 | * If you use JSX, or can statically analyze that the Factory calls belongs to 55 | * React, then you can chose to opt-in to one of the optimizations modes. 56 | */ 57 | 58 | /** 59 | * INLINE 60 | * PRODUCTION MODE 61 | * 62 | * Inline mode simply creates the element objects inline in the code, with 63 | * a lookup for current owner/context as well as resolving default props. 64 | * If defaults aren't known statically, then we create a factory that can help 65 | * assign defaults quickly on the newly created object. 66 | */ 67 | 68 | var Button_assignDefaults = React.createDefaultsFactory(Button); 69 | 70 | { 71 | type: Button, 72 | props: Button_assignDefaults({ 73 | foo: bar, 74 | children: [ 75 | { type: 'span', props: { children: a }, key: null, ref: null, _owner: React._currentOwner, _context: React._currentContext }, 76 | { type: 'span', props: { children: b }, key: null, ref: null, _owner: React._currentOwner, _context: React._currentContext } 77 | ] 78 | }), 79 | 80 | key: 'mybutton', 81 | ref: myButtonRef, 82 | 83 | _owner: React._currentOwner, 84 | _context: React._currentContext 85 | } 86 | 87 | /** 88 | * POOLED MODE 89 | * 90 | * Pooled mode doesn't allocate any new objects. Instead it gets mutable objects 91 | * from a pool and reuses them. It overrides the props on the pooled object. 92 | */ 93 | 94 | var P1 = React.createElementPool({ 95 | type: Button, 96 | key: 'mybutton', 97 | props: { 98 | foo: null, 99 | children: null 100 | } 101 | }); 102 | var P2 = React.createElementPool({ 103 | type: 'span', 104 | props: { 105 | children: null 106 | } 107 | }); 108 | var A2 = React.createArrayPool(2); // Number of items in the array 109 | var t1, t1p, t1c, t2; 110 | 111 | ( 112 | t1 = P1(), 113 | t1.ref = myButtonRef, 114 | t1p = t1.props, 115 | t1p.foo = bar, 116 | t1p.children = A2(), 117 | t1c = t1p.children, 118 | t1c[0] = (t2 = P2(), t2.props.children = a, t2), 119 | t1c[1] = (t2 = P2(), t2.props.children = b, t2), 120 | t1 121 | ) 122 | 123 | /** 124 | * NATIVE COMPONENTS 125 | * 126 | * Note that DOM nodes are no longer functions on React.DOM, instead they're 127 | * just strings. JSX will convert any lower-case tag name, or if it has a dash, 128 | * into a string value instead of a scope reference. This makes them compatible 129 | * with custom tags (Web Components). 130 | */ 131 | 132 | /** 133 | * TEMPLATE STRINGS 134 | * 135 | * You could create an add-on sugar which uses ES6 template strings to create 136 | * elements. It becomes more palatable if all your components are registered 137 | * through strings. 138 | */ 139 | 140 | X` 141 | 142 | ${a} 143 | ${b} 144 | 145 | ` 146 | -------------------------------------------------------------------------------- /04 - Layout/prototype/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an experimental prototype of an integrated React layout algorithm 3 | * defined in terms of Effects implemented as ECMAScript generators. 4 | * 5 | * There are two types of "Effects": 6 | * 7 | * - Component (function): When this Effect is yielded, the component is 8 | * rendered as deep as necessary until an Object Effect is yielded. The value 9 | * of the yielded effect is an object passed to the continuation. 10 | * 11 | * - Object (any): When an Object is raised, the rendering is interrupted. 12 | * The yielded object is passed into the continuation of the Component effect, 13 | * along with a continuation to keep rendering the rest of the component. When 14 | * the continuation is rendered, the object passed to the continuation is 15 | * passed back to the raised Component Effect. 16 | * 17 | * Combining these two effects seems to be sufficient to implement the layout 18 | * algorithms that we want to support. 19 | * 20 | * In this particular example algorithm, the width/height of a layout component 21 | * is the yielded Object. The continuation value is the top/left offset of the 22 | * layout component after it has been placed. 23 | * 24 | * TODO: Expand the Effects to other types and let them bubble (e.g. context). 25 | */ 26 | 27 | "use strict"; 28 | 29 | /* React */ 30 | 31 | function $(fn, ...args) { 32 | return fn.bind(null, ...args); 33 | } 34 | 35 | /* App */ 36 | 37 | function Quad(props) { 38 | const node = document.createElement('span'); 39 | node.style.background = props.background; 40 | node.style.width = props.width + 'px'; 41 | node.style.height = props.height + 'px'; 42 | node.style.top = props.top + 'px'; 43 | node.style.left = props.left + 'px'; 44 | node.textContent = props.text || ''; 45 | return node; 46 | } 47 | 48 | function* Box(props) { 49 | const position = yield { 50 | width: props.width, 51 | height: props.height 52 | }; 53 | return $(Quad, { 54 | width: props.width, 55 | height: props.height, 56 | background: props.background, 57 | top: position.top, 58 | left: position.left, 59 | text: props.text 60 | }); 61 | } 62 | 63 | function* FlexibleBox({ width = 0, height = 0, background }) { 64 | const rect = yield { width, height }; 65 | return $(Quad, { 66 | width: rect.width, 67 | height: rect.height, 68 | background: background, 69 | top: rect.top, 70 | left: rect.left 71 | }); 72 | } 73 | 74 | function Intl(props) { 75 | // TODO: Provide context 76 | return props.child; 77 | } 78 | 79 | function IntlNumber(number) { 80 | // TODO: Read context 81 | return $(Text, '' + number); 82 | } 83 | 84 | function* Horizontal(...children) { 85 | const continuations = []; 86 | let x = 0; 87 | let y = 0; 88 | for (let child of children) { 89 | const { value: size, continuation } = yield child; 90 | continuations.push({ 91 | continuation: continuation, 92 | left: x 93 | }); 94 | x += size.width; 95 | y = size.height > y ? size.height : y; 96 | } 97 | const offset = yield { 98 | width: x, 99 | height: y 100 | }; 101 | return continuations.map(child => $(child.continuation, { 102 | top: offset.top, 103 | left: offset.left + child.left 104 | })); 105 | } 106 | 107 | function* Vertical(...children) { 108 | const continuations = []; 109 | let x = 0; 110 | let y = 0; 111 | for (let child of children) { 112 | const { value: size, continuation } = yield child; 113 | continuations.push({ 114 | continuation: continuation, 115 | top: y 116 | }); 117 | x = size.width > x ? size.width : x; 118 | y += size.height; 119 | } 120 | const offset = yield { 121 | width: x, 122 | height: y 123 | }; 124 | return continuations.map(child => $(child.continuation, { 125 | top: offset.top + child.top, 126 | left: offset.left 127 | })); 128 | } 129 | 130 | function* Div(props, ...children) { 131 | // A "div" or "View" is something that people keep asking for. What they mean 132 | // is a quad, that can be styled with background/border, behind some content. 133 | // However, the interesting part is that this quad should be sized to fit all 134 | // the nested content. 135 | const { value: size, continuation } = yield $(Vertical, ...children); 136 | const offset = yield size; 137 | return [ 138 | $(Quad, { 139 | top: offset.top, 140 | left: offset.left, 141 | width: size.width, 142 | height: size.height, 143 | background: props.background 144 | }), 145 | $(continuation, offset) 146 | ]; 147 | } 148 | 149 | function Text(content) { 150 | return $(Box, { 151 | width: textMeasuringContext.measureText('' + content).width, 152 | height: textLineHeight, 153 | text: content 154 | }); 155 | } 156 | 157 | function Awesomeness() { 158 | return $(Horizontal, 159 | $(Text, 'Awesomeness Index: '), 160 | $(IntlNumber, 123.45) 161 | ); 162 | } 163 | 164 | function* Body(child) { 165 | const { continuation } = yield child; 166 | return $(continuation, { 167 | top: 10, 168 | left: 10 169 | }); 170 | } 171 | 172 | /** 173 | * Simplified Flexbox 174 | */ 175 | function* VerticalFlex(props, ...flexItems) { 176 | const children = []; 177 | let maxWidth = 0; 178 | let flexAccumulation = 0; 179 | let availableHeight = props.height; 180 | for (let { item, flex = 0 } of flexItems) { 181 | const { value: size, continuation } = yield item; 182 | maxWidth = size.width > maxWidth ? size.width : maxWidth; 183 | flexAccumulation += flex; 184 | if (!flex) { 185 | availableHeight -= size.height; 186 | } 187 | children.push({ flex, continuation, height: size.height }); 188 | } 189 | const offset = yield { width: maxWidth, height: props.height }; 190 | let accumulatedTop = 0; 191 | const positionedChildren = []; 192 | for (let { flex, continuation, height } of children) { 193 | if (flex) { 194 | height = availableHeight * (flex / flexAccumulation); 195 | } 196 | positionedChildren.push( 197 | $(continuation, { 198 | top: offset.top + accumulatedTop, 199 | left: offset.left, 200 | width: maxWidth, 201 | height: height 202 | }) 203 | ); 204 | accumulatedTop += height; 205 | } 206 | return positionedChildren; 207 | } 208 | 209 | function Reflexity(child1, child2) { 210 | return $(VerticalFlex, { height: 300 }, 211 | { item: child1 }, 212 | { item: $(Div, { background: '#f00' }, $(Text, 'Some stuff '), $(Text, 'in between!')) }, 213 | { item: $(FlexibleBox, { background: '#00f' }), flex: 1 }, 214 | { item: child2 }, 215 | { item: $(FlexibleBox, { background: '#0f0' }), flex: 0.5 } 216 | ); 217 | } 218 | 219 | function App() { 220 | return $(Body, 221 | $(Div, { background: '#eee' }, 222 | $(Reflexity, 223 | $(Horizontal, $(Text, 'Hello '), $(Text, 'World!')), 224 | $(Intl, { 225 | locale: 'en-US', 226 | child: $(Awesomeness) 227 | }) 228 | ) 229 | ) 230 | ); 231 | } 232 | 233 | /* React DOM */ 234 | 235 | function resolveChild(child) { 236 | const element = child(); 237 | if (typeof element === 'function') { 238 | return resolveChild(element, container); 239 | } else if (Array.isArray(element)) { 240 | throw new Error('Arrays not valid here. Cannot resolve multiple results here.'); 241 | } else if (element[Symbol.iterator]) { 242 | const iterator = element[Symbol.iterator](); 243 | let rec = iterator.next(); 244 | while (!rec.done) { 245 | if (typeof rec.value === 'function') { 246 | const resolvedResult = resolveChild(rec.value); 247 | rec = iterator.next(resolvedResult); 248 | } else { 249 | break; 250 | } 251 | } 252 | return { 253 | value: rec.value, 254 | continuation: function(props) { 255 | // TODO: If this is a child, it needs to be resolved. 256 | return iterator.next(props).value; 257 | } 258 | }; 259 | } else { 260 | throw new Error('Unhandled branch'); 261 | } 262 | } 263 | 264 | function render(element, container) { 265 | if (typeof element === 'function') { 266 | render(element(), container); 267 | } else if (Array.isArray(element)) { 268 | element.forEach(child => render(child, container)); 269 | } else if (element[Symbol.iterator]) { 270 | const iterator = element[Symbol.iterator](); 271 | let rec = iterator.next(); 272 | while (!rec.done) { 273 | if (typeof rec.value === 'function') { 274 | const resolvedResult = resolveChild(rec.value); 275 | rec = iterator.next(resolvedResult); 276 | } else { 277 | rec = iterator.next(); 278 | } 279 | } 280 | const child = rec.value; 281 | render(child, container); 282 | } else if (element.nodeType) { 283 | container.appendChild(element); 284 | } 285 | } 286 | 287 | const textMeasuringContext = document.createElement('canvas').getContext('2d'); 288 | const rootStyle = window.getComputedStyle( 289 | document.getElementById('container') 290 | ); 291 | textMeasuringContext.font = rootStyle.font; 292 | const textLineHeight = parseInt(rootStyle.lineHeight); 293 | 294 | /* Initialization */ 295 | 296 | render($(App), document.getElementById('container')); 297 | --------------------------------------------------------------------------------