From 0973eaed78e32a1b792ec3b283213f95661267ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B0=B4=E6=BE=9C?= Date: Fri, 15 Mar 2019 21:14:27 +0800 Subject: [PATCH 1/4] feat: support hooks --- packages/rax-server-renderer/package.json | 6 +++--- packages/rax-server-renderer/src/hookInstance.js | 16 ++++++++++++++++ packages/rax-server-renderer/src/index.js | 11 +++++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 packages/rax-server-renderer/src/hookInstance.js diff --git a/packages/rax-server-renderer/package.json b/packages/rax-server-renderer/package.json index 5adabc281a..a86fc4433e 100644 --- a/packages/rax-server-renderer/package.json +++ b/packages/rax-server-renderer/package.json @@ -1,6 +1,6 @@ { "name": "rax-server-renderer", - "version": "0.6.5", + "version": "1.0.0", "description": "Rax renderer for server-side render.", "license": "BSD-3-Clause", "main": "lib/index.js", @@ -12,7 +12,7 @@ "url": "https://github.com/alibaba/rax/issues" }, "homepage": "https://github.com/alibaba/rax#readme", - "devDependencies": { - "rax": "^0.6.5" + "peerDependencies": { + "rax": "^1.0.0" } } diff --git a/packages/rax-server-renderer/src/hookInstance.js b/packages/rax-server-renderer/src/hookInstance.js new file mode 100644 index 0000000000..b8f30d3f4b --- /dev/null +++ b/packages/rax-server-renderer/src/hookInstance.js @@ -0,0 +1,16 @@ +class HookInstance { + constructor() { + this._hookID = 0; + this._hooks = {}; + } + + getHookID() { + return ++ this._hookID; + } + + getHooks() { + return this._hooks; + } +} + +export default HookInstance; \ No newline at end of file diff --git a/packages/rax-server-renderer/src/index.js b/packages/rax-server-renderer/src/index.js index cb769bf0dc..c33e9b4397 100644 --- a/packages/rax-server-renderer/src/index.js +++ b/packages/rax-server-renderer/src/index.js @@ -1,3 +1,6 @@ +import { shared } from 'rax'; +import HookInstance from './hookInstance'; + const EMPTY_OBJECT = {}; const TRUE = true; const UNITLESS_NUMBER_PROPS = { @@ -146,7 +149,6 @@ function renderElementToString(element, context, options) { if (type) { const props = element.props || EMPTY_OBJECT; - if (type.prototype && type.prototype.render) { const instance = new type(props, context, updater); // eslint-disable-line new-cap let currentContext = instance.context = context; @@ -182,7 +184,12 @@ function renderElementToString(element, context, options) { var renderedElement = instance.render(); return renderElementToString(renderedElement, currentContext, options); } else if (typeof type === 'function') { - var renderedElement = type(props, context); + const instance = new HookInstance(); + shared.Host.owner = { + _instance: instance + }; + + const renderedElement = type(props, context); return renderElementToString(renderedElement, context, options); } else if (typeof type === 'string') { const isVoidElement = VOID_ELEMENTS[type]; From 8edd22bf81483356c267027bec8f5787c80870bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B0=B4=E6=BE=9C?= Date: Mon, 18 Mar 2019 19:35:06 +0800 Subject: [PATCH 2/4] refactor: new reactive instance --- .../src/ReactiveComponent.js | 33 +++++++++++++++++++ .../rax-server-renderer/src/hookInstance.js | 16 --------- packages/rax-server-renderer/src/index.js | 9 +++-- 3 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 packages/rax-server-renderer/src/ReactiveComponent.js delete mode 100644 packages/rax-server-renderer/src/hookInstance.js diff --git a/packages/rax-server-renderer/src/ReactiveComponent.js b/packages/rax-server-renderer/src/ReactiveComponent.js new file mode 100644 index 0000000000..1c8855def5 --- /dev/null +++ b/packages/rax-server-renderer/src/ReactiveComponent.js @@ -0,0 +1,33 @@ +/** + * Functional Reactive Component Class Wrapper + */ +class ReactiveComponent { + constructor(pureRender) { + // A pure function + this._render = pureRender; + this._hookID = 0; + this._hooks = {}; + // Handles store + this.didMount = []; + this.didUpdate = []; + this.willUnmount = []; + } + + getHooks() { + return this._hooks; + } + + getHookID() { + return ++this._hookID; + } + + render() { + this._hookID = 0; + + let children = this._render(this.props, this.context); + + return children; + } +} + +export default ReactiveComponent; \ No newline at end of file diff --git a/packages/rax-server-renderer/src/hookInstance.js b/packages/rax-server-renderer/src/hookInstance.js deleted file mode 100644 index b8f30d3f4b..0000000000 --- a/packages/rax-server-renderer/src/hookInstance.js +++ /dev/null @@ -1,16 +0,0 @@ -class HookInstance { - constructor() { - this._hookID = 0; - this._hooks = {}; - } - - getHookID() { - return ++ this._hookID; - } - - getHooks() { - return this._hooks; - } -} - -export default HookInstance; \ No newline at end of file diff --git a/packages/rax-server-renderer/src/index.js b/packages/rax-server-renderer/src/index.js index c33e9b4397..c5e060dae7 100644 --- a/packages/rax-server-renderer/src/index.js +++ b/packages/rax-server-renderer/src/index.js @@ -1,5 +1,5 @@ import { shared } from 'rax'; -import HookInstance from './hookInstance'; +import ReactiveComponent from './ReactiveComponent'; const EMPTY_OBJECT = {}; const TRUE = true; @@ -184,12 +184,15 @@ function renderElementToString(element, context, options) { var renderedElement = instance.render(); return renderElementToString(renderedElement, currentContext, options); } else if (typeof type === 'function') { - const instance = new HookInstance(); + const instance = new ReactiveComponent(type); + instance.props = props; + instance.context = context; + shared.Host.owner = { _instance: instance }; - const renderedElement = type(props, context); + const renderedElement = instance.render(); return renderElementToString(renderedElement, context, options); } else if (typeof type === 'string') { const isVoidElement = VOID_ELEMENTS[type]; From 21c2c340434b7a5fd5b44955e6225ccd76466759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B0=B4=E6=BE=9C?= Date: Mon, 18 Mar 2019 19:54:50 +0800 Subject: [PATCH 3/4] test: add tests for hooks --- .../src/__tests__/renderToString.js | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/rax-server-renderer/src/__tests__/renderToString.js b/packages/rax-server-renderer/src/__tests__/renderToString.js index 262c81f550..69f10e3bda 100644 --- a/packages/rax-server-renderer/src/__tests__/renderToString.js +++ b/packages/rax-server-renderer/src/__tests__/renderToString.js @@ -1,6 +1,6 @@ /* @jsx createElement */ -import {createElement} from 'rax'; +import {createElement, useState, useEffect} from 'rax'; import {renderToString} from '../index'; describe('renderToString', () => { @@ -91,4 +91,34 @@ describe('renderToString', () => { let str = renderToString(); expect(str).toBe(''); }); + + it('render with state hook', () => { + function MyComponent(props) { + const [name, setName] = useState(props.name); + + return ( +

Hello {name}

+ ); + }; + + let str = renderToString(); + expect(str).toBe('

Hello rax

'); + }); + + it('render with effect hook', () => { + function MyComponent(props) { + const [name, setName] = useState(props.name); + + useEffect(() => { + // ... + }); + + return ( +

Hello {name}

+ ); + }; + + let str = renderToString(); + expect(str).toBe('

Hello rax

'); + }); }); From a80153e2478e887c1a6728b175b768ee9ab1934a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B0=B4=E6=BE=9C?= Date: Wed, 27 Mar 2019 17:40:32 +0800 Subject: [PATCH 4/4] feat: support context hook --- .../src/ReactiveComponent.js | 33 ------------ .../src/__tests__/renderToString.js | 50 +++++++++++++++++-- packages/rax-server-renderer/src/index.js | 39 ++++++++++++++- 3 files changed, 85 insertions(+), 37 deletions(-) delete mode 100644 packages/rax-server-renderer/src/ReactiveComponent.js diff --git a/packages/rax-server-renderer/src/ReactiveComponent.js b/packages/rax-server-renderer/src/ReactiveComponent.js deleted file mode 100644 index 1c8855def5..0000000000 --- a/packages/rax-server-renderer/src/ReactiveComponent.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Functional Reactive Component Class Wrapper - */ -class ReactiveComponent { - constructor(pureRender) { - // A pure function - this._render = pureRender; - this._hookID = 0; - this._hooks = {}; - // Handles store - this.didMount = []; - this.didUpdate = []; - this.willUnmount = []; - } - - getHooks() { - return this._hooks; - } - - getHookID() { - return ++this._hookID; - } - - render() { - this._hookID = 0; - - let children = this._render(this.props, this.context); - - return children; - } -} - -export default ReactiveComponent; \ No newline at end of file diff --git a/packages/rax-server-renderer/src/__tests__/renderToString.js b/packages/rax-server-renderer/src/__tests__/renderToString.js index 69f10e3bda..dbb8400df8 100644 --- a/packages/rax-server-renderer/src/__tests__/renderToString.js +++ b/packages/rax-server-renderer/src/__tests__/renderToString.js @@ -1,6 +1,6 @@ /* @jsx createElement */ -import {createElement, useState, useEffect} from 'rax'; +import {createElement, useState, useEffect, createContext, useContext, useReducer} from 'rax'; import {renderToString} from '../index'; describe('renderToString', () => { @@ -95,7 +95,7 @@ describe('renderToString', () => { it('render with state hook', () => { function MyComponent(props) { const [name, setName] = useState(props.name); - + return (

Hello {name}

); @@ -108,7 +108,7 @@ describe('renderToString', () => { it('render with effect hook', () => { function MyComponent(props) { const [name, setName] = useState(props.name); - + useEffect(() => { // ... }); @@ -121,4 +121,48 @@ describe('renderToString', () => { let str = renderToString(); expect(str).toBe('

Hello rax

'); }); + + it('render with context hook', () => { + const NumberContext = createContext(5); + + function MyComponent() { + const value = useContext(NumberContext); + + return ( +
The answer is {value}.
+ ); + }; + + let str = renderToString(); + expect(str).toBe('
The answer is 5.
'); + }); + + it('render with reducer hook', () => { + const initialState = {count: 0}; + + function reducer(state, action) { + switch (action.type) { + case 'reset': + return initialState; + case 'increment': + return {count: state.count + 1}; + case 'decrement': + return {count: state.count - 1}; + default: + // A reducer must always return a valid state. + // Alternatively you can throw an error if an invalid action is dispatched. + return state; + } + } + + function MyComponent({initialCount}) { + const [state] = useReducer(reducer, {count: initialCount}); + return ( +
Count: {state.count}
+ ); + } + + let str = renderToString(); + expect(str).toBe('
Count: 0
'); + }); }); diff --git a/packages/rax-server-renderer/src/index.js b/packages/rax-server-renderer/src/index.js index c5e060dae7..4d2039a08e 100644 --- a/packages/rax-server-renderer/src/index.js +++ b/packages/rax-server-renderer/src/index.js @@ -1,5 +1,4 @@ import { shared } from 'rax'; -import ReactiveComponent from './ReactiveComponent'; const EMPTY_OBJECT = {}; const TRUE = true; @@ -129,6 +128,44 @@ const updater = { } }; +/** + * Functional Reactive Component Class Wrapper + */ +class ReactiveComponent { + constructor(pureRender) { + // A pure function + this._render = pureRender; + this._hookID = 0; + this._hooks = {}; + // Handles store + this.didMount = []; + this.didUpdate = []; + this.willUnmount = []; + } + + getHooks() { + return this._hooks; + } + + getHookID() { + return ++this._hookID; + } + + readContext(context) { + const Provider = context.Provider; + + return Provider.defaultValue; + } + + render() { + this._hookID = 0; + + let children = this._render(this.props, this.context); + + return children; + } +} + function renderElementToString(element, context, options) { if (typeof element === 'string') { return escapeText(element);