Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
家好,我卡頌。
近日,Meta開(kāi)源了一款「CSS-in-JS庫(kù)」 —— StyleX。看命名方式,Style - X是不是有點(diǎn)像JS - X,他們有關(guān)系么?當(dāng)然有。
JSX是一種「用JS描述HTML」的語(yǔ)法規(guī)范,廣泛應(yīng)用于前端框架中(比如React、SolidJS...),由Meta公司提出。
同樣的,按照Meta的設(shè)想,StyleX是一種「用JS描述CSS」的語(yǔ)法規(guī)范。
早在React Conf 2019[1],Meta工程師「Frank」就介紹了這種Meta內(nèi)部使用的「CSS-in-JS庫(kù)」。
從Meta內(nèi)部使用,到大會(huì)對(duì)外宣傳,這期間肯定已經(jīng)經(jīng)歷大量?jī)?nèi)部項(xiàng)目的洗禮。而從做完宣傳到最終開(kāi)源,又經(jīng)歷了快5年時(shí)間。
那么,這款Meta出品、打磨這么長(zhǎng)時(shí)間的「CSS-in-JS庫(kù)」,到底有什么特點(diǎn)呢?
本文讓我們來(lái)聊聊。
市面上有非常多「CSS解決方案」,比如:
為什么需要這些方案?原生CSS哪里不好?在這里,我們舉個(gè)小例子(例子來(lái)源于「React Conf 2019」)。考慮如下代碼:
CSS文件如下:
.blue {color: blue;}
.red {color: red;}
HTML文件如下:
<p class="red blue">我是什么顏色?</p>
請(qǐng)問(wèn)p標(biāo)簽是什么顏色的?
從class來(lái)看,blue在red后面,p應(yīng)該是藍(lán)色的么?
實(shí)際上,樣式取決于他們?cè)跇邮奖碇卸x的順序,.red的定義在.blue后面,所以p應(yīng)該是紅色的。
是不是已經(jīng)有點(diǎn)暈了?再增加點(diǎn)難度。如果.red和.blue分別在兩個(gè)文件中定義呢?
# css文件1
.blue {color: blue;}
# css文件2
.red {color: red;}
那p的樣式就取決于最終打包代碼中樣式文件的加載順序。
上面只是原生CSS中「選擇器優(yōu)先級(jí)相關(guān)的一個(gè)缺陷」(除此外還有其他缺陷,比如「作用域缺失」...)。隨著項(xiàng)目體積增大、項(xiàng)目維護(hù)時(shí)間變長(zhǎng)、項(xiàng)目維護(hù)人員更迭,這些缺陷會(huì)被逐漸放大。
正是由于這些原因,才出現(xiàn)了各種「CSS解決方案」。
StyleX的API很少,掌握下面兩個(gè)就能上手使用:
比如:
import * as stylex from 'stylex';
// 創(chuàng)建樣式
const styles = stylex.create({
red: {color: 'red'},
});
// 定義props
const redStyleProps = stylex.props(styles.red);
使用時(shí):
<div {...redStyleProps}>文字顏色是紅色</div>
stylex是如何解決上面提到的red blue優(yōu)先級(jí)問(wèn)題呢?其實(shí)很簡(jiǎn)單,考慮如下代碼:
import * as stylex from 'stylex';
// 創(chuàng)建樣式
const styles = stylex.create({
red: {color: 'red'},
blue: {color: 'blue'}
});
// 使用
<p {...styles.props(styles.red, styles.blue)}></p>
樣式的優(yōu)先級(jí)只需要考慮styles.props中的定義順序(blue在red后面,所以顏色為blue),不需要考慮樣式表的存在。
有些同學(xué)會(huì)說(shuō),看起來(lái)和常見(jiàn)的CSS-in-JS沒(méi)啥區(qū)別啊。那stylex相比于他們的優(yōu)勢(shì)是啥呢?
首先要明確,stylex雖然以CSS-in-JS的形式存在,但本質(zhì)上他是一種「用JS描述CSS的規(guī)范」。文章開(kāi)頭也提到,他的定位類似JSX。
既然是規(guī)范,那他就不是對(duì)CSS的簡(jiǎn)單封裝、增強(qiáng),而是一套「自定義的樣式編寫(xiě)規(guī)范」,只不過(guò)這套規(guī)范最終會(huì)被編譯為CSS。
作為對(duì)比,Less、Sass這樣的「CSS預(yù)處理器」就是對(duì)CSS語(yǔ)法的封裝、增強(qiáng)
那么,stylex都有哪些規(guī)范呢?
比如,stylex鼓勵(lì)將樣式與組件寫(xiě)在同一個(gè)文件,類似Vue的SFC(單文件組件)。這么做除了讓組件的樣式與邏輯更方便維護(hù),也減少了stylex編譯的實(shí)現(xiàn)難度。
再比如,CSS中各種選擇器的復(fù)雜組合增強(qiáng)了選擇器的靈活性。但同時(shí)也增強(qiáng)了不確定性。舉個(gè)例子,考慮如下三個(gè)選擇器:
這些對(duì).className應(yīng)用的選擇器將影響.className的某些后代。當(dāng)這樣的選擇器多了后,很可能會(huì)在開(kāi)發(fā)者不知道的情況下改變某些后代元素的樣式。
遇到這種情況我們一般會(huì)怎么處理呢?正確的選擇當(dāng)然是找到上述影響后代的選擇器,再修改他。
但大家工作都這么忙,遇到這種問(wèn)題,多半就是用新的選擇器覆寫(xiě)樣式,必要的時(shí)候還會(huì)加!important后綴。久而久之,這代碼就沒(méi)法維護(hù)了。
為了規(guī)避這種情況,在stylex中,除了「可繼承樣式」(指「當(dāng)父元素應(yīng)用后,子孫元素默認(rèn)會(huì)繼承的樣式」,比如color)外,不支持這些「可以改變子孫后代樣式的選擇器」。
那我該如何讓子孫組件獲得父組件同樣的樣式呢?通過(guò)props透?jìng)靼 ?/span>
也就是說(shuō),stylex禁用了CSS中可能造成混淆的選擇器,用JS的靈活性彌補(bǔ)這部分功能的缺失。
有些同學(xué)可能會(huì)說(shuō):這些功能,其他「CSS-in-JS庫(kù)」也能做啊。
這就要談到「CSS-in-JS庫(kù)」最大的劣勢(shì) —— 為了計(jì)算出最終樣式,在運(yùn)行時(shí)會(huì)造成額外的樣式計(jì)算開(kāi)銷。
stylex通過(guò)編譯來(lái)減少運(yùn)行時(shí)的開(kāi)銷。比如對(duì)于上面提到過(guò)的stylex的代碼:
import * as stylex from 'stylex';
// 創(chuàng)建樣式
const styles = stylex.create({
red: {color: 'red'},
});
// 定義props
const redStyleProps = stylex.props(styles.red);
編譯后的產(chǎn)物包括如下兩部分:
JS的編譯產(chǎn)物:
import * as stylex from 'stylex';
const redStyleProps = {className: 'x1e2nbdu'};
CSS的編譯產(chǎn)物:
.x1e2nbdu {
color: red;
}
所以,運(yùn)行時(shí)實(shí)際運(yùn)行的代碼始終為:
<div {...{className: 'x1e2nbdu'}}>...</div>
對(duì)于再?gòu)?fù)雜的樣式,stylex都會(huì)通過(guò)編譯生成「可復(fù)用的原子類名」。
即使是跨文件使用樣式,比如我們?cè)诹硪粋€(gè)文件也定義個(gè)使用color: 'red'樣式的stylex屬性foo:
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
foo: {
color: 'red',
},
bar: {
backgroundColor: 'blue',
},
});
會(huì)得到如下編譯結(jié)果,其中x1e2nbdu是一個(gè)原子類名,他是上一個(gè)文件中styles.red的編譯產(chǎn)物:
import * as stylex from '@stylexjs/stylex';
const styles = {
foo: {
color: 'x1e2nbdu',
$$css: true,
},
bar: {
backgroundColor: 'x1t391ir',
$$css: true,
},
};
隨著項(xiàng)目體積增大,樣式表的體積也能控制在合理的范圍內(nèi)。這種對(duì)原子類名的控制粒度是其他「CSS-in-JS庫(kù)」辦不到的。
stylex相比TailwindCSS這樣的原子CSS有什么優(yōu)勢(shì)呢?
這就要談到原子CSS的一個(gè)特點(diǎn) —— 使用約定好的字符串實(shí)現(xiàn)樣式。比如,使用TailwindCSS定義圖片的樣式:
<img class="w-24 h-24 rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512">
效果如下:
由于樣式都是由不同的「原子類名字符串」組合而成,TS沒(méi)法分析,這就沒(méi)法實(shí)現(xiàn)「樣式的類型安全」。
什么叫「樣式的類型安全」?通俗的講,如果我實(shí)現(xiàn)一個(gè)組件,組件通過(guò)style props定義樣式,我只希望使用者能夠改變color與fontSize兩個(gè)樣式屬性,不能修改其他屬性。如果能實(shí)現(xiàn)這一點(diǎn),就是「樣式的類型安全」。
「樣式的類型安全」有什么意義呢?舉個(gè)例子:設(shè)想開(kāi)發(fā)基礎(chǔ)組件庫(kù)的團(tuán)隊(duì)使用stylex。那么當(dāng)業(yè)務(wù)團(tuán)隊(duì)使用該組件庫(kù)時(shí),就只能自定義組件的一些樣式(由組件庫(kù)團(tuán)隊(duì)約束)。
當(dāng)基礎(chǔ)組件庫(kù)升級(jí)時(shí),組件庫(kù)團(tuán)隊(duì)能很好對(duì)組件樣式向下兼容(因?yàn)橹乐挥心男邮皆试S被修改)。
在stylex中,由于stylex.create的產(chǎn)物本質(zhì)是對(duì)象,所以我們可以為每個(gè)產(chǎn)物定義類型聲明。比如在如下代碼中,我們限制了組件style props只能接受如下stylex樣式:
import type {StyleXStyles} from '@stylexjs/stylex';
type Props = {
// ...
style?: StyleXStyles<{
color?: string;
backgroundColor?: string;
borderColor?: string;
borderTopColor?: string;
borderEndColor?: string;
borderBottomColor?: string;
borderStartColor?: string;
}>;
};
我猜想,當(dāng)更多人知道stylex后,他會(huì)收到比當(dāng)初TailwindCSS火時(shí)更多的兩級(jí)分化的評(píng)價(jià)。
畢竟,stylex的設(shè)計(jì)初衷是為了解決Meta內(nèi)部復(fù)雜應(yīng)用的樣式管理。如果:
那大概率是不能接受stylex設(shè)計(jì)理念中的這些約束。
對(duì)于stylex,你怎么看?
[1]
React Conf 2019: https://www.youtube.com/watch?v=9JZHodNR184&t=270s
前端開(kāi)發(fā)中,Vue 一直以其簡(jiǎn)單、高效的框架而備受開(kāi)發(fā)者青睞。然而,隨著 React 在市場(chǎng)上的流行,許多開(kāi)發(fā)者開(kāi)始對(duì) JSX(JavaScript XML)這種聲明式編程風(fēng)格產(chǎn)生興趣。本文將探討 JSX 在 Vue3 中的應(yīng)用,并對(duì)其是否成為 Vue3 前端開(kāi)發(fā)的未來(lái)進(jìn)行論證。
在開(kāi)始之前,我們先來(lái)了解一下 Vue 本身的模版語(yǔ)法和 JSX 分別是什么。
Vue3 模版語(yǔ)法是 Vue.js 中常用的一種聲明式模板語(yǔ)法,使用 HTML 語(yǔ)法來(lái)描述視圖。在模板語(yǔ)法中,可以通過(guò)插值、指令和事件綁定等方式來(lái)將數(shù)據(jù)與視圖關(guān)聯(lián)起來(lái)。這是其簡(jiǎn)單易用和上手快的主要原因之一。
<template>
<div>
<h1>{{ title }}</h1>
<p v-if="showText">{{ text }}</p>
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
title: 'Vue3 Template Syntax',
text: 'This is a demo text.',
showText: true,
list: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
],
};
},
};
</script>
Vue3 的模板語(yǔ)法使用雙花括號(hào)({{ }})進(jìn)行數(shù)據(jù)插值,使用v-if和v-for等指令處理?xiàng)l件和循環(huán)邏輯。
JSX 是一種 JavaScript 的語(yǔ)法擴(kuò)展,它允許在 JavaScript 代碼中編寫(xiě)類似于 XML 的結(jié)構(gòu)。React 是第一個(gè)廣泛使用 JSX 的框架,它將組件的結(jié)構(gòu)和邏輯封裝在 JSX 中,用于描述 UI 組件的層次結(jié)構(gòu)。隨著 React 的成功,也隨著時(shí)間的推移,JSX 逐漸成為了一種通用的模式,被許多其他框架和庫(kù)所采用支持。
React示例:
import React, { useState } from 'react';
function JSXComponent() {
const [title, setTitle] = useState('JSX in React');
const [showText, setShowText] = useState(true);
const list = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
];
return (
<div>
<h1>{title}</h1>
{showText && <p>This is a demo text.</p>}
<ul>
{list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
export default JSXComponent;
Vue3示例:
import { defineComponent, ref } from 'vue';
const JSXComponent = defineComponent({
setup() {
const title = ref('JSX in Vue3');
const showText = ref(true);
const list = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
];
return {
title,
showText,
list,
};
},
render() {
return (
<div>
<h1>{this.title}</h1>
{this.showText && <p>This is a demo text.</p>}
<ul>
{this.list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
},
});
export default JSXComponent;
從上面不難看出,在 JSX 中,我們可以直接使用 JavaScript 表達(dá)式(如{title}),也可以使用條件語(yǔ)句(如{showText && <p>This is a demo text.</p>})和數(shù)組的map方法來(lái)處理循環(huán)邏輯。
這些只是舉例,有很多的 JavaScript 語(yǔ)法和方法等,都可以在這里使用。總之,使用 JSX 開(kāi)發(fā),可以很大程度上利用好 JavaScript,開(kāi)發(fā)更加方便。
在 Vue3 中,我們可以通過(guò) Vue 官方提供的項(xiàng)目腳手架工具 create-vue 來(lái)新建一個(gè)支持JSX的項(xiàng)目。首先,確保你安裝了最新版本的 Node.js(我用的是 16+ 版本),然后執(zhí)行以下命令:
npm init vue@latest
這個(gè)命令將會(huì)安裝并執(zhí)行 create-vue 工具。在執(zhí)行過(guò)程中,你會(huì)看到一些可選的功能配置提示,其中會(huì)有如下內(nèi)容:
Vue.js - The Progressive JavaScript Framework
? Project name: … vue-project
? Add TypeScript? … No / Yes
? Add JSX Support? … No / Yes
? Add Vue Router for Single Page Application development? … No / Yes
? Add Pinia for state management? … No / Yes
? Add Vitest for Unit Testing? … No / Yes
? Add an End-to-End Testing Solution? ? No
? Add ESLint for code quality? … No / Yes
? Add Prettier for code formatting? … No / Yes
Scaffolding project in ...
執(zhí)行到? Add JSX Support?時(shí)選擇Yes,這樣 JSX 就會(huì)自動(dòng)安裝。如果不確定是否要開(kāi)啟某個(gè)功能,你可以直接按下回車鍵選擇 No。
當(dāng)然,你也可以在已有的Vue項(xiàng)目中配置JSX。
在已有項(xiàng)目中配置JSX,首先需要安裝相關(guān)依賴:
npm install --save-dev @vue/babel-plugin-jsx
然后,在項(xiàng)目的vite.config.ts文件中進(jìn)行配置。具體的配置內(nèi)容如下圖所示:
image.png
配置完成后,現(xiàn)在我們就可以在項(xiàng)目中使用 JSX 語(yǔ)法來(lái)開(kāi)發(fā)了。這樣,我們可以根據(jù)具體的場(chǎng)景選擇使用 Vue 模板或 JSX 語(yǔ)法。
總的來(lái)說(shuō),配置 JSX 是非常簡(jiǎn)單的,通過(guò)上述步驟,我們可以輕松地在 Vue 項(xiàng)目中使用 JSX,發(fā)揮其簡(jiǎn)潔、易讀的優(yōu)勢(shì),讓我們的代碼更加優(yōu)雅和高效。
現(xiàn)在,我們來(lái)對(duì)比一些具體的代碼示例,看看 Vue3 模板語(yǔ)法和 JSX 之間的差異。
1000.webp
Vue3 模板語(yǔ)法使用雙花括號(hào){{}}進(jìn)行數(shù)據(jù)插值,而 JSX 使用花括號(hào){}。
模板示例:
<template>
<p>{{ message }}</p>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const message = ref('Hello, JSX!');
return { message };
},
};
</script>
JSX示例:
import { defineComponent } from 'vue';
const DynamicData = defineComponent({
setup() {
const message = ref('Hello, JSX!');
return { message };
},
render() {
return <p>{this.message}</p>;
},
});
在 Vue3 中,我們可以使用v-if指令進(jìn)行條件渲染,而在 JSX 中,我們使用 JavaScript 的條件表達(dá)式。
模板示例:
<template>
<div>
<p v-if="showContent">Content is visible.</p>
<p v-else>Content is hidden.</p>
<button @click="toggleContent">Toggle</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const showContent = ref(true);
function toggleContent() {
showContent.value = !showContent.value;
}
return { showContent, toggleContent };
},
};
</script>
JSX示例:
import { defineComponent } from 'vue';
const ConditionalRender = defineComponent({
setup() {
const showContent = ref(true);
return { showContent };
},
render() {
return (
<div>
{this.showContent ? <p>Content is visible.</p> : <p>Content is hidden.</p>}
<button onClick={() => this.showContent = !this.showContent}>Toggle</button>
</div>
);
},
}};
在 Vue3 中,我們使用v-for指令來(lái)處理列表渲染,而在 JSX 中,我們使用 JavaScript 的map方法。
模板示例:
<template>
<ul>
<li v-for="item in items" :key="item">{{ item }}</li>
</ul>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const items = ref(['Apple', 'Banana', 'Orange']);
return { items };
},
};
</script>
JSX示例:
import { defineComponent } from 'vue';
const ListRendering = defineComponent({
setup() {
const items = ref(['Apple', 'Banana', 'Orange']);
return { items };
},
render() {
return (
<ul>
{this.items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
},
});
接下來(lái),我們將對(duì) Vue 模板和 JSX 進(jìn)行對(duì)比,從多個(gè)方面分析它們的優(yōu)勢(shì)與劣勢(shì)。
Vue 模板使用 HTML 語(yǔ)法,更加接近常規(guī) HTML 結(jié)構(gòu),因此對(duì)于前端開(kāi)發(fā)者來(lái)說(shuō)比較容易上手。然而,「隨著項(xiàng)目的復(fù)雜性增加,模板中可能會(huì)包含大量的指令和插值,導(dǎo)致代碼變得冗長(zhǎng)。」 例如,條件渲染、循環(huán)遍歷等情況都需要使用 Vue 特定的指令。「相比之下,JSX 在 JavaScript 語(yǔ)法的基礎(chǔ)上,使用類似 XML 的結(jié)構(gòu),使得代碼更加緊湊和直觀。」
模板示例:
<template>
<div>
<h1 v-if="showTitle">{{ title }}</h1>
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
JSX示例:
const MyComponent = () => {
return (
<div>
{showTitle && <h1>{title}</h1>}
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
從上面的對(duì)比可以看出,JSX語(yǔ)法更加簡(jiǎn)潔,尤其在條件渲染和循環(huán)遍歷方面更加靈活。
Vue.js 本身就支持組件化開(kāi)發(fā),但是在 Vue 模板中,組件的定義與使用是通過(guò)特定的 HTML 標(biāo)簽和 Vue 指令實(shí)現(xiàn)的。這種方式雖然簡(jiǎn)單易懂,但是在代碼重用和維護(hù)方面可能會(huì)有一些限制。相比之下,JSX 在 React 中的組件化開(kāi)發(fā)非常強(qiáng)大,組件可以直接在 JavaScript 中定義,并且支持更加靈活的組合方式。
模板示例:
<template>
<div>
<my-component :prop1="value1" :prop2="value2" />
</div>
</template>
JSX示例:
const MyComponentWrapper = () => {
return (
<div>
<MyComponent prop1={value1} prop2={value2} />
</div>
);
};
從上面的對(duì)比可以看出,JSX 允許在JavaScript中直接定義組件,并且組件的傳參更加直觀。
由于 Vue 模板使用了特定的指令和 HTML 語(yǔ)法,IDE 對(duì)于代碼的支持可能相對(duì)有限。而 JSX 是 JavaScript 的擴(kuò)展語(yǔ)法,因此可以與類型檢查工具(如TypeScript)完美結(jié)合,同時(shí)得到更好的 IDE 支持,例如自動(dòng)補(bǔ)全、代碼跳轉(zhuǎn)等。
Vue 模板的語(yǔ)法是固定的,不能隨意擴(kuò)展。而 JSX 作為 JavaScript 的一部分,不需要額外的指令或語(yǔ)法,可以通過(guò)編寫(xiě)函數(shù)和組件來(lái)擴(kuò)展其語(yǔ)法,也使得處理邏輯、計(jì)算和條件渲染變得更加靈活和方便。
JSX 作為 React 的核心特性,擁有龐大的社區(qū)和豐富的生態(tài)系統(tǒng),為開(kāi)發(fā)者提供了海量的工具和庫(kù)。
同時(shí) JSX 在多個(gè)框架中得到了廣泛支持,開(kāi)發(fā)者們可以更輕松地在不同的項(xiàng)目和技術(shù)棧之間切換,而無(wú)需學(xué)習(xí)不同的模板語(yǔ)法。如文章上面“如何配置JSX?”中對(duì)已有項(xiàng)目的配置,將 JSX 作為插件寫(xiě)到 Vue Plugin 即可配置完成。
Vue3 的模板語(yǔ)法和 JSX 各有優(yōu)劣,因此它們?cè)诓煌膱?chǎng)景下有不同的適用性。
無(wú)論是從開(kāi)發(fā)體驗(yàn)、技術(shù)生態(tài)還是未來(lái)趨勢(shì)來(lái)看,JSX它使得組件的模板結(jié)構(gòu)更加清晰、聲明性,提供了更強(qiáng)大的 JavaScript 表達(dá)能力,同時(shí)也增強(qiáng)了與其他技術(shù)棧的互通性。雖然傳統(tǒng)的 Vue2 模板語(yǔ)法在一定程度上仍然適用,但通過(guò)引入 JSX,Vue3 在前端開(kāi)發(fā)領(lǐng)域擁有更廣闊的發(fā)展前景。開(kāi)發(fā)者們可以更加便利地構(gòu)建復(fù)雜的交互界面,同時(shí)又能享受到 Vue3 帶來(lái)的性能優(yōu)勢(shì)。
看一眼 API,乍一看仿佛和 fard 類似的 API,仿佛又寫(xiě)了個(gè)跨端小程序框架?
然而并不是……
voe 是一個(gè)底層小程序框架
意思是它實(shí)現(xiàn)了小程序的雙線程,利用“沙箱” 隔離 web 環(huán)境,屏蔽 dom 能力
接下來(lái)結(jié)合 微信小程序 介紹一下主要思路:
目標(biāo)
絕對(duì)的控制力,意思是用戶不能操作 dom,不能使用 web API,不能使用完整的 jsx 和 html,不能……反正就是啥都不能!
就好像 sm 角色一樣,s 對(duì) m 的絕對(duì)控制與虐待,m 只能服從命令與受虐
所以我把小程序的雙線程架構(gòu)又稱為 【主奴架構(gòu)】
沙箱
小程序中不能操作 dom,不是因?yàn)樗帘瘟?dom API 或者屏蔽了事件,這樣做是不切實(shí)際的
大家都是尋找一個(gè)非 dom 環(huán)境作為沙箱,比如 v8,比如 worker,比如 native,比如 wasm
以上都是 OK 的,我猜微信小程序等也是用的類似的沙箱
voe 使用 web worker 作為沙箱
為什么不使用 v8 或 native?
這就是純粹的個(gè)人選擇了,不選擇 v8 或 native 應(yīng)該是,但是偏偏我個(gè)人更偏前一點(diǎn),web worker 的計(jì)算力默認(rèn)是優(yōu)于 v8 或 native 的(同等硬件水平),但是 v8 也有好處,比如 node 可以使用 cookie,然后擁有一些先進(jìn)的 API
將框架拆到兩個(gè)不同的線程中
重點(diǎn)來(lái)了,兩個(gè)線程中,如何安排框架工作呢?
有幾個(gè)原則:
于是乎,就變成下面這個(gè)樣子:
然后,困難如約而至,沙箱與主線程之間的鴻溝來(lái)自 dom 元素和 事件函數(shù),這兩者無(wú)法傳遞
我絞盡腦汁,想了一個(gè)完全之策
將不能傳遞的東西保存到自己線程中并建立映射,將索引傳到另一個(gè)線程
比如,事件是這樣傳遞的:
let handlers = new WeakSet() if (props) { for (const k in props) { if (k[0] === 'o' && k[1] === 'n') { let e = props[k] let id = handlers.size + 1 handlers.set({ id: e }) props[k] = id } } }
將事件放到 map 中存起來(lái),然后將 id 傳給主線程,主線程事件觸發(fā)的時(shí)候,將 id 和 event 參數(shù)交還給 worker
for (const k in props) { if (k[0] === 'o' && k[1] === 'n') { let id = props[k] props[k] = event => { // 不能傳太多,此處省略對(duì)事件的簡(jiǎn)化操作 worker.postMessage({ type: EVENT, event, id }) } } }
然后在 worker 中,根據(jù)索引映射,找到對(duì)應(yīng)的事件并觸發(fā)
是的沒(méi)錯(cuò)就是這樣,這個(gè)方法是萬(wàn)能的,比如我們的 diff 方法
既然 diff 無(wú)法將 dom 傳出去,那么我們就傳 dom 的 index
if (oldVNode ==null||oldVNode.type!==newVNode.type) { parent.insertBefore(createNode(newVNode), node) }
比如這個(gè)方法,parent 和 node 都是 dom 元素,是沒(méi)辦法傳遞的,我們就……傳他們的索引,may be 長(zhǎng)這樣:
[ [0,'insertBefore',1] ]
dom 是這樣的:
<div id="root" index="0"> <ul index="1"> <li index="2" /> <li index="3" /> </ul> </div>
如果此時(shí)我們要?jiǎng)h除 index 為 3 的節(jié)點(diǎn),那對(duì)應(yīng)生成的結(jié)構(gòu),應(yīng)該是這樣:
[ [1,'removeChild',3] ]
刺不刺激,我們成功找到了一個(gè)思路,能夠?qū)崿F(xiàn)不同的 diff 算法啦
要知道,微信小程序就沒(méi)有找到類似的思路,他們的 diff 還是 virtual-dom 的那套古老的深度遍歷,代碼多性能差……
綜上所述,上面介紹了雙線程的主要思路,這些思路不僅僅適用于這個(gè)框架,同樣適用于其他跨端的場(chǎng)景
vue 3
這里簡(jiǎn)單說(shuō)一下 vue 3,嗯大家看到,voe 的名字和 API 神似 vue 3,事實(shí)上我確實(shí)將 vue 3 的核心抄過(guò)來(lái)了,包括依賴收集,響應(yīng)式,狀態(tài)更新,組合函數(shù)……
這只是順手的事兒,其實(shí)比起 voe 的核心思路,API 是沒(méi)什么所謂的
因?yàn)閹缀跛械墓荆绻胍阕约旱男〕绦颍贾荒苓^(guò)來(lái)參考思路,然后 API 很可能就和微信保持一致了
所以我覺(jué)得 vue 3 的 API 足夠簡(jiǎn)單,正好可以弱化 API
就抄過(guò)來(lái)了……
大家可以可以將 voe 作為 vue 3 的最小實(shí)現(xiàn),用于協(xié)助閱讀源碼也是很 OK 的哈!
雙線程 vs 異步渲染
題外話,大家應(yīng)該都知道我之前寫(xiě)的框架 fre.js,也應(yīng)該對(duì) concurrent(時(shí)間切片)、suspense 等異步渲染的機(jī)制有所了解
如今我又來(lái)搞 web worker,是因?yàn)樗鼈z的思路是類似的,場(chǎng)景也是一樣的
兩者的場(chǎng)景同樣都是可視化,高幀率動(dòng)畫(huà),大量數(shù)據(jù)與計(jì)算……
可惜本身這種場(chǎng)景需求實(shí)在太少了,所以 preact 和 vue 團(tuán)隊(duì)紛紛發(fā)聲,表示不想搞也不需要搞::>_<::
但是到我這來(lái)說(shuō)的話,其實(shí)我不在意框架有沒(méi)有人用,也不在于業(yè)務(wù)的,我更加傾向于一種技術(shù)創(chuàng)新,所以從這個(gè)方面,只要是新的思路,總歸有它的價(jià)值
總結(jié)
呼~(yú)終于寫(xiě)完了,在掘金發(fā)文,必須要長(zhǎng)啊
最后放上 voe 的 github:
github.com/132yse/voe
作者:132
鏈接:https://juejin.im/post/5dd1edf3e51d4561ea3fb3cd
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。