网友真实露脸自拍10p,成人国产精品秘?久久久按摩,国产精品久久久久久无码不卡,成人免费区一区二区三区

小程序模板網

2018新媒體下半年深度預測:增長恐慌,流量洼地

發布時間:2018-07-04 10:06 所屬欄目:小程序開發教程

這是本系列的第二篇,過去兩周,已經有相當成果出來。本文介紹其中一部分可靠的思路,這個比京東的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.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(){}
}))

具體實現參這里,本文就不貼上來了。

RubyLouvre/anu

那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";
      }
    }
  },


易優小程序(企業版)+靈活api+前后代碼開源 碼云倉庫:starfork
本文地址:http://www.xiuhaier.com/wxmini/doc/course/24619.html 復制鏈接 如需定制請聯系易優客服咨詢:800182392 點擊咨詢
QQ在線咨詢
AI智能客服 ×