這裏是第二篇,第一篇在這裏javascript
此次講Component,以及它的一些輕量依賴。vue
順便說下司徒正美的preact源碼學習java
感受比我寫的好多了,圖文並茂,還能提出和其它如React的源碼比較。node
我惟一好點的可能就是代碼幾乎每行都有註釋,而且使用了typescript添加了類型的標註。react
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
還有其它的更新方式。typescript
這裏的代碼是經過typescript
重寫過的因此有所不一樣,
可是更好的瞭解一個完整的Component
總體應該有什麼。segmentfault
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更新dom
setState
保存舊的this.state
到this.prevState
裏,而後新的state是直接設置在this.state
。
而後經過enqueueRender
來加入隊列中,這個更新是在異步中的。因此不要寫出這種代碼異步
test() { // 這裏的setState已經入異步棧, this.setState({...}) $.post(...() => { // 再次入異步棧,再一次執行, this.setState({...}) }) }
能夠把兩次setState
合併到一塊兒作。
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
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
部分了。