1 引言

很高兴这一期的话题是由 epitath 的作者 grsabreu 提供的。

前端发展了 20 多年,随着发展中国家越来越多的互联网从业者涌入,现在前端知识玲琅满足,概念、库也越来越多。虽然内容越来越多,但作为个体的你的时间并没有增多,如何持续学习新知识,学什么将会是个大问题。

前端精读通过吸引优质的用户,提供最前沿的话题或者设计理念,虽然每周一篇文章不足以概括这一周的所有焦点,但可以保证你阅读的这十几分钟没有在浪费时间,每一篇精读都是经过精心筛选的,我们既讨论大家关注的焦点,也能找到仓库角落被遗忘的珍珠。

2 概述

在介绍 Epitath 之前,先介绍一下 renderProps。

renderProps 是 jsx 的一种实践方式,renderProps 组件并不渲染 dom,但提供了持久化数据与回调函数帮助减少对当前组件 state 的依赖。

RenderProps 的概念

react-powerplug 就是一个 renderProps 工具库,我们看看可以做些什么:

<Toggle initial={true}>
{({ on, toggle }) => <Checkbox checked={on} onChange={toggle} />}
</Toggle>

Toggle 就是一个 renderProps 组件,它可以帮助控制受控组件。比如仅仅利用 Toggle,我们可以大大简化 Modal 组件的使用方式:

