普通视图

发现新文章,点击刷新页面。
昨天以前OnionTalk

2021年回顾

2022年1月30日 15:59

工作

挣扎

2021 年,是我在西安腾讯的第二年,去年抱着满腔的热情加入公司,认识了很多不错的人,年底也收获了还不错的绩效,但是整个人却就是开心不起来。呆小瓜,包括我身边的很多朋友,都觉得,我没有之前开心了。现在想想那段时间,一方面是自己的状态确实太紧绷了,每天睡觉前都在担心 on-call,即使下班后也是盯着企业微信,担心有人找。另一方面又感觉自己没有任何成长,在不停的做重复的事情,没有时间思考,开始不断怀疑否定自己。这样的状态持续了大概三个月左右,在确定了未来业务方向自己实在不感兴趣,和呆小瓜吐露心声并获得她的支持之后,我便开始了新的工作规划。

机遇

在经历了几轮面试之后,我成功拿到了 3 个 offer。很幸运,自己之前在 toughtworks 的经验,还不错的英语底子以及爱折腾的经历给自己加了不少的分,让我保持了尚可的竞争力。综合考虑下我选择来到上海,加入 Afterpay(现在应该叫 Block 了)。

成长

Afterpay 的工作节奏和我之前在 thoughtworks 的工作节奏比较一致,这也给了我更多的时间去思考。前端的工作内容其实大同小异,并没有什么新的花样。但是和两年前相比,由于有了不同的经历,自己的视角也有所不同。过去的我更多的是从一个前端的角度去出发,把产品需求转变成技术代码。但现在自己会思考更多,不仅是 technical 上(从前到后端到端,如何规避技术风险,如何选择合适的技术高效支持业务,如何针对不同的业务场景,选择合适的模型),也会开始从产品本身,从商业模型上思考如何打造更好的产品。我想我应该还是更适合做产品吧,希望 2022 年,能够逐步构建自己真正满意的产品,不管是从业务上孵化,还是自己的 side project。

生活

Move

伴随工作地点的变换,我和呆小瓜离开了我们居住了快 3 年的温馨的西安小窝,搬到了上海的出租屋。强调是出租屋,是因为在我们有限的预算下,能够租到的房子实在屈指可数,目前这套已经是矮子里面找将军,相对不错的一套了,不过相比之前自己的房子,仍有诸多不足之处。我们也不止一次的吐槽过如同纸糊的窗户,超小的电视,以及形同虚设的隔音。虽然有很多槽点,但是生活也有了一些惊喜,比如无意间的发现的步行就可到达的菜市场,意外好吃的干米粉,热情的卤菜阿姨和卖干货的大姐,以及我们给我们提供无尽造梗素材的周边的一切。虽然和西安相比,还是有点不太习惯这边的生活,但是一切都在变好。即使是出租屋,也在一天天变得温馨。

颠倒

呆小瓜因为要陪我来上海,也换了新的工作,当她给前公司提离职时,离职原因美其名曰“随军”(源自父母爱情的一个梗)。到了上海后,呆小瓜的工作比我强度要高,我们的位置就像是 2021 的彼此倒了个个,我开始成为后勤主管,而呆小瓜开始做女强人。这样的生活和我们最初的设想其实有所偏差,毕竟我们现在连 7 点吃晚饭都做不到,更别说之前设想好的培养兴趣爱好了。我也有些自责让她为了陪我来上海就变得这么辛苦。但是呆小瓜就是这么一个小仙女,一眼就能看出我心里在想什么,明明自己每天工作的很累了但是还是装作没事人一样听见我的烂梗就哈哈大笑。新的一年,希望呆小瓜能够轻松一点,也希望我们能实现每天 7 点吃晚饭的目标。

厨艺

