作者:Chryseis,未经授权禁止转载。
remax项目搭建
- 使用Remax cli命令
npx create-remax-app remax-wechat
- 命令成功后生成如下目录接口
remax-wechat/
┳ package.json
┣ dist/
┣ node_modules/
┣ public/
┣ src/
┗━┓ app.js
┣ app.css
┣ app.config.js
┣ pages/
┗━┓ index/
┗━┓
┣ index.js
┣ index.css
┣ index.config.js
- 引入状态管理,由于Remax的特性,状态管理可以是任何react的状态管理库,我采用dva作为状态管理(原因是与vuex基本一致)
yarn add remax-dva
- 安装dva后,需要修改app.js,添加model文件
// app.js
import './app.less';
import app from '@/models';
const App = app.start(({ children }) => {
return children;
});
export default App;
// model/index.js
import dva from 'remax-dva';
import global from './global';
const app = dva();
app.model(global);
export default app;
//model/global.js
export default {
namespace: 'global',
state: {},
effects: {},
reducers: {},
};
- 配置remax.config
const path = require('path');
const less = require('@remax/plugin-less');
module.exports = {
plugins: [less()],
configWebpack({ config, webpack, addCSSRule }) {
config.resolve.alias.merge({
src: path.resolve(__dirname, 'src'),
});
},
};
- remax会根据
NODE_ENV
自动读取不同的环境变量
NODE_ENV | env文件 |
---|---|
development | .env.development.local,.env.development,.env.local,.env |
test | .env.test.local,.env.test,.env |
production | .env.production.local,env.production,env.local,.env |
只有
REMAX_APP_
开头的变量才会注入
remax特点
- 采用React coding,且React hooks语法全部支持
- 引入原生组件无需配置usingComponents,直接import即可
- 采用css modules 避免了样式重复
- js方法,js变量,新的组件创建使用更加方便
- ts完美支持
- 调试更方便,可以接入react devtools
- 原生小程序页面和remax页面可以共存
remax实现原理
react-reconciler是什么?
个人理解:react-reconciler充当了一个调节器,在react-core执行各种虚拟dom操作过程中提供hooks和已处理完成的vNode,给渲染器使用。
remax实现原理采用了react-reconciler
,react-reconciler
是react和render之间的一个自定义处理器。
Remax通过reconciler的生成一份自定义的VNode Tree,再遍历Tree递归模版渲染出对应的小程序页面。在生成之前Remax会为每个组件生成一份模版,然后把这份模版写入每个page页面里。由于小程序本身View与Js分离,因此在拿到Vnode时,需要通过小程序自身的setData触发小程序渲染。
//page.wxml
<import src="/base.wxml"/>
<template is="REMAX_TPL" data="{{root: root}}" />
<template is="REMAX_TPL" data="{{ root: modalRoot }}" />
// data
{
"root": {
"children": [
4
],
"nodes": {
"4": {
"id": 4,
"type": "view",
"props": {
"hover-class": "none",
"hover-stop-propagation": false,
"hover-start-time": 50,
"hover-stay-time": 400
},
"children": [
3
],
"nodes": {
"3": {
"id": 3,
"type": "plain-text",
"text": "Subpage Test"
}
}
}
}
},
"modalRoot": {
"children": []
},
"__webviewId__": 34
}
Remax在reconciler中是如何运行的
reconciler中的hostConfig
协调阶段 | 开始提交 | 提交阶段 | 提交完成 |
---|---|---|---|
createInstance | prepareCommit | appendChild | resetAfterCommit |
createTextInstance | appendChildToContainer | commitMount | |
shouldSetTextContent | insertBefore | ||
appendInitialChild | insertInContainerBefore | ||
finalizeInitialChildren | removeChild | ||
prepareUpdate | removeChildFromContainer | ||
commitTextUpdate | |||
commitUpdate | |||
resetTextContent |
这些api提供了React在执行阶段中自定义的能力。
remax中的hostConfig
export default {
...,
resetAfterCommit: (container: Container) => {
container.applyUpdate();
},
getChildHostContext: () => {
return childHostContext;
},
createInstance(type: string, newProps: any, container: Container) {
const id = generate();
const node = new VNode({
id,
type: DOM_TAG_MAP[type] ?? type,
props: {},
container,
});
node.props = processProps(newProps, node, id);
return node;
},
createTextInstance(text: string, container: Container) {
const id = generate();
const node = new VNode({
id,
type: TYPE_TEXT,
props: null,
container,
});
node.text = text;
return node;
},
commitTextUpdate(node: VNode, oldText: string, newText: string) {
if (oldText !== newText) {
node.text = newText;
node.update();
}
},
prepareUpdate(node: VNode, type: string, lastProps: any, nextProps: any) {
lastProps = processProps(lastProps, node, node.id);
nextProps = processProps(nextProps, node, node.id);
return diffProperties(lastProps, nextProps);
},
commitUpdate(node: VNode, updatePayload: any, type: string, oldProps: any, newProps: any) {
node.props = processProps(newProps, node, node.id);
node.update(updatePayload);
},
...
};
remax如何React应用到小程序呢?
通过remax的编译后的源码可以知道,remax生成页面的Page对象的方法是createPageConfig,然后createPageConfig中页面onLoad生命周期中,把react注入。
this.container = new Container(this, 'root');
this.modalContainer = new Container(this, 'modalRoot');
const pageElement = React.createElement(PageWrapper, {
page: this,
query,
modalContainer: this.modalContainer,
ref: this.wrapperRef,
});
if (app && app._mount) {
this.element = createPortal(pageElement, this.container, this.pageId);
app._mount(this);
} else {
this.element = render(pageElement, this.container);
}
其中Container构建起由react reconciler生成的VNode传递给小程序data的工作。
requestUpdate(update: SpliceUpdate | SetUpdate) {
this.updateQueue.push(update);
}
applyUpdate() {
...
this.context.setData(updatePayload, () => {
nativeEffector.run();
/* istanbul ignore next */
if (RuntimeOptions.get('debug')) {
console.log(`setData => 回调时间:${new Date().getTime() - startTime}ms`, updatePayload);
}
});
this.updateQueue = [];
}
requestUpdate负责接收VNode的改变,累积在updateQueue中,applyUpdate负责提交VNode给小程序data,触发页面渲染。
requestUpdate | applyUpdate |
---|---|
commitTextUpdate | resetAfterCommit |
commitUpdate | |
appendInitialChild | |
appendChild | |
insertBefore | |
removeChild | |
appendChildToContainer | |
insertInContainerBefore | |
removeChildFromContainer | |
hideInstance | |
hideTextInstance | |
unhideInstance | |
unhideTextInstance |
Remax 和 mpVue 运行模式比较
动态模版
Remax采用动态模版,以Vnode作为更新数据,可以更精准更新数据,实现更纯粹,不需要分析语法重新构建小程序wxml,把React原汁原味使用到小程序中
静态模版
mpvue 的运行时和 Vue 的运行时是强关联的,首先我们来看看 Vue 的运行时。
一个 .vue 的单文件由三部分构成: template
, script
, style
。
橙色路径部分, template 会在编译的过程中,在 vue-loader
中通过 ast 进行分析,最终生成一段 render 函数,执行 render 函数会生成虚拟 dom 树,虚拟 DOM 树是对真实 DOM 树的抽象,树中的节点被称作 vnode 。
Vue 拿到 虚拟 DOM 树之后,就可以去和上次老的 虚拟 DOM 树 做 patch diff
对比。patch 阶段之后,vue 就会使用真实的操作 DOM 的方法(比如说 insertBefore
, appendChild
之类的),去操作 DOM 结点,更新视图。
同时,绿色路径的部分,在实例化 Vue 的时候,会对数据 data 做响应式的处理,在监测到 data 发生改变时,会调用 render 函数,生成最新的虚拟 DOM 树, 接着对比老的虚拟 DOM 树进行 patch, 找出最小修改代价的 vnode 节点进行修改。
(图片来源于网络)
而 mpvue 的运行时,会首先将 patch 阶段的 DOM 操作相关方法置空,也就是什么都不做。其次, 在创建 Vue 实例的同时,还会偷偷的调用 Page()
用于生成了小程序的 page 实例。然后 运行时的 patch 阶段会直接调用 $updateDataToMp()
方法,这个方法会获取挂在在 page 实例上维护的数据 ,然后通过 setData
方法更新到视图层。
(图片来源于网络)
Remax的限制
原生组件中不支持带有function类型的prop
参考链接
remax
zhuanlan.zhihu.com/p/83324871 zhuanlan.zhihu.com/p/79788488
react
zh-hans.reactjs.org/docs/codeba… github.com/facebook/re… juejin.cn/post/684490…
taro
fiber
github.com/acdlite/rea… medium.com/react-in-de… zhuanlan.zhihu.com/p/57346388