class App extends React.Component {
state = { visible: false }; showModal = () => {
this.setState({
visible: true
});
}; handleOk = e => {
this.setState({
visible: false
});
}; handleCancel = e => {
this.setState({
visible: false
});
}; render() {
return (
<div>
<Button type="primary" onClick={this.showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</div>
);
}
} ReactDOM.render(<App />, mountNode);

这是 Modal 标准代码,我们可以使用 Toggle 简化为:

class App extends React.Component {
render() {
return (
<Toggle initial={false}>
{({ on, toggle }) => (
<Button type="primary" onClick={toggle}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={on}
onOk={toggle}
onCancel={toggle}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
)}
</Toggle>
);
}
} ReactDOM.render(<App />, mountNode);

省掉了 state、一堆回调函数,而且代码更简洁,更语义化。

renderProps 内部管理的状态不方便从外部获取,因此只适合保存业务无关的数据,比如 Modal 显隐。

RenderProps 嵌套问题的解法

renderProps 虽然好用,但当我们想组合使用时,可能会遇到层层嵌套的问题:

<Counter initial={5}>
{counter => {
<Toggle initial={false}>
{toggle => {
<MyComponent counter={counter.count} toggle={toggle.on} />;
}}
</Toggle>;
}}
</Counter>

因此 react-powerplugin 提供了 compose 函数,帮助聚合 renderProps 组件:

import { compose } from 'react-powerplug'

const ToggleCounter = compose(
<Counter initial={5} />,
<Toggle initial={false} />
) <ToggleCounter>
{(toggle, counter) => (
<ProductCard {...} />
)}
</ToggleCounter>

使用 Epitath 解决嵌套问题

Epitath 提供了一种新方式解决这个嵌套的问题:

const App = epitath(function*() {
const { count } = yield <Counter />
const { on } = yield <Toggle /> return (
<MyComponent counter={count} toggle={on} />
)
}) <App />

renderProps 方案与 Epitath 方案,可以类比为 回调 方案与 async/await 方案。Epitath 和 compose 都解决了 renderProps 可能带来的嵌套问题,而 compose 是通过将多个 renderProps merge 为一个,而 Epitath 的方案更接近 async/await 的思路,利用 generator 实现了伪同步代码。

3 精读

Epitath 源码一共 40 行,我们分析一下其精妙的方式。

下面是 Epitath 完整的源码:

import React from "react";
import immutagen from "immutagen"; const compose = ({ next, value }) =>
next
? React.cloneElement(value, null, values => compose(next(values)))
: value; export default Component => {
const original = Component.prototype.render;
const displayName = `EpitathContainer(${Component.displayName ||
"anonymous"})`; if (!original) {
const generator = immutagen(Component); return Object.assign(
function Epitath(props) {
return compose(generator(props));
},
{ displayName }
);
} Component.prototype.render = function render() {
// Since we are calling a new function to be called from here instead of
// from a component class, we need to ensure that the render method is
// invoked against `this`. We only need to do this binding and creation of
// this function once, so we cache it by adding it as a property to this
// new render method which avoids keeping the generator outside of this
// method's scope.
if (!render.generator) {
render.generator = immutagen(original.bind(this));
} return compose(render.generator(this.props));
}; return class EpitathContainer extends React.Component {
static displayName = displayName;
render() {
return <Component {...this.props} />;
}
};
};

immutagen

immutagen 是一个 immutable generator 辅助库,每次调用 .next 都会生成一个新的引用,而不是自己发生 mutable 改变:

import immutagen from "immutagen";

const gen = immutagen(function*() {
yield 1;
yield 2;
return 3;
})(); // { value: 1, next: [function] } gen.next(); // { value: 2, next: [function] }
gen.next(); // { value: 2, next: [function] } gen.next().next(); // { value: 3, next: undefined }

compose

看到 compose 函数就基本明白其实现思路了:

const compose = ({ next, value }) =>
next
? React.cloneElement(value, null, values => compose(next(values)))
: value;
const App = epitath(function*() {
const { count } = yield <Counter />;
const { on } = yield <Toggle />;
});

通过 immutagen,依次调用 next,生成新组件,且下一个组件是上一个组件的子组件,因此会产生下面的效果:

yield <A>
yield <B>
yield <C>
// 等价于
<A>
<B>
<C />
</B>
</A>

到此其源码精髓已经解析完了。

存在的问题

crimx 在讨论中提到,Epitath 方案存在的最大问题是,每次 render 都会生成全新的组件,这对内存是一种挑战。

稍微解释一下,无论是通过 原生的 renderProps 还是 compose,同一个组件实例只生成一次,React 内部会持久化这些组件实例。而 immutagen 在运行时每次执行渲染,都会生成不可变数据,也就是全新的引用,这会导致废弃的引用存在大量 GC 压力,同时 React 每次拿到的组件都是全新的,虽然功能相同。

4 总结

epitath 巧妙的利用了 immutagen 的不可变 generator 的特性来生成组件,并且在递归 .next 时,将顺序代码解析为嵌套代码,有效解决了 renderProps 嵌套问题。

喜欢 epitath 的同学赶快入手吧!同时我们也看到 generator 手动的步骤控制带来的威力,这是 async/await 完全无法做到的。

是否可以利用 immutagen 解决 React Context 与组件相互嵌套问题呢?还有哪些其他前端功能可以利用 immutagen 简化的呢?欢迎加入讨论。

5 更多讨论

讨论地址是:精读《Epitath - renderProps 新用法》 · Issue #106 · dt-fe/weekly

如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

原文地址:https://segmentfault.com/a/1190000016682454

精读《Epitath 源码 - renderProps 新用法》的更多相关文章

  1. 精读《V8 引擎 Lazy Parsing》

    1. 引言 本周精读的文章是 V8 引擎 Lazy Parsing,看看 V8 引擎为了优化性能,做了怎样的尝试吧! 这篇文章介绍的优化技术叫 preparser,是通过跳过不必要函数编译的方式优化性 ...

  2. 深入浏览器工作原理和JS引擎(V8引擎为例)

    浏览器工作原理和JS引擎 1.浏览器工作原理 在浏览器中输入查找内容,浏览器是怎样将页面加载出来的?以及JavaScript代码在浏览器中是如何被执行的? 大概流程可观察以下图: 首先,用户在浏览器搜 ...

  3. [翻译] V8引擎的解析

    原文:Parsing in V8 explained 本文档介绍了 V8 引擎是如何解析 JavaScript 源代码的,以及我们将改进它的计划. 动机 我们有个解析器和一个更快的预解析器(~2x), ...

  4. 一文搞懂V8引擎的垃圾回收

    引言 作为目前最流行的JavaScript引擎,V8引擎从出现的那一刻起便广泛受到人们的关注,我们知道,JavaScript可以高效地运行在浏览器和Nodejs这两大宿主环境中,也是因为背后有强大的V ...