经过去年年夜饭的洗礼,自己今年的厨艺又精进了很多,下面是几个自己今年最满意的菜。

  • 肉炒一切(蒜薹,香干,洋葱)
  • 芋儿鸡(YYDS)
  • 萝卜(扁豆)炖牛肉(没有老妈做的味道那么浓,但是意外的合我和呆小瓜的口味)
  • 锅巴饭(偷师石泉)和砂锅(自成一派)
  • 清蒸鱼(主要是鱼的功劳) 希望新的一年能解锁新的菜单,能够开始烹饪一些不一样的新食材。

总结

每年回顾,都会有很多遗憾,这里列几个个人觉得做得非常不好的地方

  • 没有太多的时间陪伴父母(去年过年没回家,今年打算错峰过年)。
  • 又是几乎没有读书的一年,知识来源过于零散化,虽然开始使用双链笔记进行整理记录,但是没有形成体系。
  • 成长上有点停滞不前,对于新的东西学的很快,但是还是没有发现自己的核心竞争力。
  • 博客更新几乎停滞。本来想着工作节奏慢一点就能多写点东西但是零散时间大多数被 b 站和游戏占据。
  • 没有发展任何新的技能和爱好。

如果我能够把上面这些事情全部都做完美,不留遗憾,那我新的一年应该是个超人了。但是我并不是什么超人,遗憾也总会有,暂且实际一点,希望明年能少一点遗憾,加油!

浏览器跨tab通信的几种办法

2021年10月14日 14:53

什么场景下需要跨tab通信?

有一些日常中很常见的场景,如果支持跨tab通信,可能能给用户更好的体验。

博客Dark Mode切换

想象你在浏览 Dan Abramov的博客,你同时打开了好几个tab学习一系列知识。夜晚来临,你将一个页面切换成了Dark Mode。当你阅读完这一篇去阅读下一篇时,页面却不是以Dark Mode呈现的。你不得不按下F5刷新页面来寻求一致的阅读体验。

需要频繁登录的CMS系统

想象你在网店管理系统中管理你网店中的商品,你打开了好几个tab,分别管理商品类型,商品详情以及商品库存。你正在管理你的商品详情,突然你的登录session过期,你点击重新登录。但是当你切换到商品库存页面时,你发现你依旧无法操作,需要再次登录。你不得已点击了登录按钮,和你辛苦编辑了10分钟但没有保存的商品库存说了拜拜。

以上场景其实无伤大雅,你的系统依旧运转,核心功能依旧正常。但是借助跨tab通信,你能给用户提供更好更顺滑的体验。

Cookie & IndexedDB

要实现数据的一致,很容易想到的就是统一的数据存储。在有后端加入的时候,后端其实就是前端的统一数据存储。在纯前端的环境下,比较常见的数据存储包括

sessionStorage 由于只存活于单个tab session中,所以不能用于跨tab通信。localStorage 有其他的应用方式,下面会深入分析。所以在这个章节我们主要关注cookie和IndexedDB。

实现思路

实现思路其实非常简单,就是通过轮询数据源,来确保每个tab的数据都是最新的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Cookie Way
// Producer Tab, 当然你也可以用js-cookie这种库更高效的操作cookie
document.cookie = "blabla"

// Consumer Tab
setInterval(() => {
 const cookie = document.cookie;
 //Use Cookie to do something
 doSomething(cookie)
}, 1000)

// IndexedDB way
// Producer Tab
const transaction = db.transaction(["customers"], "readwrite");

const objectStore = transaction.objectStore("customers");
customerData.forEach(function(customer) {
 const request = objectStore.add(customer);
 request.onsuccess = function(event) {
 // do some callback
 };
});

// Consumer Tab
setInterval(() => {
 const transaction = db.transaction(["customers"]);
 const objectStore = transaction.objectStore("customers");
 const request = objectStore.get("444-44-4444");
 request.onsuccess = function(event) {
 doSomething(request.result.name);
};
}, 1000)

需要注意的是,cookie的读写是同步的,在cookie比较大的时候可能会阻塞主线程造成页面卡顿。而IndexedDB的API都是异步的,所以不会有cookie同步读写的性能问题。

持续部署新法宝 - Github Actions

