關於React :你不可不知的19件事兒

這篇文章是我個人對本屆React Conf的一些精華總結。所有講座都值得一看,所以我建議大家回顧第一天和第二天的所有錄像。我在文末給每個講座都註明了視頻時間戳。

總結的有些內容可能會讓你感到驚訝,其實我也是!這些內容並不都是技術主題的,但是都貫穿着同一條主線。

一切都是爲了服務於開發者體驗

Tom Occhino提到這個話題後我就一直在思考它。其實所有演講中都能看到這種思想的影子,這也是爲什麼我如此熱愛開發工具和前端的原因所在。

React 旨在創造一種開發體驗,使開發者們能夠更加輕鬆的學習並實現更加強大的功能,通過更快的迭代來提高生產力,以及提升開發軟件的規模。這些事情使我像 React 一樣。我覺得 Facebook 在交付方面做得很好。

這一切有什麼意義呢?很簡單,我們做這些就是爲了改善用戶體驗,爲了提升用戶的生產力。我們要幫助他們,讓他們用優雅的方式來實現目標。雖然我們背後要做的工作可能不會都那麼輕鬆,但我們應該讓用戶感受到如沐春風。

由於React是一種網關技術,而且有63%的JavaScript開發人員正在使用它,因此他們的團隊非常重視社區。他們通過了《貢獻者公約》,歡迎大家提出批評。作爲一個社區,我們應該能夠接受批評,而不是處處爲自己辯護。Elbert Hubbard說:“不想要批評的話,除非我們什麼都不說、什麼都不做、什麼都不承擔。” React所做的事情以及其原因纔是關鍵所在。這自然會招致批評,進而讓技術不斷成長。

令人欣喜的易用性、性能和併發模式

你在使用React時是否遇到過焦點問題?我就遇到過。焦點確實很重要,原因有很多,它可以幫助人們瀏覽頁面,這對不用鼠標的用戶來說非常重要,稍後會再討論這個話題。總之我們很高興看到React團隊準備將可訪問性納入架構之中。

大會期間我思考最多的事情之一就是性能。Facebook要處理的一些性能問題是我們大多數人永遠都遇不到的,但他們從這些教訓中所學到的知識仍然可以用來改善用戶體驗。重點在於感知到的速度,而不是單純的頁面加載延遲。

Yuzhi Zheng在她的演講中講了選擇性 Hydration 的例子。你可能也聽說過Suspense,它能改善Web上的整體用戶體驗。

併發模式

假如我們要製作一個關聯用戶輸入的可過濾列表。用React來做時,除非你不在乎性能,否則你就得對列表的更新做防抖和優化。

併發模式能讓 React 對低優先級的工作中斷響應,從而提升React應用的響應速度。這使得用戶輸入之類的事情,比重新渲染列表之類的事情具備更高的優先級。React將可以同時處理多個狀態更新,這將幫助我們消除卡頓和過於頻繁的DOM更新,還可以讓我們能優先處理懸停和文本輸入之類的交互,我們知道用戶希望這些內容能夠快速的被處理處理。

