精讀《React Conf 2019 - Day2》

1 引言

這是繼 精讀《React Conf 2019 - Day1》 以後的第二篇,補充了 React Conf 2019 次日的內容。前端

2 概述 & 精讀

次日的內容更爲精彩,筆者會重點介紹比較乾貨的部分。react

Fast refresh

Fast refresh 是更好的 react-hot-loader 替代方案,目前僅支持 react-native 平臺,很快就會支持 react-dom 平臺。git

相比不支持 Function component、沒法錯誤恢復、更新常常失靈的 hot reloading 來講,fast refresh 還擁有如下幾個優勢:程序員

  • 狀態保持。
  • 支持 Function Component Hooks。
  • 更快的更新速度。

Fast refresh 更新速度更快,是基於 Function Component 生成了 「簽名」,從而最大成都避免銷燬重渲染,儘量保持對組件的 rerender 刷新。下面介紹簽名機制的工做原理。github

Fast refresh 對每一個 Function component 都生成了一份專屬簽名,用以描述這個組件核心狀態,當這個核心狀態改變時,就只能銷燬重渲染了,但對於不觸及核心的修改就能進行代價很是小的 rerender。spring

這個簽名包含了 hooks 和參數名:編程

// signature: "useState{isLoggedIn}"

function ExampleComponent() {
  const [isLoggedIn, setIsLoggedIn] = useState(true);
}
複製代碼

好比當參數名變動時,這個組件的邏輯已發生改動,此時只能銷燬並重渲染了。所以實際上經過對簽名的對比來判斷是否要銷燬並重刷新組件:react-native

// signature: "useState{isLoggedOut}"

function ExampleComponent() {
  const [isLoggedOut, setIsLoggedOut] = useState(true);
}
複製代碼

同理,當 hooks 從 useState 改爲了 useReducer,簽名也會發生變化從而致使完全的重渲染。設計模式

但除此以外,好比對樣式的修改、Dom 結構的修改都不會觸發簽名的變化,從而保證了 「對不觸及邏輯的改動進行高效的輕量 renreder」。性能優化

然而 Fast refresh 也有以下侷限性:

  • 還不能友好支持 Class component。
  • 混合導出 React 和非 React 組件時沒法精確的 hot reload。
  • 更高的內存要求。

能夠看到,Fast Refresh 隨着功能推廣與內置,如今已經覆蓋了 Facebook 95% 以上 hot reload 場景了:

這部份內容不只揭開了 hot reload 技術內幕,還對其功能進行了進一步優化,2019 年的 React 開發體系已經進入精細化階段。

重寫 React devtools

React devtools 的更新終於被正式介紹了,原本筆者覺得新的 devtools 只是支持了 hooks,但聽完分享後發現還有更多有用的改進,包括:

  • 更高的性能。
  • 更多特性支持。
  • 更好用戶體驗。

找到節點渲染鏈路

並非每一個 React 節點都參與渲染,新版 React devtools 能夠展現出 rendered by:

調試 Suspense

在 Day1 中講到的 Suspense 特性能夠在 React devtools 調試了:

經過點擊時鐘 icon,能夠模擬 Suspense 處於 pendding 或 ready 狀態。

加強調試能力

能夠經過點擊直接跳轉到組件源碼:

最新版已加強至點擊按鈕後直接經過 Source 打開源碼位置,這樣能夠快速經過 UI 尋找到代碼。同時還能夠看到,經過點擊 debugger 按鈕將當前組件信息打到控制檯調試。

除此以外還能夠動態修改組件的 props 與 hook state,大大加強了調試能力。

profiler

分析工具也獲得了加強,如今能夠看到每一個組件被渲染了幾回以及從新渲染的緣由:

好比上圖組件被渲染了 4 次,主要有兩個緣由:Hooks 改變與 Props 改變。

除此以外,還優化了更多細節體驗,好比高亮搜索、HOC 的展現優化、嵌套層級過多時不會佔用過多的橫向寬度等等。