  5. Chrome V8引擎系列随笔 (1):Math.Random()函数概览

    先让大家来看一幅图,这幅图是V8引擎4.7版本和4.9版本Math.Random()函数的值的分布图,我可以这么理解 .从下图中,也许你会认为这是个二维码?其实这幅图告诉我们一个道理,第二张图的点的分 ...

  6. (译)V8引擎介绍

    V8是什么? V8是谷歌在德国研发中心开发的一个JavaScript引擎.开源并且用C++实现.可以用于运行于客户端和服务端的Javascript程序. V8设计的初衷是为了提高浏览器上JavaScr ...

  7. 浅谈Chrome V8引擎中的垃圾回收机制

    垃圾回收器 JavaScript的垃圾回收器 JavaScript使用垃圾回收机制来自动管理内存.垃圾回收是一把双刃剑,其好处是可以大幅简化程序的内存管理代码,降低程序员的负担,减少因 长时间运转而带 ...

  8. V8引擎嵌入指南

    如果已读过V8编程入门那你已经熟悉了如句柄(handle).作用域(scope)和上下文(context)之类的关键概念,以及如何将V8引擎作为一个独立的虚拟机来使用.本文将进一步讨论这些概念,并介绍 ...

  9. 浅谈V8引擎中的垃圾回收机制

    最近在看<深入浅出nodejs>关于V8垃圾回收机制的章节,转自:http://blog.segmentfault.com/skyinlayer/1190000000440270 这篇文章 ...

  10. 深入出不来nodejs源码-V8引擎初探

    原本打算是把node源码看得差不多了再去深入V8的,但是这两者基本上没办法分开讲. 与express是基于node的封装不同,node是基于V8的一个应用,源码内容已经渗透到V8层面,因此这章简述一下 ...

随机推荐

  1. jquery——属性操作、特殊效果

    1. attr().prop() 取出或者设置某个属性的值 <!DOCTYPE html> <html lang="en"> <head> &l ...

  2. Android的网络通信

    Android平台有三种网络接口可以使用,他们分别是:java.net.*(标准Java接口).Org.apache接口和Android.net.*(Android网络接口).大多数的Android应 ...

  3. HDU 5775 L - Bubble Sort 树状数组

    给定一段冒泡排序的代码,要求输出每个数字能到达的最右边的位置和最左边的位置的差 因为那段冒泡排序的代码是每次选取一个最小的数,放在左边的,所以,每个数最多能到达右边的位置应该是起始位置i+右边有多少个 ...

  4. Docker 镜像制作 CentOS+JDK+Tomcat

    [root@localhost createImages]# ls apache-tomcat-.tar.gz server-jre-8u121-linux-x64.tar.gz [root@loca ...

  5. IDEA/Eclipse安装 Alibaba Java Coding Guidelines 插件

    为了让开发者更加方便.并且达到快速规范代码格式的目的并实行起来,阿里巴巴基于<阿里巴巴Java开发规约>手册内容,研发了一套自动化的IDE检测插件(IDEA.Eclipse).它就是Ali ...

  6. phpize使用方法

    phpize是用来扩展php扩展模块的,通过phpize可以建立php的外挂模块,下面介绍一个它的使用方法,需要的朋友可以参考下 安装(fastcgi模式)的时候,常常有这样一句命令: 代码如下: / ...

  7. pat1069. The Black Hole of Numbers (20)

    1069. The Black Hole of Numbers (20) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, ...

  8. C++中的引用和指针

    引用和指针有何区别?何时只能使用指针而不能使用引用?    引用是一个别名,不能为 NULL 值,不能被重新分配:指针是一个存放地址的变量.当需要对变量重新赋以另外的地址或赋值为 NULL 时只能使用 ...

  9. Dos窗口一闪而过,如何查看错误?

    问:Dos窗口一闪而过,如何查看错误? 答:在执行程序最后追加pause或者read(,),即可查看错误信息.

  10. HTML5 有哪些不同类型的存储?

    HTML 5 支持本地存储,在之前版本中是通过 Cookie 实现的.HTML5 本地存储速度快而且安全. 有两种不同的对象可用来存储数据: localStorage 适用于长期存储数据,浏览器关闭后 ...