React團隊分享了許多併發模式的示例,建議參考(https://reactjs.org/docs/concurrent-mode-patterns.html)。

CSS-in-JS-at-FB

Frank Yan宣佈Facebook正在構建自己的CSS-in-JS庫,我很感興趣。剛開始我覺得難道我們目前的庫還不夠嗎?這使我們有機會進一步瞭解 Facebook 面臨的一些擴展問題,以及他們解決問題的創造性方式。

CSS的維護工作很容易失控。來看下面的例子:

.blue { color: blue; }

.red { color: red; }
<span class="red blue">
Which color will I be?
</span>

示例中,我們希望文本結果爲 blue。因爲這個類在類聲明中排第二,因此我們應該可以認爲它具有優先權。但事實並非如此。.red類在層疊樣式表中排第二,所以會優先展示爲紅色。如果這些類位於不同的樣式表中,則它們在頁面中的加載順序就會很重要。
這個例子很簡單,但這麼簡單的問題也會失控。Facebook想靠他們的新的庫來解決特異性戰爭、主題化和可訪問性之類的問題。

演講中的一些有趣的細節:

  • 開發人員能以像素爲單位來編碼,然後在REM中編譯;
  • 他們通過實現類型檢查(捕獲和修復錯字、檢測和刪除未使用的樣式、避免跨瀏覽器陷阱)來提升安全性;
  • 向開發人員顯示可訪問性錯誤;

  • 組件可以有能被覆蓋的默認樣式(包括類型安全!);
  • 重複規則會被移除,這樣CSS文件就能瘦身了(Facebook最近重寫了前端,從413kb瘦身到74kb)。

原子CSS

每個類都創建一個唯一的屬性值,這是用來優化組件的。(https://youtu.be/UxoX2faIgDQ?t=4939

.c0 { color: blue; }
.c1 { color: red; }
.c2 { font-size: 16px; }
// Generated Component (Pre-Optimized)
const styles = {
blue: {color: 'c0'},
default: {color: 'c1', fontSize: 'c2'},
};

function MyComponent(props) {
return (
<span className={styles(
'default',
props.isBlue && 'blue',
)}>
Hello World!
</span>
);
}

這個例子展示了CSS怎樣原子化,它還展示瞭如何使用props設置span的顏色,但這段代碼還能進一步優化。

// The styles block is no longer needed
function MyComponent(props) {
return (
<span className={styles(
(props.isBlue ? 'c0 ' : 'c1 ') + 'c2 '
)}>
Hello World!
</span>
);
}

這些東西非常有趣,我期待能儘快看到他們發佈這個庫。

數據驅動的JavaScript

想讓頁面看上去更快、交互更迅速嗎?Ashley Watkins也在思考這個問題。她提醒了我,可以考慮用調整數據獲取的方法來改善用戶體驗。我本就對Relay感興趣,聽了她的演講就更躍躍欲試。

分段代碼分割

Facebook一直在設法加快FMP的速度。他們的一種方法是“分段代碼分割”。

使用這種方法時,你可以一次下載並分段交付。拿Facebook帖子來說可以分爲三段。

  1. 載入中
  2. 顯示
  3. 分析工具

每個階段都可以有自己的代碼獲取和渲染過程。FMP所需的所有數據都可以在加載階段獲取代碼的時候同時獲取。

應該關注首次有效渲染的時間

爲了儘可能優化用戶體驗,你應該關注的是首次有效渲染(FMP)所需的時間,其實這就是主要內容顯示在頁面上所需的時間。有許多指標可供參考以縮短加載時間,但FMP是非常關鍵的一個。

你可以在 Relay 中使用GraphQL進行流式查詢。你可以將某些數據標記爲關鍵數據,而將其他數據標記爲非關鍵數據。然後你可以先從服務器獲取最重要的內容,並在獲取其餘數據的同時開始顯示內容,這樣你就可以在內容到達時立即開始渲染。

數據驅動的代碼分割

這個驚到我了。Relay太強了,無可爭議。Relay有一項新功能,可讓你擴展查詢以查看渲染特定數據類型時所需的組件代碼。你可以將代碼視爲數據。當服務器解析你的GraphQL查詢時,它可以讓客戶端知道後者將需要下載哪些組件代碼,從而可以更快地獲取它們。

Ashley的演講太棒了,她承諾說這些僅僅是個開始。我還沒使用過Relay,但我迫不及待想開始了,相信你也會這麼做(尤其當你瞭解了它更多的能耐後)。

解決全球的飢餓問題

大會第一天主要談的是技術話題,討論生態系統即將到來的衆多功能如何改善用戶體驗。

Tania Papazafeiropoulou則一轉話題,開始探討全球範圍的饑荒問題,以及她正在研究的一種很酷的產品OLIO。它可以幫助人們分享食物,而不是浪費食物,而且它是由 React 打造的。

這個演講並沒有大肆宣傳React的新功能,但它讓人們更加認識到React的力量根源所在。React(和React Native)使Tania的團隊能夠快速構建產品,並開始對社會產生積極的影響。

改進 REST 的體驗和安全性

RESTful API並不是一個新的熱門概念。它們是在2000年正式定義的,自從它被定義以來已經取得了顯著的成績。話雖如此,REST確實有一些值得挑戰的問題。

Facebook用GraphQL迴應了這些挑戰。GraphQL爲我們提供了對數據的可理解的定義。它可以讓客戶只獲取所需的內容。這樣渲染速度就能顯著加快,因爲你不必下載太多數據。

Tejas Kumar很好地總結了新舊技術的差異:

https://youtu.be/UxoX2faIgDQ?t=9586

REST

  • ❌沒有規範的數據格式
  • ❌猜謎遊戲(不允許的請求會以400、401還是404響應呢?)
  • ❌無意義的對話
  • ❌無合約協議

GRAPHQL

  • ✅規範的數據格式
  • ✅沒有猜謎遊戲
  • ✅有意義的討論(影響用戶的事物)
  • ✅強有力的合約協議

許多人都喜歡GraphQL,但有時它不是 API 的選擇。Tejas和他的團隊開發了一種工具,可以消除REST的一些陷阱。它包括Swagger的代碼生成和OpenAPI規範。

React的引擎(構建自定義渲染器)

如果你曾經演示過以前編寫過的代碼,肯定知道渲染器經常會出錯。於是Sophie Alpert就在演講中教我們怎樣構建一個React渲染器。

我不覺得我是React專家(目前不是)。我從沒看過React代碼庫,覺得那肯定很難吧。但隨着我繼續學習和掌握React,最終還是到了研究代碼庫的時候。不過看Sophie在React Conf講臺上現場構建一個自定義渲染器,似乎也沒那麼難嘛。

本地化(一件很重要的事)

我們這些母語是英語的開發人員做產品時往往不會首先考慮本地化問題。值得慶幸的是,還好我意識到了這一點,以後會更認真地對待它。

我們總覺得用戶和我們一樣,所以沒那麼關心本地化的問題。可現實中用戶並不是都和你一樣的!這就是爲什麼我們需要用戶測試、獲得用戶反饋,並做出更加包容的產品,適應所有的用戶類別。

去年Nat Alison提出了一個問題:“React有其他語言版本嗎?”當時的答案是否定的。

爲什麼這個問題如此重要?Nat總結得很好。如果只有說英語的人才能使用React,那麼會有多少人沒法使用這些工具來開發出令人讚歎的產品?如果只讓說英語的人來塑造我們的數字世界,我們的損失得有多大?世界上只有20%的人口英語。如果我們不幫助其他語種的人們使用React,將來只會自討苦吃!

Nat和幾千同行在過去一年中取得了難以置信的成就。但還有更多工作要做,如果你會兩種語言,可以爲此做出貢獻。

https://isreacttranslatedyet.com/

無障礙馬拉松

就像本地化一樣,可訪問性/無障礙功能可能也不是輕鬆的活計。開發無障礙功能時,你需要改變思考方式。你必須考慮更多的受衆,考慮可能與你不同的人羣。有時候這很困難,但我們都能做到。

跑馬拉松是另一個可能很難做到的例子。根據RunRepeat的資料,2018年有1,298,725人完成了馬拉松比賽。這並不是他們天生就能做到的事情,他們是一步一個腳印,最終實現了自己的目標。

我們面對無障礙功能也要有這樣的精神。一步步來,就不會覺得那麼不堪重負了。React Conf 吸引我的其中一點就是從新的角度學習軟件開發和了解世界。Brittany Feenstra的演講又一次擴大了我的視野,讓我更深入地思考無障礙功能。

我不會在一夜之間完成無障礙馬拉松,但可以每天一點點向前推進。值得慶幸的是,在此過程中有很多好工具可用:

https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd
https://github.com/FormidableLabs/eslint-plugin-react-native-a11y

你不需要Redux(對嗎?)

在2019年,管理React狀態有許多方法可用(甚至包括 easy-peasy):

https://reactjs.org/docs/react-component.html#setstate
https://reactjs.org/docs/context.html
https://reactjs.org/docs/hooks-intro.html
https://github.com/reduxjs/redux
https://github.com/jamiebuilds/unstated
https://github.com/mobxjs/mobx
https://www.apollographql.com/docs/react/data/local-state/
https://easy-peasy-v3.now.sh/

選擇困難症又犯了。然而哪個選項纔是正確的取決於你的情況。請記住,開發人員體驗是服務用戶體驗的。因此,我喜歡用盡量簡單,能隨我的原始決策改變而變化的方式來管理狀態。

很高興看到React現在內置了很多選項。你可以使用Context和Hooks做很多事情,而無需其他依賴項。

爲了快速行動並完成目標,你必須願意放棄以前完成的工作。要讓你的產品與衆不同,你需要愛上重構並走出舒適區。我認爲管理React狀態的許多選項都反映了這一點。有些選項需要很多樣板,有些則不需要。有些是構建好的,有些不是。跟着感覺走,將來隨時按需調整。

夢迴1999

這場演講的標題就很吸引人。1999年的編程是什麼樣的?那時我才九歲。有些人九歲就開始寫代碼,可惜我不是其中之一。

這場演講也是必看的。Lee Byron送上了一段精心打造的內容。他帶我們回到了PHP和LAMP堆棧成爲Web開發工具的年代。他向年輕人講述了Facebook開發React、GraphQL和Relay等工具之前走過的那些歷程。對於老一輩來說,那是值得懷念的時光。

我一直都很尊重Facebook所做的工程工作,而這場演講捋清了背後的成因。使用React就好像是一種特權,現在我知道這種感覺來自哪裏了,正是Lee這樣的人爲社區所做的一切激勵着我們前進。

連開發工具都和UX相關

第二天的第一場演講是Brian Vaughn做的。用React構建內容的開發人員大概都用過React Dev Tools。它們幫我擺脫了許多我自己製造出來的混亂局面。

React Dev Tools得到了全面的重寫,爲我們帶來:

  • 更好的性能
  • 新的API支持
  • 新的UX功能

看看,連開發工具都專注於提供出色的UX!

一項讓我印象深刻的改進是幫助團隊瀏覽之前不熟悉的項目。我們沒接觸過的代碼看起來肯定很陌生,但我們也都知道哪怕是自己寫的代碼時間久了也快不認識了。現在,我們可以看到props如何流過組件,可以過濾組件樹,可以深入檢查組件以及如何將hook與dev tool一起使用。另一個好東西是suspense開關。這個功能看起來很簡單,但價值不可估量。

再加上共享配置文件的改進,新的開發工具讓我們更容易找出渲染事物的起源。第三方也有類似的工具,但現在我們可以直接在開發工具中瞭解渲染背後的內容了。

其他內容還有不少,建議大家自行探索。

https://github.com/welldone-software/why-did-you-render

可 Suspense 的數據(Relay太棒了)

我在一個小項目裏一直在使用GraphQL,而且非常喜歡它。接下來我得試試Relay,看看它們的組合有怎樣的能量。

React Suspense的宗旨是讓我們能夠先顯示某些UI,而無需等待所有UI準備就緒。

來看一個組件的基本示例,該組件在獲取數據時顯示加載狀態(使用Suspense):

const Composer = (props) => {
const data = useQuery(graphql`
query ComposerQuery {
me {
photo {
uri
}
}
}
`, variables);

return (
<div>
<img src={data.me.photo.uri} />
</div>
);
}

const Home = (props) => (
<Suspense fallback={<Placeholder />}>
<Composer />
</Suspense>
);

在此示例中,我們有一個Composer組件,該組件可獲取個人資料圖片的URI,然後顯示它。你可以看到,在Home組件中我們將Composer包裝在一個Suspense塊中。然後在加載數據時將渲染Placeholder。這種模式可以被視爲“渲染時獲取”。
這種模式的加載順序如下:

如你所見,這使我們能輕鬆處理數據加載。我們可以回退到placeholder或spinner等加載組件來提供更好的用戶體驗。

上面的模式提供了很多好處和靈活性。但Facebook團隊認爲,想要找出組件所需的數據時沒必要先渲染組件。爲了解決這個問題,他們開始使用一種稱爲“獲取時渲染”的模式。

爲了實現“獲取時渲染”,Facebook團隊將useQuery分解爲兩部分,分別是preloadQuery和usePreloadedQuery。這到底是什麼意思呢?

preloadQuery

該API將獲取數據併爲結果提供參考,但它沒有提供實際數據。

你將在顯示新用戶界面的那個事件處理程序中調用此API。例如,如果用戶單擊鏈接,觸發導航到新頁面的動作,則我們處理單擊的事件處理程序將使用preloadQuery。另一個例子是將鼠標懸停在某處以打開工具提示。onHover事件處理程序將調用preloadQuery。

usePreloadedQuery

該API接受preloadQuery調用的結果。實際上它本身不會獲取任何數據。它查看preloadQuery的當前狀態。如果準備就緒,它將顯示結果;如果尚未準備就緒,它將暫停;如果查詢失敗,則可能引發錯誤。

無論發生什麼情況,usePreloadedQuery都不會觸發新的獲取,這使其高效且可預測。

使用這兩個API代替useQuery後,加載順序如下所示:

強烈建議大家聽聽Joe Savona的演講。他總結得更好更深入。這是我最喜歡的演講之一,因爲我對這種模式所帶來的可能性感到非常激動,並且迫不及待地想嘗試一下。

如小說般的 React

Jenn Creighton發表了我最喜歡的哲學演講。她從事創意寫作工作。創意寫作一直是我的最愛。像Jenn一樣,我曾經幻想成爲作家。她在演講中解釋了一個概念,這個概念以一種有趣(且出乎意料)的方式轉化爲編碼。

展示,不要講述

我們看一下傳達相同含義的兩種方法(Jenn講的例子)。

她累了。

她的步伐愈加艱難,一點點挪向牀邊,身體也越來越沉重了,最後一頭扎進了被褥裏。

表達她累壞了,同樣的意思,哪種表達更有力量?答案自不必說。我們軟件工程師經常會陷入講述的陷阱裏。我們抽象、抽象、抽象,直到抽乾一切。

大多數情況下,我會盡力避免代碼重複。這條原則自有其意義,但就像寫作不能循規蹈矩一樣,有時我們也需要打破軟件開發的規則。比較一下兩段結果相同的不同代碼。

const Nav = ({ links }) => (
<nav>
{
links.map(link => (
<Link to={link.to}>{link.name}</Link>
))
}
</nav>
);

const Header = () => {
const links = [
{ name: 'Home', to: '/home' },
{ name: 'Settings', to: '/settings' },
];

return (
<>
<Nav links={links} />
</>
);
}

這看起來很好用,但如果我們想添加一個導航項該怎麼辦? 例如一個登錄按鈕。

const links = [
{ name: 'Home', to: '/home' },
{ name: 'Settings', to: '/settings' },
{ name: 'Login', to: '??' },
];

我們的Nav組件無法處理這種情況。我們可以很容易地實現一種處理它的方法,但這也很容易失控。我們可以重構Nav組件,變成像這樣:

const Nav = ({ links }) => (
<nav>
{
links.map(link => {
return link.to
? <Link to={link.to}>{link.name}</Link>
: <a onClck={link.onClick}>{link.name}</Link>
})
}
</nav>
);

這樣也行,但我們得涵蓋多少邊緣情況?最後Nav組件的推理會不會太過複雜? 我們可以用另一種方式重寫Header組件。

const Header = () => {
const links = [
{ name: 'Home', to: '/home' },
{ name: 'Settings', to: '/settings' },
{ name: 'Login', to: '??' },
];

return (
<nav>
<Link to="/home">Home</link>
<Link to="/settings">Settings</link>
<a onClick={onSelectLogin}>Login</a>
<nav/>
);
}

我簡化了Jenn演講中的例子,但應該足夠說明問題了。第二個Header組件更容易推理。儘管我們現在可能會重複一點東西,但這並不會有多大壞處。如果我們想將Icon組件添加到其中一個鏈接中,則不必再處理Nav組件中的所有極端情況,只需將其添加到所需位置即可。
Jenn還引用了Neil Gaiman的妙語,我得給大家分享一下。

請記住,爲了做到完美,你遲早要放開當下的執念,然後繼續寫下一篇。

在構建心理健康寫作平臺Wrabit的時候,我一直在追求完美。有時候這讓我覺得自己不像一個開發人員,有時候讓我感到疲倦。最後我就只去關注那些自己可以理解、可以交付、將來隨時可以重構的內容了。

正如Jenn所說,重構並非失敗。

UX驅動的流體動畫

我做過的動畫不算太多。我設想的未來中,我將從Dribbble(動畫之類的東西)中獲得出色的UI設計,並用在實踐中。動畫絕對是優秀設計的重點所在,但我們玩動畫時也得牢記用戶體驗。

Alex Holachek的演講也拓寬了我的思路。我喜歡UI交互,它們讓我的內心感到溫暖舒適。但我並沒有考慮到所有用戶,這是我的錯。

對於使用諾基亞6的用戶來說,精美的動畫該怎麼展示出來呢?畢竟大多數人都會選擇購買中端機型甚至低端機型,而不是新款iPhone。

Alex的演講讓我更多去考慮大衆的水平。平均的設備性能,平均的數據帶寬,等等。爲大衆打造的產品,高端用戶是不會覺得不滿的。

另外,event.preventDefault()會對滾動產生不良影響。

真實的迭代體驗

今年的講座內容千姿百態。Luca Demasco與Zach Rispoli合作開發了Wick編輯器,向我們展示了迭代的過程,很新奇的話題。

Wick編輯器是一個免費的開源工具,可用於創建遊戲、動畫以及你能想到的任何東西。當Luca展示當前版本時,UI確實給我留下了深刻的印象,看起來很直觀,並且有很多功能。但一開始並不是這樣的。

Luca和朋友通過不斷的迭代才達到了今天的狀態。他們也不只是爲了迭代而迭代。他們將Wick帶入了許多環境(學校、圖書館等),並在許多類型的用戶(初學者、中級、年輕人、老人)面前展示了界面。他們採取了獲取,保留,擴張 (Laser-Focused Approach) 的方法,並收集了大量反饋,才讓Wick有了今天的成果。

在我考慮如何迭代自己的產品時,這種真實的過程給了我啓發。如何快速啓動項目並有目的地迭代?我沒有那種體驗,所以有時候會缺少信心,但我很希望能享受這個過程。看到像Luca這樣的人分享經驗使我備受鼓舞,我感謝大會上所有人的真誠分享

簡單事物的複雜性

你是否在用react-select?沒用過?其實你可能只是沒意識到而已。

這個組件在GitHub上有超過18000個星,每週有150萬次下載,好多啊。

你可能不會想到一個簡單的React組件會有那麼複雜。Jed Watson開發了一個漂亮的React組件,並很好地實現了它的目的。它有很多功能,並且開箱即用。

Jed走過了一段漫長(有時很艱苦)的道路,終於打造出了今天的react-select。他就開發廣泛流行的開源項目的過程分享了深刻的見解。他還告訴大家,爲什麼一件簡單的事情往往可能非常複雜。

當Jed展示了react-select到v2.0的演變時,我受到了啓發。他重申了重構的重要性,還講到如果你不再追求完美,就能做到很多很棒的事情。

優雅的透明度

政府支出是一個重大話題。我們應該看到我們的稅收去向,以便讓政府負責。

Lizzie Salita證明了政府網站可以高效、易用且美觀。當她演示USAspending.gov支出瀏覽器時,我真的很驚訝。再對比其加拿大版本,你就能知道React可以爲用戶體驗帶來多大的改變。

我正在慢慢開始涉足政治領域。儘管我一直在努力獲取信息,投出負責任的選票,但需要做的事情還要很多。諸如USAspending.gov之類的工具能讓參政變得更加輕鬆有趣。我認爲我們應該繼續開發這樣的工具,讓每個人都能瞭解最新情況,以便所有人都可以參與塑造我們的未來。

奇蹟驅動的開發

大會的最後一場演講真讓我震驚。Alex Anderson構建了一個瘋狂而複雜的飛船模擬器,稱爲Thorium

Thorium模擬器使諸如獅門航天中心之類的許多組織能夠爲各種人羣提供STEM相關的活動。這些空間中心使學生能夠通過高度互動和富含教育意義的小組活動來成長。

React Conf上的很多演講和與會者所做的優秀工作,是由美好的願望驅使的。Alex的工作之所以如此突出,是因爲他的熱情從他所說的每句話中迸發出來。他熱愛自己的工作,並且是一位非常有才華的工程師,他正在使用現代技術來爲兒童和成人建立良好的體驗。

Alex的演講最打動我的是奇蹟驅動開發的概念。你是否想過技術的可能性?想過你自己身上潛藏的能力嗎?

這些問題驅使我們建立有趣、愉快和真正美好的體驗。這些問題促使Facebook的工程師和世界各地的公司利用技術來塑造我們的世界。

我在今年的React Conf上,從所有人那裏學到了很多東西。我很高興能參加大會,感受到的是滿滿的驚奇與興奮。

我迫不及待地想看到這種感受能驅動我開發出怎樣的奇蹟!

原文鏈接https://blog.anthonymorris.dev/19-takeaways-from-react-conf-2019

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章