react codemod

codemod 是一個代碼重構的方式,經過 AST 方式精準觸達代碼,咱們能夠認爲 codemod 是一個更聰明的「查找/替換」。

codemod 主要有如下三種使用方式:

  • 重命名。
  • 代碼排序。
  • 必定程度的代碼替換。

接下來就講到 react codemod 了,它是 react 場景的 codemod 解決方案,facebook 是這麼使用 react codemod 的:

  • 遷移 facebook 代碼。
  • 涉及幾萬個組件。
  • 修復了 3500 個文件的 React.PropTypes。
  • 修復了 8500 個文件的生命週期 unsafe。
  • 修復了 20000 個文件的 createClass 轉 JSX。

使用方式:

npx react-codemod React-PropTypes-to-prop-types
複製代碼

能夠看到,經過 cli 對文件進行一次性重構處理。除此以外,再列舉幾種使用場景:

  • create-element-to-jsx 將 React.createElement 轉換爲 JSX。
  • error-boundaries 將 unstable_handleError 改成 componentDidCatch
  • findDOMNode 將 React.createClassthis.getDOMNode() 改成 React.findDOMNode
  • sort-comp 將 Class Component 生命週期按照規範排序,eslint-plugin-react 插件也有相同能力。

理論上來說,全部 codemode 作的事情均可以替換爲 eslint 的 autofix 來完成,好比 sort-comp 就同時被 codemode 和 eslint 支持。

Suspense

要理解 Suspense,就要理解 Suspense 與普通 loading 有什麼區別。

從代碼角度來講,Suspense 能夠類比爲 try/catch 的體驗。爲了簡化代碼複雜度,咱們能夠用 try/catch 包裹代碼,從而簡化 try 區塊代碼複雜度,並將兜底代碼放在 catch 區塊:

try {
  // 只要考慮正確狀況
} catch {
  // 錯誤時 fallback
}
複製代碼

Suspense 也同樣,它在渲染 React 組件時若是遇到了 Promise 拋出的 Error,就會進入 fallback,因此 fallback 含義是 Loading 中狀態:

<Suspense fallback={<Spinner />}>
  <ProfilePage /> </Suspense>
複製代碼

與此同時,實際業務組件中的取數也不須要擔憂取數是否正在進行中,只要直接處理拿到數據的狀況就行了:

function ProfileDetails() {
  // 直接使用 user,不用擔憂失敗。
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}
複製代碼

進一步的,若是要處理組件渲染的異常,再使用 ErrorBoundary 包裹便可,此時的 fallback 含義是組件加載異常的錯誤狀態:

function Home(props) {
  return (
    <ErrorBoundary fallback={<ErrorMessage />}> <Suspense fallback={<Placeholder />}> <Composer /> </Suspense> </ErrorBoundary>
  );
}
複製代碼

Suspense 模式的取數好處是 「fetch on render」,即渲染與取數同時進行,而普通模式的取數是 「fetch after render」,即渲染完成後再經過 useEffect 取數,此時取數時機已晚。

隊列加載

假設 ComposerNewsFeed 組件內部都經過 useQuery 取數,那麼並行取數時加載機制以下:

這可能有兩個問題:組件內部加載順序不統一與組件間加載順序不統一。

若是組件內部有圖片,可能圖片與組件渲染實際不一致,此時能夠利用 Suspense 統一 hold 全部子組件的特性,將圖片加載改成 Suspense 模式:

<div>
  <YourImage src={uri} alt={...} /> <MoreComposer /> </div>
複製代碼

同一個 Suspense 能夠等待全部子元素都 Ready 後纔會一把渲染出 UI,所以能夠看到網頁被一次性刷新而不是分部刷新。

第二個問題是組件間加載順序不統一,可能致使先渲染了文章內容,再渲染出文章頭部,此時若是區塊高度不固定,文章頭部可能會撐開,致使文章內容下移,用戶的閱讀體驗會遭到打斷。能夠經過 suspense ordering 解決這個問題:

