前言
-
和上次說的一樣這次帶來
preact
的解讀 -
preact實際上把它當作是一個精簡版
react
就好了。 -
這次我抄下了
preact
,並且改寫了程式碼, 命名為zreact
-
把之前將事件,props之類的單獨放出來,這樣這份
zreact
。 -
可以支援ie8,雖然並沒有什麼用。
-
這次程式碼解讀順序按使用preact的程式碼順序。
-
這裡是第一篇,createElement,也就是vue,react的render所返回的VNode物件。
-
平常則是使用babel+jsx來生成createElement呼叫。
-
vue常用則是template,但是通過webpack會做到預先轉換為render。
一、jsx的轉換原理。
對於preact來說,最常見的就是jsx。
下面是一個最簡單的使用preact。
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
例項。
二、createElement的實現
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
。
三、clone-element
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
了。