這是繼 精讀《React Conf 2019 - Day1》 以後的第二篇,補充了 React Conf 2019 次日的內容。前端
次日的內容更爲精彩,筆者會重點介紹比較乾貨的部分。react
Fast refresh 是更好的 react-hot-loader 替代方案,目前僅支持 react-native 平臺,很快就會支持 react-dom 平臺。git
相比不支持 Function component、沒法錯誤恢復、更新常常失靈的 hot reloading 來講,fast refresh 還擁有如下幾個優勢:程序員
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 也有以下侷限性:
能夠看到,Fast Refresh 隨着功能推廣與內置,如今已經覆蓋了 Facebook 95% 以上 hot reload 場景了:
這部份內容不只揭開了 hot reload 技術內幕,還對其功能進行了進一步優化,2019 年的 React 開發體系已經進入精細化階段。
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 的展現優化、嵌套層級過多時不會佔用過多的橫向寬度等等。
codemod 是一個代碼重構的方式,經過 AST 方式精準觸達代碼,咱們能夠認爲 codemod 是一個更聰明的「查找/替換」。
codemod 主要有如下三種使用方式:
接下來就講到 react codemod 了,它是 react 場景的 codemod 解決方案,facebook 是這麼使用 react codemod 的:
使用方式:
npx react-codemod React-PropTypes-to-prop-types
複製代碼
能夠看到,經過 cli 對文件進行一次性重構處理。除此以外,再列舉幾種使用場景:
React.createElement
轉換爲 JSX。unstable_handleError
改成 componentDidCatch
。React.createClass
中 this.getDOMNode()
改成 React.findDOMNode
。理論上來說,全部 codemode 作的事情均可以替換爲 eslint 的 autofix 來完成,好比 sort-comp 就同時被 codemode 和 eslint 支持。
要理解 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
取數,此時取數時機已晚。
隊列加載
假設 Composer
與 NewsFeed
組件內部都經過 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
表示從上到下,那麼必定會先渲染頭部再渲染文章內容,這樣文章內容就不會都抖動了。
相比 「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 相比 useQuery 的確有了一些體驗提高,然而這個優化比較追求極致,對大部分國內項目來講可能還走不到 facebook 這麼極致的性能優化,因此投入產出比顯得不是那麼高,並且這個開發方式對開發者不是太友好,由於它讓請求的時機割裂到兩個模塊中。
但畢竟用戶體驗是大於開發者體驗的,React 儘可能經過提升開發者體驗來間接提升用戶體驗,使雙方都滿意,但像 preloadQuery 就沒法二者兼顧了,爲了用戶體驗能夠適當的下降一些開發者體驗。
這個分享講述瞭如何提高代碼維護效率,畢竟一個月後可能連本身寫的代碼都看不懂了。hydrosquall 經過類比地圖的方式解釋了程序員是如何維護代碼的。
首先看咱們是如何認路的。認路分爲三個層次:
能夠看到這三種方式是逐層遞進的,那麼類比到代碼就有意思了:
能夠看到,地圖有幾種抽象層次,好比忽略了細節的紐約地鐵線路圖:
或者是包含豐富地面信息的地鐵線路圖:
抽象到什麼層次取決於用戶使用的場景,那麼代碼抽象也是如此。hydrosquall 作了一個工具自動分析出代碼調用關係:js-callgraph
這就像路牌同樣,能夠更高效的看出代碼結構,也包括了數據流結構,因爲篇幅限制,感興趣的同窗能夠看 原視頻 瞭解更多。
本章講了寫做(小說)與寫代碼的關聯,總結出以下幾個重點:
更多能夠去看 原視頻。
首先要使用一個真實的手機設備調試,不然可能出現 PC Chrome 一切正常,而手機上實際效果性能不好的狀況!
手勢下拉退出
利用 react-spring 和 react-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;
});
複製代碼
將 useDrag
與 y
綁定後,就能夠用在 UI 組件上了:
<StyledNotification
as={animated.div}
onTouchStart={bind().onTouchStart}
style={{
opacity: y.interpolate([0, 100], [1, 0]),
transform: y.interpolate(y => `translateY(${y}px)`)
}}
/>
複製代碼
將 opacity
與 transform
與位置 y
綁定就能夠作出下拉消失的效果。
滑動的洞見
接着講到了滑動的三個洞見:
接着咱們須要預測用戶的意圖,好比在一個相似微信消息列表頁左右滑動時:
這須要更多設計思考。
在設計手勢動畫時要考慮三個要點:
最後提到了動畫兼容性與性能,好比儘可能只使用 transform
與 opacity
能夠保證移動端的流暢度,不一樣移動設備的默認手勢效果不一樣,最好經過 touch-action
禁用默認行爲以達到更好的兼容性與效果。
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 對象。這個拓展函數能夠拿到 state
、render
並返回新的 state
做爲 extract
時 render
的輸入。使用例子是這樣的:
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 是一個動畫製做工具,但拓展了一些 js 編程能力,所以能夠很好的將動畫與遊戲結合在一塊兒:
演講介紹了 wick editor 的演化過程:
從很簡陋的 MVP 版本開始(1 周)
到 Pre-Alpha(4 月)
Alpha(5 月)
Beta(1.5 年)
重點是 1.0 版本採用 React 重寫了!繼 Beta 以後又經歷了 1 年:
這個團隊最棒的地方是,將遊戲與教育結合,針對不一樣場景作了不少用戶調研並根據反饋持續改進。
react-select 的做者 Jed Watson 被請來啦。做爲一個看上去很簡單組件(select)的開發者,卻擁有如此大的關注量(1.8w star),那做者有着怎樣的心路歷程呢?
react-select 看似簡單的名字背後其實有挺多的功能,好比做者列舉了一些功能層面的內容:
在設計層面:
隨着 Star 逐漸上漲,愈來愈多的需求被提出,核心庫代碼量愈來愈大,甚至許多需求之間都是相互衝突的,並且做者天天都會被上百個 Issue 與 PR 吵醒。作一個業務 Select 可能只要 5 分鐘,但作一個開源 Select 卻要 5 年,緣由是一個簡單的 Select 如何知足全部不一樣業務場景?這絕對是個巨大的挑戰。
好比用戶即須要受控也要非受控的組件,如何知足好這個需求同時又讓代碼更可維護呢?
假設咱們擁有一個受控的組件 SelectComponent
,那麼它的主要 props 是 value
與 onChange
,若是要拓展成一個既支持 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));
複製代碼
後面還有一些風格化、開源協做的思考,這裏就不展開了,對這部分感興趣的同窗能夠查看原視頻瞭解更多。
usaspending.gov 這個網站使用 React 建設,能夠查看美國政府支持財政的明細,經過流暢的體驗讓更多用戶能夠了解國家財政支出,進一步推進財政支出的透明化。因爲並不涉及前端技術的介紹,主要是產品介紹,所以精讀就不詳細展開了。
順便說一句,智能分析數據就用 QuickBI,QuickBI 是咱們團隊研發的一款智能 BI 服務平臺,若是你將美國政府的財政支持做爲數據集輸入,你會分析得更透徹。
最後介紹的是使用 React 製做的星艦模擬器,看上去像一個遊戲:
有星系圖、船體、駕駛員信息、武器裝備、燃料、通訊等等內容。甚至能夠模擬太空駕駛,進行任務,能夠實時多人協同。對太空迷們的吸引力很大,感興趣的同窗建議直接觀看 視頻。
次日的內容很是全面,涉及了 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 許可證)