function Home(props) {
  return (
    <SuspenseList revealOrder="forwards"> <Suspense fallback={<ComposerFallback />}> <Composer /> </Suspense> <Suspense fallback={<FeedFallback />}> <NewsFeed /> </Suspense> </SuspenseList>
  );
}
複製代碼

好比 forwards 表示從上到下,那麼必定會先渲染頭部再渲染文章內容,這樣文章內容就不會都抖動了。

Render as you fetch

相比 「fetch on render」,更高級別的優化是 「Render as you fetch」,即取數在渲染時機以前。

好比頁面路由的跳轉、Hover 到一個區塊,此時若是取數由這個動做觸發,就能夠再次將取數時機提早,Facebook 爲此創造了一個新的 Hook:usePreloadedQuery

用法是,在某個事件中取數,好比點擊頁面跳轉按鈕時,經過 preloadQuery 預取數,獲得的結果並非取數結果,而是一個標識,在渲染組件中,把這個標識傳給 usePreloadedQuery 能夠拿到真實取數結果:

// 組件 A 的 onClick
const reference = preloadQuery(query, variables);
// 組件 B 的 render
const data = usePreloadedQuery(query, reference);
複製代碼

能夠看到,取數真正觸發的時機在渲染函數執行以前,因此在 usePreloadedQuery 調用時取數確定已經在路上,甚至已經完成。相比之下,普通的 useQuery 函數存在下面幾個問題:

  • 因爲取數過程存在狀態變化,可能致使組件在 「取數無心義」 狀態下從新渲染屢次。
  • 可能取數還未完成就觸發重渲染。
    • 沒有取消的機制,沒有清除結果的機制。
  • 沒有辦法惟一標識組件。

preloadQuery 的好處就是將取數時機與 UI 分離,這樣能夠更細粒度的控制邏輯:

  • 調用 preloadQuery 時:
    • 在組件銷燬時取消取數。
    • 有新取數觸發時取消取數。
    • 銷燬一些輪詢機制。
  • 渲染組件調用 usePreloadedQuery 時:
    • 不會再觸發取數,不會觸發意外的 re-render。
    • 不須要清空,由於取數不在這裏發起。
    • 不須要清理輪詢。

可見 preloadQuery 相比 useQuery 的確有了一些體驗提高,然而這個優化比較追求極致,對大部分國內項目來講可能還走不到 facebook 這麼極致的性能優化,因此投入產出比顯得不是那麼高,並且這個開發方式對開發者不是太友好,由於它讓請求的時機割裂到兩個模塊中。

但畢竟用戶體驗是大於開發者體驗的,React 儘可能經過提升開發者體驗來間接提升用戶體驗,使雙方都滿意,但像 preloadQuery 就沒法二者兼顧了,爲了用戶體驗能夠適當的下降一些開發者體驗。

如何維護代碼

這個分享講述瞭如何提高代碼維護效率,畢竟一個月後可能連本身寫的代碼都看不懂了。hydrosquall 經過類比地圖的方式解釋了程序員是如何維護代碼的。

首先看咱們是如何認路的。認路分爲三個層次:

  • 隨意走走。
    • 經過一些地標判斷方向。
  • 有方向的尋路。
    • 經過跟隨同伴或者瞭解更多本地信息找到目的地。
  • 地圖。
    • 經過 GPS 定位。
    • 經過模擬地圖方式指出路線。

能夠看到這三種方式是逐層遞進的,那麼類比到代碼就有意思了:

  • 隨意走走(滾動查看源代碼 + ctrl/f 查找代碼 + grep 搜索)。
    • 入口(找到入口節點,查看數據結構)。
    • 標記(查看代碼註釋、查看 README)。
    • 發信號彈(斷點、console.log 等調試行爲)
  • 找到方向。
    • git blame 查看 owner,或直接根據文檔找到 codeowners。
  • 地圖。
    • 幸運的話你能夠找到一份架構流程圖。

