React轉微信小程式:從React類定義到Component呼叫
這是本系列的第二篇,過去兩週,已經有相當成果出來。本文介紹其中一部分可靠的思路,這個比京東的taro更具可靠性。如果覺得看不過癮,可以看anu的原始碼,裡面包含了miniapp的轉換器。
微信小程式是面向配置物件程式設計,不暴露Page,App,Component等核心物件的原型,只提供三個工廠方法,因此無法實現繼承。App,Page,Component所在的JS的依賴處理也很弱智,你需要宣告在同一目錄下的json檔案中。
比如說
Component({
properties: {},
data: {},
onClick: function(){}
})
properties與data都是同一個東西,properties只是用來定義data中的資料的預設值與型別,相當於React的defaultProps與propTypes。如何轉換呢?
import {Component} form "./wechat"
Class AAA extends Component{
constructor(props){
super(props);
this.state = {}
}
static propTypes = {}
static defaultProps = {}
onClick(){}
render(){}
}
export AAA;
首先我們要提供一個wechat.js檔案,裡面提供Component, Page, App 這幾個基類,現在只是空實現,但已經足夠了,保證它在除錯不會出錯。我們要的是`Class AAA extends Component`這個語句的內容。學了babel,對JS語法更加熟悉了。這個語句在babel6中稱為ClassExpression,到babel7中又叫ClassDeclaration。babel有一個叫”babel-traverse”的包,可以將我們的程式碼的AST,然後根據語法的成分進行轉換(詳見這文章 yq.aliyun.com/articles/62…)。ClassDeclaration的引數為一個叫path的物件,我們通過 path.node.superClass.name 就能拿到Component這個字樣。如果我們的類定義是下面的這樣,path.node.superClass.name 則為App。
Class AAA extends App{
constructor(props){
super(props);
this.state = {}
}
}
App, Page, Component對應的json差異很大,拿到這個可以方便我們區別對待。
然後我們繼續定義一個ImportDeclaration處理器,將import語句去掉。
定義ExportDefaultDeclaration與ExportNamedDeclaration處理器,將export語句去掉。
到這裡我不得不展示一下我的轉碼器的全貌了。我是通過rollup得到所有模組的路徑與檔案內容,然後通過babel進行轉譯。babel轉換是通過babel.transform。babel本來就有許多叫babel-plugin-transform-xxx的外掛,它是專門處理那些es5無法識別的新語法。我們需要在這後面加上一個新外掛叫miniappPlugin
// https://github.com/RubyLouvre/anu/blob/master/packages/render/miniapp/translator/transform.js
const syntaxClassProperties = require("babel-plugin-syntax-class-properties")
const babel = require(`babel-core`)
const visitor = require("./visitor");
var result = babel.transform(code, {
babelrc: false,
plugins: [
`syntax-jsx`,
// "transform-react-jsx",
`transform-decorators-legacy`,
`transform-object-rest-spread`,
miniappPlugin,
]
})
function miniappPlugin(api) {
return {
inherits: syntaxClassProperties,
visitor: visitor
};
}
miniappPlugin的結構異常簡單,它繼承一個叫syntaxClassProperties的外掛,這外掛原來用來解析es6 class的屬性的,因為我們的目標也是抽取React類中的defaultProps, propsTypes靜態屬性。
visitor的結構很簡單,就是各種JS語法的描述。
const t = require("babel-types");
module.exports = {
ClassDeclaration: 抽取父類的名字與轉換構造器,
ClassExpression: 抽取父類的名字與轉換構造器,
ImportDeclaration(path) {
path.remove() //移除import語句,小程式會自動在外面包一層,變成AMD模組
},
ExportDefaultDeclaration(path){
path.remove() //AMD不認識export語句,要刪掉,或轉換成module.exports
},
ExportNamedDeclaration(path){
path.remove() //AMD不認識export語句,要刪掉,或轉換成module.exports
}
}
我再介紹一下visitor的處理器是怎麼用的,處理器其實會執行兩次。我們的AST樹每個節點會被執行兩次,如果學過DFS的同學會明白,第一次訪問後,做些處理,然後進行它內部的節點,處理後再訪問一次。於是visitor也可以這樣定義。
ClassDeclaration:{
enter(path){},
exit(path){}
}
如果以函式形式定義,那麼它只是作為enter來用。
AST會從上到下執行,我們先拿到類名的名字與父類的名字,我們定義一個modules的物件,儲存資訊。
enter(path) {
let className = path.node.superClass ? path.node.superClass.name : "";
let match = className.match(/.?(App|Page|Component)/);
if (match) {
//獲取類的元件型別與名字
var componentType = match[1];
if (componentType === "Component") {
modules.componentName = path.node.id.name;
}
modules.componentType = componentType;
}
},
我們在第二次訪問這個類定義時,要將類定義轉換為函式呼叫。即
Class AAA extends Component ---> Component({})
實現如下,將原來的類刪掉(因此才在exit時執行),然後新建一個函式呼叫語句。我們可以通過babel-types這個句實現。具體看這裡。比如說:
const call = t.expressionStatement(
t.callExpression(t.identifier("Component"), [ t.objectExpression([])])
);
path.replaceWith(call);
就能產生如下程式碼,將我們的類定義從原位置替換掉。
Component({})
但我們不能是一個空物件啊,因此我們需要收集它的方法。
我們需要在visitors物件新增一個ClassMethod處理器,收集原來類的方法。類的方法與物件的方法不一樣,物件的方法叫成員表示式,需要轉換一下。我們首先弄一個陣列,用來放東西。
var methods = []
module.exports= {
ClassMethod: {
enter(path){
var methodName = path.node.key.name
var method = t.ObjectProperty(
t.identifier(methodName),
t.functionExpression(
null,
path.node.params,
path.node.body,
path.node.generator,
path.node.async
)
);
methods.push(method)
}
}
然後我們在ClassDeclaration或ClassExpression的處理器的exit方法中改成:
const call = t.expressionStatement(
t.callExpression(t.identifier("Component"), [ t.objectExpression(methods)])
);
path.replaceWith(call);
於是函式定義就變成
Component({
constructor:function(){},
render:function(){},
onClick: function(){}
})
到這裡,我們開始另一個問題了。小程式雖然是抄React,但又想別出心裁,於是一些屬性與方法是不一樣的。比如說data對應state, setData對應setState,早期的版本還有forceUpdate之類的。data對應一個物件,你可以有千奇百怪的寫法。
this.state ={ a: 1}
this["state"] = {b: 1};
this.state = {}
this.state.aa = 1
你想hold住這麼多奇怪的寫法是很困難的,因此我們可以對constructor方法做些處理,然後其他方法做些約束,來減少轉換的成本。什麼處理constructor呢,我們可以定義一個onInit方法,專門劫持constructor方法,將this.state變成this.data。
function onInit(config){
if(config.hasOwnProperty("constructor")){
config.constructor.call(config);
}
config.data = config.state|| {};
delete config.state
return config;
}
Component(onInit({
constructor:function(){},
render:function(){},
onClick: function(){}
}))
具體實現參這裡,本文就不貼上來了。
那this.setState怎麼轉換成this.setData呢。這是一個函式呼叫,語法上稱之為**CallExpression**。我們在visitors上定義同名的處理器。
CallExpression(path) {
var callee = path.node.callee || Object;
if ( modules.componentType === "Component" ) {
var property = callee.property;
if (property && property.name === "setState") {
property.name = "setData";
}
}
},
至少,將React類定義轉換成Component({})
呼叫方式 成功了。剩下就是將import語句處理一下,因為要小程式中,如果這個元件引用了其他元件,需要在其json中新增useComponens物件,將這些元件名及連結寫上去。換言之,小程式太懶了,處處都要手動。有了React轉碼器,這些事可以省掉。
其次是render方法的轉換,怎麼變成一個wxml檔案呢,`{}單花括號的內容要轉換成
`”{{}}”`雙引號+雙花括號 ,wx:if, wx:for的模擬等等,且聽下回分解。
相關文章
- React轉小程式現狀React
- React原始碼解析(三):react-componentReact原始碼
- React 手稿 – Component stateReact
- [轉]如何用React寫小程式-2React
- React Native——Component(元件)React Native元件
- 業內首個 React Native轉微信小程式引擎 Alita 正式釋出React Native微信小程式
- [轉] 如何實現 React 寫小程式-1React
- 微信小程式從入坑到入門微信小程式
- 利用react-to-web-component封裝react控制元件ReactWeb封裝控制元件
- React從入門到精通系列之(1)安裝ReactReact
- React 的 PureComponent Vs ComponentReact
- React 小案例 路由跳轉React路由
- React(0.13) 定義一個checked元件React元件
- React(0.13)定義一個使用動畫React動畫
- React(0.13)定義一個checked元件React元件
- React從入門到精通系列之(21)React頂級APIReactAPI
- react篇章-React 元件-ES6 class 來定義一個元件React元件
- 微信小程式使用微信雲託管新增自定義域名並轉發到pexels.com微信小程式
- React Server Component: 混合式渲染ReactServer
- 微信小程式實現轉義換行符微信小程式
- 【React深入】從Mixin到HOC再到HookReactHook
- React Native 從入門到原理React Native
- SAP UI5 Web Component React應用如何在Component之間跳轉UIWebReact
- 從0到1構建適配不同端(微信小程式、H5、React-Native 等)的taro + dva應用微信小程式H5React
- 手把手教你React Native實戰從 React到Rn《二》React Native
- React從入門到精通學習系列之(1)安裝ReactReact
- 從react轉職到vue開發的專案準備ReactVue
- 微信小程式元件化解決方案wx-component微信小程式元件化
- React元件方法的兩種定義方式React元件
- 從0到1上線一個微信小程式微信小程式
- React中的HTML轉義寫法ReactHTML
- 在小程式中使用 React with HooksReactHook
- 微信小程式自定義tabBar微信小程式tabBar
- 微信小程式 自定義tabbar微信小程式tabBar
- 微信小程式自定義事件微信小程式事件
- React.Component 損害了複用性?React
- React Component裡的狀態機PatternReact
- React 從入門到進階之路(七)React