• 软件:1159
  • 资讯:38676|
  • 收录网站:95940|

IT精英团

React转微信小程序:从React组件定义到Component调用

React转微信小程序:从React组件定义到Component调用

浏览次数:
评论次数:
编辑: mob604756fdc4e1
信息来源: 51CTO博客
更新日期: 2021-05-13 16:04:38
摘要

React转微信小程序:从React组件定义到Component调用,mob604756fdc4e1的博客原创的Java文章。

  • 资讯详情

这是本系列的第二篇,过去两周,已经有相当成果出来。本文介绍其中一部分可靠的思路,这个比京东的 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,然后根据语法的成分进行转换(详见这文章 https://yq.aliyun.com/articles/62671)。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.jsconst 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(){}}))

具体实现参这里,本文就不贴上来了。

https://github.com/RubyLouvre/anu/tree/master/packages/render/miniapp/translator/helpers

那 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 的模拟等等,且听下回分解。


标签: Java
React转微信小程序:构思
« 上一篇
返回列表
下一篇 »
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
你会是第一个来这里评论的人吗?
最近发布资讯
更多