能夠看到,地圖有幾種抽象層次,好比忽略了細節的紐約地鐵線路圖:

或者是包含豐富地面信息的地鐵線路圖:

抽象到什麼層次取決於用戶使用的場景,那麼代碼抽象也是如此。hydrosquall 作了一個工具自動分析出代碼調用關係:js-callgraph

這就像路牌同樣,能夠更高效的看出代碼結構,也包括了數據流結構,因爲篇幅限制,感興趣的同窗能夠看 原視頻 瞭解更多。

寫做與寫代碼

本章講了寫做(小說)與寫代碼的關聯,總結出以下幾個重點:

  • 寫小說和寫代碼都是創造行爲。
  • 寫代碼須要抽象思惟,寫小說也要有抽象思惟構造人物和情節。
  • Show, don't tell,寫做自然就是申明式的,和數據驅動很類似。

更多能夠去看 原視頻

移動端動畫最佳實踐

首先要使用一個真實的手機設備調試,不然可能出現 PC Chrome 一切正常,而手機上實際效果性能不好的狀況!

手勢下拉退出

利用 react-springreact-use-gesture 作一個下滑消失的 Demo:

import { animated, useSpring } from "react-spring";
import { useDrag } from "react-use-gesture";

const [{ y }, set] = useSpring(() => {
  y: 0;
});
複製代碼

首先定義一個 y 縱向位置,經過 useDrag 將拖拽操做與 UI 綁定,經過回調將其與 y 數據綁定:

const bind = useDrag(({ last, movement: [, movementY], memo = y.value }) => {
  if (last) {
    // 拖拽結束時,若是偏移量超過 50 則效果和結束同樣,直接將 y 設置爲 100
    const notificationClosed = movementY > 50;

    return set({
      y: notificationClosed ? 100 : 0,
      onReset: notificationClosed && removeNotification
    });
  }

  // y 的位置區間在 0~100
  set([{ y: clamp(0, 100, memo + movementY) }]);

  return memo;
});
複製代碼

useDragy 綁定後,就能夠用在 UI 組件上了:

<StyledNotification
  as={animated.div}
  onTouchStart={bind().onTouchStart}
  style={{
    opacity: y.interpolate([0, 100], [1, 0]),
    transform: y.interpolate(y => `translateY(${y}px)`)
  }}
/>
複製代碼

opacitytransform 與位置 y 綁定就能夠作出下拉消失的效果。

滑動的洞見

接着講到了滑動的三個洞見:

  1. 要馬上響應,任何延遲都會形成用戶額外精神負擔。
  2. 滾動速度衰減能夠提高用戶體驗:

接着咱們須要預測用戶的意圖,好比在一個相似微信消息列表頁左右滑動時:

  • 是否想取消手勢交互?
  • 是否想展現出更多交互按鈕?
  • 是否想刪除全部內容?

這須要更多設計思考。

  1. 橡皮筋滾動,即列表頁能夠一直向下拉,上面部分像橡皮筋同樣能夠被拉出空白頁的效果。

在設計手勢動畫時要考慮三個要點:

  • 使用移動增量做爲手勢動畫的基準點。
  • 動畫和手勢應該隨時能夠被中斷,經過 springs 便可實現。
  • 完成手勢後的動畫速度應該與手勢速度至關,這樣視覺體驗更天然。

最後提到了動畫兼容性與性能,好比儘可能只使用 transformopacity 能夠保證移動端的流暢度,不一樣移動設備的默認手勢效果不一樣,最好經過 touch-action 禁用默認行爲以達到更好的兼容性與效果。

唱片與 React

J.Dash 擁有十年軟件開發經驗,同時也賣過不少唱片,他介紹了唱片行業與軟件開發的共同點。

唱片行業須要音樂編排能力,這與編碼能力相似,都存在良好的設計模式,而且須要團隊合做,開發過程當中會遇到一些痛苦的經歷,但最終完成音樂和項目時都會得到知足的喜悅。

