## 1 引言

这是继 [精读《React Conf 2019 - Day1》](https://github.com/dt-fe/weekly/blob/v2/127.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Conf%202019%20-%20Day1%E3%80%8B.md) 之后的第二篇，补充了 React Conf 2019 第二天的内容。

## 2 概述 & 精读

第二天的内容更为精彩，笔者会重点介绍比较干货的部分。

### Fast refresh

[Fast refresh](https://facebook.github.io/react-native/docs/fast-refresh) 是更好的 react-hot-loader 替代方案，目前仅支持 react-native 平台，很快就会支持 react-dom 平台。

相比不支持 Function component、无法错误恢复、更新经常失灵的 hot reloading 来说，fast refresh 还拥有以下几个优点：

- 状态保持。
- 支持 Function Component Hooks。
- 更快的更新速度。

Fast refresh 更新速度更快，是基于 Function Component 生成了 “签名”，从而最大成都避免销毁重渲染，尽可能保持对组件的 rerender 刷新。下面介绍签名机制的工作原理。

Fast refresh 对每个 Function component 都生成了一份专属签名，用以描述这个组件核心状态，当这个核心状态改变时，就只能销毁重渲染了，但对于不触及核心的修改就能进行代价非常小的 rerender。

这个签名包含了 hooks 和参数名：

```js
// signature: "useState{isLoggedIn}"

function ExampleComponent() {
  const [isLoggedIn, setIsLoggedIn] = useState(true);
}
```

比如当参数名变更时，这个组件的逻辑已发生改动，此时只能销毁并重渲染了。因此实际上通过对签名的对比来判断是否要销毁并重刷新组件：

```js
// 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 场景了：

<img width=500 src="https://img.alicdn.com/tfs/TB1bjm1mYr1gK0jSZR0XXbP8XXa-1598-1018.png">

这部分内容不仅揭开了 hot reload 技术内幕，还对其功能进行了进一步优化，2019 年的 React 开发体系已经进入精细化阶段。

### 重写 React devtools

React devtools 的更新终于被正式介绍了，本来笔者以为新的 devtools 只是支持了 hooks，但听完分享后发现还有更多有用的改进，包括：

- 更高的性能。
- 更多特性支持。
- 更好用户体验。

**找到节点渲染链路**

并不是每个 React 节点都参与渲染，新版 React devtools 可以展示出 rendered by：

<img width=500 src="https://img.alicdn.com/tfs/TB1OSWomV67gK0jSZPfXXahhFXa-2354-668.png">

**调试 Suspense**

在 Day1 中讲到的 Suspense 特性可以在 React devtools 调试了：

<img width=500 src="https://img.alicdn.com/tfs/TB1W790m4D1gK0jSZFsXXbldVXa-1816-660.png">

通过点击时钟 icon，可以模拟 Suspense 处于 pendding 或 ready 状态。

**增强调试能力**

可以通过点击直接跳转到组件源码：

<img width=500 src="https://img.alicdn.com/tfs/TB1IBK1m4n1gK0jSZKPXXXvUXXa-1806-772.png">

最新版已增强至点击按钮后直接通过 Source 打开源码位置，**这样可以快速通过 UI 寻找到代码**。同时还可以看到，通过点击 debugger 按钮将当前组件信息打到控制台调试。

除此之外还可以动态修改组件的 props 与 hook state，大大增强了调试能力。

**profiler**

分析工具也得到了增强，现在可以看到每个组件被渲染了几次以及重新渲染的原因：

<img width=500 src="https://img.alicdn.com/tfs/TB1cla1m7P2gK0jSZPxXXacQpXa-1824-690.png">

比如上图组件被渲染了 4 次，主要有两个原因：Hooks 改变与 Props 改变。

除此之外，还优化了更多细节体验，比如高亮搜索、HOC 的展示优化、嵌套层级过多时不会占用过多的横向宽度等等。

### react codemod

codemod 是一个代码重构的方式，通过 AST 方式精准触达代码，我们可以认为 codemod 是一个更聪明的“查找/替换”。

codemod 主要有以下三种使用方式：

- 重命名。
- 代码排序。
- 一定程度的代码替换。

接下来就讲到 [react codemod](https://github.com/reactjs/react-codemod) 了，它是 react 场景的 codemod 解决方案，facebook 是这么使用 react codemod 的：

- 迁移 facebook 代码。
- 涉及几万个组件。
- 修复了 3500 个文件的 React.PropTypes。
- 修复了 8500 个文件的生命周期 unsafe。
- 修复了 20000 个文件的 createClass 转 JSX。

使用方式：

```bash
npx react-codemod React-PropTypes-to-prop-types
```

可以看到，通过 cli 对文件进行一次性重构处理。除此之外，再列举几种使用场景：

- create-element-to-jsx 将 `React.createElement` 转换为 JSX。
- error-boundaries 将 `unstable_handleError` 改为 `componentDidCatch`。
- findDOMNode 将 `React.createClass` 中 `this.getDOMNode()` 改为 `React.findDOMNode`。
- sort-comp 将 Class Component 生命周期按照规范排序，[eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md) 插件也有相同能力。

理论上来讲，所有 codemode 做的事情都可以替换为 eslint 的 autofix 来完成，比如 sort-comp 就同时被 codemode 和 eslint 支持。

### Suspense

要理解 Suspense，就要理解 Suspense 与普通 loading 有什么区别。

从代码角度来说，Suspense 可以类比为 `try/catch` 的体验。为了简化代码复杂度，我们可以用 `try/catch` 包裹代码，从而简化 try 区块代码复杂度，并将兜底代码放在 catch 区块：

```js
try {
  // 只要考虑正确情况
} catch {
  // 错误时 fallback
}
```

Suspense 也一样，它在渲染 React 组件时如果遇到了 Promise 抛出的 Error，就会进入 `fallback`，所以 `fallback` 含义是 Loading 中状态：

```jsx
<Suspense fallback={<Spinner />}>
  <ProfilePage />
</Suspense>
```

与此同时，实际业务组件中的取数也不需要担心取数是否正在进行中，只要直接处理拿到数据的情况就好了：

```jsx
function ProfileDetails() {
  // 直接使用 user，不用担心失败。
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}
```

进一步的，如果要处理组件渲染的异常，再使用 `ErrorBoundary` 包裹即可，此时的 `fallback` 含义是组件加载异常的错误状态：

```jsx
function Home(props) {
  return (
    <ErrorBoundary fallback={<ErrorMessage />}>
      <Suspense fallback={<Placeholder />}>
        <Composer />
      </Suspense>
    </ErrorBoundary>
  );
}
```

Suspense 模式的取数好处是 “fetch on render”，即渲染与取数同时进行，而普通模式的取数是 “fetch after render”，即渲染完成后再通过 `useEffect` 取数，此时取数时机已晚。

**队列加载**

假设 `Composer` 与 `NewsFeed` 组件内部都通过 `useQuery` 取数，那么并行取数时加载机制如下：

<img width=500 src="https://img.alicdn.com/tfs/TB1AonZm7L0gK0jSZFtXXXQCXXa-1770-778.png">

这可能有两个问题：组件内部加载顺序不统一与组件间加载顺序不统一。

如果组件内部有图片，可能图片与组件渲染实际不一致，此时可以利用 Suspense 统一 hold 所有子组件的特性，将图片加载改为 Suspense 模式：

```jsx
<div>
  <YourImage src={uri} alt={...} />
  <MoreComposer />
</div>
```

同一个 Suspense 可以等待所有子元素都 Ready 后才会一把渲染出 UI，因此可以看到网页被一次性刷新而不是分部刷新。

第二个问题是组件间加载顺序不统一，可能导致先渲染了文章内容，再渲染出文章头部，此时如果区块高度不固定，文章头部可能会撑开，导致文章内容下移，用户的阅读体验会遭到打断。可以通过 `suspense ordering` 解决这个问题：

```jsx
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` 可以拿到真实取数结果：

```js
// 组件 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](http://github.com/hydrosquall) 通过类比地图的方式解释了程序员是如何维护代码的。

首先看我们是如何认路的。认路分为三个层次：

- 随意走走。
  - 通过一些地标判断方向。
- 有方向的寻路。
  - 通过跟随同伴或者了解更多本地信息找到目的地。
- 地图。
  - 通过 GPS 定位。
  - 通过模拟地图方式指出路线。

可以看到这三种方式是逐层递进的，那么类比到代码就有意思了：

- 随意走走（滚动查看源代码 + ctrl/f 查找代码 + grep 搜索）。
  - 入口（找到入口节点，查看数据结构）。
  - 标记（查看代码注释、查看 README）。
  - 发信号弹（断点、console.log 等调试行为）
- 找到方向。
  - git blame 查看 owner，或直接根据文档找到 codeowners。
- 地图。
  - 幸运的话你可以找到一份架构流程图。

可以看到，地图有几种抽象层次，比如忽略了细节的纽约地铁线路图：

<img width=400 src="https://img.alicdn.com/tfs/TB14k7pmYr1gK0jSZR0XXbP8XXa-1014-702.png">

或者是包含丰富地面信息的地铁线路图：

<img width=400 src="https://img.alicdn.com/tfs/TB1Sbwpm.T1gK0jSZFrXXcNCXXa-692-750.png">

抽象到什么层次取决于用户使用的场景，那么代码抽象也是如此。[hydrosquall](http://github.com/hydrosquall) 做了一个工具自动分析出代码调用关系：[js-callgraph](https://github.com/persper/js-callgraph)

![](https://img.alicdn.com/tfs/TB1HsRRmubviK0jSZFNXXaApXXa-2042-592.png)

这就像路牌一样，可以更高效的看出代码结构，也包括了数据流结构，由于篇幅限制，感兴趣的同学可以看 [原视频](https://youtu.be/JDDxR1a15Yo?t=6579) 了解更多。

### 写作与写代码

本章讲了写作（小说）与写代码的关联，总结出如下几个重点：

- 写小说和写代码都是创造行为。
- 写代码需要抽象思维，写小说也要有抽象思维构造人物和情节。
- Show, don't tell，写作天然就是申明式的，和数据驱动很相似。

更多可以去看 [原视频](https://youtu.be/JDDxR1a15Yo?t=9135)。

### 移动端动画最佳实践

首先要使用一个真实的手机设备调试，否则可能出现 PC Chrome 一切正常，而手机上实际效果性能很差的情况！

**手势下拉退出**

利用 [react-spring](https://github.com/react-spring/react-spring) 和 [react-use-gesture](react-use-gesture) 做一个下滑消失的 Demo：

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

const [{ y }, set] = useSpring(() => {
  y: 0;
});
```

首先定义一个 `y` 纵向位置，通过 `useDrag` 将拖拽操作与 UI 绑定，通过回调将其与 `y` 数据绑定：

```js
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 组件上了：

```jsx
<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` 绑定就可以做出下拉消失的效果。

**滑动的洞见**

接着讲到了滑动的三个洞见：

1. 要立刻响应，任何延迟都会造成用户额外精神负担。
2. 滚动速度衰减可以提升用户体验:

<img width=500 src="https://img.alicdn.com/tfs/TB1HocDm1H2gK0jSZJnXXaT1FXa-1348-878.gif">

接着我们需要预测用户的意图，比如在一个类似微信消息列表页左右滑动时：

- 是否想取消手势交互？
- 是否想展示出更多交互按钮？
- 是否想删除所有内容？

这需要更多设计思考。

1. 橡皮筋滚动，即列表页可以一直向下拉，上面部分像橡皮筋一样可以被拉出空白页的效果。

在设计手势动画时要考虑三个要点：

- 使用移动增量作为手势动画的基准点。
- 动画和手势应该随时可以被中断，通过 springs 即可实现。
- 完成手势后的动画速度应该与手势速度相当，这样视觉体验更自然。

最后提到了动画兼容性与性能，比如尽量只使用 `transform` 与 `opacity` 可以保证移动端的流畅度，不同移动设备的默认手势效果不同，最好通过 `touch-action` 禁用默认行为以达到更好的兼容性与效果。

### 唱片与 React

J.Dash 拥有十年软件开发经验，同时也卖过很多唱片，他介绍了唱片行业与软件开发的共同点。

唱片行业需要音乐编排能力，这与编码能力类似，都存在良好的设计模式，并且需要团队合作，开发过程中会遇到一些痛苦的经历，但最终完成音乐和项目时都会获得满足的喜悦。

### 函数式编程

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

申明式 UI 是未来，未来则是 Comonadic。

所谓申明式 UI 可以用下面的公式表达：

```js
type render = (state: State) => View;
```

然后用一段公式介绍了 Comonadic：

```js
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 版本做一个解释：

```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` 的输入。使用例子是这样的：

```jsx
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 抛出了异常 😂。

下面是笔者稍微修改后的例子，至少能跑起来：

```js
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](https://www.wickeditor.com/) 是一个开源的动画、游戏制作软件。

wick editor 是一个动画制作工具，但拓展了一些 js 编程能力，因此可以很好的将动画与游戏结合在一起：

<img width=300 src="https://img.alicdn.com/tfs/TB1hLJpnbr1gK0jSZR0XXbP8XXa-1766-1002.png">

演讲介绍了 wick editor 的演化过程：

从很简陋的 MVP 版本开始（1 周）

<img width=300 src="https://img.alicdn.com/tfs/TB11sdsneL2gK0jSZFmXXc7iXXa-1192-764.png">

到 Pre-Alpha（4 月）

<img width=300 src="https://img.alicdn.com/tfs/TB1TMJnnXY7gK0jSZKzXXaikpXa-1306-858.png">

Alpha（5 月）

<img width=300 src="https://img.alicdn.com/tfs/TB1608pnoD1gK0jSZFGXXbd3FXa-1794-1186.png">

Beta（1.5 年）

<img width=300 src="https://img.alicdn.com/tfs/TB1CKJpnoD1gK0jSZFGXXbd3FXa-1274-854.png">

重点是 1.0 版本采用 React 重写了！继 Beta 之后又经历了 1 年：

<img width=300 src="https://img.alicdn.com/tfs/TB1ZZhrneH2gK0jSZFEXXcqMpXa-906-596.png">

这个团队最棒的地方是，将游戏与教育结合，针对不同场景做了很多用户调研并根据反馈持续改进。

### React Select

[react-select](https://github.com/JedWatson/react-select) 的作者 [Jed Watson](https://github.com/JedWatson) 被请来啦。作为一个看上去很简单组件（select）的开发者，却拥有如此大的关注量（1.8w star），那作者有着怎样的心路历程呢？

react-select 看似简单的名字背后其实有挺多的功能，比如作者列举了一些功能层面的内容：

- autocomplete - 输入时搜索。
- 单、多选。
- focus 管理。
- 下拉框层级与位置，比如可以放在根 DOM 节点，也可以作为当前节点的子元素。
- 异步下拉框内容。
- 键盘、触控。
- Createble，即在搜索时如果没有内容可以动态创建。
- 等等。

<img width=300 src="https://img.alicdn.com/tfs/TB1RYS1mubviK0jSZFNXXaApXXa-1166-226.png">

在设计层面：

- 申明式。
- 可以被定制。
- 性能要求。
- 等等。

随着 Star 逐渐上涨，越来越多的需求被提出，核心库代码量越来越大，甚至许多需求之间都是相互冲突的，而且作者每天都会被上百个 Issue 与 PR 吵醒。做一个业务 Select 可能只要 5 分钟，但做一个开源 Select 却要 5 年，原因是一个简单的 Select 如何满足所有不同业务场景？这绝对是个巨大的挑战。

比如用户即需要受控也要非受控的组件，如何满足好这个需求同时又让代码更可维护呢？

假设我们拥有一个受控的组件 `SelectComponent`，那么它的主要 props 是 `value` 与 `onChange`，如果要拓展成一个既支持 `defaultValue`（非受控）又支持 `value`（受控）的组件，我们可以创建一个 `manageState` 组件对 `SelectComponent` 进行封装：

```jsx
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 组件：

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

export default manageState(Select);
```

同理对异步的封装也可以放在 `makeAsync` 函数中：

```jsx
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` 都是对数据处理的拓展，所以这三者之间才可以融洽的组合：

```js
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](https://www.alibabacloud.com/zh/product/quickbi)，QuickBI 是我们团队研发的一款智能 BI 服务平台，如果你将美国政府的财政支持作为数据集输入，你会分析得更透彻。

### React + 星舰模拟器

最后介绍的是使用 React 制作的星舰模拟器，看上去像一个游戏：

<img width=500 src="https://img.alicdn.com/tfs/TB1HrxAneL2gK0jSZPhXXahvXXa-1946-1104.png">

有星系图、船体、驾驶员信息、武器装备、燃料、通信等等内容。甚至可以模拟太空驾驶，进行任务，可以实时多人协同。对太空迷们的吸引力很大，感兴趣的同学建议直接观看 [视频](https://youtu.be/JDDxR1a15Yo?t=28638)。

## 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](https://github.com/dt-fe/weekly/issues/217)

**如果你想参与讨论，请 [点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明：自由转载-非商用-非衍生-保持署名（[创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)）
