【注意】最后更新于 June 15, 2020,文中内容可能已过时,请谨慎使用。
前言
- 这里是第二篇,第一篇在这里
- 这次讲 Component,以及它的一些轻量依赖。
- 顺便说下司徒正美的 preact 源码学习
- 感觉比我写的好多了,图文并茂,还能提出和其它如 React 的源码比较。
- 我唯一好点的可能就是代码几乎每行都有注释,并且使用了 typescript 添加了类型的标注。
Component 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import { h, Component, render } from "preact"
class App extends Component {
constructor(props, context) {
super(props, context)
this.state = {
num: 0
}
}
test() {
this.setState(state => {
state.num += 1
})
}
render(props, state, context) {
return <h1 onClick={test.bind(this)}>{state.num}<h1/>
}
}
render(<App/>, document.body)
|
上面是一个简单的点击改变当前状态的组件示例。
其中与vue
不同preact
通过Component.prototype.setState
来触发新的 dom 改变。
当然preact
还有其它的更新方式。
Component 代码
这里的代码是通过typescript
重写过的所以有所不同,
但是更好的了解一个完整的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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
| import { FORCE_RENDER } from "./constants";
import { renderComponent } from "./vdom/component";
import { VNode } from "./vnode";
import { enqueueRender } from "./render-queue";
import { extend } from "./util";
import { IKeyValue } from "./types";
export class Component {
/**
* 默认props
*/
public static defaultProps?: IKeyValue;
/**
* 当前组件的状态,可以修改
*/
public state: IKeyValue;
/**
* 由父级组件传递的状态,不可修改
*/
public props: IKeyValue;
/**
* 组件上下文,由父组件传递
*/
public context: IKeyValue;
/**
* 组件挂载后的dom
*/
public base?: Element;
/**
* 自定义组件名
*/
public name?: string;
/**
* 上一次的属性
*/
public prevProps?: IKeyValue;
/**
* 上一次的状态
*/
public prevState?: IKeyValue;
/**
* 上一次的上下文
*/
public prevContext?: IKeyValue;
/**
* 被移除时的dom缓存
*/
public nextBase?: Element;
/**
* 在一个组件被渲染到 DOM 之前
*/
public componentWillMount?() => void;
/**
* 在一个组件被渲染到 DOM 之后
*/
public componentDidMount?() => void;
/**
* 在一个组件在 DOM 中被清除之前
*/
public componentWillUnmount?() => void;
/**
* 在新的 props 被接受之前
* @param { IKeyValue } nextProps
* @param { IKeyValue } nextContext
*/
public componentWillReceiveProps?(nextProps: IKeyValue, nextContext: IKeyValue) => void;
/**
* 在 render() 之前. 若返回 false,则跳过 render,与 componentWillUpdate 互斥
* @param { IKeyValue } nextProps
* @param { IKeyValue } nextState
* @param { IKeyValue } nextContext
* @returns { boolean }
*/
public shouldComponentUpdate?(nextProps: IKeyValue, nextState: IKeyValue, nextContext: IKeyValue) => boolean;
/**
* 在 render() 之前,与 shouldComponentUpdate 互斥
* @param { IKeyValue } nextProps
* @param { IKeyValue } nextState
* @param { IKeyValue } nextContext
*/
public componentWillUpdate?(nextProps: IKeyValue, nextState: IKeyValue, nextContext: IKeyValue) => void;
/**
* 在 render() 之后
* @param { IKeyValue } previousProps
* @param { IKeyValue } previousState
* @param { IKeyValue } previousContext
*/
public componentDidUpdate?(previousProps: IKeyValue, previousState: IKeyValue, previousContext: IKeyValue) => void;
/**
* 获取上下文,会被传递到所有的子组件
*/
public getChildContext?() => IKeyValue;
/**
* 子组件
*/
public _component?: Component;
/**
* 父组件
*/
public _parentComponent?: Component;
/**
* 是否加入更新队列
*/
public _dirty: boolean;
/**
* render 执行完后的回调队列
*/
public _renderCallbacks?: any[];
/**
* 当前组件的key用于复用
*/
public _key?: string;
/**
* 是否停用
*/
public _disable?: boolean;
/**
* react标准用于设置component实例
*/
public _ref?: (component: Component | null) => void;
/**
* VDom暂定用于存放组件根dom的上下文
*/
public child?: any;
constructor(props: IKeyValue, context: IKeyValue) {
// 初始化为true
this._dirty = true;
this.context = context;
this.props = props;
this.state = this.state || {};
}
/**
* 设置state并通过enqueueRender异步更新dom
* @param state 对象或方法
* @param callback render执行完后的回调。
*/
public setState(state: IKeyValue, callback?: () => void): void {
const s: IKeyValue = this.state;
if (!this.prevState) {
// 把旧的状态保存起来
this.prevState = extend({}, s);
}
// 把新的state和并到this.state
if (typeof state === "function") {
const newState = state(s, this.props);
if (newState) {
extend(s, newState);
}
} else {
extend(s, state);
}
if (callback) {
// 添加回调
this._renderCallbacks = this._renderCallbacks || [];
this._renderCallbacks.push(callback);
}
// 异步队列更新dom,通过enqueueRender方法可以保证在一个任务栈下多次setState但是只会发生一次render
enqueueRender(this);
}
/**
* 手动的同步更新dom
* @param callback 回调
*/
public forceUpdate(callback: () => void) {
if (callback) {
this._renderCallbacks = this._renderCallbacks || [];
this._renderCallbacks.push(callback);
}
// 重新同步执行render
renderComponent(this, FORCE_RENDER);
}
/**
* 用来生成VNode的函数
* @param props
* @param state
* @param context
*/
public render(props?: IKeyValue, state?: IKeyValue, context?: IKeyValue): VNode | void {
// console.error("not set render");
}
}
|
如果你看过原来的preact
的代码会发觉多了很多可选属性,
其中除了child
这个属性其它实际上官方的也有,但是都是可选属性。
这里重点说setState
和forceUpdate
这两个触发 dom 更新
setState
保存旧的this.state
到this.prevState
里,然后新的 state 是直接设置在this.state
。
然后通过enqueueRender
来加入队列中,这个更新是在异步中的。所以不要写出这种代码
1
2
3
4
5
6
7
8
| test() {
// 这里的setState已经入异步栈,
this.setState({...})
$.post(...() => {
// 再次入异步栈,再一次执行,
this.setState({...})
})
}
|
可以把两次setState
合并到一起做。
render-queue
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
| import { Component } from "./component";
import options from "./options";
import { defer } from "./util";
import { renderComponent } from "./vdom/component";
let items: Component[] = [];
/**
* 把Component放入队列中等待更新
* @param component 组件
*/
export function enqueueRender(component: Component) {
if (!component._dirty) {
// 防止多次render
component._dirty = true;
const len = items.push(component);
if (len === 1) {
// 在第一次时添加一个异步render,保证同步代码执行完只有一个异步render。
const deferFun = options.debounceRendering || defer;
deferFun(rerender);
}
}
}
/**
* 根据Component队列更新dom。
* 可以setState后直接执行这个方法强制同步更新dom
*/
export function rerender() {
let p: Component | undefined;
const list = items;
items = [];
while ((p = list.pop())) {
if (p._dirty) {
// 防止多次render。
renderComponent(p);
}
}
}
|
最终通过renderComponent
来重新diff
更新dom
forceUpdate
则是直接同步更新不过传入了一个标记FORCE_RENDER
。
顺便写下options
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
| import { VNode } from "./vnode";
import { Component } from "component";
const options: {
// render更新后钩子比componentDidUpdate更后面执行
afterUpdate?: (component: Component) => void;
// dom卸载载前钩子比componentWillUnmount更先执行
beforeUnmount?: (component: Component) => void;
// dom挂载后钩子比componentDidMount更先执行
afterMount?: (component: Component) => void;
// setComponentProps时强制为同步render
syncComponentUpdates?: boolean;
// 自定义异步调度方法,会异步执行传入的方法
debounceRendering?: (render: () => void) => void;
// vnode实例创建时的钩子
vnode?: (vnode: VNode) => void;
// 事件钩子,可以对event过滤返回的会代替event参数
event?: (event: Event) => any;
// 是否自动对事件方法绑定this为组件,默认为true(preact没有)
eventBind?: boolean;
} = {
eventBind: true
};
export default options;
|
后记
- 感觉有了更多的注释,就没有必要说明太多了。
- 下一篇应该是到了
renderComponent
和diff
部分了。
文章作者
上次更新
2020-06-15 18:12:42 +08:00
(9c054d8)
许可协议
CC BY-NC-ND 4.0