2020年2月22日 19:21

什么是Github Actions

Github Actions 是 Github 推出的一项服务,旨在以自定义工作流的方式来简化团队开发协作中的各种需求。比如:

  1. 基于 Code Commit 的代码构建/测试/打包,也就是常说的持续集成。
  2. 应用的自动发布和部署,和上一步连起来就是所谓的持续部署。
  3. 自动管理 PR(如自动 assign viewer)
  4. 自动管理 issue(如自动给 issue 分类,自动关闭长期不更新的 issue )

Github Actions的优点

1. 灵活性

对比主要竞品 Jenkin/GoCD 以及 Circle CI/ Travis CI, 相较于 Jenkins/GoCD 只能够自建服务,CirCle CI(2.0以前) / Travis CI 只能依靠官方提供的服务,Github Actions 可以使用 Github 官方的 Runner,同时 Github Actions 也支持使用用户自建的(比如搭载某内网环境的instance)Runner。Github Actions 还自带 Linux/macOS/Windows 三种类型的 Runner,能够满足不同用户群体在不同场景的需要。

2. 复用性

对于开发者而言,编写重复的代码永远都是一种灾难。而在 Github Actions 的准则中,每一条 Action 都是一个最小的可复用单元,只需要一次编写,便可以在之后随时随地的使用。更棒的是,Action 可以通过组合的方式来使用,完成更为复杂的功能。

3. 可维护性

每一条 Action 其实就是一个 JavaScript module/Docker container,由于Action 本身就是代码,所以 Action 本身是可以很好的被版本管理的。同时由于支持使用 JavaScript 编写 Action,只要你想,你可以使用大量的测试来保证你的 Action 的质量。

层叠上下文和层叠顺序

2019年10月11日 22:49

为什么 z-index 不工作了?

在某些场景下,我们经常会发现z-index不知为何就不工作了,比如下面的场景。(demo:stackblitz demo)

1
2
3
<div class="block-a">block-a z-index 1</div>
<div class="block-b">block-b z-index 2</div>
<div class="block-c">block-c z-index 3</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.block-a {
 background: #ffb3a7;
 z-index: 1;
 position: relative;
 top: 60px;
 opacity: .8;
 width: 500px;
 height: 90px
}

.block-b {
 background: #fff143;
 z-index: 2;
 opacity: .8;
 height: 100px;
 width: 320px;
 position: fixed;
}

.block-c {
 background:#bddd22;
 opacity: .8;
 z-index: 3;
 width: 200px;
 padding-top: 300px;
}

div {
 color: #50616d;
 font-size: 24px;
 text-align: center;
 padding-top: 40px;
 border: #50616d 1px dashed;
}

block-a的 z-index 是 1, block-b 的 z-index 是 2, block-c 的 z-index 是3。 按照之前的理解,覆盖关系应该是block-c覆盖block-b覆盖block-a。但是实际结果却如下图。

重学前端学习笔记(1)--Js类型

2019年8月26日 13:45

基础类型

  1. Undefined
  2. Null
  3. Boolean
  4. String
  5. Number
  6. Symbol
  7. Object

boolean这个类型比较简单,不单独作解释。Symbol这个类型太复杂,后面单独总结。

Undefined 和 Null

  • undefined并不是一个关键字。我们日常访问的undefined其实是访问的global级别的undefined变量,undefined变量的值是基础类型undefined。但是在现代浏览器中,undefined都不能被覆写。
  • undefined代表一个变量初始化了但是没有赋值,任何变量初始化前值都是undefined。
  • null是JavaScript的关键字,表示的是一个空值。
  • 所有变量定义都不要赋undefined值,这样会让代码难以理解(到底是代码没有赋值还是?)

String

  • string的最大长度是2^53-1,但是长度并不是指代字符数,而是字符串的UTF16编码的和,比如需要计算一个emoji的长度,你会发现是2。
  • 字符串方法比如length和charAt都是基于字符串编码和的。
  • 字符串一旦构造便不可更改,属于值类型。

