和上次說的同樣此次帶來preact
的解讀javascript
preact實際上把它看成是一個精簡版react
就行了。html
此次我抄下了preact
,而且改寫了代碼, 命名爲zreact
vue
把以前將事件,props之類的單獨放出來,這樣這份zreact
。java
能夠支持ie8,雖然並無什麼用。node
此次代碼解讀順序按使用preact的代碼順序。react
這裏是第一篇,createElement,也就是vue,react的render所返回的VNode對象。webpack
日常則是使用babel+jsx來生成createElement調用。git
vue經常使用則是template,可是經過webpack會作到預先轉換爲render。github
對於preact來講,最多見的就是jsx。
下面是一個最簡單的使用preact。web
import {h, render, Component} from "preact"; /** @jsx h */ // 經過上面的註釋告訴babel使用什麼方法做爲VNode的建立函數。 // 若是不使用這個默認會是React.createElement, // 或者經過下面的babel配置來修改 class App extends Component { render(props) { return <h1>App</h1>; } } var test = ""; render( <div className="test"> <span style={test}>測試</span> <App></App> </div> , document.body)
.babelrc
{ "presets": ["es2015"], "plugins": [ ["transform-react-jsx", { "pragma":"h" }] ] }
經過babel轉換後會變成
import {h, render} from "preact"; class App extends Component { render() { return h("h1", null, "App"); } } var test = ""; render( h( "div", { className: "test" }, h("span", { style: test }, "測試"), h(App) ), document.body )
因此對於preact最早執行的東西是這個h
函數也就是createElement
對於jsx
標準的createElement
函數簽名爲
interface IKeyValue { [name: string]: any; } /** * 標準JSX轉換函數 * @param {string|Component} nodeName 組件 * @param {IKeyValue} attributes 組件屬性 * @param {any[]} childs 這個VNode的子組件 */ function h( nodeName: string | function, attributes: IKeyValue, ...childs: any[] ): VNode; class VNode { public nodeName: string| Component; public children: any[]; public attributes: IKeyValue | undefined; public key: any | undefined; }
因此這裏的標準jsx
很是簡單。
第一個參數爲原生html組件或者Component
類。
第二個參數爲該組件的屬性,及自定義屬性。
第三個參數及後面的全部都是這個組件的子組件。
其中第三個及後面的參數爲數組就會被分解放入子組件中。
最後返回一個VNode
實例。
function h(nodeName: string | Component, attributes: IKeyValue, ...args: any[]) { // 初始化子元素列表 const stack: any[] = []; const children: any[] = []; // let i: number; // let child: any; // 是否爲原生組件 let simple: boolean; // 上一個子元素是否爲原生組件 let lastSimple: boolean = false; // 把剩餘的函數參數所有倒序放入stack for (let i = args.length; i--; ) { stack.push(args[i]); } // 把元素上屬性的children放入棧 if (attributes && attributes.children != null) { if (!stack.length) { stack.push(attributes.children); } // 刪除 delete attributes.children; } // 把stack一次一次取出 while (stack.length) { // 取出最後一個 let child: any = stack.pop(); if (child && child.pop !== undefined) { // 若是是個數組就倒序放入stack for (let i = child.length; i-- ; ) { stack.push(child[i]); } } else { // 清空布爾 if (typeof child === "boolean") { child = null; } // 判斷當前組件是否爲自定義組件 simple = typeof nodeName !== "function"; if (simple) { // 原生組件的子元素處理 if (child == null) { // null to "" child = ""; } else if (typeof child === "number") { // num to string child = String(child); } else if (typeof child !== "string") { // 不是 null,number,string 的不作處理 // 而且設置標記不是一個字符串 simple = false; } } if (simple && lastSimple) { // 當前爲原生組件且子元素爲字符串,而且上一個也是。 // 就把當前子元素加到上一次的後面。 children[children.length - 1] += child; } else { // 其它狀況直接加入children children.push(child); } /* else if (children === EMPTY_CHILDREN) { children = [child]; } */ // 記錄此次的子元素狀態 lastSimple = simple; } } const p = new VNode(); // 設置原生組件名字或自定義組件class(function) p.nodeName = nodeName; // 設置子元素 p.children = children; // 設置屬性 p.attributes = attributes == null ? undefined : attributes; // 設置key p.key = attributes == null ? undefined : attributes.key; // vnode 鉤子 if (options.vnode !== undefined) { options.vnode(p); } return p; }
這個標準jsx的VNode
生成函數很簡單,這邊要注意的是子組件是連續的字符串。
會被合併成一個,這樣能夠防止在生成dom時,建立多餘的Text
。
import { h } from "./h"; import { VNode } from "./vnode"; import { extend } from "./util"; /** * 經過VNode對象新建一個自定義的props,children的VNode對象 * @param vnode 舊vnode * @param props 新的props * @param children 新的子組件 */ export function cloneElement(vnode: VNode, props: any, ...children: any[]) { const child: any = children.length > 0 ? children : vnode.children; return h( vnode.nodeName, extend({}, vnode.attributes, props), child, ); }
clone-element依賴於createElement
此次的blog感受好短,我已經沒有東西寫了。
話說回來vue的template,如今看來不如說是一個變異的jsx語法。
感受明明是在讀preact源碼卻對vue的實現更加的理解了。
下一篇應該是Component
了。