diff --git a/examples/typescript/package.json b/examples/typescript/package.json index 95ae248a6..766428400 100644 --- a/examples/typescript/package.json +++ b/examples/typescript/package.json @@ -22,6 +22,6 @@ "@types/react-dom": "^16.0.3", "react": "^16.2.0", "react-dom": "^16.2.0", - "react-hot-loader": "next" + "react-hot-loader": "^4.0.0-beta.22" } } diff --git a/src/proxy/createClassProxy.js b/src/proxy/createClassProxy.js index 628922cfd..21be5b79d 100644 --- a/src/proxy/createClassProxy.js +++ b/src/proxy/createClassProxy.js @@ -21,6 +21,17 @@ const has = Object.prototype.hasOwnProperty const proxies = new WeakMap() +const blackListedClassMembers = [ + 'constructor', + 'render', + 'componentDidMount', + 'componentWillReceiveProps', + 'componentWillUnmount', + + 'getInitialState', + 'getDefaultProps', +] + const defaultRenderOptions = { componentWillReceiveProps: identity, componentWillRender: identity, @@ -72,7 +83,9 @@ function createClassProxy(InitialComponent, proxyKey, options) { } function proxiedUpdate() { - inject(this, proxyGeneration, injectedMembers) + if (this) { + inject(this, proxyGeneration, injectedMembers) + } } function lifeCycleWrapperFactory(wrapperName, sideEffect = identity) { @@ -87,6 +100,24 @@ function createClassProxy(InitialComponent, proxyKey, options) { } } + function methodWrapperFactory(wrapperName, realMethod) { + return function wrappedMethod(...rest) { + return realMethod.apply(this, rest) + } + } + + const fakeBasePrototype = Base => + Object.getOwnPropertyNames(Base) + .filter(key => !blackListedClassMembers.includes(key)) + .filter(key => { + const descriptor = Object.getOwnPropertyDescriptor(Base, key) + return typeof descriptor.value === 'function' + }) + .reduce((acc, key) => { + acc[key] = methodWrapperFactory(key, Base[key]) + return acc + }, {}) + const componentDidMount = lifeCycleWrapperFactory( 'componentDidMount', target => { @@ -124,8 +155,9 @@ function createClassProxy(InitialComponent, proxyKey, options) { return renderOptions.componentDidRender(result) } - const defineProxyMethods = Proxy => { + const defineProxyMethods = (Proxy, Base = {}) => { defineClassMembers(Proxy, { + ...fakeBasePrototype(Base), render: proxiedRender, componentDidMount, componentWillReceiveProps, @@ -139,7 +171,7 @@ function createClassProxy(InitialComponent, proxyKey, options) { if (!isFunctionalComponent) { ProxyComponent = proxyClassCreator(InitialComponent, postConstructionAction) - defineProxyMethods(ProxyComponent) + defineProxyMethods(ProxyComponent, InitialComponent.prototype) ProxyFacade = ProxyComponent } else { @@ -255,6 +287,7 @@ function createClassProxy(InitialComponent, proxyKey, options) { } else { checkLifeCycleMethods(ProxyComponent, NextComponent) Object.setPrototypeOf(ProxyComponent.prototype, NextComponent.prototype) + defineProxyMethods(ProxyComponent, NextComponent.prototype) if (proxyGeneration > 1) { injectedMembers = mergeComponents( ProxyComponent, diff --git a/test/proxy/consistency.test.js b/test/proxy/consistency.test.js index 36a4bed27..05cae00f2 100644 --- a/test/proxy/consistency.test.js +++ b/test/proxy/consistency.test.js @@ -257,6 +257,32 @@ describe('consistency', () => { proxy.update(Update2Class) expect(instance.render()).toBe(42) }) + + it('should stand-for all class members', () => { + class Initial { + constructor() { + this.methodB = this.methodB.bind(this) + } + + methodA() {} + + methodB() {} + + render() {} + } + + const proxy = createProxy(Initial) + const Class = proxy.get() + expect(Object.getOwnPropertyNames(Class.prototype)).toEqual([ + 'constructor', + 'methodA', + 'methodB', + 'render', + 'componentDidMount', + 'componentWillReceiveProps', + 'componentWillUnmount', + ]) + }) }) })