Get the App
SLTechnology News&Howtos  ›  Development  › 

How to transfer React to WeChat Mini Programs

Shulou Source: shulou.com Published: 2022-06-01 06:29:02 09月11日 Update

本篇内容主要讲解"React怎么转微信小程序",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"React怎么转微信小程序"吧!

微信小程序是面向配置对象编程,不暴露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.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.call_Expression(t.identifier("Component"), [ t.object_Expression([])])

);

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.function_Expression(

null,

path.node.params,

path.node.body,

path.node.generator,

path.node.async

)

);

methods.push(method)

}

}

然后我们在ClassDeclaration或ClassExpression的处理器的exit方法中改成:

const call = t.expressionStatement(

t.call_Expression(t.identifier("Component"), [ t.object_Expression(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(){}

}))

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

RubyLouvre/anu

那this.setState怎么转换成this.setData呢。这是一个函数调用,语法上称之为**CallExpression**。我们在visitors上定义同名的处理器。

Call_Expression(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怎么转微信小程序"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

Tags: Methods processing objects statements programs processors contents functions names syntax plug-ins files components just properties such as extraction things code writing Apple Docker Huawei Linux macOS MariaDB Microsoft MySQL NVidia OPPO Reno MariaDB Huawei Redmi macOS Linux