├── 01 - Core ├── 01 - Classes.js ├── 02 - Mixins.js ├── 03 - Stateless Functions.js ├── 04 - Modules.js ├── 05 - Elements.js ├── 06 - Refs.js ├── 07 - Imperative Bridge.js └── 08 - Transferring Props.js ├── 02 - Web Components └── TBD ├── 03 - Animations └── TBD ├── 04 - Layout ├── 01 - Primitives.js ├── 02 - Layout Components.js ├── 03 - Usage.js ├── 04 - Inline Styles.md └── prototype │ ├── index.html │ └── index.js ├── 05 - Workers ├── 01 - Serializable Elements.js └── 02 - Nested Components.js ├── 06 - Embedded Queries └── TBD ├── 07 - Returning State ├── 01 - Stateful Functions.js ├── 02 - Module Pattern.js ├── 03 - Default Props and Initial State.js ├── 04 - Callback Chaining.js └── 05 - Async Sequence.js ├── 08 - Types ├── 01 - Elements.js └── 02 - DOM Elements.js ├── 09 - Reduce State └── 01 - Declarative Component Module.js └── README.md /01 - Core/01 - Classes.js: -------------------------------------------------------------------------------- 1 | // The only reason we need a React depencency here is because the base class 2 | // provides the this.setState method. 3 | import { Component } from "react"; 4 | 5 | // We can inline a named export call. 6 | export class Button extends Component { 7 | 8 | // Prop types are defined using built-in language support using a TypeScript 9 | // compatible syntax. Notice the subtle syntax difference between the colon 10 | // and the equal sign. 11 | props : { 12 | width: number 13 | } 14 | 15 | // Default properties can be defined as a static property initializer. 16 | // The value of each property is shallow copied onto the final props object. 17 | static defaultProps = { 18 | width: 100 19 | } 20 | 21 | // Initial state is defined using a property initializer. In this simple 22 | // form it behaves identical to TypeScript. You may refer to this.props 23 | // within this initializer to make initial state a function of props. 24 | state = { 25 | counter: Math.round(this.props.width / 10) 26 | } 27 | 28 | // Instead of relying on auto-binding magic inside React, we use a property 29 | // initializer with an arrow function. This effectively creates a single 30 | // bound method per instance - the same as auto-binding. 31 | handleClick = (event) => { 32 | event.preventDefault(); 33 | this.setState({ counter: this.state.counter + 1 }); 34 | } 35 | 36 | // Props, state and context are passed into render as a convenience to avoid 37 | // the need for aliasing or referring to `this`. 38 | render(props, state, context) { 39 | return ( 40 |
41 | This button has been clicked: {state.counter} times 42 |
44 | ); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /01 - Core/02 - Mixins.js: -------------------------------------------------------------------------------- 1 | import { mixin } from "react-utils"; 2 | 3 | // Chainable mixins 4 | 5 | const A = { 6 | 7 | componentDidMount() { 8 | super(); // This will end up calling an empty function, placed by mixin() 9 | console.log('A'); 10 | } 11 | 12 | }; 13 | 14 | class B { 15 | 16 | static getQueries() { 17 | super(); // This will end up calling an empty function, placed by mixin() 18 | console.log('B') 19 | } 20 | 21 | componentDidMount() { 22 | console.log('B'); 23 | super(); // This will end up calling A.componentDidMount 24 | } 25 | 26 | } 27 | 28 | class C extends mixin(A, B) { 29 | 30 | static getQueries() { 31 | super(); // This calls B.getQueries 32 | console.log('C'); 33 | } 34 | 35 | componentDidMount() { 36 | super(); // This calls B.prototype.componentDidMount 37 | console.log('C'); 38 | } 39 | 40 | } 41 | 42 | C.getQueries(); // B, C 43 | new C().componentDidMount(); // B, A, C 44 | 45 | 46 | import { Component } from "react"; 47 | 48 | // A component that mixes in all of C's functionality 49 | 50 | class Component extends mixin(Component, C) { 51 | render() { 52 | return
; 53 | } 54 | } 55 | 56 | // Solvable but confusing/complex issues: 57 | 58 | export class C extends mixin(A, B) { 59 | 60 | // This state intializer overrides the state initializer in the base class. 61 | // The current React class system merges the two. 62 | state = { 63 | b: true 64 | } 65 | 66 | componentDidMount() { 67 | // You forgot to put a super call here but there's no runtime warning since 68 | // the mixin logic happens before this class is created. 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /01 - Core/03 - Stateless Functions.js: -------------------------------------------------------------------------------- 1 | // A simple component, that isn't stateful, can be provided as a single 2 | // function that accepts props. This provides React with a hint that this 3 | // component can be collapsed and that its state doesn't need to be preserved. 4 | // It also encourages micro-componentization instead of custom helper functions 5 | // outside the system. 6 | export function Button(props : { width: number, onClick: function }) { 7 | return ( 8 |
9 | Fancy button 10 |
12 | ); 13 | } 14 | 15 | // When named exports are used, it may be valid to have multiple components 16 | // in the same file. Destructuring can be used to provide convenience aliasing 17 | // and defaults to props. 18 | export function Checkbox({ checked = true, width }) { 19 | return ( 20 |
21 | Fancy checkbox 22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /01 - Core/05 - Elements.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ELEMENT OBJECT LITERAL 3 | * 4 | * A React component module will no longer export a helper function to create 5 | * virtual elements. Instead it's the responsibility of the consumer to 6 | * efficiently create the virtual element. 7 | * 8 | * Languages that compile to JS can choose to implement the element signature 9 | * in whatever way is idiomatic for that language: 10 | */ 11 | 12 | { 13 | type: Button, 14 | props: { 15 | foo: bar, 16 | children: [ 17 | { type: 'span', props: { children: a } }, 18 | { type: 'span', props: { children: b } } 19 | ] 20 | }, 21 | 22 | // optional 23 | key: 'mybutton', 24 | ref: myButtonRef 25 | } 26 | 27 | /** 28 | * JSX 29 | */ 30 | 31 | 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 | -------------------------------------------------------------------------------- /01 - Core/06 - Refs.js: -------------------------------------------------------------------------------- 1 | // This is a refs proposal that uses callbacks instead of strings. The callback 2 | // can be easily statically typed and solves most use cases. 3 | 4 | // When a ref is attached, it gets passed the instance as the first argument. 5 | // When a ref is detached, the callback is invoked with null as the argument. 6 | 7 | // refs on DOM nodes gives the DOM node handle directly, not an intermediate 8 | // form. 9 | 10 | class Foo { 11 | 12 | myDivRef : ?HTMLDivElement; 13 | 14 | handleTick() { 15 | this.setState({ width: this.myDivRef.offsetWidth }); 16 | } 17 | 18 | render() { 19 | return ( 20 | 21 |
this.myDivRef = node} /> 22 | this.myDivRef} /> 23 | 24 | ); 25 | } 26 | 27 | } 28 | 29 | // It's possible to distinguish whether a ref as ever been mounted or if it has 30 | // been mounted during this particular reconciliation phase. 31 | 32 | class Component { 33 | 34 | buttonWasEverMounted : boolean; 35 | buttonWasMountedThisPass : boolean; 36 | button : ?Button; 37 | 38 | mountButtonRef = button => { 39 | if (button) { 40 | this.buttonWasEverMounted = true; 41 | this.buttonWasMountedThisPass = true; 42 | } 43 | this.button = button; 44 | } 45 | 46 | componentWillMount() { 47 | this.componentWillUpdate(); 48 | } 49 | 50 | componentDidMount() { 51 | this.componentDidUpdate(); 52 | } 53 | 54 | componentWillUpdate() { 55 | this.buttonWasMountedThisPass = false; 56 | } 57 | 58 | componentDidUpdate() { 59 | if (this.buttonWasEverMounted) { 60 | console.log('button was mounted at least once'); 61 | } 62 | if (this.buttonWasMountedThisPass) { 63 | console.log('button was mounted during this render pass'); 64 | } 65 | } 66 | 67 | render() { 68 | return
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /07 - Returning State/03 - Default Props and Initial State.js: -------------------------------------------------------------------------------- 1 | /** Stateful Functions **/ 2 | 3 | // Default props and state may be implemented as default values in arguments 4 | // or destructuring assignment. The downside is that you have to replicate the 5 | // same defaults in all callback functions. 6 | 7 | function handleClick({ onClick = console.log }, { count = 0 }, event) { 8 | if (event.button === 1) { 9 | return { count: 0 }; 10 | } 11 | onClick(); 12 | return { count: count + 1 }; 13 | } 14 | 15 | export function Counter({ width = 100 }, state = { count: 0 }) { 16 | return ( 17 |
18 | Clicked {state.count} times 19 |
21 | ); 22 | } 23 | 24 | /** Module Pattern **/ 25 | 26 | // Using the module pattern, we can expose the same methods as 27 | 28 | export const defaultProps = { onClick: console.log, width: 100 }; 29 | 30 | export const getInitialState = (props) => ({ count: 0 }); 31 | 32 | function handleClick(props, state, event) { 33 | if (event.button === 2) { 34 | return { count: 0 }; 35 | } 36 | props.onClick(); 37 | return { count: state.count + 1 }; 38 | } 39 | 40 | export function render(props, state) { 41 | return ( 42 |
43 | Clicked {state.count} times 44 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /07 - Returning State/04 - Callback Chaining.js: -------------------------------------------------------------------------------- 1 | let Foo = (props, state = { dir: 1 }, update) => { 2 | function *handleToggle() { 3 | if (props.isActive) { 4 | // Invert direction 5 | var dir = -state.dir; 6 | yield update({ ...state }); 7 | yield props.onToggle(dir); 8 | } 9 | return update(state); 10 | }; 11 | 12 | return
; 13 | }; 14 | 15 | type Props = { addition ?: number }; 16 | type State = { counter : number }; 17 | export type Bar = (Props, State, Update) => Element; 18 | export let Bar : Bar = (props, state = { counter: 0 }, update) => { 19 | 20 | let { addition = 1 } = props; // Default props 21 | 22 | let handleToggle = (value) => update({ ...state, counter: state.counter + value }); 23 | 24 | let handleClick = () => { 25 | return update({ ...state, counter: state.counter + addition }); 26 | }; 27 | 28 | return ( 29 |
30 | 31 |
32 | ); 33 | 34 | }; 35 | 36 | // Language trickery to get named arguments, default props and initial state 37 | // This is so not readable. 38 | // props : defaultProps : type definition 39 | // state = initialProps : type definition 40 | let Baz = ({ 41 | props : { addition = 5 } : { addition : number }, 42 | state = { counter : 1 } : { counter : number }, 43 | update : Update, 44 | context : any 45 | }) => { 46 | return ; 47 | }; 48 | -------------------------------------------------------------------------------- /07 - Returning State/05 - Async Sequence.js: -------------------------------------------------------------------------------- 1 | import type { Effect, Element, Component } from 'react'; 2 | import sleep from 'then-sleep'; 3 | 4 | const initialState = { 5 | cancelMove() {}, 6 | stops: 0, 7 | x: 0, 8 | y: 0 9 | }; 10 | 11 | type StopEvent = { 12 | lastPosition: [number, number] 13 | }; 14 | 15 | type PropTypes = { 16 | onStop: StopEvent => Effect 17 | }; 18 | 19 | const Foo : Component = ({ props, state = initialState, update, dispatch }) => { 20 | 21 | function * move(event, cancel) { 22 | yield state.cancelMove(); 23 | 24 | yield update({ 25 | ...state, 26 | cancelMove: cancel, 27 | x: event.x, 28 | y: event.y 29 | }); 30 | 31 | yield sleep(500); 32 | 33 | yield dispatch({ 34 | action: 'STOPPED_MOVING' 35 | }); 36 | yield update({ 37 | ...state, 38 | stops: state.stops + 1 39 | }); 40 | yield props.onStop({ 41 | lastPosition: [state.x, state.y] 42 | }, cancel); 43 | } 44 | 45 | function leave(event) { 46 | return state.cancelMove(); 47 | } 48 | 49 | return ( 50 |
53 | Stopped moving {state.stops} times. 54 |
55 | ); 56 | 57 | } 58 | 59 | Foo.defaultProps = { 60 | onStop() {} 61 | }; 62 | 63 | export default Foo; 64 | -------------------------------------------------------------------------------- /08 - Types/01 - Elements.js: -------------------------------------------------------------------------------- 1 | // This is using a made up type definition language. So far, this is not a 2 | // complete type definition. It's a placeholder for describing terminology. 3 | 4 | type ReactNode = ReactElement | ReactFragment | ReactText; 5 | 6 | type ReactElement = ReactCompositeElement | ReactNativeElement; 7 | 8 | type ReactNativeElement = { 9 | type: string, 10 | props: { 11 | children: ReactNodeList 12 | }, 13 | key: string | boolean | number | null, 14 | ref: string | null 15 | }; 16 | 17 | type ReactCompositeElement = { 18 | type : ReactClass | ReactModule, 19 | props : TProps, 20 | key: string | boolean | number | null, 21 | ref: string | null 22 | }; 23 | 24 | type ReactFragment = Array; 25 | 26 | type ReactNodeList = ReactNode | ReactEmpty; 27 | 28 | type ReactText = string | number; 29 | 30 | type ReactEmpty = null | undefined | boolean; 31 | -------------------------------------------------------------------------------- /08 - Types/02 - DOM Elements.js: -------------------------------------------------------------------------------- 1 | // More Precise DOM Specific Element Definitions 2 | 3 | type ReactDOMNode = ReactDOMElement | ReactDOMFragment | ReactText; 4 | 5 | type ReactDOMElement = ReactCompositeElement | { 6 | ...ReactNativeElement, 7 | props: { 8 | className: string, 9 | children: ReactDOMNodeList, 10 | ... 11 | } 12 | }; 13 | 14 | type ReactDOMFragment = Array; 15 | 16 | type ReactDOMNodeList = ReactDOMNode | ReactEmpty; 17 | 18 | // Remaining issues 19 | 20 | // How do we ensure that specific DOM tags have more precise prop types 21 | // and they can only accept certain children? TypeScript does have the ability 22 | // to differentiate types based on constant string values using a kind of 23 | // pattern matching. 24 | 25 | // How do we describe that a composite element must resolve to a certain 26 | // subset of ReactNodes such as ReactDOMElement or ReactHTMLAnchorElement? 27 | -------------------------------------------------------------------------------- /09 - Reduce State/01 - Declarative Component Module.js: -------------------------------------------------------------------------------- 1 | // Declarative React Component 2 | 3 | export default let FancyButton = { 4 | 5 | // getInitialState 6 | initialize(props) { 7 | return { 8 | count: Math.round(props.width / 10), 9 | name: null 10 | }; 11 | }, 12 | 13 | // downwards render output 14 | render(props, state) { 15 | return ( 16 |
17 | {state.name || 'Loading name...'} 18 | Clicked {state ? state.count : 0} times 19 |
21 | ); 22 | }, 23 | 24 | // subscribe to third party data asynchronously 25 | observe(props, state) { 26 | return { 27 | data: request(props.uri) 28 | }; 29 | }, 30 | 31 | // reduce whenever a signal fires into new state 32 | reduce(props, state, signals) { 33 | let data = signals.data || state.data; 34 | let click = signals.button.click; 35 | if (click) { 36 | if (click.button === 1) { 37 | return { count: 0, name: data }; 38 | } 39 | return { count: state.count + 1, name: data }; 40 | } 41 | return state; 42 | }, 43 | 44 | // upwards signals back to the owner (events) 45 | output(props, state, signals) { 46 | return { 47 | name: state.name, 48 | click: signals.button.click 49 | }; 50 | } 51 | 52 | }; 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The Future of React(?) 2 | ====================== 3 | 4 | Welcome to the potential future of React projects. This repo is showcasing 5 | example code for APIs that we have not yet implemented nor agreed on. 6 | Its purpose is to provide a platform for discussing future changes. It also 7 | provides a shared goal so that we can figure out how to incrementally get to our 8 | ideal API. 9 | 10 | Structure 11 | --------- 12 | 13 | You'll notice that the files in this repo are structured by numbered files. More 14 | stable proposals have lower numbers and more comments. Higher level proposals 15 | are built on top of these. You're expected to be familiar with the core 16 | proposals before reading the higher level examples. 17 | 18 | __Core__ 19 | 1. [Classes](./01 - Core/01 - Classes.js) 20 | 2. [Mixins](./01 - Core/02 - Mixins.js) 21 | 3. [Stateless Functions](./01 - Core/03 - Stateless Functions.js) 22 | 4. [Modules](./01 - Core/04 - Modules.js) 23 | 5. [Elements](./01 - Core/05 - Elements.js) 24 | 6. [Refs](./01 - Core/06 - Refs.js) 25 | 7. [Imperative Bridge](./01 - Core/07 - Imperative Bridge.js) 26 | 27 | __Web Components__ 28 | - (TBD) 29 | 30 | __Animations__ 31 | - (TBD) 32 | 33 | __Layout__ 34 | 1. [Primitives](./04 - Layout/01 - Primitives.js) 35 | 2. [Layout Components](./04 - Layout/02 - Layout Components.js) 36 | 3. [Usage](./04 - Layout/03 - Usage.js) 37 | 4. [Inline Styles](./04 - Layout/04 - Inline Styles.md) 38 | 39 | __Workers__ 40 | 1. [Serializable Elements](./05 - Workers/01 - Serializable Elements.js) 41 | 2. [Nested Components](./05 - Workers/02 - Nested Components.js) 42 | 43 | __Embedded Queries__ 44 | - (TBD) 45 | 46 | __Returning State__ 47 | 1. [Stateful Functions](./07 - Returning State/01 - Stateful Functions.js) 48 | 2. [Module Pattern](./07 - Returning State/02 - Module Pattern.js) 49 | 3. [Default Props and Initial State](./07 - Returning State/03 - Default Props and Initial State.js) 50 | 51 | __Types__ 52 | 1. [Elements](./08 - Types/01 - Elements.js) 53 | 2. [DOM Elements](./08 - Types/02 - DOM Elements.js) 54 | 55 | Syntax 56 | ------ 57 | 58 | The language syntax used here is using a hypothetical future JavaScript syntax. 59 | It's a mix of ECMAScript 6, proposals for ECMAScript 7, TypeScript and JSX. The 60 | idea is that it should be proposals that have a legitimate chance of being 61 | standardized. 62 | 63 | Contribute 64 | ---------- 65 | 66 | Would you like to take part of the discussion? Open up an issue or pull request. 67 | --------------------------------------------------------------------------------