Number

  • Js中除开规定有理数,还规定了NaN,Infinity,-Infinity
  • Js中有+0和-0,在加减法中没有问题,在乘除法中,如果是 x * -0 会得到 -0,x/-0 会得到 -Infinity。
  • Js中的Number类型范围是-2^53-2^53,如果不使用BigInt,只能表示这个范围之内的整数
  • 非整数类型的Number无法使用 == 或者===来进行比较,0.2 + 0.1 == = 0.3 会返回false,如果想要得到正确的匹配结果,请使用 Math.abs(0.1 + 0.2 -0.3) <= Number.EPSILON

Object

  • Js中对象是属性的集合。属性分为数据属性和访问器属性,以key-value结构进行存储,key可以是字符串或者是个Symbol类型。
  • Js中的类和Java中的类有一些不同,Java中的类代表的是真正的类型,但是Js中的类只是运行时对象的一个私有属性。
  • Number/String/Boolean和Symbol这四个基本类型都自带对应的构造器,如果使用 new 操作符,会返回一个对象类型而不是基本类型。
  • Js中的基本类型和对象的边界很模糊,可以直接在基础类型上调用方法,比如"abc".charAt(0),也可以通过给原型添加方法给基础类型增加方法。
  • 为什么基础类型可以调用对象方法?本质上是 . 运算符提供了装箱操作,这个运算符会根据基础类型构造一个临时对象。

类型转换

值转换表如下

StringToNumber

String到Number的类型转换支持十进制/二进制/八进制和十六进制

  • 30
  • 0b111
  • 0o13
  • 0xFF

同时还支持科学计数法

  • 1e3
  • -1e-2

和parseInt和parseFloat相比,

RxJS入门指南

2019年7月17日 22:08

Prerequisite(先导知识)

本文是RxJS的入门知识,在文章中会涉及到大量的JavaScript示例代码,想要更好的理解本文,你需要具备以下知识。

  1. 理解基本的ES6和TypeScript语法
  2. 了解过观察者模式

Observable? Observer?

在RxJSObservable和Observer。要想很好的理解两个概念,我需要借用一个生活中很常见的例子,买房。

在这个图中,我们的购房者一直在密切的关注我们的房价。房价随着时间波动,购房者可能会根据波动的房价而采取一系列的行动,比如购入或者继续观望。购房者与房价的这样一种关系其实就构成了一种观察者关系。套用到观察者模式中,

  • 房价 – Observable
  • 购房者 – Observer
  • 购房者观察房价 – Subscribe(订阅)

再结合买房的例子,我们可以很学术的描述Observable和Observer的行为。

  • Obserable 可被观察(房价被购房者关注),并且随时间变化发出(emit)不同值(房价波动)。
  • Observer 观察Observable(购房者关注房价),并在Observable(房价)发出不同值(房价波动)时做出响应(买房或者观望)。
  • Observable和Observer之间通过订阅(Subscription)来建立观察关系。
  • 当Observable没有Observer的时候,即使发出了值,也不会产生任何影响(无购房意愿者不会响应房价波动)

RxJS中的Observable和Observer

有了基本的Observable和Observer的概念,我们再来看看RxJS中的Observable和Observer。

创建一个Observable

我们可以调用 Observable.create 方法来创建一个Observable,这个方法接受一个函数作为参数,这个函数叫做 producer 函数, 用来生成 Observable的值。该函数的参数是observer,调用observer.next()就可以生成一个有一系列值的Observable。

1
2
3
4
5
6
import { Observable } from 'rxjs';

const observable = Observable.create(observer => {
 observer.next('foo');
 observer.next('bar');
})

但是运行这段代码后并不会发生任何事情,我们需要一个Observer去Subscribe这个Observable,然后Observer基于Observable发出的值做出响应。

Subscribe一个Observerable

我们通过下面的这段代码去Subscribe一个Observable

1
2
3
4
5
6
7
8
import { Observable } from 'rxjs';

const observable = Observable.create(observer => {
 observer.next('foo');
 observer.next('bar');
})

observable.subscribe(console.log)

运行代码,console中就会依次打印 foo / bar 了

图片懒加载从简单到复杂

2019年1月30日 22:53

为什么要做图片的懒加载

假设在用户访问某个页面时就加载这个页面全部的图片(即使这些图片并不处在用户的当前的视窗中),在弱网环境或者网速较慢的环境下,这些“冗余”图片的下载会占用用户本来就非常有限的带宽,伤害用户体验(比如影响其他资源的下载)。所以对于网站的图片,理想的做法是懒加载(按需加载)。

图片懒加载的原理

在浏览器内部对于各种资源有着一套自己的优先级定义,浏览器会优先加载优先级高的资源

如果我们不去进行图片的懒加载,默认情况下,资源的priority如下。

这些优先级标记为high的图片会占用其他资源的下载带宽,可能会造成某些比较关键的资源(比如xhr call)加载缓慢,拖慢页面速度。

图片懒加载的简单实现

图片懒加载的思路一般时当页面加载时加载一个尺寸很小的占位图片(1kb以下),然后再通过js选择性的去加载真正的图片。

一个最简单的的实现如下

1
2
<!-- index.html -->
 <img src="placeholder.jpg" data-src="real_image.jpt" />
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// index.css

 img[data-src] {
 filter: blur(0.2em);
 }

 img {
 filter: blur(0em);
 transition: filter 0.5s;
 }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// index.js
(function lazyLoad(){
 const imageToLazy = document.querySelectorAll('img[data-src]');
 const loadImage = function (image) {
 image.setAttribute('src', image.getAttribute('data-src'));
 image.addEventListener('load', function() {
 image.removeAttribute("data-src");
 })
 }

 imageToLazy.forEach(function(image){
 loadImage(image);
 })
})()

通过懒加载之后,资源优先级如下。

Deep In React(五)setState中的黑魔法

2019年1月14日 22:57

以下代码全部基于React15(React16代码太复杂了看不懂哇- -)。

setState不一定是同步的

在React官方文档中有这么一句话state-updates-may-be-asynchronous

下面这两个很经典也是新人很容易糊涂的场景就是由上面这句模棱两可的话带来的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Demo extends Component {
 state = {
 count: 1
 }

 onClickHandler = () => {
 this.setState({count: this.state.count + 1});
 console.log(this.state.count); // console.log 结果 1
 this.setState({count: this.state.count + 1});
 console.log(this.state.count); // console.log 结果 1
 }

 render() {
 const { count } = this.state;
 return (
 <button onClick={this.onClickHandler}>{count}</button>
 );
 }
}

class SetTimeoutDemo extends Component {
 state = {
 count: 1
 }

 onClickSetTimeoutHandler = () => {
 setTimeout(() => {
 this.setState({count: this.state.count + 1});
 console.log(this.state.count); // console.log 结果 2
 this.setState({count: this.state.count + 1});
 console.log(this.state.count); // console.log 结果 3
 }, 0);
 }

 render() {
 const { count } = this.state;
 return (
 <button onClick={this.onClickSetTimeoutHandler}>{count}</button>
 );
 }
}

stackBlitz的demo在这 ClickerSetTimeoutClicker

Js中的防抖与节流

2019年1月2日 23:14

为什么需要防抖和节流?

日常与浏览器打交道比较多的前端开发者对于浏览器的各种事件想必都不会陌生,我们会针对某一个特定的事件绑定对应的响应函数,在事件被触发时让浏览器自动调用该函数。

1
2
3
4
5
function onResizeHandler() {
 // do something on resize
}

window.addEventListener('resize', onResizeHandler);

如果只是一些触发频率很低的事件,那么上面的代码并没有什么问题。但是如果像resize这样可能在短时间内被频繁触发的事件(比如click/keydown/touchStart等),我们不去做任何的处理的话,可能导致事件的响应函数在短时间内被大量的触发,造成浏览器卡顿,伤害用户体验。

而节流和防抖一个很经典的应用场景就是去控制我们的事件的触发,节省浏览器开销。

什么是防抖

JavaScript中的防抖指的是只有在x ms内没有调用该函数,才会真正调用该函数。如下图

该图上方是事件触发的次数,下方是响应函数触发的次数,可以发现在事件大量被触发时(色块密集),响应函数并没有被触发。当事件停止触发一段事件后(三个色块的时间间隔)后,响应函数才会被真正的触发。

如果你想自己试试上面的例子,可以访问这个 codePen

防抖的简单实现

防抖函数其实是一个高阶函数,接收一个函数和防抖事件作为参数,返回值为防抖处理的函数。

的实现其实很简单,我们需要的是是一个定时器,如果在定时器的定时结束前,响应函数被触发的话则重置这个定时器的时间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function debounce(fn, wait, leading=false) {
 /** @type {number} */
 let timer;
 /** @type {number} */
 let lastCallTime;
 /** @type {boolean} */
 let isInvoked = false;
 return function debounced(...args) {
 const context = this;
 const thisCallTime = Date.now();
 if(leading) {
 if(!isInvoked) {
 fn.apply(context, args);
 isInvoked = true;
 }
 if(thisCallTime - lastCallTime >= wait) {
 fn.apply(context, args);
 }
 lastCallTime = Date.now();
 return;
 }
 clearTimeout(timer);
 timer = setTimeout(() => fn.apply(context, args), wait);
 }
}

需要注意的是,上面的函数添加了leading参数。传统的防抖函数响应函数第一次触发也需要等待x ms,使用该参数可让响应函数立即进行触发。

git中的二分查找– git bisect

2018年12月24日 13:45

从二分查找说起

二分查找是一种很简单的算法,适用的对象是 已经排序的数组 。一个最简单的二分查找的实现如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * @param {Array} sortedList 排好序的list
 * @param {number} target 需要检索的值
 * @return {number} 检索到的值的index,未检索到返回-1
 */

function binarySearch(sortedList, target) {
 let left = 0;
 let right = sortedList.length - 1;
 let middle = parseInt((right+left) / 2);
 while(sortedList[middle] !== target && left !== right) {
 if(sortedList[middle] > target) {
 right = middle - 1;
 } else {
 left = middle + 1;
 }

 middle = parseInt((right + left) / 2);
 }
 return sortedList[middle] === target ? middle : -1;
}

一个简单的递归实现如下

使用yeoman创建属于你自己的脚手架

2018年12月17日 00:00

什么是yeoman generator

真正开始写一个yeoman脚手架之前,我们需要先搞清楚 generator 这个名词。

generator是yeoman提供的对于脚手架的封装,每一个generator是一个单独的npm package。不同的generator之间可以通过组合生成新的generator(e.g. generator-generator是对于generator-node的组合)

如果你想使用一个已经发布的generator,你只需要

1
2
3
yarn global add yo generator-[generator-name] # e.g generator-node

yo [generator-name] # e.g yo node

如何编写一个yeoman generator

上面也提到过generator其实是yeoman提供的一个对于脚手架抽象的封装,所以在yeoman对于generator的项目结构也做出了一些开发者必须要满足的要求。

在项目的package.json中

  • 项目必须依赖于 yeoman-generator,
  • 项目 keywords 必须有 yemoan-generator
  • package名应该以generator-[generator-name]的格式进行命名

项目结构

yeoman generator支持两种不同的项目组织模式

模式1

├───package.json
└───generators/
 ├───app/
 │ └───index.js
 └───router/
 └───index.js

模式2

├───package.json
├───app/
│ └───index.js
└───router/
 └───index.js

需要注意的是, 第二种模式下必须要在pacakge.json里面显式的做出声明

1
2
3
4
5
6
{
 "files": [
 "app",
 "router"
 ]
}

一个最简单的generator

以下是一个最简单的Generator

Deep In React (四) stack reconciliation

2018年11月19日 07:51

前提

本篇文章中所有的Host Component均以DOMComponent为例。

Internal Instance更新

在之前我们建立了Internal Instance的mount和unmount方法用来处理Internal Instance的挂载和卸载。为了完成更新功能,我们需要建立一个叫receive的方法。

Composite Component更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CompositeComponent {
 receive(nextElement) {
 const previousElement = this.currentElement;
 const previousProps = previousElement.props;
 const publicInstance = this.publicInstance;
 const previousRenderedComponent = this.renderedComponent;
 const previousRenderedElement = previousRenderedComponent.currentElement;

 // 更新
 this.currentElement = nextElement;
 const type = nextElement.type;
 const nextProps = nextElement.props;

 let nextRenderedElement;

 if (isClass(type)) {
 if (publicInstance.componentWillUpdate) {
 publicInstance.componentWillUpdate(nextProps);
 }

 publicInstance.props = nextProps;

 nextRenderedComponent = publicInstance.render();
 } else if (typeof type === "function") {
 nextRenderedComponent = type(nextProps);
 }
 }
}

上面的这个render方法对应的是当Composite Component的type没有发生改变的时候,我们只对对应的component进行更新,而不是每次有任何更新都进行重新的mount。而这一个过程同样也是一个递归的过程。

Deep In React (三)Internal Instance

2018年11月13日 00:54

从一次挂载说起

如果你写过React代码,那么ReactDOM.render(<App />, container) 可能是你最熟悉的一段代码了。<App />是一个React Element,container是一个node节点。这个方法是把React element映射到真正的DOM结构的一个触发器,当你调用这个方法之后,会把React Element渲染成virtual DOM Tree。

首先有必要提一下Element和virtual DOM Tree这两个概念。

React Element可能会存在type为Component Element的节点。而Virtual DOM Tree指代完全映射为真实DOM结构的树,所有节点的type都是string类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
// React Element Tree
 type: App,
 props: {
 children:[{
 type: 'div',
 ...
 }, {
 type: Button,
 ...
 }]
 }
}

// 最终渲染成的virtual DOM Tree
{
// Virtual DOM Tree
 type: 'div',
 props: {
 children:[{
 type: 'div',
 ...
 }, {
 type: 'button',
 ...
 }]
 }
}
}

渲染是怎么发生的?

React在接受<App />这个Element的时候,其实是不知道这个Element的深度是多少,每个节点对应的DOM元素是什么的,<App />这个Element下,可能只是一个简单的<div />,也可能是许多复杂组件的组合。因此,React需要自顶向下进行递归的渲染,最终得到一个对应到真实DOM结构的Virtual DOM树。

Deep In React (二) Component,Element和渲染

2018年11月2日 06:26

先导知识: React Component和React Element

在React中,有着组件(component)和元素(element)两个概念。我们日常可能听到过很多次Component这个名词,但是对于Element这个名词在React中似乎聊得并不多。今天就来给大家讲讲什么是Element,什么是Component,以及他们和React渲染的关系。

React Element

React Element从数据结构上来讲就是简单的plain object, 这个plain object会提供对于整个React组件树的描述。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
 type: 'button',
 props: {
 classNames: 'foo',
 children: {
 type: 'span',
 props: {
 children: 'click'
 }
 }
}
讲这个Element翻译成HTML就是如下结构
1
2
3
<button class="foo">
 <span>click</span>
</button>

需要注意的是type这个属性,在上面的例子中,type是一个string,而type是string的element React将其定义为HostElement。Host的意思是这个元素会根据不同的平台对应不同的Native View。比如ReactDOM会对应成相应的HTML Tag,而React-Native则会对应成Native View。

而除开HostElement, React还支持Component类型的element。我们在React代码中经常会这样组合我们的组件。

1
2
3
4
5
6
 <Container>
 <Header />
 <Sidebar />
 <div>content</div>
 <Footer />
 </Container>

这段代码对应生成的element的描述如下

❌
❌