Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] New context API #963

Closed
wants to merge 4 commits into from
Closed

[WIP] New context API #963

wants to merge 4 commits into from

Conversation

jtmthf
Copy link

@jtmthf jtmthf commented Dec 27, 2017

This PR is an implementation of React's proposed new context API for Preact. This implementation is built on top of the existing context API. React's corresponding PR is facebook/react#11818 and the RFC is reactjs/rfcs#2.

These are the following tasks and considerations I've identified that will need to be accomplished before merging.

  • Add tests. I'm planning for now on recreating the existing context based tests, but using the new API.
  • TypeScript definitions.
  • Flow definitions.
  • Devtools support warning on rendering a Provider without any Consumer's.
  • Code documentation improvements.
  • Wait for New version of context reactjs/rfcs#2 to be approved.
  • Examine size reduction opportunities. Currently this adds 200 bytes to the (min + gzip) size.
  • Consider refactoring to remove the old context API and only support this new API. This would be inline with Preact traditionally only supporting the most modern parts of React's API. Currently it appears that Facebook may deprecate and remove old context at some future point.

@coveralls
Copy link

coveralls commented Dec 27, 2017

Coverage Status

Coverage remained the same at 100.0% when pulling 9e5969f on jtmthf:new-context into a502502 on developit:master.

@zeromake
Copy link

my context api realization

link
demo

create-context.js

import { Component } from "./component";
import { extendComponent } from "./util";

export function createContext(value) {
    var context = {
        default: value
    }
    function Provider(p, c) {
        Component.call(this, p, c);
        this.c = [];
        this.p = this.p.bind(this);
        this.u = this.u.bind(this);
    }
    extendComponent(Provider, {
        p(subscriber) {
            this.c.push(subscriber);
            return this.props.value;
        },
        u(subscriber) {
            this.c = this.c.filter(function (i) { return i !== subscriber; });
        },
        getChildContext() {
            var provider = {
                push: this.p,
                pop: this.u,
                context: context,
            };
            let providers = this.context.providers;
            if (providers) {
                providers.push(provider);
            } else {
                providers = [provider];
            }
            return { providers };
        },
        componentWillReceiveProps(nextProps) {
            if (this.props.value !== nextProps.value) {
                this.c.forEach(function (subscriber) {
                    subscriber(nextProps.value);
                });
            }
        },
        render() {
            return this.props.children && this.props.children[0];
        }
    });
    function Consumer(p, c) {
        Component.call(this, p, c);
        this.updateContext = this.updateContext.bind(this);
        if (c.providers) {
            for (var i = c.providers.length - 1; i >= 0; i--) {
                var provider = c.providers[i];
                if (provider.context === context) {
                    var value = provider.push(this.updateContext);
                    this.state = {
                        value: value,
                    };
                    this.popSubscriber = provider.pop;
                    break;
                }
            }
        }
    }
    extendComponent(Consumer, {
        updateContext(val) {
            this.setState({ value: val });
        },
        componentWillUnmount() {
            if (this.popSubscriber) {
                this.popSubscriber(this.updateContext);
            }
        },
        render() {
            return this.props.children && this.props.children[0](this.state.value);
        }
    });
    context.Provider = Provider
    context.Consumer = Consumer
    return context;
}

util.js

export function extendComponent(obj, prop) {
	function __() { this.constructor = obj; }
	__.prototype = Component.prototype
	obj.prototype = new __();
	extend(obj.prototype, prop)
}

test/test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
     <script src="../dist/preact.js"></script>
    <!-- <script src="../node_modules/preact/dist/preact.dev.js"></script> -->
    <title>Document</title>
