Skip to content

Commit 8dd2e2c

Browse files
author
YuChengKai
committed
classification
1 parent ce8bae9 commit 8dd2e2c

File tree

3 files changed

+622
-640
lines changed

3 files changed

+622
-640
lines changed

Framework/framework-zh.md

-209
Original file line numberDiff line numberDiff line change
@@ -193,215 +193,6 @@ p.a = 2 // bind `value` to `2`
193193
p.a // -> Get 'a' = 2
194194
```
195195

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-
405196
# 路由原理
406197

407198
前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。目前单页面使用的路由就只有两种实现方式

0 commit comments

Comments
 (0)