小程序跨端框架实践之Remax篇( 三 )


onAppConfig() {...// 获取原始小程序配置originConfig和 remax app.config.js配置tmp// 做合并处理const appJSON =JSON.parse(JSON.stringify(originConfig));tmp.pages.forEach(function (item) {if (appJSON.pages.indexOf(item) ==-1) {appJSON.pages.push(item);}});tmp.subPackages.forEach(function (item) {let needAdd =true;for (let i =0, a = appJSON.subPackages; i < a.length; i++) {if (a[i].root=== item.root) {needAdd =false; a[i] = item;break;}}if (needAdd) {appJSON.subPackages.push(item);}});...return appJSON;}经过以上处理,原小程序的app.json内容就和remax.config.js内容合并了,以上代码只处理了pages和subPackages,如果觉得还有什么需要合并的也可以在这里处理 。此时,build生成的产物app.json,就保留了原小程序的内容并且合并了Remax小程序的内容 。
产物里的app.js也没有原生小程序里的代码,那原有的逻辑怎么办呢 。重新在Remax写一遍吗?大可不必 。
我们可以自定义一个运行时插件:
function createBothFun(remaxConfig, originConfig, key) {const remaxFun = remaxConfig[key];const originFun = originConfig[key];return function () {// 这里的this就是微信的app 里的thisremaxFun.apply(this,arguments);originFun.apply(this,arguments);};}const lifeCycles=['onLaunch','onShow','onHide','onPageNotFound','onError']function tryMergeConfig(remaxConfig, originConfig) {for (const key in originConfig) {if (key ==='constructor') {console.log('xxx');} elseif (lifeCycles.indexOf(key) >=0) {remaxConfig[key] =createBothFun(remaxConfig, originConfig, key);} else {remaxConfig[key] = originConfig[key];}}}const mergeConfig = (remaxConfig, originConfig) => {tryMergeConfig(remaxConfig, originConfig);return remaxConfig;};export default {onAppConfig({ config }) {let __app = App;let originConfig;App =function (origin) {originConfig = origin;};__non_webpack_require__('./app-origin.js');App = __app;//merge configconfig =mergeConfig(config, originConfig);const onLaunch = config.onLaunch;config.onLaunch= (...args) => {if (onLaunch) {onLaunch.apply(config, args);}};return config;},};把原来的app.js重命名为app-origin.js,然后在onAppConfig函数中使用__non_webpack_require__('./app-origin.js'); 请注意,这里的相对路径是产物里的相对路径,经过以上一番操作后,我们的原小程序就可以真正和Remax混合运行了 。
但是这样一来,我们的Remax似乎变得不能跨端了,因为它只能编译成你public目录里放置的原生小程序类型 。
难道混合和跨端只能是鱼和熊掌不可兼得?后面我们将介绍使用工程化的方法来实现鱼和熊掌兼得 。
4.2 Modal API化你可能注意到了Remax的文档里面列举了10个控件,但我说它只有9个控件,官方的文档上也说只有9个控件,为什么呢?因为Modal严格的说来不能算是一个控件 。
Modal实际上是调用createPortal这个API来创建一个节点覆盖在其它内容之上,在web端是使用的ReactDOM的createPortal,在小程序端是使用的@remax/runtime这个包里提供的同名方法 。实际上portal在两端挂载的位置也不太一样,在Web端是直接的body上创建了一个新的div,而在小程序上则是挂在页面实例一个叫modalContainer的节点 。在实际使用中,使用Modal组件去显示弹窗非常不方便,所以还是得把它变成API调用的方式 。
以小程序端为例:
import...import { createPortal } from'@remax/runtime';import {ReactReconcilerInst } from'@remax/runtime/esm/render';let createPortal__ = createPortal;let ReactReconcilerInst__ = ReactReconcilerInst;const styles = {modalContainer: {...},};export default function withModal(TargetComponent) {const WrappedModalComponent = (props) => {const { mask,...other } = props;const component =useRef();const container =getCurrentPage().modalContainer;return createPortal__(<View style={{ ...styles.modalContainer,pointerEvents: mask ?'auto':'none' }}><TargetComponent {...other} mask={mask} show={show} close={close} ref={component} /></View>,container)};WrappedModalComponent.hide= (conext) => {const container =getCurrentPage().modalContainer;if (container._rootContainer) {ReactReconcilerInst__.updateContainer(null, container._rootContainer,null,function () {});}container.applyUpdate();};WrappedModalComponent.show= (props) => {const container =getCurrentPage().modalContainer;if (!container._rootContainer) {container._rootContainer=ReactReconcilerInst__.createContainer(container,false,false);}const element = React.createElement(WrappedModalComponent, props);ReactReconcilerInst__.updateContainer(element, container._rootContainer,null,function () {});context.modalContainer.applyUpdate();};return WrappedModalComponent;}export { withModal };使用示例:
//在需要弹窗的组件上使用withModal装饰器@withModalexport default MyComponent(props) {...return<View>{...}</View>}//在不支持装饰器的模式下也可以直接调用function MyComponent(props) {...return<View>{...}</View>} const ModaledComponent =withModal(MyComponent)//在需要使用弹窗的地方使用ModaledComponent.show(props);//展示弹窗ModaledComponent.hide();


推荐阅读