</head>
<body>
    <div id="app">
    </div>
    <script>
        'use strict';
        var h = preact.h;
        var render = preact.render;
        var Component = preact.Component;
        const ThemeContext = preact.createContext({
            background: 'red',
            color: 'white'
        });
        const ContextMap = {
            1: { background: 'red', color: 'white' },
            2: { background: 'green', color: 'white' }
        }
        class App extends Component {
            constructor() {
                super()
                this.state = {
                    map: 1
                }
            }
            select() {
                this.setState({
                    map: this.state.map === 1 ? 2: 1
                })
            }
            render () {
                return (
                    h("div", null,
                        h(ThemeContext.Provider, {value: ContextMap[this.state.map === 1 ? 2: 1]},
                            h(Header, {map: this.state.map})
                        ),
                        h(ThemeContext.Provider, {value: ContextMap[this.state.map]},
                            h(Header, {map: this.state.map === 1 ? 2: 1})
                        ),
                        h("button", {onClick: () => this.select()}, "测试")
                    )
                    
                );
            }
        }
        class Header extends Component {
            // shouldComponentUpdate() {
            //     return false
            // }
            render () {
                return h(Title, this.props, "Hello React Context API")
            }
        }
        class Title extends Component {
            render () {
                return h(ThemeContext.Consumer, null,
                    context => h(this.props.map === 1 ? "h1": "h2", {style: {background: context.background, color: context.color}},
                        this.props.children
                    )
                )
            }
        }
        render(
            h(App), 
            document.getElementById('app')
        );

    </script>
    <script src="../devtools.js"></script>
</body>
</html>

@developit
Copy link
Member

developit commented Feb 23, 2018

Hi @jtmthf & @zeromake!

What would you two think of combining these and publishing it as a dedicated module? That way we can link to it from Preact core. Seem reasonable? It could live in the new preactjs GitHub org.

@zeromake
Copy link

@developit I think we can, will non-invasive react new api publishing it as a dedicated module.

@valotas
Copy link
Contributor

valotas commented Mar 17, 2018

What is the status on this? Since I would love to make use of it, I do have an implementation here. It is in typescript and lacks some tests.

@developit Since I mostly used the code here and wouldn't like to hijack anything, I am ok on moving my repository anywhere that would make sense and also giving ownership to either @jtmthf or @zeromake.

All I want is to make use of this feature :)

@valotas
Copy link
Contributor

valotas commented Mar 24, 2018

Having a closer look at it. If we do not always need to use Consumer "inside" a Provider, the implementation can be simplified and doesn't depend on preact's context at all. From the rfcs:

If a consumer is rendered without a matching provider as its ancestor, it receives the default value passed to createContext, ensuring type safety.

it looks like that this should be the case.

@developit maybe it makes more sense to ditch context in favor of the new api.

@valotas
Copy link
Contributor

valotas commented Apr 29, 2018

Just published preact-context with an implementation

@kontrollanten
Copy link

Thanks for your work! Are there any news on this?

@gerhardsletten
Copy link

This can be used by overriding preact-compat in webpack-alias:

resolve: {
  alias: {
    'react': path.resolve(__dirname, '../src/helpers/preact-compat'),
    'react-dom': 'preact-compat'
  }
}

And in your preact-compat.js override:

import {
  version,
  DOM,
  PropTypes,
  Children,
  render,
  createClass,
  createFactory,
  createElement,
  cloneElement,
  isValidElement,
  findDOMNode,
  unmountComponentAtNode,
  Component,
  PureComponent,
  unstable_renderSubtreeIntoContainer,
  __spread
} from 'preact-compat'
import { createContext } from 'preact-context'

export {
  version,
  DOM,
  PropTypes,
  Children,
  render,
  createClass,
  createFactory,
  createElement,
  cloneElement,
  isValidElement,
  findDOMNode,
  unmountComponentAtNode,
  Component,
  PureComponent,
  unstable_renderSubtreeIntoContainer,
  __spread,
  createContext // <-- Add it here
}
export default {
  version,
  DOM,
  PropTypes,
  Children,
  render,
  createClass,
  createFactory,
  createElement,
  cloneElement,
  isValidElement,
  findDOMNode,
  unmountComponentAtNode,
  Component,
  PureComponent,
  unstable_renderSubtreeIntoContainer,
  __spread,
  createContext // <-- And here
}

@marvinhagemeister
Copy link
Member

Good news: createContext is part of Preact X 🎉

@jackbravo
Copy link
Contributor

So, is the new context API already part of preact? https://github.com/valotas/preact-context is no longer needed?

@marvinhagemeister
Copy link
Member

yup

JoviDeCroock pushed a commit that referenced this pull request Oct 11, 2019
Proposing removing the reference to the `preact-context` library, as it confuses making people think that core preact doesn't support the new Context API, which according to #963 (comment), it does.
@jtmthf jtmthf deleted the new-context branch October 11, 2019 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants