@@ -193,215 +193,6 @@ p.a = 2 // bind `value` to `2`
193
193
p .a // -> Get 'a' = 2
194
194
```
195
195
196
- # NextTick 原理分析
197
-
198
- ` nextTick ` 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。
199
-
200
- 在 Vue 2.4 之前都是使用的 microtasks,但是 microtasks 的优先级过高,在某些情况下可能会出现比事件冒泡更快的情况,但如果都使用 macrotasks 又可能会出现渲染的性能问题。所以在新版本中,会默认使用 microtasks,但在特殊情况下会使用 macrotasks,比如 v-on。
201
-
202
- 对于实现 macrotasks ,会先判断是否能使用 ` setImmediate ` ,不能的话降级为 ` MessageChannel ` ,以上都不行的话就使用 ` setTimeout `
203
-
204
- ``` js
205
- if (typeof setImmediate !== ' undefined' && isNative (setImmediate)) {
206
- macroTimerFunc = () => {
207
- setImmediate (flushCallbacks)
208
- }
209
- } else if (
210
- typeof MessageChannel !== ' undefined' &&
211
- (isNative (MessageChannel ) ||
212
- // PhantomJS
213
- MessageChannel .toString () === ' [object MessageChannelConstructor]' )
214
- ) {
215
- const channel = new MessageChannel ()
216
- const port = channel .port2
217
- channel .port1 .onmessage = flushCallbacks
218
- macroTimerFunc = () => {
219
- port .postMessage (1 )
220
- }
221
- } else {
222
- /* istanbul ignore next */
223
- macroTimerFunc = () => {
224
- setTimeout (flushCallbacks, 0 )
225
- }
226
- }
227
- ```
228
-
229
- ` nextTick ` 同时也支持 Promise 的使用,会判断是否实现了 Promise
230
-
231
- ``` js
232
- export function nextTick (cb ?: Function , ctx ?: Object ) {
233
- let _resolve
234
- // 将回调函数整合进一个数组中
235
- callbacks .push (() => {
236
- if (cb) {
237
- try {
238
- cb .call (ctx)
239
- } catch (e) {
240
- handleError (e, ctx, ' nextTick' )
241
- }
242
- } else if (_resolve) {
243
- _resolve (ctx)
244
- }
245
- })
246
- if (! pending) {
247
- pending = true
248
- if (useMacroTask) {
249
- macroTimerFunc ()
250
- } else {
251
- microTimerFunc ()
252
- }
253
- }
254
- // 判断是否可以使用 Promise
255
- // 可以的话给 _resolve 赋值
256
- // 这样回调函数就能以 promise 的方式调用
257
- if (! cb && typeof Promise !== ' undefined' ) {
258
- return new Promise (resolve => {
259
- _resolve = resolve
260
- })
261
- }
262
- }
263
- ```
264
-
265
- # React 生命周期分析
266
-
267
- 在 V16 版本中引入了 Fiber 机制。这个机制一定程度上的影响了部分生命周期的调用,并且也引入了新的 2 个 API 来解决问题。
268
-
269
- 在之前的版本中,如果你拥有一个很复杂的复合组件,然后改动了最上层组件的 ` state ` ,那么调用栈可能会很长
270
-
271
- ![ ] ( https://user-gold-cdn.xitu.io/2018/6/25/164358b0310f476c?w=685&h=739&f=png&s=61462 )
272
-
273
- 调用栈过长,再加上中间进行了复杂的操作,就可能导致长时间阻塞主线程,带来不好的用户体验。Fiber 就是为了解决该问题而生。
274
-
275
- Fiber 本质上是一个虚拟的堆栈帧,新的调度器会按照优先级自由调度这些帧,从而将之前的同步渲染改成了异步渲染,在不影响体验的情况下去分段计算更新。
276
-
277
- ![ ] ( https://user-gold-cdn.xitu.io/2018/6/25/164358f89595d56f?w=1119&h=600&f=png&s=330885 )
278
-
279
- 对于如何区别优先级,React 有自己的一套逻辑。对于动画这种实时性很高的东西,也就是 16 ms 必须渲染一次保证不卡顿的情况下,React 会每 16 ms(以内) 暂停一下更新,返回来继续渲染动画。
280
-
281
- 对于异步渲染,现在渲染有两个阶段:` reconciliation ` 和 ` commit ` 。前者过程是可以打断的,后者不能暂停,会一直更新界面直到完成。
282
-
283
- ** Reconciliation** 阶段
284
-
285
- - ` componentWillMount `
286
- - ` componentWillReceiveProps `
287
- - ` shouldComponentUpdate `
288
- - ` componentWillUpdate `
289
-
290
- ** Commit** 阶段
291
-
292
- - ` componentDidMount `
293
- - ` componentDidUpdate `
294
- - ` componentWillUnmount `
295
-
296
- 因为 ` reconciliation ` 阶段是可以被打断的,所以 ` reconciliation ` 阶段会执行的生命周期函数就可能会出现调用多次的情况,从而引起 Bug。所以对于 ` reconciliation ` 阶段调用的几个函数,除了 ` shouldComponentUpdate ` 以外,其他都应该避免去使用,并且 V16 中也引入了新的 API 来解决这个问题。
297
-
298
- ` getDerivedStateFromProps ` 用于替换 ` componentWillReceiveProps ` ,该函数会在初始化和 ` update ` 时被调用
299
-
300
- ``` js
301
- class ExampleComponent extends React .Component {
302
- // Initialize state in constructor,
303
- // Or with a property initializer.
304
- state = {};
305
-
306
- static getDerivedStateFromProps (nextProps , prevState ) {
307
- if (prevState .someMirroredValue !== nextProps .someValue ) {
308
- return {
309
- derivedData: computeDerivedState (nextProps),
310
- someMirroredValue: nextProps .someValue
311
- };
312
- }
313
-
314
- // Return null to indicate no change to state.
315
- return null ;
316
- }
317
- }
318
- ```
319
-
320
- ` getSnapshotBeforeUpdate ` 用于替换 ` componentWillUpdate ` ,该函数会在 ` update ` 后 DOM 更新前被调用,用于读取最新的 DOM 数据。
321
-
322
- ## V16 生命周期函数用法建议
323
-
324
- ``` js
325
- class ExampleComponent extends React .Component {
326
- // 用于初始化 state
327
- constructor () {}
328
- // 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
329
- // 因为该函数是静态函数,所以取不到 `this`
330
- // 如果需要对比 `prevProps` 需要单独在 `state` 中维护
331
- static getDerivedStateFromProps (nextProps , prevState ) {}
332
- // 判断是否需要更新组件,多用于组件性能优化
333
- shouldComponentUpdate (nextProps , nextState ) {}
334
- // 组件挂载后调用
335
- // 可以在该函数中进行请求或者订阅
336
- componentDidMount () {}
337
- // 用于获得最新的 DOM 数据
338
- getSnapshotBeforeUpdate () {}
339
- // 组件即将销毁
340
- // 可以在此处移除订阅,定时器等等
341
- componentWillUnmount () {}
342
- // 组件销毁后调用
343
- componentDidMount () {}
344
- // 组件更新后调用
345
- componentDidUpdate () {}
346
- // 渲染组件函数
347
- render () {}
348
- // 以下函数不建议使用
349
- UNSAFE_componentWillMount () {}
350
- UNSAFE_componentWillUpdate (nextProps , nextState ) {}
351
- UNSAFE_componentWillReceiveProps (nextProps ) {}
352
- }
353
- ```
354
-
355
- # setState
356
-
357
- ` setState ` 在 React 中是经常使用的一个 API,但是它存在一些问题,可能会导致犯错,核心原因就是因为这个 API 是异步的。
358
-
359
- 首先 ` setState ` 的调用并不会马上引起 ` state ` 的改变,并且如果你一次调用了多个 ` setState ` ,那么结果可能并不如你期待的一样。
360
-
361
- ``` js
362
- handle () {
363
- // 初始化 `count` 为 0
364
- console .log (this .state .count ) // -> 0
365
- this .setState ({ count: this .state .count + 1 })
366
- this .setState ({ count: this .state .count + 1 })
367
- this .setState ({ count: this .state .count + 1 })
368
- console .log (this .state .count ) // -> 0
369
- }
370
- ```
371
-
372
- 第一,两次的打印都为 0,因为 ` setState ` 是个异步 API,只有同步代码运行完毕才会执行。` setState ` 异步的原因我认为在于,` setState ` 可能会导致 DOM 的重绘,如果调用一次就马上去进行重绘,那么调用多次就会造成不必要的性能损失。设计成异步的话,就可以将多次调用放入一个队列中,在恰当的时候统一进行更新过程。
373
-
374
- 第二,虽然调用了三次 ` setState ` ,但是 ` count ` 的值还是为 1。因为多次调用会合并为一次,只有当更新结束后 ` state ` 才会改变,三次调用等同于如下代码
375
-
376
- ``` js
377
- Object .assign (
378
- {},
379
- { count: this .state .count + 1 },
380
- { count: this .state .count + 1 },
381
- { count: this .state .count + 1 },
382
- )
383
- ```
384
-
385
- 当然你也可以通过以下方式来实现调用三次 ` setState ` 使得 ` count ` 为 3
386
-
387
- ``` js
388
- handle () {
389
- this .setState ((prevState ) => ({ count: prevState .count + 1 }))
390
- this .setState ((prevState ) => ({ count: prevState .count + 1 }))
391
- this .setState ((prevState ) => ({ count: prevState .count + 1 }))
392
- }
393
- ```
394
-
395
- 如果你想在每次调用 ` setState ` 后获得正确的 ` state ` ,可以通过如下代码实现
396
-
397
- ``` js
398
- handle () {
399
- this .setState ((prevState ) => ({ count: prevState .count + 1 }), () => {
400
- console .log (this .state )
401
- })
402
- }
403
- ```
404
-
405
196
# 路由原理
406
197
407
198
前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。目前单页面使用的路由就只有两种实现方式
0 commit comments