element.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import { act, mountElement, serverRender, createElement } from './r.js';
  2. import { log as globalLog, error as globalError, EComponent2Status, getComponent2Status, updateComponent2Status, getIdFromAppxInstance, instanceKeyPropNames, shallowCompare, } from './utils.js';
  3. import HandlersController from './handlers.js';
  4. import { reactContext, useSyncMiniData } from './hooks.js';
  5. import { ETargetPlatform } from './types.js';
  6. import { platformConfig } from './platform.js';
  7. var FUNCTIONAL_MINI_PAGE_DOM_PLACEHOLDER = 'MINIFISH_PAGE_DOM_PLACEHOLDER';
  8. var DANGER_ZONE_BYPASS_FUNCTION_CALL_WITH_DATA = 'DANGER_ZONE_BYPASS_FUNCTION_CALL_WITH_DATA'; // 绕过所有函数方法,直接返回 data。不要在生产环境使用!
  9. function compositeElementWithContext(
  10. //@ts-expect-error
  11. id,
  12. //@ts-expect-error
  13. elementFn, appxContext,
  14. //@ts-expect-error
  15. pendingProps) {
  16. var el = createElement(elementFn, pendingProps);
  17. // eslint-disable-next-line react/no-children-prop
  18. return createElement(reactContext.Provider, { value: appxContext, key: id, children: [el] }, el);
  19. }
  20. export function flushReactTree(elementMap) {
  21. var children = [];
  22. for (var id in elementMap) {
  23. if (!Object.prototype.hasOwnProperty.call(elementMap, id))
  24. continue;
  25. var item = elementMap[id];
  26. if (item.unmounted) {
  27. continue;
  28. }
  29. if (item.elementInstance && !item.pendingProps) {
  30. children.push(item.elementInstance);
  31. continue;
  32. }
  33. var contextElement = compositeElementWithContext(id, item.elementFn, item.appxContext, item.pendingProps);
  34. children.push(contextElement);
  35. item.elementInstance = contextElement;
  36. item.pendingProps = null;
  37. }
  38. var parent = createElement('div', {}, children);
  39. return parent;
  40. }
  41. export function functionalMiniElement(element, displayName /* 用于问题排查,和小程序 axml 无关 */, elementType, defaultProps, _targetPlatform, elementOption) {
  42. var _a;
  43. if (displayName === void 0) { displayName = ''; }
  44. if (elementOption === void 0) { elementOption = {}; }
  45. var targetPlatform = _targetPlatform !== null && _targetPlatform !== void 0 ? _targetPlatform : ETargetPlatform.alipay;
  46. // 配置客户端环境
  47. 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;
  48. displayName = displayName || element.name;
  49. if (!displayName) {
  50. console.warn('为了方便问题排查,请传入组件的 displayName 参数,或不要使用匿名函数组件 https://medium.com/@stevemao/do-not-use-anonymous-functions-to-construct-react-functional-components-c5408ec8f4c7');
  51. }
  52. var nameTag = "[".concat(elementType, "/").concat(displayName || '(unnamed)', "]");
  53. //@ts-expect-error
  54. var log = function () {
  55. var args = [];
  56. for (var _i = 0; _i < arguments.length; _i++) {
  57. args[_i] = arguments[_i];
  58. }
  59. globalLog.apply(null, [nameTag].concat(args));
  60. };
  61. var logErrorAndThrow = function (err) {
  62. if (!err)
  63. return;
  64. err.message = "".concat(nameTag, " ").concat(err.message);
  65. globalError(err);
  66. throw err;
  67. };
  68. if (elementType === 'component') {
  69. if (getComponent2Status() === EComponent2Status.UNKNOWN) {
  70. updateComponent2Status();
  71. }
  72. if (getComponent2Status() === EComponent2Status.INVALID) {
  73. // 如果还是 Unknown,就先不管了
  74. 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"));
  75. }
  76. }
  77. var platformExposedEvents = elementType === 'page' ? pageEvents : componentEvents;
  78. var defaultPropKeys = Object.keys(defaultProps || {});
  79. var observers = {};
  80. var elementMap = {};
  81. //@ts-expect-error
  82. var commonTestRenderer;
  83. function updateReactTree() {
  84. act(function () {
  85. var parent = flushReactTree(elementMap);
  86. //@ts-expect-error
  87. if (!commonTestRenderer) {
  88. commonTestRenderer = mountElement(parent);
  89. }
  90. else {
  91. commonTestRenderer.update(parent);
  92. }
  93. });
  94. }
  95. var WrappedElementFn = function (props) {
  96. var miniData;
  97. if (props && props[DANGER_ZONE_BYPASS_FUNCTION_CALL_WITH_DATA]) {
  98. miniData = props[DANGER_ZONE_BYPASS_FUNCTION_CALL_WITH_DATA];
  99. if (typeof miniData === 'string' && miniData.indexOf('%7B') === 0) {
  100. miniData = JSON.parse(decodeURIComponent(miniData));
  101. }
  102. }
  103. else {
  104. try {
  105. miniData = element.call(undefined, props);
  106. }
  107. catch (e) {
  108. //@ts-expect-error
  109. e.message = "\u6E32\u67D3\u51FA\u9519 ".concat(e.message);
  110. return logErrorAndThrow(e);
  111. }
  112. }
  113. if (typeof miniData === 'undefined') {
  114. log('函数组件没有返回渲染数据,请检查代码逻辑');
  115. miniData = {};
  116. }
  117. else if (typeof miniData !== 'object') {
  118. 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));
  119. return logErrorAndThrow(e);
  120. }
  121. // 不允许和 props 里的 key 重复
  122. var conflictKeys = [];
  123. var dataKeys = Object.keys(miniData) || [];
  124. for (var key in props) {
  125. if (dataKeys.indexOf(key) >= 0) {
  126. conflictKeys.push(key);
  127. }
  128. }
  129. if (conflictKeys.length > 0) {
  130. 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(', ')));
  131. return logErrorAndThrow(e);
  132. }
  133. // 上面的 early-return 都是检查和抛错,忽略 hooks 规则
  134. useSyncMiniData(miniData || {});
  135. return createElement('div', {}, FUNCTIONAL_MINI_PAGE_DOM_PLACEHOLDER);
  136. };
  137. // 在 onload 的时候,正式创建一个 react 组件
  138. function hookLoadToMountReactComponent() {
  139. var args = [];
  140. for (var _i = 0; _i < arguments.length; _i++) {
  141. args[_i] = arguments[_i];
  142. }
  143. var appxInstance = this;
  144. var id = getIdFromAppxInstance(appxInstance);
  145. if (elementMap[id])
  146. throw new Error("duplicate id of appx instance, this might be a bug of minifish hooks. id: ".concat(id));
  147. var context = generateInstanceContext(appxInstance, false);
  148. appxInstance[instanceKeyPropNames] = defaultPropKeys;
  149. log('will mount react component');
  150. var initProps = {};
  151. if (elementType === 'component') {
  152. initProps = getPropsFromInstance(appxInstance, defaultPropKeys);
  153. }
  154. if (elementType === 'page') {
  155. initProps = {
  156. query: args[0] || {},
  157. };
  158. }
  159. elementMap[id] = {
  160. appxContext: context,
  161. appxId: id,
  162. elementFn: WrappedElementFn,
  163. pendingProps: initProps,
  164. unmounted: false,
  165. propKeys: defaultPropKeys,
  166. };
  167. updateReactTree();
  168. // 触发 onLoad
  169. if (elementType === 'page') {
  170. log('页面(Page)已经 Mount,开始触发 onLoad');
  171. return context.handlersController.callHandlers(pageLifeCycleToMount, appxInstance, args); // 直接调用controller 内部的方法,插队执行 onLoad
  172. }
  173. else {
  174. log("\u7EC4\u4EF6\uFF08Component\uFF09\u5DF2\u7ECF Mount\uFF0C\u5F00\u59CB\u89E6\u53D1 ".concat(componentLifeCycleToMount));
  175. return context.handlersController.callHandlers(componentLifeCycleToMount, appxInstance, args);
  176. }
  177. }
  178. //@ts-expect-error
  179. function dispatchNewProps(appxInstance, nextProps) {
  180. var id = getIdFromAppxInstance(appxInstance);
  181. var instance = elementMap[id];
  182. if (instance) {
  183. instance.pendingProps = nextProps;
  184. updateReactTree();
  185. }
  186. }
  187. function hookUnloadToUnmount() {
  188. var appxInstance = this;
  189. var id = getIdFromAppxInstance(appxInstance);
  190. log("will unmount react element of ".concat(id));
  191. if (!elementMap[id]) {
  192. 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"));
  193. }
  194. elementMap[id].unmounted = true;
  195. updateReactTree();
  196. // TODO: 清理 appx context
  197. }
  198. var handlersController = new HandlersController(nameTag);
  199. //@ts-expect-error
  200. var anyUnknownContext = function (ctx) {
  201. if (!ctx)
  202. throw new Error('ctx is required');
  203. var id = getIdFromAppxInstance(ctx);
  204. return !elementMap[id];
  205. };
  206. var anyContext = function () { return true; };
  207. if (elementType === 'page') {
  208. handlersController.addHandler(pageLifeCycleToMount, anyUnknownContext, hookLoadToMountReactComponent);
  209. handlersController.addHandler(pageLifeCycleToUnmount, anyContext, hookUnloadToUnmount);
  210. }
  211. else {
  212. // 组件的关键生命周期
  213. handlersController.addHandler(componentLifeCycleToMount, anyUnknownContext, hookLoadToMountReactComponent);
  214. handlersController.addHandler(componentLifeCycleToUnmount, anyContext, hookUnloadToUnmount);
  215. // 把 props 变更分发到对应的组件里
  216. if (targetPlatform === ETargetPlatform.alipay) {
  217. handlersController.addHandler('deriveDataFromProps', anyContext, function hookDeriveDataFromProps(nextProps) {
  218. if (this.props && this.props === nextProps) {
  219. // 可能是setData触发的,不要死循环了
  220. return;
  221. }
  222. else {
  223. if (shallowCompare(this.props, nextProps, ['$slots'])) {
  224. return;
  225. }
  226. }
  227. return dispatchNewProps(this, nextProps);
  228. });
  229. }
  230. else if (targetPlatform === ETargetPlatform.wechat) {
  231. var props_1 = defaultPropKeys.join(', ');
  232. log("\u5C06\u6CE8\u518C\u4EE5\u4E0B key \u7684 props \u66F4\u65B0\uFF1A".concat(props_1));
  233. observers = Object.assign((_a = {},
  234. // 忽略函数参数,直接从 this 里面找
  235. // 搞不懂小程序的设计逻辑,为什么非要在 props 里夹着 data
  236. _a[props_1] = function () {
  237. var args = [];
  238. for (var _i = 0; _i < arguments.length; _i++) {
  239. args[_i] = arguments[_i];
  240. }
  241. log('observer is being called', args);
  242. var newProps = getPropsFromInstance(this, defaultPropKeys);
  243. return dispatchNewProps(this, newProps);
  244. //
  245. },
  246. _a), observers);
  247. }
  248. }
  249. // 做一次预渲染,获取所有 appx 需要的属性
  250. var generateInstanceContext = function (
  251. //@ts-expect-error
  252. instance, ifServerRender) {
  253. return {
  254. instance: instance,
  255. handlersController: handlersController,
  256. ifServerRender: ifServerRender,
  257. debugLog: log,
  258. platformConfig: platformConfig[targetPlatform],
  259. };
  260. };
  261. // 收集 initData
  262. var initData = {};
  263. var fakeAppxInstance = {
  264. $id: "_minifish_hooks_pre_render_".concat(Math.random()),
  265. //@ts-expect-error
  266. setData: function (data) {
  267. initData = Object.assign({}, initData || {}, data);
  268. },
  269. };
  270. var fakeCtx = generateInstanceContext(fakeAppxInstance, true);
  271. var serverEl = compositeElementWithContext(fakeAppxInstance.$id, WrappedElementFn, fakeCtx, defaultProps || {});
  272. serverRender(serverEl);
  273. log('serverRendered with initData', initData);
  274. // 拼装对象,喂给 appx。产出的配置应该只能喂一次,喂多了 appx 不认。
  275. handlersController.lockHandlerNames();
  276. // 把生命周期和一般事件处理分开归类,因为组件的事件处理要多裹一层
  277. var lifeCycleHandlers = {};
  278. var userEventHandlers = {};
  279. var componentPageEventsHandlers = {};
  280. var handlers = handlersController.getHandlersImplProxy();
  281. for (var name_1 in handlers) {
  282. if (platformExposedEvents.indexOf(name_1) >= 0) {
  283. //@ts-expect-error
  284. lifeCycleHandlers[name_1] = handlers[name_1];
  285. }
  286. else if (componentPageEvents.indexOf(name_1) >= 0) {
  287. //@ts-expect-error
  288. componentPageEventsHandlers[name_1] = handlers[name_1];
  289. }
  290. else {
  291. //@ts-expect-error
  292. userEventHandlers[name_1] = handlers[name_1];
  293. }
  294. }
  295. var finalOptions = buildOptions(elementType, defaultProps, initData, lifeCycleHandlers, userEventHandlers, elementOption.options, observers, componentPageEventsHandlers);
  296. log('element options', finalOptions);
  297. return finalOptions;
  298. }