前言

  • 这里是第二篇,第一篇在这里
  • 这次讲 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这个属性其它实际上官方的也有,但是都是可选属性。

这里重点说setStateforceUpdate这两个触发 dom 更新

setState保存旧的this.statethis.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;

后记

  • 感觉有了更多的注释,就没有必要说明太多了。
  • 下一篇应该是到了renderComponentdiff部分了。