123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- import { act, mountElement, serverRender, createElement } from './r.js';
- import { log as globalLog, error as globalError, EComponent2Status, getComponent2Status, updateComponent2Status, getIdFromAppxInstance, instanceKeyPropNames, shallowCompare, } from './utils.js';
- import HandlersController from './handlers.js';
- import { reactContext, useSyncMiniData } from './hooks.js';
- import { ETargetPlatform } from './types.js';
- import { platformConfig } from './platform.js';
- var FUNCTIONAL_MINI_PAGE_DOM_PLACEHOLDER = 'MINIFISH_PAGE_DOM_PLACEHOLDER';
- var DANGER_ZONE_BYPASS_FUNCTION_CALL_WITH_DATA = 'DANGER_ZONE_BYPASS_FUNCTION_CALL_WITH_DATA'; // 绕过所有函数方法,直接返回 data。不要在生产环境使用!
- function compositeElementWithContext(
- //@ts-expect-error
- id,
- //@ts-expect-error
- elementFn, appxContext,
- //@ts-expect-error
- pendingProps) {
- var el = createElement(elementFn, pendingProps);
- // eslint-disable-next-line react/no-children-prop
- return createElement(reactContext.Provider, { value: appxContext, key: id, children: [el] }, el);
- }
- export function flushReactTree(elementMap) {
- var children = [];
- for (var id in elementMap) {
- if (!Object.prototype.hasOwnProperty.call(elementMap, id))
- continue;
- var item = elementMap[id];
- if (item.unmounted) {
- continue;
- }
- if (item.elementInstance && !item.pendingProps) {
- children.push(item.elementInstance);
- continue;
- }
- var contextElement = compositeElementWithContext(id, item.elementFn, item.appxContext, item.pendingProps);
- children.push(contextElement);
- item.elementInstance = contextElement;
- item.pendingProps = null;
- }
- var parent = createElement('div', {}, children);
- return parent;
- }
- export function functionalMiniElement(element, displayName /* 用于问题排查,和小程序 axml 无关 */, elementType, defaultProps, _targetPlatform, elementOption) {
- var _a;
- if (displayName === void 0) { displayName = ''; }
- if (elementOption === void 0) { elementOption = {}; }
- var targetPlatform = _targetPlatform !== null && _targetPlatform !== void 0 ? _targetPlatform : ETargetPlatform.alipay;
- // 配置客户端环境
- var _b = platformConfig[targetPlatform], pageEvents = _b.pageEvents, componentEvents = _b.componentEvents, buildOptions = _b.buildOptions, componentLifeCycleToMount = _b.componentLifeCycleToMount, componentLifeCycleToUnmount = _b.componentLifeCycleToUnmount, pageLifeCycleToMount = _b.pageLifeCycleToMount, pageLifeCycleToUnmount = _b.pageLifeCycleToUnmount, getPropsFromInstance = _b.getPropsFromInstance, componentPageEvents = _b.componentPageEvents;
- displayName = displayName || element.name;
- if (!displayName) {
- console.warn('为了方便问题排查,请传入组件的 displayName 参数,或不要使用匿名函数组件 https://medium.com/@stevemao/do-not-use-anonymous-functions-to-construct-react-functional-components-c5408ec8f4c7');
- }
- var nameTag = "[".concat(elementType, "/").concat(displayName || '(unnamed)', "]");
- //@ts-expect-error
- var log = function () {
- var args = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- args[_i] = arguments[_i];
- }
- globalLog.apply(null, [nameTag].concat(args));
- };
- var logErrorAndThrow = function (err) {
- if (!err)
- return;
- err.message = "".concat(nameTag, " ").concat(err.message);
- globalError(err);
- throw err;
- };
- if (elementType === 'component') {
- if (getComponent2Status() === EComponent2Status.UNKNOWN) {
- updateComponent2Status();
- }
- if (getComponent2Status() === EComponent2Status.INVALID) {
- // 如果还是 Unknown,就先不管了
- throw new Error("\u65E0\u6CD5\u6CE8\u518C ".concat(nameTag, " \u7EC4\u4EF6\uFF0C\u56E0\u4E3A\u5F53\u524D\u5C0F\u7A0B\u5E8F\u73AF\u5883\u672A\u5F00\u542F component2\u3002\u914D\u7F6E\u5165\u53E3\u5728 mini.project.json : { \"compileOptions\": { \"component2\": true } }\uFF0C\u6216\u5728 IDE \u8BE6\u60C5>\u9879\u76EE\u914D\u7F6E\u4E2D\u52FE\u9009"));
- }
- }
- var platformExposedEvents = elementType === 'page' ? pageEvents : componentEvents;
- var defaultPropKeys = Object.keys(defaultProps || {});
- var observers = {};
- var elementMap = {};
- //@ts-expect-error
- var commonTestRenderer;
- function updateReactTree() {
- act(function () {
- var parent = flushReactTree(elementMap);
- //@ts-expect-error
- if (!commonTestRenderer) {
- commonTestRenderer = mountElement(parent);
- }
- else {
- commonTestRenderer.update(parent);
- }
- });
- }
- var WrappedElementFn = function (props) {
- var miniData;
- if (props && props[DANGER_ZONE_BYPASS_FUNCTION_CALL_WITH_DATA]) {
- miniData = props[DANGER_ZONE_BYPASS_FUNCTION_CALL_WITH_DATA];
- if (typeof miniData === 'string' && miniData.indexOf('%7B') === 0) {
- miniData = JSON.parse(decodeURIComponent(miniData));
- }
- }
- else {
- try {
- miniData = element.call(undefined, props);
- }
- catch (e) {
- //@ts-expect-error
- e.message = "\u6E32\u67D3\u51FA\u9519 ".concat(e.message);
- return logErrorAndThrow(e);
- }
- }
- if (typeof miniData === 'undefined') {
- log('函数组件没有返回渲染数据,请检查代码逻辑');
- miniData = {};
- }
- else if (typeof miniData !== 'object') {
- var e = new Error("\u51FD\u6570\u7EC4\u4EF6\u8FD4\u56DE\u7684\u6E32\u67D3\u6570\u636E\u4E0D\u5408\u6CD5\uFF0C\u6536\u5230\u7684\u7C7B\u578B\u4E3A ".concat(typeof miniData));
- return logErrorAndThrow(e);
- }
- // 不允许和 props 里的 key 重复
- var conflictKeys = [];
- var dataKeys = Object.keys(miniData) || [];
- for (var key in props) {
- if (dataKeys.indexOf(key) >= 0) {
- conflictKeys.push(key);
- }
- }
- if (conflictKeys.length > 0) {
- var e = new Error("\u5C0F\u7A0B\u5E8F\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u8FD4\u56DE\u7684\u6E32\u67D3\u6570\u636E\u548C props \u91CC\u7684 key \u91CD\u590D\uFF1A".concat(conflictKeys.join(', ')));
- return logErrorAndThrow(e);
- }
- // 上面的 early-return 都是检查和抛错,忽略 hooks 规则
- useSyncMiniData(miniData || {});
- return createElement('div', {}, FUNCTIONAL_MINI_PAGE_DOM_PLACEHOLDER);
- };
- // 在 onload 的时候,正式创建一个 react 组件
- function hookLoadToMountReactComponent() {
- var args = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- args[_i] = arguments[_i];
- }
- var appxInstance = this;
- var id = getIdFromAppxInstance(appxInstance);
- if (elementMap[id])
- throw new Error("duplicate id of appx instance, this might be a bug of minifish hooks. id: ".concat(id));
- var context = generateInstanceContext(appxInstance, false);
- appxInstance[instanceKeyPropNames] = defaultPropKeys;
- log('will mount react component');
- var initProps = {};
- if (elementType === 'component') {
- initProps = getPropsFromInstance(appxInstance, defaultPropKeys);
- }
- if (elementType === 'page') {
- initProps = {
- query: args[0] || {},
- };
- }
- elementMap[id] = {
- appxContext: context,
- appxId: id,
- elementFn: WrappedElementFn,
- pendingProps: initProps,
- unmounted: false,
- propKeys: defaultPropKeys,
- };
- updateReactTree();
- // 触发 onLoad
- if (elementType === 'page') {
- log('页面(Page)已经 Mount,开始触发 onLoad');
- return context.handlersController.callHandlers(pageLifeCycleToMount, appxInstance, args); // 直接调用controller 内部的方法,插队执行 onLoad
- }
- else {
- log("\u7EC4\u4EF6\uFF08Component\uFF09\u5DF2\u7ECF Mount\uFF0C\u5F00\u59CB\u89E6\u53D1 ".concat(componentLifeCycleToMount));
- return context.handlersController.callHandlers(componentLifeCycleToMount, appxInstance, args);
- }
- }
- //@ts-expect-error
- function dispatchNewProps(appxInstance, nextProps) {
- var id = getIdFromAppxInstance(appxInstance);
- var instance = elementMap[id];
- if (instance) {
- instance.pendingProps = nextProps;
- updateReactTree();
- }
- }
- function hookUnloadToUnmount() {
- var appxInstance = this;
- var id = getIdFromAppxInstance(appxInstance);
- log("will unmount react element of ".concat(id));
- if (!elementMap[id]) {
- log("\u627E\u4E0D\u5230 id \u4E3A ".concat(id, " \u7684\u5B9E\u4F8B\uFF0C\u8BF7\u68C0\u67E5\u662F\u5426\u5F00\u542F\u4E86 component2 \u3002\u8FD9\u4E5F\u53EF\u80FD\u662F Minifish \u7684 bug\u3002"));
- }
- elementMap[id].unmounted = true;
- updateReactTree();
- // TODO: 清理 appx context
- }
- var handlersController = new HandlersController(nameTag);
- //@ts-expect-error
- var anyUnknownContext = function (ctx) {
- if (!ctx)
- throw new Error('ctx is required');
- var id = getIdFromAppxInstance(ctx);
- return !elementMap[id];
- };
- var anyContext = function () { return true; };
- if (elementType === 'page') {
- handlersController.addHandler(pageLifeCycleToMount, anyUnknownContext, hookLoadToMountReactComponent);
- handlersController.addHandler(pageLifeCycleToUnmount, anyContext, hookUnloadToUnmount);
- }
- else {
- // 组件的关键生命周期
- handlersController.addHandler(componentLifeCycleToMount, anyUnknownContext, hookLoadToMountReactComponent);
- handlersController.addHandler(componentLifeCycleToUnmount, anyContext, hookUnloadToUnmount);
- // 把 props 变更分发到对应的组件里
- if (targetPlatform === ETargetPlatform.alipay) {
- handlersController.addHandler('deriveDataFromProps', anyContext, function hookDeriveDataFromProps(nextProps) {
- if (this.props && this.props === nextProps) {
- // 可能是setData触发的,不要死循环了
- return;
- }
- else {
- if (shallowCompare(this.props, nextProps, ['$slots'])) {
- return;
- }
- }
- return dispatchNewProps(this, nextProps);
- });
- }
- else if (targetPlatform === ETargetPlatform.wechat) {
- var props_1 = defaultPropKeys.join(', ');
- log("\u5C06\u6CE8\u518C\u4EE5\u4E0B key \u7684 props \u66F4\u65B0\uFF1A".concat(props_1));
- observers = Object.assign((_a = {},
- // 忽略函数参数,直接从 this 里面找
- // 搞不懂小程序的设计逻辑,为什么非要在 props 里夹着 data
- _a[props_1] = function () {
- var args = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- args[_i] = arguments[_i];
- }
- log('observer is being called', args);
- var newProps = getPropsFromInstance(this, defaultPropKeys);
- return dispatchNewProps(this, newProps);
- //
- },
- _a), observers);
- }
- }
- // 做一次预渲染,获取所有 appx 需要的属性
- var generateInstanceContext = function (
- //@ts-expect-error
- instance, ifServerRender) {
- return {
- instance: instance,
- handlersController: handlersController,
- ifServerRender: ifServerRender,
- debugLog: log,
- platformConfig: platformConfig[targetPlatform],
- };
- };
- // 收集 initData
- var initData = {};
- var fakeAppxInstance = {
- $id: "_minifish_hooks_pre_render_".concat(Math.random()),
- //@ts-expect-error
- setData: function (data) {
- initData = Object.assign({}, initData || {}, data);
- },
- };
- var fakeCtx = generateInstanceContext(fakeAppxInstance, true);
- var serverEl = compositeElementWithContext(fakeAppxInstance.$id, WrappedElementFn, fakeCtx, defaultProps || {});
- serverRender(serverEl);
- log('serverRendered with initData', initData);
- // 拼装对象,喂给 appx。产出的配置应该只能喂一次,喂多了 appx 不认。
- handlersController.lockHandlerNames();
- // 把生命周期和一般事件处理分开归类,因为组件的事件处理要多裹一层
- var lifeCycleHandlers = {};
- var userEventHandlers = {};
- var componentPageEventsHandlers = {};
- var handlers = handlersController.getHandlersImplProxy();
- for (var name_1 in handlers) {
- if (platformExposedEvents.indexOf(name_1) >= 0) {
- //@ts-expect-error
- lifeCycleHandlers[name_1] = handlers[name_1];
- }
- else if (componentPageEvents.indexOf(name_1) >= 0) {
- //@ts-expect-error
- componentPageEventsHandlers[name_1] = handlers[name_1];
- }
- else {
- //@ts-expect-error
- userEventHandlers[name_1] = handlers[name_1];
- }
- }
- var finalOptions = buildOptions(elementType, defaultProps, initData, lifeCycleHandlers, userEventHandlers, elementOption.options, observers, componentPageEventsHandlers);
- log('element options', finalOptions);
- return finalOptions;
- }
|