函數式編程

Declaratives UIs are the future, and the future is Comonadic. - Phil Freeman

申明式 UI 是將來,將來則是 Comonadic。

所謂申明式 UI 能夠用下面的公式表達:

type render = (state: State) => View;
複製代碼

而後用一段公式介紹了 Comonadic:

class Functor w => Comonad w where
  extract   :: w a -> a
  duplicate :: w a -> w (w a)
  extend    :: (w a -> a) -> w a -> w b
複製代碼

用 JS 版本作一個解釋:

const Store = ({ state, render }) => ({
  extend: f => Store({ state, render: state => f(Store({ state, render })) }),
  extract: () => render(state)
});
複製代碼

extract 調用後會進行申明式渲染 UI,即 render(state)

extend 表示拓展,接收一個拓展函數做爲參數,返回一個新的 Store 對象。這個拓展函數能夠拿到 staterender 並返回新的 state 做爲 extractrender 的輸入。使用例子是這樣的:

const App = Store({
  state: { msg: "World" },
  render: ({ msg }) => <p>Hello {msg}</p>
});

App.extend(({ state }) =>
  state.msg === "World" ? { msg: "ReactConf" } : state
).extract(); // <p> Hello ReactConf </p>
複製代碼

然而尷尬的是,筆者看了好久也沒看懂 Store 函數,最後運行了一下發現這個 Demo 拋出了異常 😂。

下面是筆者稍微修改後的例子,至少能跑起來:

const Store = ({ state, render }) => ({
  extend: f => Store({ state, render: state => render(f({ state, render })) }),
  extract: () => render(state)
});

const app = Store({
  state: { msg: "Hello World" },
  render: ({ msg }) => console.log("render " + msg)
});

app
  .extend(({ state }) => {
    return { msg: state.msg + " extend1" };
  })
  .extend(({ state }) => {
    return { msg: state.msg + " extend2" };
  })
  .extract(); // render Hello World extend2 extend1
複製代碼

然而做者的意思還是未解之謎,但願對函數式瞭解的同窗能夠在評論區指點一下。

wick editor

wick editor 是一個開源的動畫、遊戲製做軟件。

wick editor 是一個動畫製做工具,但拓展了一些 js 編程能力,所以能夠很好的將動畫與遊戲結合在一塊兒:

演講介紹了 wick editor 的演化過程:

從很簡陋的 MVP 版本開始(1 周)

到 Pre-Alpha(4 月)

Alpha(5 月)

Beta(1.5 年)

重點是 1.0 版本採用 React 重寫了!繼 Beta 以後又經歷了 1 年:

這個團隊最棒的地方是,將遊戲與教育結合,針對不一樣場景作了不少用戶調研並根據反饋持續改進。

React Select

react-select 的做者 Jed Watson 被請來啦。做爲一個看上去很簡單組件(select)的開發者,卻擁有如此大的關注量(1.8w star),那做者有着怎樣的心路歷程呢?

react-select 看似簡單的名字背後其實有挺多的功能,好比做者列舉了一些功能層面的內容:

  • autocomplete - 輸入時搜索。
  • 單、多選。
  • focus 管理。
  • 下拉框層級與位置,好比能夠放在根 DOM 節點,也能夠做爲當前節點的子元素。
  • 異步下拉框內容。
  • 鍵盤、觸控。
  • Createble,即在搜索時若是沒有內容能夠動態建立。
  • 等等。

在設計層面:

  • 申明式。
  • 能夠被定製。
  • 性能要求。
  • 等等。

隨着 Star 逐漸上漲,愈來愈多的需求被提出,核心庫代碼量愈來愈大,甚至許多需求之間都是相互衝突的,並且做者天天都會被上百個 Issue 與 PR 吵醒。作一個業務 Select 可能只要 5 分鐘,但作一個開源 Select 卻要 5 年,緣由是一個簡單的 Select 如何知足全部不一樣業務場景?這絕對是個巨大的挑戰。

