這是本系列的最後一篇,由於之後就是機密了。但這篇會公開一些很是有用的思路。小程序封死了操做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
但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這個類變成這樣:
所以咱們最好在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找我。