From 710d8102b0e3efbb3f03f260bb2d560a38e2b5e1 Mon Sep 17 00:00:00 2001 From: ykforerlang <1527997464@qq.com> Date: Mon, 1 Jul 2019 10:04:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(alita=20wx-react=20wx-router=20wx-react-re?= =?UTF-8?q?dux):=20=E4=BF=AE=E6=94=B9=E5=85=A5=E5=8F=A3=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E8=BD=AC=E5=8C=96=EF=BC=8C=E6=9B=B4=E5=8A=A0=E5=8F=8B?= =?UTF-8?q?=E5=A5=BD=E7=9A=84=E6=94=AF=E6=8C=81Provider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/Todo/RNApp.js | 11 - examples/TodoWP/RNApp.js | 35 +- examples/TodoWP/package.json | 8 +- examples/TodoWP/project.config.json | 2 +- .../src/components/AddTodoTemplate.wxml | 2 +- package.json | 2 +- packages/wx-mobx-react/README.md | 3 + .../miniprogram_dist/Provider.js | 74 +++++ .../miniprogram_dist/disposeOnUnmount.js | 56 ++++ .../wx-mobx-react/miniprogram_dist/index.js | 27 ++ .../wx-mobx-react/miniprogram_dist/inject.js | 130 ++++++++ .../miniprogram_dist/observer.js | 307 ++++++++++++++++++ .../miniprogram_dist/propTypes.js | 198 +++++++++++ .../miniprogram_dist/utils/EventEmitter.js | 15 + .../utils/hoistNonReactStatics.js | 70 ++++ .../miniprogram_dist/utils/utils.js | 115 +++++++ packages/wx-mobx-react/package.json | 11 + .../miniprogram_dist/Provider.js | 17 + packages/wx-react-redux/package.json | 2 +- packages/wx-react/miniprogram_dist/index.js | 10 +- packages/wx-react/miniprogram_dist/util.js | 14 + packages/wx-react/package.json | 2 +- .../miniprogram_dist/NullComponent.js | 13 + packages/wx-router/miniprogram_dist/index.js | 12 +- packages/wx-router/package.json | 2 +- src/struc/handleEntry.js | 55 ++-- 26 files changed, 1127 insertions(+), 66 deletions(-) create mode 100644 packages/wx-mobx-react/README.md create mode 100644 packages/wx-mobx-react/miniprogram_dist/Provider.js create mode 100644 packages/wx-mobx-react/miniprogram_dist/disposeOnUnmount.js create mode 100644 packages/wx-mobx-react/miniprogram_dist/index.js create mode 100644 packages/wx-mobx-react/miniprogram_dist/inject.js create mode 100644 packages/wx-mobx-react/miniprogram_dist/observer.js create mode 100644 packages/wx-mobx-react/miniprogram_dist/propTypes.js create mode 100644 packages/wx-mobx-react/miniprogram_dist/utils/EventEmitter.js create mode 100644 packages/wx-mobx-react/miniprogram_dist/utils/hoistNonReactStatics.js create mode 100644 packages/wx-mobx-react/miniprogram_dist/utils/utils.js create mode 100644 packages/wx-mobx-react/package.json create mode 100644 packages/wx-router/miniprogram_dist/NullComponent.js diff --git a/examples/Todo/RNApp.js b/examples/Todo/RNApp.js index 9a28707..6e8af01 100644 --- a/examples/Todo/RNApp.js +++ b/examples/Todo/RNApp.js @@ -25,17 +25,6 @@ const store = createStore(todoApp, applyMiddleware(thunk, promiseMiddleware)) export default class RNApp extends PureComponent { - static childContextTypes = { - store: PropTypes.object - } - - getChildContext() { - return { - store: Platform.OS === 'wx' ? store : {} - } - } - - render() { return ( diff --git a/examples/TodoWP/RNApp.js b/examples/TodoWP/RNApp.js index ae83d2f..3439bb9 100644 --- a/examples/TodoWP/RNApp.js +++ b/examples/TodoWP/RNApp.js @@ -1,6 +1,4 @@ import React, { PureComponent, h } from "@areslabs/wx-react"; -import { Platform } from "@areslabs/wx-react-native"; -import PropTypes from "@areslabs/wx-prop-types"; import { createStore, applyMiddleware } from "@areslabs/wx-redux"; import promiseMiddleware from "@areslabs/wx-redux-promise"; import thunk from "@areslabs/wx-redux-thunk"; @@ -9,18 +7,27 @@ import { Provider } from "@areslabs/wx-react-redux"; import { Router, Route } from "@areslabs/wx-router"; const store = createStore(todoApp, applyMiddleware(thunk, promiseMiddleware)); const RNAppClass = class RNApp extends PureComponent { - static childContextTypes = { - store: PropTypes.object - }; - - getChildContext() { - return { - store: Platform.OS === 'wx' ? store : {} - }; + render() { + return h(Provider, { + store: store, + diuu: "DIUU00001" + }, h(Router, { + diuu: "DIUU00002" + }, h(Route, { + diuu: "DIUU00003" + }))); } }; -const RNApp = new RNAppClass({}); -RNApp.childContext = RNApp.getChildContext ? RNApp.getChildContext() : {}; -export default RNApp; -wx._historyConfig = {...(wx._historyConfig || {}), ...{"Todoinit":"/src/components/index"}}; \ No newline at end of file +React.render(h(RNAppClass, { + diuu: React.rootUuid +}), null, {}); +const rootContext = React.getRootContext(); +export default { + childContext: rootContext +}; +wx._historyConfig = { ...(wx._historyConfig || {}), + ...{ + "Todoinit": "/src/components/index" + } +}; \ No newline at end of file diff --git a/examples/TodoWP/package.json b/examples/TodoWP/package.json index b5d8638..a176c00 100644 --- a/examples/TodoWP/package.json +++ b/examples/TodoWP/package.json @@ -2,15 +2,15 @@ "name": "Todo", "version": "0.0.1", "dependencies": { - "@areslabs/wx-router": "^1.0.0", + "@areslabs/wx-router": "beta", "@areslabs/wx-animated": "^1.0.1", - "@areslabs/wx-react": "^1.0.0", + "@areslabs/wx-react": "beta", "@areslabs/wx-react-native": "^1.0.0", "@areslabs/wx-prop-types": "^1.0.0", - "@areslabs/wx-react-redux": "^1.0.0", + "@areslabs/wx-react-redux": "beta", "@areslabs/wx-redux": "^1.0.0", "@areslabs/wx-redux-actions": "^1.0.0", "@areslabs/wx-redux-promise": "^1.0.0", "@areslabs/wx-redux-thunk": "^1.0.0" } -} \ No newline at end of file +} diff --git a/examples/TodoWP/project.config.json b/examples/TodoWP/project.config.json index fa94ffe..8d57045 100644 --- a/examples/TodoWP/project.config.json +++ b/examples/TodoWP/project.config.json @@ -14,7 +14,7 @@ "compileType": "miniprogram", "libVersion": "2.2.2", "appid": "wx67a52214b440b81b", - "projectname": "TodoListWP", + "projectname": "tmptmp", "isGameTourist": false, "condition": { "search": { diff --git a/examples/TodoWP/src/components/AddTodoTemplate.wxml b/examples/TodoWP/src/components/AddTodoTemplate.wxml index 618c0d4..269db5d 100644 --- a/examples/TodoWP/src/components/AddTodoTemplate.wxml +++ b/examples/TodoWP/src/components/AddTodoTemplate.wxml @@ -3,7 +3,7 @@ diff --git a/package.json b/package.json index 67f47eb..dc4600a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "weixin", "redux" ], - "version": "1.0.1", + "version": "1.0.2-beta.1", "description": "首个ReactNative转微信小程序引擎", "main": "./lib/index.js", "scripts": { diff --git a/packages/wx-mobx-react/README.md b/packages/wx-mobx-react/README.md new file mode 100644 index 0000000..02b2ec6 --- /dev/null +++ b/packages/wx-mobx-react/README.md @@ -0,0 +1,3 @@ +## wx-mobx-react + +The code is a copy from [mobx-react](https://github.com/mobxjs/mobx-react). We made some adjustments for Wechat \ No newline at end of file diff --git a/packages/wx-mobx-react/miniprogram_dist/Provider.js b/packages/wx-mobx-react/miniprogram_dist/Provider.js new file mode 100644 index 0000000..3443ec2 --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/Provider.js @@ -0,0 +1,74 @@ +import { Children, Component } from "react" +import { polyfill } from "react-lifecycles-compat" +import * as PropTypes from "./propTypes" + +const specialReactKeys = { children: true, key: true, ref: true } + +class Provider extends Component { + static contextTypes = { + mobxStores: PropTypes.objectOrObservableObject + } + + static childContextTypes = { + mobxStores: PropTypes.objectOrObservableObject.isRequired + } + + constructor(props, context) { + super(props, context) + this.state = {} + copyStores(props, this.state) + } + + render() { + return Children.only(this.props.children) + } + + getChildContext() { + const stores = {} + // inherit stores + copyStores(this.context.mobxStores, stores) + // add own stores + copyStores(this.props, stores) + return { + mobxStores: stores + } + } + + static getDerivedStateFromProps(nextProps, prevState) { + if (!nextProps) return null + if (!prevState) return nextProps + + // Maybe this warning is too aggressive? + if ( + Object.keys(nextProps).filter(validStoreName).length !== + Object.keys(prevState).filter(validStoreName).length + ) + console.warn( + "MobX Provider: The set of provided stores has changed. Please avoid changing stores as the change might not propagate to all children" + ) + if (!nextProps.suppressChangedStoreWarning) + for (let key in nextProps) + if (validStoreName(key) && prevState[key] !== nextProps[key]) + console.warn( + "MobX Provider: Provided store '" + + key + + "' has changed. Please avoid replacing stores as the change might not propagate to all children" + ) + + return nextProps + } +} + +function copyStores(from, to) { + if (!from) return + for (let key in from) if (validStoreName(key)) to[key] = from[key] +} + +function validStoreName(key) { + return !specialReactKeys[key] && key !== "suppressChangedStoreWarning" +} + +// TODO: kill in next major +polyfill(Provider) + +export default Provider diff --git a/packages/wx-mobx-react/miniprogram_dist/disposeOnUnmount.js b/packages/wx-mobx-react/miniprogram_dist/disposeOnUnmount.js new file mode 100644 index 0000000..c74fc54 --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/disposeOnUnmount.js @@ -0,0 +1,56 @@ +import * as React from "react" +import { patch, newSymbol } from "./utils/utils" + +const storeKey = newSymbol("disposeOnUnmount") + +function runDisposersOnWillUnmount() { + if (!this[storeKey]) { + // when disposeOnUnmount is only set to some instances of a component it will still patch the prototype + return + } + this[storeKey].forEach(propKeyOrFunction => { + const prop = + typeof propKeyOrFunction === "string" ? this[propKeyOrFunction] : propKeyOrFunction + if (prop !== undefined && prop !== null) { + if (typeof prop !== "function") { + throw new Error( + "[mobx-react] disposeOnUnmount only works on functions such as disposers returned by reactions, autorun, etc." + ) + } + prop() + } + }) + this[storeKey] = [] +} + +export function disposeOnUnmount(target, propertyKeyOrFunction) { + if (Array.isArray(propertyKeyOrFunction)) { + return propertyKeyOrFunction.map(fn => disposeOnUnmount(target, fn)) + } + + if (!target instanceof React.Component) { + throw new Error("[mobx-react] disposeOnUnmount only works on class based React components.") + } + + if (typeof propertyKeyOrFunction !== "string" && typeof propertyKeyOrFunction !== "function") { + throw new Error( + "[mobx-react] disposeOnUnmount only works if the parameter is either a property key or a function." + ) + } + + // add property key / function we want run (disposed) to the store + const componentWasAlreadyModified = !!target[storeKey] + const store = target[storeKey] || (target[storeKey] = []) + + store.push(propertyKeyOrFunction) + + // tweak the component class componentWillUnmount if not done already + if (!componentWasAlreadyModified) { + patch(target, "componentWillUnmount", runDisposersOnWillUnmount) + } + + // return the disposer as is if invoked as a non decorator + if (typeof propertyKeyOrFunction !== "string") { + return propertyKeyOrFunction + } +} diff --git a/packages/wx-mobx-react/miniprogram_dist/index.js b/packages/wx-mobx-react/miniprogram_dist/index.js new file mode 100644 index 0000000..15b2da5 --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/index.js @@ -0,0 +1,27 @@ +import { spy, configure, getDebugName } from "mobx" +import { Component } from "@areslabs/wx-react" + +export { + observer, + Observer, + renderReporter, + componentByNodeRegistry as componentByNodeRegistery, + componentByNodeRegistry, + trackComponents, +} from "./observer" + +export { default as Provider } from "./Provider" +export { default as inject } from "./inject" +export { disposeOnUnmount } from "./disposeOnUnmount" + +import * as propTypes from "./propTypes" +export { propTypes } +export { propTypes as PropTypes } + +import { errorsReporter } from "./observer" +export const onError = fn => errorsReporter.on(fn) + +/* DevTool support */ +// See: https://github.com/andykog/mobx-devtools/blob/d8976c24b8cb727ed59f9a0bc905a009df79e221/src/backend/installGlobalHook.js + +import { renderReporter, componentByNodeRegistry, trackComponents } from "./observer" diff --git a/packages/wx-mobx-react/miniprogram_dist/inject.js b/packages/wx-mobx-react/miniprogram_dist/inject.js new file mode 100644 index 0000000..eb8ec52 --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/inject.js @@ -0,0 +1,130 @@ +import { Component, createElement, HocComponent } from "@areslabs/wx-react" +import hoistStatics from "./utils/hoistNonReactStatics" +import * as PropTypes from "./propTypes" +import { observer } from "./observer" +import { isStateless } from "./utils/utils" + +const injectorContextTypes = { + mobxStores: PropTypes.objectOrObservableObject +} +Object.seal(injectorContextTypes) + +const proxiedInjectorProps = { + contextTypes: { + get: function() { + return injectorContextTypes + }, + set: function(_) { + console.warn( + "Mobx Injector: you are trying to attach `contextTypes` on an component decorated with `inject` (or `observer`) HOC. Please specify the contextTypes on the wrapped component instead. It is accessible through the `wrappedComponent`" + ) + }, + configurable: true, + enumerable: false + }, + isMobxInjector: { + value: true, + writable: true, + configurable: true, + enumerable: true + } +} + +/** + * Store Injection + */ +function createStoreInjector(grabStoresFn, component, injectNames) { + let displayName = + "inject-" + + (component.displayName || + component.name || + (component.constructor && component.constructor.name) || + "Unknown") + if (injectNames) displayName += "-with-" + injectNames + + class Injector extends HocComponent { + static displayName = displayName + + storeRef = instance => { + this.wrappedInstance = instance + } + + render() { + // Optimization: it might be more efficient to apply the mapper function *outside* the render method + // (if the mapper is a function), that could avoid expensive(?) re-rendering of the injector component + // See this test: 'using a custom injector is not too reactive' in inject.js + let newProps = {} + for (let key in this.props) + if (this.props.hasOwnProperty(key)) { + newProps[key] = this.props[key] + } + var additionalProps = + grabStoresFn(this.context.mobxStores || {}, newProps, this.context) || {} + for (let key in additionalProps) { + newProps[key] = additionalProps[key] + } + + if (!isStateless(component)) { + newProps.ref = this.storeRef + } + + return createElement(component, newProps) + } + } + + // Static fields from component should be visible on the generated Injector + hoistStatics(Injector, component) + + Injector.wrappedComponent = component + Object.defineProperties(Injector, proxiedInjectorProps) + + return Injector +} + +function grabStoresByName(storeNames) { + return function(baseStores, nextProps) { + storeNames.forEach(function(storeName) { + if ( + storeName in nextProps // prefer props over stores + ) + return + if (!(storeName in baseStores)) + throw new Error( + "MobX injector: Store '" + + storeName + + "' is not available! Make sure it is provided by some Provider" + ) + nextProps[storeName] = baseStores[storeName] + }) + return nextProps + } +} + +/** + * higher order component that injects stores to a child. + * takes either a varargs list of strings, which are stores read from the context, + * or a function that manually maps the available stores from the context to props: + * storesToProps(mobxStores, props, context) => newProps + */ +export default function inject(/* fn(stores, nextProps) or ...storeNames */) { + let grabStoresFn + if (typeof arguments[0] === "function") { + grabStoresFn = arguments[0] + return function(componentClass) { + let injected = createStoreInjector(grabStoresFn, componentClass) + injected.isMobxInjector = false // supress warning + // mark the Injector as observer, to make it react to expressions in `grabStoresFn`, + // see #111 + injected = observer(injected) + injected.isMobxInjector = true // restore warning + return injected + } + } else { + const storeNames = [] + for (let i = 0; i < arguments.length; i++) storeNames[i] = arguments[i] + grabStoresFn = grabStoresByName(storeNames) + return function(componentClass) { + return createStoreInjector(grabStoresFn, componentClass, storeNames.join("-")) + } + } +} diff --git a/packages/wx-mobx-react/miniprogram_dist/observer.js b/packages/wx-mobx-react/miniprogram_dist/observer.js new file mode 100644 index 0000000..6b5d639 --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/observer.js @@ -0,0 +1,307 @@ +import React, { Component, PureComponent } from "@areslabs/wx-react" +import { createAtom, Reaction, _allowStateChanges, $mobx } from "mobx" +import hoistStatics from "./utils/hoistNonReactStatics" + +import EventEmitter from "./utils/EventEmitter" +import inject from "./inject" +import { patch as newPatch, newSymbol } from "./utils/utils" + +const mobxAdminProperty = $mobx || "$mobx" +const mobxIsUnmounted = newSymbol("isUnmounted") + + +let warnedAboutObserverInjectDeprecation = false + +// WeakMap; +export const componentByNodeRegistry = typeof WeakMap !== "undefined" ? new WeakMap() : undefined +export const renderReporter = new EventEmitter() + +const skipRenderKey = newSymbol("skipRender") +const isForcingUpdateKey = newSymbol("isForcingUpdate") + + +/** + * Helper to set `prop` to `this` as non-enumerable (hidden prop) + * @param target + * @param prop + * @param value + */ +function setHiddenProp(target, prop, value) { + if (!Object.hasOwnProperty.call(target, prop)) { + Object.defineProperty(target, prop, { + enumerable: false, + configurable: true, + writable: true, + value + }) + } else { + target[prop] = value + } +} + + +/** + * Errors reporter + */ + +export const errorsReporter = new EventEmitter() + +/** + * Utilities + */ + +function patch(target, funcName) { + newPatch(target, funcName, reactiveMixin[funcName]) +} + +function shallowEqual(objA, objB) { + //From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js + if (is(objA, objB)) return true + if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) { + return false + } + const keysA = Object.keys(objA) + const keysB = Object.keys(objB) + if (keysA.length !== keysB.length) return false + for (let i = 0; i < keysA.length; i++) { + if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { + return false + } + } + return true +} + +function is(x, y) { + // From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js + if (x === y) { + return x !== 0 || 1 / x === 1 / y + } else { + return x !== x && y !== y + } +} + +function makeComponentReactive(render) { + function reactiveRender() { + isRenderingPending = false + let exception = undefined + let rendering = undefined + reaction.track(() => { + try { + rendering = _allowStateChanges(false, baseRender) + } catch (e) { + exception = e + } + }) + if (exception) { + errorsReporter.emit(exception) + throw exception + } + return rendering + } + + // Generate friendly name for debugging + const initialName = + this.displayName || + this.name || + (this.constructor && (this.constructor.displayName || this.constructor.name)) || + "" + const rootNodeID = + (this._reactInternalInstance && this._reactInternalInstance._rootNodeID) || + (this._reactInternalInstance && this._reactInternalInstance._debugID) || + (this._reactInternalFiber && this._reactInternalFiber._debugID) + /** + * If props are shallowly modified, react will render anyway, + * so atom.reportChanged() should not result in yet another re-render + */ + setHiddenProp(this, skipRenderKey, false) + /** + * forceUpdate will re-assign this.props. We don't want that to cause a loop, + * so detect these changes + */ + setHiddenProp(this, isForcingUpdateKey, false) + + // wire up reactive render + const baseRender = render.bind(this) + let isRenderingPending = false + + const reaction = new Reaction(`${initialName}#${rootNodeID}.render()`, () => { + if (!isRenderingPending) { + // N.B. Getting here *before mounting* means that a component constructor has side effects (see the relevant test in misc.js) + // This unidiomatic React usage but React will correctly warn about this so we continue as usual + // See #85 / Pull #44 + isRenderingPending = true + if (typeof this.componentWillReact === "function") this.componentWillReact() // TODO: wrap in action? + if (this[mobxIsUnmounted] !== true) { + // If we are unmounted at this point, componentWillReact() had a side effect causing the component to unmounted + // TODO: remove this check? Then react will properly warn about the fact that this should not happen? See #73 + // However, people also claim this might happen during unit tests.. + let hasError = true + try { + setHiddenProp(this, isForcingUpdateKey, true) + if (!this[skipRenderKey]) Component.prototype.forceUpdate.call(this) + hasError = false + } finally { + setHiddenProp(this, isForcingUpdateKey, false) + if (hasError) reaction.dispose() + } + } + } + }) + reaction.reactComponent = this + reactiveRender[mobxAdminProperty] = reaction + this.render = reactiveRender + return reactiveRender.call(this) +} + +/** + * ReactiveMixin + */ +const reactiveMixin = { + componentWillUnmount: function() { + this.render[mobxAdminProperty] && this.render[mobxAdminProperty].dispose() + this[mobxIsUnmounted] = true + }, + + componentDidMount: function() { + + }, + + componentDidUpdate: function() { + + }, + + shouldComponentUpdate: function(nextProps, nextState) { + // update on any state changes (as is the default) + if (this.state !== nextState) { + return true + } + // update if props are shallowly not equal, inspired by PureRenderMixin + // we could return just 'false' here, and avoid the `skipRender` checks etc + // however, it is nicer if lifecycle events are triggered like usually, + // so we return true here if props are shallowly modified. + return !shallowEqual(this.props, nextProps) + } +} + +function makeObservableProp(target, propName) { + const valueHolderKey = newSymbol(`reactProp_${propName}_valueHolder`) + const atomHolderKey = newSymbol(`reactProp_${propName}_atomHolder`) + function getAtom() { + if (!this[atomHolderKey]) { + setHiddenProp(this, atomHolderKey, createAtom("reactive " + propName)) + } + return this[atomHolderKey] + } + Object.defineProperty(target, propName, { + configurable: true, + enumerable: true, + get: function() { + getAtom.call(this).reportObserved() + return this[valueHolderKey] + }, + set: function set(v) { + if (!this[isForcingUpdateKey] && !shallowEqual(this[valueHolderKey], v)) { + setHiddenProp(this, valueHolderKey, v) + setHiddenProp(this, skipRenderKey, true) + getAtom.call(this).reportChanged() + setHiddenProp(this, skipRenderKey, false) + } else { + setHiddenProp(this, valueHolderKey, v) + } + } + }) +} + +/** + * Observer function / decorator + */ +export function observer(arg1, arg2) { + if (typeof arg1 === "string") { + throw new Error("Store names should be provided as array") + } + if (Array.isArray(arg1)) { + // TODO: remove in next major + // component needs stores + if (!warnedAboutObserverInjectDeprecation) { + warnedAboutObserverInjectDeprecation = true + console.warn( + 'Mobx observer: Using observer to inject stores is deprecated since 4.0. Use `@inject("store1", "store2") @observer ComponentClass` or `inject("store1", "store2")(observer(componentClass))` instead of `@observer(["store1", "store2"]) ComponentClass`' + ) + } + if (!arg2) { + // invoked as decorator + return componentClass => observer(arg1, componentClass) + } else { + return inject.apply(null, arg1)(observer(arg2)) + } + } + const componentClass = arg1 + + if (componentClass.isMobxInjector === true) { + console.warn( + "Mobx observer: You are trying to use 'observer' on a component that already has 'inject'. Please apply 'observer' before applying 'inject'" + ) + } + if (componentClass.__proto__ === PureComponent) { + console.warn( + "Mobx observer: You are using 'observer' on React.PureComponent. These two achieve two opposite goals and should not be used together" + ) + } + + // Stateless function component: + // If it is function but doesn't seem to be a react class constructor, + // wrap it to a react class automatically + if ( + typeof componentClass === "function" && + (!componentClass.prototype || !componentClass.prototype.render) && + !componentClass.isReactClass && + !Component.isPrototypeOf(componentClass) + ) { + const observerComponent = observer( + class extends Component { + static displayName = componentClass.displayName || componentClass.name + static contextTypes = componentClass.contextTypes + static propTypes = componentClass.propTypes + static defaultProps = componentClass.defaultProps + render() { + return componentClass.call(this, this.props, this.context) + } + } + ) + hoistStatics(observerComponent, componentClass) + return observerComponent + } + + if (!componentClass) { + throw new Error("Please pass a valid component to 'observer'") + } + + const target = componentClass.prototype || componentClass + mixinLifecycleEvents(target) + componentClass.isMobXReactObserver = true + makeObservableProp(target, "props") + makeObservableProp(target, "state") + const baseRender = target.render + target.render = function() { + return makeComponentReactive.call(this, baseRender) + } + return componentClass +} + +function mixinLifecycleEvents(target) { + ;["componentDidMount", "componentWillUnmount", "componentDidUpdate"].forEach(function( + funcName + ) { + patch(target, funcName) + }) + if (!target.shouldComponentUpdate) { + target.shouldComponentUpdate = reactiveMixin.shouldComponentUpdate + } else { + if (target.shouldComponentUpdate !== reactiveMixin.shouldComponentUpdate) { + // TODO: make throw in next major + console.warn( + "Use `shouldComponentUpdate` in an `observer` based component breaks the behavior of `observer` and might lead to unexpected results. Manually implementing `sCU` should not be needed when using mobx-react." + ) + } + } +} diff --git a/packages/wx-mobx-react/miniprogram_dist/propTypes.js b/packages/wx-mobx-react/miniprogram_dist/propTypes.js new file mode 100644 index 0000000..62dbe6e --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/propTypes.js @@ -0,0 +1,198 @@ +import { isObservableArray, isObservableObject, isObservableMap, untracked } from "mobx" + +// Copied from React.PropTypes +function createChainableTypeChecker(validate) { + function checkType( + isRequired, + props, + propName, + componentName, + location, + propFullName, + ...rest + ) { + return untracked(() => { + componentName = componentName || "<>" + propFullName = propFullName || propName + if (props[propName] == null) { + if (isRequired) { + const actual = props[propName] === null ? "null" : "undefined" + return new Error( + "The " + + location + + " `" + + propFullName + + "` is marked as required " + + "in `" + + componentName + + "`, but its value is `" + + actual + + "`." + ) + } + return null + } else { + return validate(props, propName, componentName, location, propFullName, ...rest) + } + }) + } + + const chainedCheckType = checkType.bind(null, false) + chainedCheckType.isRequired = checkType.bind(null, true) + return chainedCheckType +} + +// Copied from React.PropTypes +function isSymbol(propType, propValue) { + // Native Symbol. + if (propType === "symbol") { + return true + } + + // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol' + if (propValue["@@toStringTag"] === "Symbol") { + return true + } + + // Fallback for non-spec compliant Symbols which are polyfilled. + if (typeof Symbol === "function" && propValue instanceof Symbol) { + return true + } + + return false +} + +// Copied from React.PropTypes +function getPropType(propValue) { + const propType = typeof propValue + if (Array.isArray(propValue)) { + return "array" + } + if (propValue instanceof RegExp) { + // Old webkits (at least until Android 4.0) return 'function' rather than + // 'object' for typeof a RegExp. We'll normalize this here so that /bla/ + // passes PropTypes.object. + return "object" + } + if (isSymbol(propType, propValue)) { + return "symbol" + } + return propType +} + +// This handles more types than `getPropType`. Only used for error messages. +// Copied from React.PropTypes +function getPreciseType(propValue) { + const propType = getPropType(propValue) + if (propType === "object") { + if (propValue instanceof Date) { + return "date" + } else if (propValue instanceof RegExp) { + return "regexp" + } + } + return propType +} + +function createObservableTypeCheckerCreator(allowNativeType, mobxType) { + return createChainableTypeChecker(function( + props, + propName, + componentName, + location, + propFullName + ) { + return untracked(() => { + if (allowNativeType) { + if (getPropType(props[propName]) === mobxType.toLowerCase()) return null + } + let mobxChecker + switch (mobxType) { + case "Array": + mobxChecker = isObservableArray + break + case "Object": + mobxChecker = isObservableObject + break + case "Map": + mobxChecker = isObservableMap + break + default: + throw new Error(`Unexpected mobxType: ${mobxType}`) + } + const propValue = props[propName] + if (!mobxChecker(propValue)) { + const preciseType = getPreciseType(propValue) + const nativeTypeExpectationMessage = allowNativeType + ? " or javascript `" + mobxType.toLowerCase() + "`" + : "" + return new Error( + "Invalid prop `" + + propFullName + + "` of type `" + + preciseType + + "` supplied to" + + " `" + + componentName + + "`, expected `mobx.Observable" + + mobxType + + "`" + + nativeTypeExpectationMessage + + "." + ) + } + return null + }) + }) +} + +function createObservableArrayOfTypeChecker(allowNativeType, typeChecker) { + return createChainableTypeChecker(function( + props, + propName, + componentName, + location, + propFullName, + ...rest + ) { + return untracked(() => { + if (typeof typeChecker !== "function") { + return new Error( + "Property `" + + propFullName + + "` of component `" + + componentName + + "` has " + + "invalid PropType notation." + ) + } + let error = createObservableTypeCheckerCreator(allowNativeType, "Array")( + props, + propName, + componentName + ) + if (error instanceof Error) return error + const propValue = props[propName] + for (let i = 0; i < propValue.length; i++) { + error = typeChecker( + propValue, + i, + componentName, + location, + propFullName + "[" + i + "]", + ...rest + ) + if (error instanceof Error) return error + } + return null + }) + }) +} + +export const observableArray = createObservableTypeCheckerCreator(false, "Array") +export const observableArrayOf = createObservableArrayOfTypeChecker.bind(null, false) +export const observableMap = createObservableTypeCheckerCreator(false, "Map") +export const observableObject = createObservableTypeCheckerCreator(false, "Object") +export const arrayOrObservableArray = createObservableTypeCheckerCreator(true, "Array") +export const arrayOrObservableArrayOf = createObservableArrayOfTypeChecker.bind(null, true) +export const objectOrObservableObject = createObservableTypeCheckerCreator(true, "Object") diff --git a/packages/wx-mobx-react/miniprogram_dist/utils/EventEmitter.js b/packages/wx-mobx-react/miniprogram_dist/utils/EventEmitter.js new file mode 100644 index 0000000..5020efe --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/utils/EventEmitter.js @@ -0,0 +1,15 @@ +export default class EventEmitter { + listeners = [] + + on(cb) { + this.listeners.push(cb) + return () => { + const index = this.listeners.indexOf(cb) + if (index !== -1) this.listeners.splice(index, 1) + } + } + + emit(data) { + this.listeners.forEach(fn => fn(data)) + } +} diff --git a/packages/wx-mobx-react/miniprogram_dist/utils/hoistNonReactStatics.js b/packages/wx-mobx-react/miniprogram_dist/utils/hoistNonReactStatics.js new file mode 100644 index 0000000..5588fef --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/utils/hoistNonReactStatics.js @@ -0,0 +1,70 @@ +/** + * Copyright (c) Areslabs. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +//FROM https://github.com/mridgway/hoist-non-react-statics/blob/v2.5.5/src/index.js + +var REACT_STATICS = { + childContextTypes: true, + contextTypes: true, + defaultProps: true, + displayName: true, + getDefaultProps: true, + getDerivedStateFromProps: true, + mixins: true, + propTypes: true, + type: true +}; + +var KNOWN_STATICS = { + name: true, + length: true, + prototype: true, + caller: true, + callee: true, + arguments: true, + arity: true +}; + +var defineProperty = Object.defineProperty; +var getOwnPropertyNames = Object.getOwnPropertyNames; +var getOwnPropertySymbols = Object.getOwnPropertySymbols; +var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +var getPrototypeOf = Object.getPrototypeOf; +var objectPrototype = getPrototypeOf && getPrototypeOf(Object); + +export default function hoistNonReactStatics(targetComponent, sourceComponent, blacklist) { + if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components + + if (objectPrototype) { + var inheritedComponent = getPrototypeOf(sourceComponent); + if (inheritedComponent && inheritedComponent !== objectPrototype) { + hoistNonReactStatics(targetComponent, inheritedComponent, blacklist); + } + } + + var keys = getOwnPropertyNames(sourceComponent); + + if (getOwnPropertySymbols) { + keys = keys.concat(getOwnPropertySymbols(sourceComponent)); + } + + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + if (!REACT_STATICS[key] && !KNOWN_STATICS[key] && (!blacklist || !blacklist[key])) { + var descriptor = getOwnPropertyDescriptor(sourceComponent, key); + try { // Avoid failures from read-only properties + defineProperty(targetComponent, key, descriptor); + } catch (e) {} + } + } + + return targetComponent; + } + + return targetComponent; +}; \ No newline at end of file diff --git a/packages/wx-mobx-react/miniprogram_dist/utils/utils.js b/packages/wx-mobx-react/miniprogram_dist/utils/utils.js new file mode 100644 index 0000000..52618d3 --- /dev/null +++ b/packages/wx-mobx-react/miniprogram_dist/utils/utils.js @@ -0,0 +1,115 @@ +export function isStateless(component) { + // `function() {}` has prototype, but `() => {}` doesn't + // `() => {}` via Babel has prototype too. + return !(component.prototype && component.prototype.render) +} + +let symbolId = 0 +function createSymbol(name) { + if (typeof Symbol === "function") { + return Symbol(name) + } + const symbol = `__$mobx-react ${name} (${symbolId})` + symbolId++ + return symbol +} + +const createdSymbols = {} +export function newSymbol(name) { + if (!createdSymbols[name]) { + createdSymbols[name] = createSymbol(name) + } + return createdSymbols[name] +} + +const mobxMixins = newSymbol("patchMixins") +const mobxPatchedDefinition = newSymbol("patchedDefinition") + +function getMixins(target, methodName) { + const mixins = (target[mobxMixins] = target[mobxMixins] || {}) + const methodMixins = (mixins[methodName] = mixins[methodName] || {}) + methodMixins.locks = methodMixins.locks || 0 + methodMixins.methods = methodMixins.methods || [] + return methodMixins +} + +function wrapper(realMethod, mixins, ...args) { + // locks are used to ensure that mixins are invoked only once per invocation, even on recursive calls + mixins.locks++ + + try { + let retVal + if (realMethod !== undefined && realMethod !== null) { + retVal = realMethod.apply(this, args) + } + + return retVal + } finally { + mixins.locks-- + if (mixins.locks === 0) { + mixins.methods.forEach(mx => { + mx.apply(this, args) + }) + } + } +} + +function wrapFunction(realMethod, mixins) { + const fn = function(...args) { + wrapper.call(this, realMethod, mixins, ...args) + } + return fn +} + +export function patch(target, methodName, ...mixinMethods) { + const mixins = getMixins(target, methodName) + + for (const mixinMethod of mixinMethods) { + if (mixins.methods.indexOf(mixinMethod) < 0) { + mixins.methods.push(mixinMethod) + } + } + + const oldDefinition = Object.getOwnPropertyDescriptor(target, methodName) + if (oldDefinition && oldDefinition[mobxPatchedDefinition]) { + // already patched definition, do not repatch + return + } + + const originalMethod = target[methodName] + const newDefinition = createDefinition( + target, + methodName, + oldDefinition ? oldDefinition.enumerable : undefined, + mixins, + originalMethod + ) + + Object.defineProperty(target, methodName, newDefinition) +} + +function createDefinition(target, methodName, enumerable, mixins, originalMethod) { + let wrappedFunc = wrapFunction(originalMethod, mixins) + + return { + [mobxPatchedDefinition]: true, + get: function() { + return wrappedFunc + }, + set: function(value) { + if (this === target) { + wrappedFunc = wrapFunction(value, mixins) + } else { + // when it is an instance of the prototype/a child prototype patch that particular case again separately + // since we need to store separate values depending on wether it is the actual instance, the prototype, etc + // e.g. the method for super might not be the same as the method for the prototype which might be not the same + // as the method for the instance + const newDefinition = createDefinition(this, methodName, enumerable, mixins, value) + Object.defineProperty(this, methodName, newDefinition) + } + }, + configurable: true, + enumerable: enumerable + } +} + diff --git a/packages/wx-mobx-react/package.json b/packages/wx-mobx-react/package.json new file mode 100644 index 0000000..eccf877 --- /dev/null +++ b/packages/wx-mobx-react/package.json @@ -0,0 +1,11 @@ +{ + "name": "wx-mobx-react", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/packages/wx-react-redux/miniprogram_dist/Provider.js b/packages/wx-react-redux/miniprogram_dist/Provider.js index cca6ce3..2610381 100644 --- a/packages/wx-react-redux/miniprogram_dist/Provider.js +++ b/packages/wx-react-redux/miniprogram_dist/Provider.js @@ -5,4 +5,21 @@ * LICENSE file in the root directory of this source tree. * */ +import React, {Component} from '@areslabs/wx-react' +import PropTypes from '@areslabs/wx-prop-types' + +export default class Provider extends Component { + + static childContextTypes = { + store: PropTypes.object + }; + + getChildContext() { + return { + store: this.props.store + }; + } + + render() {} +} diff --git a/packages/wx-react-redux/package.json b/packages/wx-react-redux/package.json index b68a30c..5fc0950 100644 --- a/packages/wx-react-redux/package.json +++ b/packages/wx-react-redux/package.json @@ -1,6 +1,6 @@ { "name": "@areslabs/wx-react-redux", - "version": "1.0.2", + "version": "1.0.3-beta.1", "description": "微信版本的react-redux", "main": "index.js", "scripts": { diff --git a/packages/wx-react/miniprogram_dist/index.js b/packages/wx-react/miniprogram_dist/index.js index 9f1554c..ecd233f 100644 --- a/packages/wx-react/miniprogram_dist/index.js +++ b/packages/wx-react/miniprogram_dist/index.js @@ -14,7 +14,7 @@ import WxNormalComp from './WxNormalComp' import tackleWithStyleObj from './tackleWithStyleObj' import styleType from './styleType' import instanceManager from './InstanceManager' -import {getPropsMethod} from './util' +import {getPropsMethod, getRootContext, rootUuid} from './util' export default { @@ -31,7 +31,9 @@ export default { styleType, h: createElement, instanceManager, - getPropsMethod + getPropsMethod, + getRootContext, + rootUuid } export { @@ -47,6 +49,8 @@ export { tackleWithStyleObj, styleType, instanceManager, - getPropsMethod + getPropsMethod, + getRootContext, + rootUuid } export const h = createElement \ No newline at end of file diff --git a/packages/wx-react/miniprogram_dist/util.js b/packages/wx-react/miniprogram_dist/util.js index 91c612f..7f52deb 100644 --- a/packages/wx-react/miniprogram_dist/util.js +++ b/packages/wx-react/miniprogram_dist/util.js @@ -145,3 +145,17 @@ export const ReactWxEventMap = { 'onLoad': 'load', 'onError': 'error' } + +export const rootUuid = '__root__' +export function getRootContext() { + const rootInst = instanceManager.getCompInstByUUID(rootUuid) + let topInst = rootInst + while (topInst._c.length !== 0) { + if (rootInst._c.length !== 1) { + console.warn('Root页包裹路由的组件,不应该存在多个节点!') + } + topInst = instanceManager.getCompInstByUUID(rootInst._c[0]) + } + + return getCurrentContext(topInst, topInst._parentContext) +} diff --git a/packages/wx-react/package.json b/packages/wx-react/package.json index 402716d..dba1c81 100644 --- a/packages/wx-react/package.json +++ b/packages/wx-react/package.json @@ -1,6 +1,6 @@ { "name": "@areslabs/wx-react", - "version": "1.0.22", + "version": "1.0.23-beta.1", "description": "微信版本的React", "main": "index.js", "scripts": { diff --git a/packages/wx-router/miniprogram_dist/NullComponent.js b/packages/wx-router/miniprogram_dist/NullComponent.js new file mode 100644 index 0000000..e76eda0 --- /dev/null +++ b/packages/wx-router/miniprogram_dist/NullComponent.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) Areslabs. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React, {Component} from '@areslabs/wx-react' + +export default class NullComponent extends Component { + render() {} +} \ No newline at end of file diff --git a/packages/wx-router/miniprogram_dist/index.js b/packages/wx-router/miniprogram_dist/index.js index 5935818..a4422cf 100644 --- a/packages/wx-router/miniprogram_dist/index.js +++ b/packages/wx-router/miniprogram_dist/index.js @@ -5,12 +5,14 @@ * LICENSE file in the root directory of this source tree. * */ - -/* -* 小程序版本的Route, Router, TabRouter 在转化的过程中消失,不需要导出 -* */ + import history from './history' +import NullComponent from './NullComponent' export { history -} \ No newline at end of file +} + +export const Router = NullComponent +export const Route = NullComponent +export const TabRouter = NullComponent diff --git a/packages/wx-router/package.json b/packages/wx-router/package.json index 0ac42c1..68e378a 100644 --- a/packages/wx-router/package.json +++ b/packages/wx-router/package.json @@ -1,6 +1,6 @@ { "name": "@areslabs/wx-router", - "version": "1.0.1", + "version": "1.0.2-beta.1", "description": "router for mini-program", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/src/struc/handleEntry.js b/src/struc/handleEntry.js index d9ee179..10f34d8 100644 --- a/src/struc/handleEntry.js +++ b/src/struc/handleEntry.js @@ -10,9 +10,10 @@ import fse from 'fs-extra' import traverse from "@babel/traverse" import * as t from '@babel/types' import {isStaticRes} from '../util/util' -import {geneCode} from "../util/uast"; +import { geneReactCode } from "../util/uast"; import basetran from '../basetran' +import {geneOrder} from '../util/util' const npath = require('path') @@ -41,8 +42,8 @@ export default async function (ast, filepath) { await basetran(ast, filepath, true) + const go = geneOrder() traverse(ast, { - enter: path => { if (path.type === 'StringLiteral' && isStaticRes(path.node.value) @@ -138,6 +139,7 @@ export default async function (ast, filepath) { appJSON.pages.push(projectRelativePath) + path.node.attributes = [t.jsxAttribute(t.jsxIdentifier('diuu'), t.stringLiteral(`DIUU${go.next}`))] return } @@ -185,6 +187,19 @@ export default async function (ast, filepath) { appJSON.tabBar.list = appJSON.tabBar.list || [] appJSON.tabBar.list.push(tabBarElement) + path.node.attributes = [t.jsxAttribute(t.jsxIdentifier('diuu'), t.stringLiteral(`DIUU${go.next}`))] + + return + } + + if (path.type === 'JSXOpeningElement') { + const jsxOp = path.node + + const key = `DIUU${go.next}` + + jsxOp.attributes.push( + t.jsxAttribute(t.jsxIdentifier('diuu'), t.stringLiteral(key)) + ) return } @@ -210,14 +225,6 @@ export default async function (ast, filepath) { path.remove() return } - - if (path.type === 'ClassMethod' - && path.node.key.name === 'render' - ) { - path.remove() - - return - } } }) @@ -301,20 +308,22 @@ export default async function (ast, filepath) { */ // be lazy - - path.node.body.push( - t.expressionStatement( - t.identifier('const RNApp = new RNAppClass({})') - ) - ) - path.node.body.push( - t.expressionStatement( - t.identifier('RNApp.childContext = RNApp.getChildContext ? RNApp.getChildContext() : {}') - ) - ) path.node.body.push( t.expressionStatement( - t.identifier('export default RNApp') + t.identifier(`React.render( + h( + RNAppClass, + { + diuu: React.rootUuid + } + ), + null, + {} +) +const rootContext = React.getRootContext() +export default { + childContext: rootContext +}`) ) ) @@ -352,7 +361,7 @@ App({}) - const entryCode = geneCode(ast) + const entryCode = await geneReactCode(ast) const dirname = npath.dirname(filepath) await fse.mkdirs(dirname) await fse.writeFile(