好比用戶即須要受控也要非受控的組件,如何知足好這個需求同時又讓代碼更可維護呢?

假設咱們擁有一個受控的組件 SelectComponent,那麼它的主要 props 是 valueonChange,若是要拓展成一個既支持 defaultValue(非受控)又支持 value(受控)的組件,咱們能夠建立一個 manageState 組件對 SelectComponent 進行封裝:

const manageState = SelectComponent => ({
  value: valueProps,
  onChange: onChangeProp,
  defaultValue,
  ...props
}) => {
  const [valueState, setValue] = useState(defaultValue);

  const value = valueProps !== undefined ? valueProps : valueState;

  const onChange = (newValue, actionMeta) => {
    if (typeof onChangeProp === "function") {
      onChangeProp(newValue, actionMeta);
    }
    setValue(newValue);
  };

  return <SelectComponent {...props} value={value} onChange={onChange}> }; 複製代碼

這樣就能夠組合爲一個受控/非受控的綜合 Select 組件:

import BaseSelect from "./Select";
import manageState from "./manageState";

export default manageState(Select);
複製代碼

同理對異步的封裝也能夠放在 makeAsync 函數中:

const makeAsync = SelectComponent => ({
  getOptions,
  defaultOptions,
  ...props
}) => {
  const [options, setOptions] = useState(defaultOptions);
  const [isLoading, setIsLoading] = useState(false);

  const onInputChange = async newValue => {
    setIsLoading(true);
    const newOptions = await getOptions(newValue);
    setIsLoading(false);
    setOptions(newOptions);
  };

  return (
    <SelectComponent {...props} options={options} isLoading={isLoading} onInputChange={onInputChange} /> ); }; 複製代碼

能夠看到,SelectComponent 是一個徹底受控的數據驅動的 UI,不管是 manageState 仍是 makeAsync 都是對數據處理的拓展,因此這三者之間才能夠融洽的組合:

import BaseSelect from "./Select";
import manageState from "./manageState";
import makeAsync from "./async";

export default manageState(Select);

export const AsyncSelect = manageState(makeAsync(Select));
複製代碼

後面還有一些風格化、開源協做的思考,這裏就不展開了,對這部分感興趣的同窗能夠查看原視頻瞭解更多。

React + 政府財政透明項目

usaspending.gov 這個網站使用 React 建設,能夠查看美國政府支持財政的明細,經過流暢的體驗讓更多用戶能夠了解國家財政支出,進一步推進財政支出的透明化。因爲並不涉及前端技術的介紹,主要是產品介紹,所以精讀就不詳細展開了。

順便說一句,智能分析數據就用 QuickBI,QuickBI 是咱們團隊研發的一款智能 BI 服務平臺,若是你將美國政府的財政支持做爲數據集輸入,你會分析得更透徹。

React + 星艦模擬器

最後介紹的是使用 React 製做的星艦模擬器,看上去像一個遊戲:

有星系圖、船體、駕駛員信息、武器裝備、燃料、通訊等等內容。甚至能夠模擬太空駕駛,進行任務,能夠實時多人協同。對太空迷們的吸引力很大,感興趣的同窗建議直接觀看 視頻

3 總結

次日的內容很是全面,涉及了 React API、開發者周邊、codemod 工具、代碼維護、寫做/音樂與代碼、動畫、函數式編程、看似簡單的 React 組件、使用 React 製做的各類腦洞大開的項目,等等。

React Conf 要展現的是一個完整的 React 世界,第一天提到了 React 是一個橋樑,正由於這個橋樑,鏈接了各行各業不一樣的人羣以及不一樣的項目,你們都有一個共同的語言:React。

"We not only react code, but react the world"。

討論地址是:精讀《React Conf 2019 - Day2》 · Issue #217 · dt-fe/weekly

若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公衆號

版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章
相關標籤/搜索