React轉微信小程序:雙模板機制,React.template爲小程序提供data

這是本系列的最後一篇,由於之後就是機密了。但這篇會公開一些很是有用的思路。小程序封死了操做DOM的可能性,而且也不讓咱們操做視圖,全部與視圖有關的東西一概接觸不了。而它的自定義組件是很是噁心,基本不配叫組件,不能繼承叫什麼組件。所以咱們使用它更早期的動態模板技術,template。javascript

個人思路以下,經過編譯組件的render方法,將裏面的自定義組件變成template類,而後在template類中本身初始化,獲得props, state再傳給原來的模板。換言之,有兩套模板。css

//源碼
import { Page } from "../wechat";
import "./page.css";
import Dog from "../components/dog/dog";
const e = "e";
class P extends Page {
  constructor(props) {
    super(props);
    this.state = {
      name: 'hehe',
      array: [
        {name: "dog1",text: "text1"},
        {name: "dog2",text: "text2"},
        {name: "dog3",text: "text3"},
      ]
    };
  }

  onClick() {
    console.log("test click1" + e);
  }
  render() {
    return (
      <div>
        <div>
          {this.state.array.map(function(el) {
            return <Dog name={el.name}>{el.text}</Dog>;
          })}
        </div>
        <Dog name={this.state.name} />
      </div>
    );
  }
}
export default P;

咱們先無論Dog組件長得怎麼樣。
爲了讓它同時支持小程序與React的render函數,咱們須要對render進行改造。將Dog,div等改形成小程序能能認識的類型,如java

<view>
    <view>
      {this.state.array.map(function(el) {
        return <template is={Dog} name={el.name}>{el.text}</template>;
      })}
    </view>
    <template is="Dog" name={this.state.name} />
  </view>

這個轉譯是如何實現呢,咱們能夠通一個插件 syntax-jsx, 它會在visitor遍歷出JSX的開標籤,閉標籤,屬性及{}容器。react

clipboard.png

但React沒法認識template標籤,所以還要改造git

//React專用
 <view>
    <view>
      {this.state.array.map(function(el) {
        return <React.template is={Dog} name={el.name}>{el.text}</React.template>;
      })}
    </view>
    <React.template is={Dog} name={this.state.name} />
  </view>

如今看小程序這邊
小程序沒法認識{},須要改變成wx:for指令es6

//小程序專用
 <view>
    <view>
      <block wx:for="{{this.state.array}}" wx:for-item="el">
         <template is="Dog" name={el.name}>{el.text}</template>;
      </block>
    </view>
    <template is="Dog" name={this.state.name} />
  </view>

小程序的template有個缺憾,它沒法認識name這樣的屬性的,所以咱們須要一個東西裝着它。那麼咱們動態建立一個數組吧,改一改React那邊github

//React專用
 <view>
    <view>
      {this.state.array.map(function(el) {
        return <React.template is={Dog} name={el.name} templatedata="data123124342">{el.text}</React.template>;
      })}
    </view>
    <React.template is={Dog} name={this.state.name} templatedata="data34343433" />
  </view>

templatedata這個屬性及它的值是babel在編譯時建立的,React.template到時會在this.data.state添加data123124342數組,內容爲一個個對象,這些對象是經過Dog.props, Dog.defaultProps, Dog.state組成。結構大概是{ props: {}, state: {} }
那麼小程序的模板變成小程序

//小程序專用
<import src="../../components/dog/dog.wxml" />
 <view>
     <view>
      <block wx:for="{{this.state.array}}" wx:for-item="el">
         <template is="Dog" wx:for="data123124342" wx:for-item="data" data="{{...data}}"></template>;
      </block>
    </view>
    <template is="Dog" wx:for="data34343433" wx:for-item="data" data="{{...data}}" />
  </view>

而咱們的render再通過編譯變成()數組

import { Page } from "../wechat";
import "./page.css";
import Dog from "../components/dog/dog";
const e = "e";
class P extends Page {
  constructor(props) {
    super(props);
    this.state = {
      name: 'hehe',
      array: [
        {name: "dog1",text: "text1"},
        {name: "dog2",text: "text2"},
        {name: "dog3",text: "text3"},
      ]
    };
  }

  onClick() {
    console.log("test click1" + e);
  }
  render() {
    return (
      React.createElement(
          "div",
          null,
          React.createElement(
            "div",
            null,
            this.state.array.map(function(el) {
              return React.createElement(React.template, {
                name: el.name,
                children: el.text,
                is: Dog,
                templatedata:"data34343433"
              });
            })
          ),
          React.createElement(React.template, {
            is: Dog,
            name: this.state.name,
            templatedata:"data34343433"
          })
        );
}
export default P;

上面的轉譯工做能夠經過transform-react-jsxbabel插件實現babel

class P extends Page這種es6定義類的方式,小程序可能也不認識,或者經過babel編譯後也太複雜。好比說taro將Dog這個類變成這樣:

clipboard.png

所以咱們最好在React中提供一個定義類的方法,叫miniCreateClass。如此一來咱們就能將Dog轉換得很簡潔

var React = require("../../wechat");
var Component = React.Component
var miniCreatClass = React.miniCreatClass

function Dog() {}

let Dog = miniCreatClass(Dog, Component, {
  render: function () {
    return React.createElement("view", null, this.state.name )
  }
}, {});
module.exports.default = Dog;

咱們再看Page類。小程序定義頁面是經過 Page 工廠實現的,大概是Page({data: {}})。小程序在這裏的令計很方便咱們進行hack,由於一個Page類只會有一個實例。

Page(createPage(P))

再看createPage的實現:

function createPage(PageClass) {
  var instance = ReactDOM.render(React.createElement(PageClass), {
    type: "div",
    root: true
  });
  var config = {
    data: {
      state: instance.state,
      props: instance.props
    },
    onLoad: function() {
      instance.$wxPage = this;
    },
    onUnload: function() {
      instance.componentWillUnmount && instance.componentWillUnmount();
    }
  };
  instance.allTemplateData.forEach(function(el) {
    if (config.data[el.templatedata]) {
      config.data[el.templatedata].push(el);
    }else{
      config.data[el.templatedata] = [el];
    }
  });
  return config;
}

最後是React.template的實現,它負責組裝給template的數據,這個template是小程序的標籤。

React.template = function(props){
//這是一個無狀態組件,負責劫持用戶傳導下來的類,修改它的原型
   var clazz = props.is;
   var a = classzz.prototype;
   var componentWillMount = a.componentWillMount;
   a.componentWillMount = function(){
    
     var ref = this._reactInternalRef;
     var arr = ref._owner.allTemplateData || (ref._owner.allTemplateData = []);
     arr.push({
       props: this.props,
       state: this.state,
       templatedata: props.templatedata
     })
     componentWillMount && componentWillMount.call(this)
   }
  var componentWillUpdate = a.componentWillUpdate;
   //...再上面同樣
   return React.createElement(clazz, props)
}

好了,個人方案就介紹到這裏了。若是有人願意與我一開始搞這東西了,歡迎在github找我。

相關文章
相關標籤/搜索