这是继 精读《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 许可证)