├── README.md ├── preact-10 └── src │ ├── clone-element.js │ ├── component.js │ ├── constants.js │ ├── create-context.js │ ├── create-element.js │ ├── diff │ ├── children.js │ ├── index.js │ └── props.js │ ├── index.js │ ├── options.js │ ├── render.js │ └── util.js ├── preact-source ├── clone-element.js ├── component.js ├── constants.js ├── dom │ └── index.js ├── h.js ├── options.js ├── preact.d.ts ├── preact.js ├── render-queue.js ├── render.js ├── util.js ├── vdom │ ├── component-recycler.js │ ├── component.js │ ├── diff.js │ └── index.js └── vnode.js ├── react-redux ├── components │ ├── Context.js │ ├── Provider.js │ └── connectAdvanced.js ├── connect │ ├── connect.js │ ├── mapDispatchToProps.js │ ├── mapStateToProps.js │ ├── mergeProps.js │ ├── selectorFactory.js │ ├── verifySubselectors.js │ └── wrapMapToProps.js ├── index.js └── utils │ ├── isPlainObject.js │ ├── shallowEqual.js │ ├── verifyPlainObject.js │ ├── warning.js │ └── wrapActionCreators.js └── redux-source ├── applyMiddleware.js ├── bindActionCreators.js ├── combineReducers.js ├── compose.js ├── createStore.js ├── index.js └── utils ├── actionTypes.js ├── isPlainObject.js └── warning.js /README.md: -------------------------------------------------------------------------------- 1 | ### 源码阅读 2 | 3 | #### redux 4 | 5 | - [入口](https://github.com/soraping/any-source/issues/5) 6 | - [utils 工具方法](https://github.com/soraping/any-source/issues/6) 7 | - [createStore](https://github.com/soraping/any-source/issues/7) 8 | - [bindActionCreators](https://github.com/soraping/any-source/issues/8) 9 | - [combineReducers](https://github.com/soraping/any-source/issues/9) 10 | - [applyMiddleware](https://github.com/soraping/any-source/issues/10) 11 | 12 | #### preact 8.x 13 | 14 | - [jsx,vnode 和 h 函数](https://github.com/soraping/any-source/issues/11) 15 | - [diff](https://github.com/soraping/any-source/issues/12) 16 | - [component](https://github.com/soraping/any-source/issues/13) 17 | 18 | #### preact 10.x 19 | 20 | > 主要对比前版本的差异,学习作者的更新思路 21 | 22 | - [试试这么读 preact 源码(一)- createElement 函数](https://github.com/soraping/any-source/issues/16) 23 | - [试试这么读 preact 源码(二)- render](https://github.com/soraping/any-source/issues/17) 24 | 25 | ### 记录心得 blog 26 | 27 | #### js 基本功 28 | 29 | - [js prototype, constructor and new](https://github.com/soraping/any-source/issues/3) 30 | 31 | - [大道至简,知易行难 -- AST](https://github.com/soraping/any-source/issues/2) 32 | 33 | - [call,apply 及其简单实现](https://github.com/soraping/any-source/issues/1) 34 | 35 | #### react && redux 36 | 37 | - [redux-observable 使用小记](https://github.com/soraping/any-source/issues/4) 38 | 39 | #### 编程思想 40 | 41 | - [IOC](https://github.com/soraping/any-source/issues/14) 42 | 43 | - [typescript 依赖注入实践](https://github.com/soraping/any-source/issues/15) 44 | -------------------------------------------------------------------------------- /preact-10/src/clone-element.js: -------------------------------------------------------------------------------- 1 | import { assign } from './util'; 2 | import { EMPTY_ARR } from './constants'; 3 | import { createVNode } from './create-element'; 4 | 5 | /** 6 | * Clones the given VNode, optionally adding attributes/props and replacing its children. 7 | * @param {import('./internal').VNode} vnode The virtual DOM element to clone 8 | * @param {object} props Attributes/props to add when cloning 9 | * @param {Array} rest Any additional arguments will be used as replacement children. 10 | */ 11 | export function cloneElement(vnode, props) { 12 | props = assign(assign({}, vnode.props), props); 13 | if (arguments.length>2) props.children = EMPTY_ARR.slice.call(arguments, 2); 14 | return createVNode(vnode.type, props, props.key || vnode.key, props.ref || vnode.ref); 15 | } 16 | -------------------------------------------------------------------------------- /preact-10/src/component.js: -------------------------------------------------------------------------------- 1 | import { assign } from './util'; 2 | import { diff, commitRoot } from './diff/index'; 3 | import options from './options'; 4 | import { Fragment } from './create-element'; 5 | 6 | /** 7 | * Base Component class. Provides `setState()` and `forceUpdate()`, which 8 | * trigger rendering 9 | * @param {object} props The initial component props 10 | * @param {object} context The initial context from parent components' 11 | * getChildContext 12 | */ 13 | export function Component(props, context) { 14 | this.props = props; 15 | this.context = context; 16 | // this.constructor // When component is functional component, this is reset to functional component 17 | // if (this.state==null) this.state = {}; 18 | // this.state = {}; 19 | // this._dirty = true; 20 | // this._renderCallbacks = []; // Only class components 21 | 22 | // Other properties that Component will have set later, 23 | // shown here as commented out for quick reference 24 | // this.base = null; 25 | // this._context = null; 26 | // this._ancestorComponent = null; // Always set right after instantiation 27 | // this._vnode = null; 28 | // this._nextState = null; // Only class components 29 | // this._prevVNode = null; 30 | // this._processingException = null; // Always read, set only when handling error 31 | // this._pendingError = null; // Always read, set only when handling error. This is used to indicate at diffTime to set _processingException 32 | } 33 | 34 | /** 35 | * Update component state and schedule a re-render. 36 | * @param {object | ((s: object, p: object) => object)} update A hash of state 37 | * properties to update with new values or a function that given the current 38 | * state and props returns a new partial state 39 | * @param {() => void} [callback] A function to be called once component state is 40 | * updated 41 | */ 42 | Component.prototype.setState = function(update, callback) { 43 | // only clone state when copying to nextState the first time. 44 | let s = (this._nextState!==this.state && this._nextState) || (this._nextState = assign({}, this.state)); 45 | 46 | // if update() mutates state in-place, skip the copy: 47 | if (typeof update!=='function' || (update = update(s, this.props))) { 48 | assign(s, update); 49 | } 50 | 51 | // Skip update if updater function returned null 52 | if (update==null) return; 53 | 54 | if (this._vnode) { 55 | if (callback) this._renderCallbacks.push(callback); 56 | enqueueRender(this); 57 | } 58 | }; 59 | 60 | /** 61 | * Immediately perform a synchronous re-render of the component 62 | * @param {() => void} [callback] A function to be called after component is 63 | * re-renderd 64 | */ 65 | Component.prototype.forceUpdate = function(callback) { 66 | let vnode = this._vnode, dom = this._vnode._dom, parentDom = this._parentDom; 67 | if (parentDom) { 68 | // Set render mode so that we can differantiate where the render request 69 | // is coming from. We need this because forceUpdate should never call 70 | // shouldComponentUpdate 71 | const force = callback!==false; 72 | 73 | let mounts = []; 74 | dom = diff(parentDom, vnode, vnode, this._context, parentDom.ownerSVGElement!==undefined, null, mounts, this._ancestorComponent, force, dom); 75 | if (dom!=null && dom.parentNode!==parentDom) { 76 | parentDom.appendChild(dom); 77 | } 78 | commitRoot(mounts, vnode); 79 | } 80 | if (callback) callback(); 81 | }; 82 | 83 | /** 84 | * Accepts `props` and `state`, and returns a new Virtual DOM tree to build. 85 | * Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx). 86 | * @param {object} props Props (eg: JSX attributes) received from parent 87 | * element/component 88 | * @param {object} state The component's current state 89 | * @param {object} context Context object, as returned by the nearest 90 | * ancestor's `getChildContext()` 91 | * @returns {import('./index').ComponentChildren | void} 92 | */ 93 | Component.prototype.render = Fragment; 94 | 95 | /** 96 | * The render queue 97 | * @type {Array} 98 | */ 99 | let q = []; 100 | 101 | /** 102 | * Asynchronously schedule a callback 103 | * @type {(cb) => void} 104 | */ 105 | const defer = typeof Promise=='function' ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout; 106 | 107 | /* 108 | * The value of `Component.debounce` must asynchronously invoke the passed in callback. It is 109 | * important that contributors to Preact can consistenly reason about what calls to `setState`, etc. 110 | * do, and when their effects will be applied. See the links below for some further reading on designing 111 | * asynchronous APIs. 112 | * * [Designing APIs for Asynchrony](https://blog.izs.me/2013/08/designing-apis-for-asynchrony) 113 | * * [Callbacks synchronous and asynchronous](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/) 114 | */ 115 | 116 | /** 117 | * Enqueue a rerender of a component 118 | * @param {import('./internal').Component} c The component to rerender 119 | */ 120 | export function enqueueRender(c) { 121 | if (!c._dirty && (c._dirty = true) && q.push(c) === 1) { 122 | (options.debounceRendering || defer)(process); 123 | } 124 | } 125 | 126 | /** Flush the render queue by rerendering all queued components */ 127 | function process() { 128 | let p; 129 | q.sort((a, b) => b._depth - a._depth); 130 | while ((p=q.pop())) { 131 | // forceUpdate's callback argument is reused here to indicate a non-forced update. 132 | if (p._dirty) p.forceUpdate(false); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /preact-10/src/constants.js: -------------------------------------------------------------------------------- 1 | export const EMPTY_OBJ = {}; 2 | export const EMPTY_ARR = []; 3 | export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i; 4 | -------------------------------------------------------------------------------- /preact-10/src/create-context.js: -------------------------------------------------------------------------------- 1 | import { enqueueRender } from './component'; 2 | 3 | export let i = 0; 4 | 5 | /** 6 | * 7 | * @param {any} defaultValue 8 | */ 9 | export function createContext(defaultValue) { 10 | const ctx = {}; 11 | 12 | const context = { 13 | _id: '__cC' + i++, 14 | _defaultValue: defaultValue, 15 | Consumer(props, context) { 16 | return props.children(context); 17 | }, 18 | Provider(props) { 19 | if (!this.getChildContext) { 20 | const subs = []; 21 | this.getChildContext = () => { 22 | ctx[context._id] = this; 23 | return ctx; 24 | }; 25 | this.shouldComponentUpdate = props => { 26 | subs.some(c => { 27 | // Check if still mounted 28 | if (c._parentDom) { 29 | c.context = props.value; 30 | enqueueRender(c); 31 | } 32 | }); 33 | }; 34 | this.sub = (c) => { 35 | subs.push(c); 36 | let old = c.componentWillUnmount; 37 | c.componentWillUnmount = () => { 38 | subs.splice(subs.indexOf(c), 1); 39 | old && old(); 40 | }; 41 | }; 42 | } 43 | return props.children; 44 | } 45 | }; 46 | 47 | context.Consumer.contextType = context; 48 | 49 | return context; 50 | } 51 | -------------------------------------------------------------------------------- /preact-10/src/create-element.js: -------------------------------------------------------------------------------- 1 | import options from "./options"; 2 | import { assign } from "./util"; 3 | 4 | /** 5 | * 创建虚拟dom 6 | * @param {*} type 组件的元素名 7 | * @param {*} props 传入的属性参数 8 | * @param {*} children 子组件 9 | */ 10 | export function createElement(type, props, children) { 11 | props = assign({}, props); 12 | 13 | if (arguments.length > 3) { 14 | children = [children]; 15 | for (let i = 3; i < arguments.length; i++) { 16 | children.push(arguments[i]); 17 | } 18 | } 19 | if (children != null) { 20 | props.children = children; 21 | } 22 | 23 | // "type" may be undefined during development. The check is needed so that 24 | // we can display a nice error message with our debug helpers 25 | if (type != null && type.defaultProps != null) { 26 | for (let i in type.defaultProps) { 27 | if (props[i] === undefined) props[i] = type.defaultProps[i]; 28 | } 29 | } 30 | let ref = props.ref; 31 | let key = props.key; 32 | if (ref != null) delete props.ref; 33 | if (key != null) delete props.key; 34 | 35 | return createVNode(type, props, key, ref); 36 | } 37 | 38 | /** 39 | * Create a VNode (used internally by Preact) 40 | * @param {import('./internal').VNode["type"]} type The node name or Component 41 | * Constructor for this virtual node 42 | * @param {object | string | number | null} props The properites of this virtual node. 43 | * If this virtual node represents a text node, this is the text of the node (string or number). 44 | * @param {string | number | null} key The key for this virtual node, used when 45 | * diffing it against its children 46 | * @param {import('./internal').VNode["ref"]} ref The ref property that will 47 | * receive a reference to its created child 48 | * @returns {import('./internal').VNode} 49 | */ 50 | export function createVNode(type, props, key, ref) { 51 | // V8 seems to be better at detecting type shapes if the object is allocated from the same call site 52 | // Do not inline into createElement and coerceToVNode! 53 | const vnode = { 54 | type, 55 | props, 56 | key, 57 | ref, 58 | _children: null, 59 | _dom: null, 60 | _lastDomChild: null, 61 | _component: null 62 | }; 63 | vnode._self = vnode; 64 | 65 | if (options.vnode) options.vnode(vnode); 66 | 67 | return vnode; 68 | } 69 | 70 | export function createRef() { 71 | return {}; 72 | } 73 | 74 | export /* istanbul ignore next */ function Fragment() {} 75 | 76 | /** 77 | * Coerce an untrusted value into a VNode 78 | * Specifically, this should be used anywhere a user could provide a boolean, string, or number where 79 | * a VNode or Component is desired instead 80 | * @param {boolean | string | number | import('./internal').VNode} possibleVNode A possible VNode 81 | * @returns {import('./internal').VNode | null} 82 | */ 83 | export function coerceToVNode(possibleVNode) { 84 | if (possibleVNode == null || typeof possibleVNode === "boolean") return null; 85 | if (typeof possibleVNode === "string" || typeof possibleVNode === "number") { 86 | return createVNode(null, possibleVNode, null, null); 87 | } 88 | 89 | if (Array.isArray(possibleVNode)) { 90 | return createElement(Fragment, null, possibleVNode); 91 | } 92 | 93 | // Clone vnode if it has already been used. ceviche/#57 94 | if (possibleVNode._dom != null || possibleVNode._component != null) { 95 | let vnode = createVNode( 96 | possibleVNode.type, 97 | possibleVNode.props, 98 | possibleVNode.key, 99 | null 100 | ); 101 | vnode._dom = possibleVNode._dom; 102 | return vnode; 103 | } 104 | 105 | return possibleVNode; 106 | } 107 | -------------------------------------------------------------------------------- /preact-10/src/diff/children.js: -------------------------------------------------------------------------------- 1 | import { diff, unmount } from './index'; 2 | import { coerceToVNode, Fragment } from '../create-element'; 3 | import { EMPTY_OBJ, EMPTY_ARR } from '../constants'; 4 | import { removeNode } from '../util'; 5 | 6 | /** 7 | * Diff the children of a virtual node 8 | * @param {import('../internal').PreactElement} parentDom The DOM element whose 9 | * children are being diffed 10 | * @param {import('../internal').VNode} newParentVNode The new virtual 11 | * node whose children should be diff'ed against oldParentVNode 12 | * @param {import('../internal').VNode} oldParentVNode The old virtual 13 | * node whose children should be diff'ed against newParentVNode 14 | * @param {object} context The current context object 15 | * @param {boolean} isSvg Whether or not this DOM node is an SVG node 16 | * @param {Array} excessDomChildren 17 | * @param {Array} mounts The list of components 18 | * which have mounted 19 | * @param {import('../internal').Component} ancestorComponent The direct parent 20 | * component to the ones being diffed 21 | * @param {Node | Text} oldDom The current attached DOM 22 | * element any new dom elements should be placed around. Likely `null` on first 23 | * render (except when hydrating). Can be a sibling DOM element when diffing 24 | * Fragments that have siblings. In most cases, it starts out as `oldChildren[0]._dom`. 25 | */ 26 | export function diffChildren(parentDom, newParentVNode, oldParentVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent, oldDom) { 27 | let childVNode, i, j, oldVNode, newDom, sibDom; 28 | 29 | let newChildren = newParentVNode._children || toChildArray(newParentVNode.props.children, newParentVNode._children=[], coerceToVNode, true); 30 | // This is a compression of oldParentVNode!=null && oldParentVNode != EMPTY_OBJ && oldParentVNode._children || EMPTY_ARR 31 | // as EMPTY_OBJ._children should be `undefined`. 32 | let oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR; 33 | 34 | let oldChildrenLength = oldChildren.length; 35 | let oldChild; 36 | 37 | // Only in very specific places should this logic be invoked (top level `render` and `diffElementNodes`). 38 | // I'm using `EMPTY_OBJ` to signal when `diffChildren` is invoked in these situations. I can't use `null` 39 | // for this purpose, because `null` is a valid value for `oldDom` which can mean to skip to this logic 40 | // (e.g. if mounting a new tree in which the old DOM should be ignored (usually for Fragments). 41 | if (oldDom == EMPTY_OBJ) { 42 | oldDom = null; 43 | if (excessDomChildren!=null) { 44 | for (i = 0; !oldDom && i < excessDomChildren.length; i++) { 45 | oldDom = excessDomChildren[i]; 46 | } 47 | } 48 | else { 49 | for (i = 0; !oldDom && i < oldChildrenLength; i++) { 50 | oldDom = oldChildren[i] && oldChildren[i]._dom; 51 | oldChild = oldChildren[i]; 52 | } 53 | } 54 | } 55 | 56 | for (i=0; i 0, 71 | // so after this loop oldVNode == null or oldVNode is a valid value. 72 | for (j=0; j} [flattened] An flat array of children to modify 132 | * @param {typeof import('../create-element').coerceToVNode} [map] Function that 133 | * will be applied on each child if the `vnode` is not `null` 134 | * @param {boolean} [keepHoles] wether to coerce `undefined` to `null` or not. 135 | * This is needed for Components without children like ``. 136 | */ 137 | export function toChildArray(children, flattened, map, keepHoles) { 138 | if (flattened == null) flattened = []; 139 | if (children==null || typeof children === 'boolean') { 140 | if (keepHoles) flattened.push(null); 141 | } 142 | else if (Array.isArray(children)) { 143 | for (let i=0; i < children.length; i++) { 144 | toChildArray(children[i], flattened, map, keepHoles); 145 | } 146 | } 147 | else { 148 | flattened.push(map ? map(children) : children); 149 | } 150 | 151 | return flattened; 152 | } 153 | -------------------------------------------------------------------------------- /preact-10/src/diff/index.js: -------------------------------------------------------------------------------- 1 | import { EMPTY_OBJ, EMPTY_ARR } from '../constants'; 2 | import { Component, enqueueRender } from '../component'; 3 | import { coerceToVNode, Fragment } from '../create-element'; 4 | import { diffChildren } from './children'; 5 | import { diffProps } from './props'; 6 | import { assign, removeNode } from '../util'; 7 | import options from '../options'; 8 | 9 | /** 10 | * Diff two virtual nodes and apply proper changes to the DOM 11 | * @param {import('../internal').PreactElement} parentDom The parent of the DOM element 12 | * @param {import('../internal').VNode | null} newVNode The new virtual node 13 | * @param {import('../internal').VNode | null} oldVNode The old virtual node 14 | * @param {object} context The current context object 15 | * @param {boolean} isSvg Whether or not this element is an SVG node 16 | * @param {Array} excessDomChildren 17 | * @param {Array} mounts A list of newly 18 | * mounted components 19 | * @param {import('../internal').Component | null} ancestorComponent The direct 20 | * parent component 21 | * @param {Element | Text} oldDom The current attached DOM 22 | * element any new dom elements should be placed around. Likely `null` on first 23 | * render (except when hydrating). Can be a sibling DOM element when diffing 24 | * Fragments that have siblings. In most cases, it starts out as `oldChildren[0]._dom`. 25 | */ 26 | export function diff(parentDom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent, force, oldDom) { 27 | // If the previous type doesn't match the new type we drop the whole subtree 28 | if (oldVNode==null || newVNode==null || oldVNode.type!==newVNode.type || oldVNode.key!==newVNode.key) { 29 | if (oldVNode!=null) unmount(oldVNode, ancestorComponent); 30 | if (newVNode==null) return null; 31 | oldVNode = EMPTY_OBJ; 32 | } 33 | 34 | let c, tmp, isNew, oldProps, oldState, snapshot, 35 | newType = newVNode.type, clearProcessingException; 36 | 37 | // When passing through createElement it assigns the object 38 | // ref on _self, to prevent JSON Injection we check if this attribute 39 | // is equal. 40 | if (newVNode._self!==newVNode) return null; 41 | 42 | if (tmp = options.diff) tmp(newVNode); 43 | 44 | try { 45 | outer: if (oldVNode.type===Fragment || newType===Fragment) { 46 | diffChildren(parentDom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, c, oldDom); 47 | 48 | // Mark dom as empty in case `_children` is any empty array. If it isn't 49 | // we'll set `dom` to the correct value just a few lines later. 50 | 51 | let i = newVNode._children.length; 52 | if (i && (tmp=newVNode._children[0]) != null) { 53 | newVNode._dom = tmp._dom; 54 | 55 | // If the last child is a Fragment, use _lastDomChild, else use _dom 56 | // We have no guarantee that the last child rendered something into the 57 | // dom, so we iterate backwards to find the last child with a dom node. 58 | while (i--) { 59 | tmp = newVNode._children[i]; 60 | if (newVNode._lastDomChild = (tmp && (tmp._lastDomChild || tmp._dom))) { 61 | break; 62 | } 63 | } 64 | } 65 | } 66 | else if (typeof newType==='function') { 67 | 68 | // Necessary for createContext api. Setting this property will pass 69 | // the context value as `this.context` just for this component. 70 | tmp = newType.contextType; 71 | let provider = tmp && context[tmp._id]; 72 | let cctx = tmp ? (provider ? provider.props.value : tmp._defaultValue) : context; 73 | 74 | // Get component and set it to `c` 75 | if (oldVNode._component) { 76 | c = newVNode._component = oldVNode._component; 77 | clearProcessingException = c._processingException = c._pendingError; 78 | newVNode._dom = oldVNode._dom; 79 | } 80 | else { 81 | // Instantiate the new component 82 | if (newType.prototype && newType.prototype.render) { 83 | newVNode._component = c = new newType(newVNode.props, cctx); // eslint-disable-line new-cap 84 | } 85 | else { 86 | newVNode._component = c = new Component(newVNode.props, cctx); 87 | c.constructor = newType; 88 | c.render = doRender; 89 | } 90 | c._ancestorComponent = ancestorComponent; 91 | if (provider) provider.sub(c); 92 | 93 | c.props = newVNode.props; 94 | if (!c.state) c.state = {}; 95 | c.context = cctx; 96 | c._context = context; 97 | isNew = c._dirty = true; 98 | c._renderCallbacks = []; 99 | } 100 | 101 | c._vnode = newVNode; 102 | 103 | // Invoke getDerivedStateFromProps 104 | if (c._nextState==null) { 105 | c._nextState = c.state; 106 | } 107 | if (newType.getDerivedStateFromProps!=null) { 108 | assign(c._nextState==c.state ? (c._nextState = assign({}, c._nextState)) : c._nextState, newType.getDerivedStateFromProps(newVNode.props, c._nextState)); 109 | } 110 | 111 | // Invoke pre-render lifecycle methods 112 | if (isNew) { 113 | if (newType.getDerivedStateFromProps==null && c.componentWillMount!=null) c.componentWillMount(); 114 | if (c.componentDidMount!=null) mounts.push(c); 115 | } 116 | else { 117 | if (newType.getDerivedStateFromProps==null && force==null && c.componentWillReceiveProps!=null) { 118 | c.componentWillReceiveProps(newVNode.props, cctx); 119 | } 120 | 121 | if (!force && c.shouldComponentUpdate!=null && c.shouldComponentUpdate(newVNode.props, c._nextState, cctx)===false) { 122 | c.props = newVNode.props; 123 | c.state = c._nextState; 124 | c._dirty = false; 125 | newVNode._lastDomChild = oldVNode._lastDomChild; 126 | break outer; 127 | } 128 | 129 | if (c.componentWillUpdate!=null) { 130 | c.componentWillUpdate(newVNode.props, c._nextState, cctx); 131 | } 132 | } 133 | 134 | oldProps = c.props; 135 | oldState = c.state; 136 | 137 | c.context = cctx; 138 | c.props = newVNode.props; 139 | c.state = c._nextState; 140 | 141 | if (tmp = options.render) tmp(newVNode); 142 | 143 | let prev = c._prevVNode || null; 144 | c._dirty = false; 145 | let vnode = c._prevVNode = coerceToVNode(c.render(c.props, c.state, c.context)); 146 | 147 | if (c.getChildContext!=null) { 148 | context = assign(assign({}, context), c.getChildContext()); 149 | } 150 | 151 | if (!isNew && c.getSnapshotBeforeUpdate!=null) { 152 | snapshot = c.getSnapshotBeforeUpdate(oldProps, oldState); 153 | } 154 | 155 | c._depth = ancestorComponent ? (ancestorComponent._depth || 0) + 1 : 0; 156 | c.base = newVNode._dom = diff(parentDom, vnode, prev, context, isSvg, excessDomChildren, mounts, c, null, oldDom); 157 | 158 | if (vnode!=null) { 159 | // If this component returns a Fragment (or another component that 160 | // returns a Fragment), then _lastDomChild will be non-null, 161 | // informing `diffChildren` to diff this component's VNode like a Fragemnt 162 | newVNode._lastDomChild = vnode._lastDomChild; 163 | } 164 | 165 | c._parentDom = parentDom; 166 | 167 | if (tmp = newVNode.ref) applyRef(tmp, c, ancestorComponent); 168 | 169 | while (tmp=c._renderCallbacks.pop()) tmp.call(c); 170 | 171 | // Don't call componentDidUpdate on mount or when we bailed out via 172 | // `shouldComponentUpdate` 173 | if (!isNew && oldProps!=null && c.componentDidUpdate!=null) { 174 | c.componentDidUpdate(oldProps, oldState, snapshot); 175 | } 176 | } 177 | else { 178 | newVNode._dom = diffElementNodes(oldVNode._dom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent); 179 | 180 | if ((tmp = newVNode.ref) && (oldVNode.ref !== tmp)) { 181 | applyRef(tmp, newVNode._dom, ancestorComponent); 182 | } 183 | } 184 | 185 | if (clearProcessingException) { 186 | c._pendingError = c._processingException = null; 187 | } 188 | 189 | if (tmp = options.diffed) tmp(newVNode); 190 | } 191 | catch (e) { 192 | catchErrorInComponent(e, ancestorComponent); 193 | } 194 | 195 | return newVNode._dom; 196 | } 197 | 198 | export function commitRoot(mounts, root) { 199 | let c; 200 | while ((c = mounts.pop())) { 201 | try { 202 | c.componentDidMount(); 203 | } 204 | catch (e) { 205 | catchErrorInComponent(e, c._ancestorComponent); 206 | } 207 | } 208 | 209 | if (options.commit) options.commit(root); 210 | } 211 | 212 | /** 213 | * Diff two virtual nodes representing DOM element 214 | * @param {import('../internal').PreactElement} dom The DOM element representing 215 | * the virtual nodes being diffed 216 | * @param {import('../internal').VNode} newVNode The new virtual node 217 | * @param {import('../internal').VNode} oldVNode The old virtual node 218 | * @param {object} context The current context object 219 | * @param {boolean} isSvg Whether or not this DOM node is an SVG node 220 | * @param {*} excessDomChildren 221 | * @param {Array} mounts An array of newly 222 | * mounted components 223 | * @param {import('../internal').Component} ancestorComponent The parent 224 | * component to the ones being diffed 225 | * @returns {import('../internal').PreactElement} 226 | */ 227 | function diffElementNodes(dom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent) { 228 | let i; 229 | let oldProps = oldVNode.props; 230 | let newProps = newVNode.props; 231 | 232 | // Tracks entering and exiting SVG namespace when descending through the tree. 233 | isSvg = newVNode.type==='svg' || isSvg; 234 | 235 | if (dom==null && excessDomChildren!=null) { 236 | for (i=0; i} [rest] 第三个参数,类似与h函数的参数设置 10 | * children. 11 | */ 12 | export function cloneElement(vnode, props) { 13 | return h( 14 | vnode.nodeName, 15 | extend(extend({}, vnode.attributes), props), 16 | arguments.length > 2 ? [].slice.call(arguments, 2) : vnode.children 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /preact-source/component.js: -------------------------------------------------------------------------------- 1 | import { FORCE_RENDER } from "./constants"; 2 | import { extend } from "./util"; 3 | import { renderComponent } from "./vdom/component"; 4 | import { enqueueRender } from "./render-queue"; 5 | 6 | /** 7 | * 定义组件的父类 Component 8 | * 所写的react组件都会继承这个类,这样就能用得上这个父类的属性和方法了 9 | * 10 | * @example 11 | * class MyFoo extends Component { 12 | * render(props, state) { 13 | * return
; 14 | * } 15 | * } 16 | * 17 | * @param {*} props 18 | * @param {*} context 19 | */ 20 | export function Component(props, context) { 21 | /** 22 | * 用来表示存在脏数据(即数据与存在的对应渲染不一致), 23 | * 例如多次在组件实例调用setState,使得_dirty为true, 24 | * 但因为该属性的存在,只会使得组件仅有一次才会被放入更新队列。 25 | */ 26 | this._dirty = true; 27 | 28 | /** 29 | * 组件上下文属性 30 | * @public 31 | * @type {object} 32 | */ 33 | this.context = context; 34 | 35 | /** 36 | * @public 37 | * @type {object} 38 | */ 39 | this.props = props; 40 | 41 | /** 42 | * @public 43 | * @type {object} 44 | */ 45 | this.state = this.state || {}; 46 | 47 | /** 48 | * render 回调函数队列 49 | */ 50 | this._renderCallbacks = []; 51 | } 52 | 53 | /** 54 | * 给 Component 类的构造函数的原型上添加若干个方法 55 | */ 56 | extend(Component.prototype, { 57 | /** 58 | * this 指向调用的组件 59 | * 用来更新state树数据 60 | * state即可以是json,也可以是function 61 | * 执行 enqueueRender 函数,将组件存入缓存队列,异步调用 render 组件 62 | * @param {*} state 新的state 63 | * @param {*} callback 更新后的回调函数 64 | */ 65 | setState(state, callback) { 66 | if (!this.prevState) this.prevState = this.state; 67 | this.state = extend( 68 | extend({}, this.state), 69 | typeof state === "function" ? state(this.state, this.props) : state 70 | ); 71 | if (callback) this._renderCallbacks.push(callback); 72 | enqueueRender(this); 73 | }, 74 | 75 | /** 76 | * 与React的forceUpdate相同,立刻同步重新渲染组件 77 | * @param {*} callback 78 | */ 79 | forceUpdate(callback) { 80 | if (callback) this._renderCallbacks.push(callback); 81 | renderComponent(this, FORCE_RENDER); 82 | }, 83 | 84 | /** 85 | * 返回组件的渲染内容的虚拟dom,此处函数体为空 86 | */ 87 | render() {} 88 | }); 89 | -------------------------------------------------------------------------------- /preact-source/constants.js: -------------------------------------------------------------------------------- 1 | // render modes 2 | 3 | // 不渲染 4 | export const NO_RENDER = 0; 5 | // react.render 其实就是同步 6 | export const SYNC_RENDER = 1; 7 | // forceUpdate 强制刷新渲染 8 | export const FORCE_RENDER = 2; 9 | // 异步渲染 10 | export const ASYNC_RENDER = 3; 11 | 12 | // 在节点中添加的属性 13 | export const ATTR_KEY = "__preactattr_"; 14 | 15 | // 用于识别那些样式不用自动添加px的正则 16 | export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i; 17 | -------------------------------------------------------------------------------- /preact-source/dom/index.js: -------------------------------------------------------------------------------- 1 | import { IS_NON_DIMENSIONAL } from "../constants"; 2 | import { applyRef } from "../util"; 3 | import options from "../options"; 4 | 5 | /** 6 | * A DOM event listener 7 | * @typedef {(e: Event) => void} EventListner 8 | */ 9 | 10 | /** 11 | * A mapping of event types to event listeners 12 | * @typedef {Object.} EventListenerMap 13 | */ 14 | 15 | /** 16 | * Properties Preact adds to elements it creates 17 | * @typedef PreactElementExtensions 18 | * @property {string} [normalizedNodeName] A normalized node name to use in diffing 19 | * @property {EventListenerMap} [_listeners] A map of event listeners added by components to this DOM node 20 | * @property {import('../component').Component} [_component] The component that rendered this DOM node 21 | * @property {function} [_componentConstructor] The constructor of the component that rendered this DOM node 22 | */ 23 | 24 | /** 25 | * A DOM element that has been extended with Preact properties 26 | * @typedef {Element & ElementCSSInlineStyle & PreactElementExtensions} PreactElement 27 | */ 28 | 29 | /** 30 | * 创建一个节点,并在这个节点上追加一个属性 normalizedNodeName 31 | * 可见,通过preact创建的节点都会存在一个额外的属性 normalizedNodeName ,类似于常量 ATTR_KEY 32 | * @param {*} nodeName 33 | * @param {*} isSvg 34 | */ 35 | export function createNode(nodeName, isSvg) { 36 | /** @type {PreactElement} */ 37 | let node = isSvg 38 | ? document.createElementNS("http://www.w3.org/2000/svg", nodeName) 39 | : document.createElement(nodeName); 40 | node.normalizedNodeName = nodeName; 41 | return node; 42 | } 43 | 44 | /** 45 | * 从父节点删除该节点 46 | * @param {*} node 47 | */ 48 | export function removeNode(node) { 49 | let parentNode = node.parentNode; 50 | if (parentNode) parentNode.removeChild(node); 51 | } 52 | 53 | /** 54 | * dom中的属性更新 55 | * @param {*} node 目标dom 56 | * @param {*} name 属性名 57 | * @param {*} old 旧dom中属性名是name的值 58 | * @param {*} value 该属性当前要修改的值 59 | * @param {*} isSvg 是否svg 60 | */ 61 | export function setAccessor(node, name, old, value, isSvg) { 62 | // 样式 63 | if (name === "className") name = "class"; 64 | // 忽略key 65 | if (name === "key") { 66 | // ignore 67 | } else if (name === "ref") { 68 | // 如果是ref 函数被改变了,则执行这两个函数 69 | // old(null) 在卸载的时候,需要传一个null作为参数作为回调。 70 | // value(node) 返回新的节点 71 | applyRef(old, null); 72 | applyRef(value, node); 73 | } else if (name === "class" && !isSvg) { 74 | // 样式,直接用class也是可以的 75 | node.className = value || ""; 76 | } else if (name === "style") { 77 | // 一种是style值是字符传类型是,就表明传的是样式名 78 | if (!value || typeof value === "string" || typeof old === "string") { 79 | node.style.cssText = value || ""; 80 | } 81 | // 另一种style为字面量时,则标示传的是样式属性的键值对 82 | if (value && typeof value === "object") { 83 | if (typeof old !== "string") { 84 | // 当就值里的属性不同是,先将样式属性依次值为空 85 | for (let i in old) if (!(i in value)) node.style[i] = ""; 86 | } 87 | // 样式属性一一赋值 88 | for (let i in value) { 89 | node.style[i] = 90 | typeof value[i] === "number" && IS_NON_DIMENSIONAL.test(i) === false 91 | ? value[i] + "px" 92 | : value[i]; 93 | } 94 | } 95 | } else if (name === "dangerouslySetInnerHTML") { 96 | // dangerouslySetInnerHTML属性设置 97 | if (value) node.innerHTML = value.__html || ""; 98 | // 事件处理函数 onClick onBlur.... 99 | } else if (name[0] == "o" && name[1] == "n") { 100 | // 如果事件名是 Capture 结尾,如 onClickCapture 101 | // useCapture 值为boolean,addEventListener第三个参数为true时,说明在捕获中执行事件 102 | let useCapture = name !== (name = name.replace(/Capture$/, "")); 103 | // 去掉头两个'on'字符,获取真实事件名 104 | name = name.toLowerCase().substring(2); 105 | // 如果新值存在则做事件监听,不存在,则移除旧dom的事件 106 | if (value) { 107 | if (!old) node.addEventListener(name, eventProxy, useCapture); 108 | } else { 109 | node.removeEventListener(name, eventProxy, useCapture); 110 | } 111 | // 在 eventProxy 会用到 112 | (node._listeners || (node._listeners = {}))[name] = value; 113 | } else if (name !== "list" && name !== "type" && !isSvg && name in node) { 114 | // 给dom自有属性赋值时,在ie或者火狐环境下可能会抛异常 115 | try { 116 | node[name] = value == null ? "" : value; 117 | } catch (e) {} 118 | if ((value == null || value === false) && name != "spellcheck") 119 | // 除了属性名为 spellcheck ,如果value值为空值,则直接删除这个属性 120 | // 属性是一个键值对,除了list,type,spellcheck这些特殊的 121 | node.removeAttribute(name); 122 | } else { 123 | // 最后处理svg 124 | let ns = isSvg && name !== (name = name.replace(/^xlink:?/, "")); 125 | // spellcheck is treated differently than all other boolean values and 126 | // should not be removed when the value is `false`. See: 127 | // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-spellcheck 128 | if (value == null || value === false) { 129 | if (ns) 130 | node.removeAttributeNS( 131 | "http://www.w3.org/1999/xlink", 132 | name.toLowerCase() 133 | ); 134 | else node.removeAttribute(name); 135 | } else if (typeof value !== "function") { 136 | if (ns) 137 | node.setAttributeNS( 138 | "http://www.w3.org/1999/xlink", 139 | name.toLowerCase(), 140 | value 141 | ); 142 | else node.setAttribute(name, value); 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * 简单的事件代理机制,相当的nb 149 | * 当这个函数被调用时,就可以触发对应的事件处理程序 150 | * @param {*} e 151 | */ 152 | function eventProxy(e) { 153 | return this._listeners[e.type]((options.event && options.event(e)) || e); 154 | } 155 | -------------------------------------------------------------------------------- /preact-source/h.js: -------------------------------------------------------------------------------- 1 | import { VNode } from "./vnode"; 2 | import options from "./options"; 3 | 4 | // 将从第3位起的参数收集起来(其实就是一堆 h 方法的执行函数) 5 | const stack = []; 6 | 7 | // 定义一个初始子组件空数组 8 | const EMPTY_CHILDREN = []; 9 | 10 | /** 11 | * 虚拟dom生成方法 12 | * `
Hello!
` 13 | * `h('div', { id: 'foo', name : 'bar' }, 'Hello!');` 14 | * 15 | * @param {*} nodeName 元素标签名 例如: `div`, `a`, `span`, etc. 16 | * @param {*} attributes jsx上面的属性 17 | * @param {*} [rest] 剩下的参数都是子组件 18 | * @return VNode实例 19 | * { 20 | nodeName: 'div', 21 | children: [ 'Hello!' ], 22 | attributes: { id: 'foo', name: 'bar' }, 23 | key: undefined } 24 | */ 25 | export function h(nodeName, attributes) { 26 | let children = EMPTY_CHILDREN, 27 | lastSimple, 28 | child, 29 | simple, 30 | i; 31 | // 遍历h函数的参数,从第三个参数开始就是子组件 32 | for (i = arguments.length; i-- > 2; ) { 33 | // 将从第3位起的参数收集起来(其实就是一堆 h方法的执行函数) 34 | stack.push(arguments[i]); 35 | } 36 | // jsx属性存在 37 | if (attributes && attributes.children != null) { 38 | // 挂载了children属性,也把这个children属性值压在stack数组内,之后再删除这个children属性 39 | if (!stack.length) stack.push(attributes.children); 40 | delete attributes.children; 41 | } 42 | // 遍历子组件 43 | while (stack.length) { 44 | // 取出子组件数组中最后一个元素,且处理子组件如果返回的是数组的情况 45 | if ((child = stack.pop()) && child.pop !== undefined) { 46 | for (i = child.length; i--; ) stack.push(child[i]); 47 | } else { 48 | /** 49 | * h函数会根据子组件的不同类型进行封装 50 | * - bool 返回 null 51 | * - null 返回 "" 52 | * - number 返回 String(number) 53 | * child 就是 h 函数从第3个起的参数 54 | */ 55 | if (typeof child === "boolean") child = null; 56 | 57 | // 如果 nodeName 是一个简单元素标签,不是一个嵌套组件的 h 函数 58 | if ((simple = typeof nodeName !== "function")) { 59 | if (child == null) child = ""; 60 | else if (typeof child === "number") child = String(child); 61 | // 如果这个 参数 不是一个简单类型的,设置标志位值 62 | else if (typeof child !== "string") simple = false; 63 | } 64 | 65 | // 将各种情况的child存进children中 66 | if (simple && lastSimple) { 67 | children[children.length - 1] += child; 68 | } else if (children === EMPTY_CHILDREN) { 69 | children = [child]; 70 | } else { 71 | children.push(child); 72 | } 73 | 74 | lastSimple = simple; 75 | } 76 | } 77 | 78 | /** 79 | * 输出虚拟dom,VNode实例对象 80 | */ 81 | let p = new VNode(); 82 | // 标签或者 h 执行函数 83 | p.nodeName = nodeName; 84 | // 子组件组成的数组,每一项也是一个vnode 85 | p.children = children; 86 | // jsx 属性 87 | p.attributes = attributes == null ? undefined : attributes; 88 | // 组件唯一key 89 | p.key = attributes == null ? undefined : attributes.key; 90 | 91 | // 对最终生成的虚拟DOM进行扩展,如果扩展 options 对象 vnode 值不为 undefined,options的vnode方法会处理这个实例对象 92 | if (options.vnode !== undefined) options.vnode(p); 93 | 94 | return p; 95 | } 96 | -------------------------------------------------------------------------------- /preact-source/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 扩展 preact 功能,主要用于兼容 react 3 | * @property {boolean} [syncComponentUpdates] 是否同步刷新组件 4 | * @property {(vnode: VNode) => void} [vnode] 用于扩展 vnode 实例 5 | * @property {(component: Component) => void} [afterMount] 钩子函数,在组件 mounted 之后执行,类似于 componentDidMount 6 | * @property {(component: Component) => void} [afterUpdate] 钩子函数,类似于 componentDidUpdate 7 | * @property {(component: Component) => void} [beforeUnmount] 钩子函数,在组件销毁之前,componentWillUnMount 8 | * @property {(rerender: function) => void} [debounceRendering] Hook invoked whenever a rerender is requested. Can be used to debounce rerenders. 9 | * @property {(event: Event) => Event | void} [event] Hook invoked before any Preact event listeners. The return value (if any) replaces the native browser event given to event listeners 10 | */ 11 | const options = {}; 12 | 13 | export default options; 14 | -------------------------------------------------------------------------------- /preact-source/preact.d.ts: -------------------------------------------------------------------------------- 1 | export = preact; 2 | export as namespace preact; 3 | 4 | declare namespace preact { 5 | type Key = string | number; 6 | type RefObject = { current?: T | null }; 7 | type RefCallback = (instance: T | null) => void; 8 | type Ref = RefObject | RefCallback; 9 | type ComponentChild = VNode | object | string | number | boolean | null; 10 | type ComponentChildren = ComponentChild[] | ComponentChild; 11 | 12 | /** 13 | * @deprecated 14 | * 15 | * Use Attributes instead 16 | */ 17 | type ComponentProps = Attributes; 18 | 19 | /** 20 | * @deprecated 21 | * 22 | * Use ClassAttributes instead 23 | */ 24 | type PreactHTMLAttributes = ClassAttributes; 25 | 26 | interface Attributes { 27 | key?: Key; 28 | jsx?: boolean; 29 | } 30 | 31 | interface ClassAttributes extends Attributes { 32 | ref?: Ref; 33 | } 34 | 35 | interface PreactDOMAttributes { 36 | children?: ComponentChildren; 37 | dangerouslySetInnerHTML?: { 38 | __html: string; 39 | }; 40 | } 41 | 42 | type ComponentFactory

= ComponentConstructor

| FunctionalComponent

; 43 | /** 44 | * Define the contract for a virtual node in preact. 45 | * 46 | * A virtual node has a name, a map of attributes, an array 47 | * of child {VNode}s and a key. The key is used by preact for 48 | * internal purposes. 49 | */ 50 | interface VNode

{ 51 | nodeName: ComponentFactory

| string; 52 | attributes: P; 53 | children: Array | string>; 54 | key?: Key | null; 55 | } 56 | 57 | type RenderableProps = Readonly< 58 | P & Attributes & { children?: ComponentChildren; ref?: Ref } 59 | >; 60 | 61 | interface FunctionalComponent

{ 62 | (props: RenderableProps

, context?: any): VNode | null; 63 | displayName?: string; 64 | defaultProps?: Partial

; 65 | } 66 | 67 | interface ComponentConstructor

{ 68 | new (props: P, context?: any): Component; 69 | displayName?: string; 70 | defaultProps?: Partial

; 71 | } 72 | 73 | // Type alias for a component considered generally, whether stateless or stateful. 74 | type AnyComponent

= FunctionalComponent

| ComponentConstructor; 75 | 76 | interface Component

{ 77 | componentWillMount?(): void; 78 | componentDidMount?(): void; 79 | componentWillUnmount?(): void; 80 | getChildContext?(): object; 81 | componentWillReceiveProps?(nextProps: Readonly

, nextContext: any): void; 82 | shouldComponentUpdate?(nextProps: Readonly

, nextState: Readonly, nextContext: any): boolean; 83 | componentWillUpdate?(nextProps: Readonly

, nextState: Readonly, nextContext: any): void; 84 | componentDidUpdate?(previousProps: Readonly

, previousState: Readonly, previousContext: any): void; 85 | } 86 | 87 | abstract class Component { 88 | constructor(props?: P, context?: any); 89 | 90 | static displayName?: string; 91 | static defaultProps?: any; 92 | 93 | state: Readonly; 94 | props: RenderableProps

; 95 | context: any; 96 | base?: HTMLElement; 97 | 98 | // From https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e836acc75a78cf0655b5dfdbe81d69fdd4d8a252/types/react/index.d.ts#L402 99 | // // We MUST keep setState() as a unified signature because it allows proper checking of the method return type. 100 | // // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257 101 | // // Also, the ` | S` allows intellisense to not be dumbisense 102 | setState( 103 | state: ((prevState: Readonly, props: Readonly

) => (Pick | S | null)) | (Pick | S | null), 104 | callback?: () => void 105 | ): void; 106 | 107 | forceUpdate(callback?: () => void): void; 108 | 109 | abstract render(props?: RenderableProps

, state?: Readonly, context?: any): ComponentChild; 110 | 111 | // Add these variables to avoid descendants shadowing them (some from properties.json for minification) 112 | private __key; 113 | private __ref; 114 | private _component; 115 | private _dirty; 116 | private _disable; 117 | private nextBase; 118 | private prevContext; 119 | private prevProps; 120 | private prevState; 121 | private __d; 122 | private __x; 123 | private __l; 124 | private __h; 125 | private __k; 126 | private __r; 127 | private __n; 128 | private __b; 129 | private __c; 130 | private __p; 131 | private __s; 132 | private __u; 133 | } 134 | 135 | function h( 136 | node: string, 137 | params: JSX.HTMLAttributes & JSX.SVGAttributes & Record | null, 138 | ...children: ComponentChildren[] 139 | ): VNode; 140 | function h

( 141 | node: ComponentFactory

, 142 | params: Attributes & P | null, 143 | ...children: ComponentChildren[] 144 | ): VNode; 145 | 146 | function render(node: ComponentChild, parent: Element | Document | ShadowRoot | DocumentFragment, mergeWith?: Element): Element; 147 | function rerender(): void; 148 | function cloneElement(element: JSX.Element, props: any, ...children: ComponentChildren[]): JSX.Element; 149 | function createRef(): RefObject; 150 | 151 | var options: { 152 | syncComponentUpdates?: boolean; 153 | debounceRendering?: (render: () => void) => void; 154 | vnode?: (vnode: VNode) => void; 155 | event?: (event: Event) => Event; 156 | }; 157 | } 158 | 159 | type Defaultize = 160 | // Distribute over unions 161 | Props extends any 162 | ? // Make any properties included in Default optional 163 | & Partial>> 164 | // Include the remaining properties from Props 165 | & Pick> 166 | : never; 167 | 168 | declare global { 169 | namespace JSX { 170 | interface Element extends preact.VNode { 171 | } 172 | 173 | interface ElementClass extends preact.Component { 174 | } 175 | 176 | interface ElementAttributesProperty { 177 | props: any; 178 | } 179 | 180 | interface ElementChildrenAttribute { 181 | children: any; 182 | } 183 | 184 | type LibraryManagedAttributes = 185 | Component extends { defaultProps: infer Defaults } 186 | ? Defaultize 187 | : Props; 188 | 189 | interface SVGAttributes extends HTMLAttributes { 190 | accentHeight?: number | string; 191 | accumulate?: "none" | "sum"; 192 | additive?: "replace" | "sum"; 193 | alignmentBaseline?: "auto" | "baseline" | "before-edge" | "text-before-edge" | "middle" | "central" | "after-edge" | "text-after-edge" | "ideographic" | "alphabetic" | "hanging" | "mathematical" | "inherit"; 194 | allowReorder?: "no" | "yes"; 195 | alphabetic?: number | string; 196 | amplitude?: number | string; 197 | arabicForm?: "initial" | "medial" | "terminal" | "isolated"; 198 | ascent?: number | string; 199 | attributeName?: string; 200 | attributeType?: string; 201 | autoReverse?: number | string; 202 | azimuth?: number | string; 203 | baseFrequency?: number | string; 204 | baselineShift?: number | string; 205 | baseProfile?: number | string; 206 | bbox?: number | string; 207 | begin?: number | string; 208 | bias?: number | string; 209 | by?: number | string; 210 | calcMode?: number | string; 211 | capHeight?: number | string; 212 | clip?: number | string; 213 | clipPath?: string; 214 | clipPathUnits?: number | string; 215 | clipRule?: number | string; 216 | colorInterpolation?: number | string; 217 | colorInterpolationFilters?: "auto" | "sRGB" | "linearRGB" | "inherit"; 218 | colorProfile?: number | string; 219 | colorRendering?: number | string; 220 | contentScriptType?: number | string; 221 | contentStyleType?: number | string; 222 | cursor?: number | string; 223 | cx?: number | string; 224 | cy?: number | string; 225 | d?: string; 226 | decelerate?: number | string; 227 | descent?: number | string; 228 | diffuseConstant?: number | string; 229 | direction?: number | string; 230 | display?: number | string; 231 | divisor?: number | string; 232 | dominantBaseline?: number | string; 233 | dur?: number | string; 234 | dx?: number | string; 235 | dy?: number | string; 236 | edgeMode?: number | string; 237 | elevation?: number | string; 238 | enableBackground?: number | string; 239 | end?: number | string; 240 | exponent?: number | string; 241 | externalResourcesRequired?: number | string; 242 | fill?: string; 243 | fillOpacity?: number | string; 244 | fillRule?: "nonzero" | "evenodd" | "inherit"; 245 | filter?: string; 246 | filterRes?: number | string; 247 | filterUnits?: number | string; 248 | floodColor?: number | string; 249 | floodOpacity?: number | string; 250 | focusable?: number | string; 251 | fontFamily?: string; 252 | fontSize?: number | string; 253 | fontSizeAdjust?: number | string; 254 | fontStretch?: number | string; 255 | fontStyle?: number | string; 256 | fontVariant?: number | string; 257 | fontWeight?: number | string; 258 | format?: number | string; 259 | from?: number | string; 260 | fx?: number | string; 261 | fy?: number | string; 262 | g1?: number | string; 263 | g2?: number | string; 264 | glyphName?: number | string; 265 | glyphOrientationHorizontal?: number | string; 266 | glyphOrientationVertical?: number | string; 267 | glyphRef?: number | string; 268 | gradientTransform?: string; 269 | gradientUnits?: string; 270 | hanging?: number | string; 271 | horizAdvX?: number | string; 272 | horizOriginX?: number | string; 273 | ideographic?: number | string; 274 | imageRendering?: number | string; 275 | in2?: number | string; 276 | in?: string; 277 | intercept?: number | string; 278 | k1?: number | string; 279 | k2?: number | string; 280 | k3?: number | string; 281 | k4?: number | string; 282 | k?: number | string; 283 | kernelMatrix?: number | string; 284 | kernelUnitLength?: number | string; 285 | kerning?: number | string; 286 | keyPoints?: number | string; 287 | keySplines?: number | string; 288 | keyTimes?: number | string; 289 | lengthAdjust?: number | string; 290 | letterSpacing?: number | string; 291 | lightingColor?: number | string; 292 | limitingConeAngle?: number | string; 293 | local?: number | string; 294 | markerEnd?: string; 295 | markerHeight?: number | string; 296 | markerMid?: string; 297 | markerStart?: string; 298 | markerUnits?: number | string; 299 | markerWidth?: number | string; 300 | mask?: string; 301 | maskContentUnits?: number | string; 302 | maskUnits?: number | string; 303 | mathematical?: number | string; 304 | mode?: number | string; 305 | numOctaves?: number | string; 306 | offset?: number | string; 307 | opacity?: number | string; 308 | operator?: number | string; 309 | order?: number | string; 310 | orient?: number | string; 311 | orientation?: number | string; 312 | origin?: number | string; 313 | overflow?: number | string; 314 | overlinePosition?: number | string; 315 | overlineThickness?: number | string; 316 | paintOrder?: number | string; 317 | panose1?: number | string; 318 | pathLength?: number | string; 319 | patternContentUnits?: string; 320 | patternTransform?: number | string; 321 | patternUnits?: string; 322 | pointerEvents?: number | string; 323 | points?: string; 324 | pointsAtX?: number | string; 325 | pointsAtY?: number | string; 326 | pointsAtZ?: number | string; 327 | preserveAlpha?: number | string; 328 | preserveAspectRatio?: string; 329 | primitiveUnits?: number | string; 330 | r?: number | string; 331 | radius?: number | string; 332 | refX?: number | string; 333 | refY?: number | string; 334 | renderingIntent?: number | string; 335 | repeatCount?: number | string; 336 | repeatDur?: number | string; 337 | requiredExtensions?: number | string; 338 | requiredFeatures?: number | string; 339 | restart?: number | string; 340 | result?: string; 341 | rotate?: number | string; 342 | rx?: number | string; 343 | ry?: number | string; 344 | scale?: number | string; 345 | seed?: number | string; 346 | shapeRendering?: number | string; 347 | slope?: number | string; 348 | spacing?: number | string; 349 | specularConstant?: number | string; 350 | specularExponent?: number | string; 351 | speed?: number | string; 352 | spreadMethod?: string; 353 | startOffset?: number | string; 354 | stdDeviation?: number | string; 355 | stemh?: number | string; 356 | stemv?: number | string; 357 | stitchTiles?: number | string; 358 | stopColor?: string; 359 | stopOpacity?: number | string; 360 | strikethroughPosition?: number | string; 361 | strikethroughThickness?: number | string; 362 | string?: number | string; 363 | stroke?: string; 364 | strokeDasharray?: string | number; 365 | strokeDashoffset?: string | number; 366 | strokeLinecap?: "butt" | "round" | "square" | "inherit"; 367 | strokeLinejoin?: "miter" | "round" | "bevel" | "inherit"; 368 | strokeMiterlimit?: string; 369 | strokeOpacity?: number | string; 370 | strokeWidth?: number | string; 371 | surfaceScale?: number | string; 372 | systemLanguage?: number | string; 373 | tableValues?: number | string; 374 | targetX?: number | string; 375 | targetY?: number | string; 376 | textAnchor?: string; 377 | textDecoration?: number | string; 378 | textLength?: number | string; 379 | textRendering?: number | string; 380 | to?: number | string; 381 | transform?: string; 382 | u1?: number | string; 383 | u2?: number | string; 384 | underlinePosition?: number | string; 385 | underlineThickness?: number | string; 386 | unicode?: number | string; 387 | unicodeBidi?: number | string; 388 | unicodeRange?: number | string; 389 | unitsPerEm?: number | string; 390 | vAlphabetic?: number | string; 391 | values?: string; 392 | vectorEffect?: number | string; 393 | version?: string; 394 | vertAdvY?: number | string; 395 | vertOriginX?: number | string; 396 | vertOriginY?: number | string; 397 | vHanging?: number | string; 398 | vIdeographic?: number | string; 399 | viewBox?: string; 400 | viewTarget?: number | string; 401 | visibility?: number | string; 402 | vMathematical?: number | string; 403 | widths?: number | string; 404 | wordSpacing?: number | string; 405 | writingMode?: number | string; 406 | x1?: number | string; 407 | x2?: number | string; 408 | x?: number | string; 409 | xChannelSelector?: string; 410 | xHeight?: number | string; 411 | xlinkActuate?: string; 412 | xlinkArcrole?: string; 413 | xlinkHref?: string; 414 | xlinkRole?: string; 415 | xlinkShow?: string; 416 | xlinkTitle?: string; 417 | xlinkType?: string; 418 | xmlBase?: string; 419 | xmlLang?: string; 420 | xmlns?: string; 421 | xmlnsXlink?: string; 422 | xmlSpace?: string; 423 | y1?: number | string; 424 | y2?: number | string; 425 | y?: number | string; 426 | yChannelSelector?: string; 427 | z?: number | string; 428 | zoomAndPan?: string; 429 | } 430 | 431 | interface PathAttributes { 432 | d: string; 433 | } 434 | 435 | interface EventHandler { 436 | (event: E): void; 437 | } 438 | 439 | type ClipboardEventHandler = EventHandler; 440 | type CompositionEventHandler = EventHandler; 441 | type DragEventHandler = EventHandler; 442 | type FocusEventHandler = EventHandler; 443 | type KeyboardEventHandler = EventHandler; 444 | type MouseEventHandler = EventHandler; 445 | type TouchEventHandler = EventHandler; 446 | type UIEventHandler = EventHandler; 447 | type WheelEventHandler = EventHandler; 448 | type AnimationEventHandler = EventHandler; 449 | type TransitionEventHandler = EventHandler; 450 | type GenericEventHandler = EventHandler; 451 | type PointerEventHandler = EventHandler; 452 | 453 | interface DOMAttributes extends preact.PreactDOMAttributes { 454 | // Image Events 455 | onLoad?: GenericEventHandler; 456 | onError?: GenericEventHandler; 457 | onLoadCapture?: GenericEventHandler; 458 | 459 | // Clipboard Events 460 | onCopy?: ClipboardEventHandler; 461 | onCopyCapture?: ClipboardEventHandler; 462 | onCut?: ClipboardEventHandler; 463 | onCutCapture?: ClipboardEventHandler; 464 | onPaste?: ClipboardEventHandler; 465 | onPasteCapture?: ClipboardEventHandler; 466 | 467 | // Composition Events 468 | onCompositionEnd?: CompositionEventHandler; 469 | onCompositionEndCapture?: CompositionEventHandler; 470 | onCompositionStart?: CompositionEventHandler; 471 | onCompositionStartCapture?: CompositionEventHandler; 472 | onCompositionUpdate?: CompositionEventHandler; 473 | onCompositionUpdateCapture?: CompositionEventHandler; 474 | 475 | // Focus Events 476 | onFocus?: FocusEventHandler; 477 | onFocusCapture?: FocusEventHandler; 478 | onBlur?: FocusEventHandler; 479 | onBlurCapture?: FocusEventHandler; 480 | 481 | // Form Events 482 | onChange?: GenericEventHandler; 483 | onChangeCapture?: GenericEventHandler; 484 | onInput?: GenericEventHandler; 485 | onInputCapture?: GenericEventHandler; 486 | onSearch?: GenericEventHandler; 487 | onSearchCapture?: GenericEventHandler; 488 | onSubmit?: GenericEventHandler; 489 | onSubmitCapture?: GenericEventHandler; 490 | onInvalid?: GenericEventHandler; 491 | 492 | // Keyboard Events 493 | onKeyDown?: KeyboardEventHandler; 494 | onKeyDownCapture?: KeyboardEventHandler; 495 | onKeyPress?: KeyboardEventHandler; 496 | onKeyPressCapture?: KeyboardEventHandler; 497 | onKeyUp?: KeyboardEventHandler; 498 | onKeyUpCapture?: KeyboardEventHandler; 499 | 500 | // Media Events 501 | onAbort?: GenericEventHandler; 502 | onAbortCapture?: GenericEventHandler; 503 | onCanPlay?: GenericEventHandler; 504 | onCanPlayCapture?: GenericEventHandler; 505 | onCanPlayThrough?: GenericEventHandler; 506 | onCanPlayThroughCapture?: GenericEventHandler; 507 | onDurationChange?: GenericEventHandler; 508 | onDurationChangeCapture?: GenericEventHandler; 509 | onEmptied?: GenericEventHandler; 510 | onEmptiedCapture?: GenericEventHandler; 511 | onEncrypted?: GenericEventHandler; 512 | onEncryptedCapture?: GenericEventHandler; 513 | onEnded?: GenericEventHandler; 514 | onEndedCapture?: GenericEventHandler; 515 | onLoadedData?: GenericEventHandler; 516 | onLoadedDataCapture?: GenericEventHandler; 517 | onLoadedMetadata?: GenericEventHandler; 518 | onLoadedMetadataCapture?: GenericEventHandler; 519 | onLoadStart?: GenericEventHandler; 520 | onLoadStartCapture?: GenericEventHandler; 521 | onPause?: GenericEventHandler; 522 | onPauseCapture?: GenericEventHandler; 523 | onPlay?: GenericEventHandler; 524 | onPlayCapture?: GenericEventHandler; 525 | onPlaying?: GenericEventHandler; 526 | onPlayingCapture?: GenericEventHandler; 527 | onProgress?: GenericEventHandler; 528 | onProgressCapture?: GenericEventHandler; 529 | onRateChange?: GenericEventHandler; 530 | onRateChangeCapture?: GenericEventHandler; 531 | onSeeked?: GenericEventHandler; 532 | onSeekedCapture?: GenericEventHandler; 533 | onSeeking?: GenericEventHandler; 534 | onSeekingCapture?: GenericEventHandler; 535 | onStalled?: GenericEventHandler; 536 | onStalledCapture?: GenericEventHandler; 537 | onSuspend?: GenericEventHandler; 538 | onSuspendCapture?: GenericEventHandler; 539 | onTimeUpdate?: GenericEventHandler; 540 | onTimeUpdateCapture?: GenericEventHandler; 541 | onVolumeChange?: GenericEventHandler; 542 | onVolumeChangeCapture?: GenericEventHandler; 543 | onWaiting?: GenericEventHandler; 544 | onWaitingCapture?: GenericEventHandler; 545 | 546 | // MouseEvents 547 | onClick?: MouseEventHandler; 548 | onClickCapture?: MouseEventHandler; 549 | onContextMenu?: MouseEventHandler; 550 | onContextMenuCapture?: MouseEventHandler; 551 | onDblClick?: MouseEventHandler; 552 | onDblClickCapture?: MouseEventHandler; 553 | onDrag?: DragEventHandler; 554 | onDragCapture?: DragEventHandler; 555 | onDragEnd?: DragEventHandler; 556 | onDragEndCapture?: DragEventHandler; 557 | onDragEnter?: DragEventHandler; 558 | onDragEnterCapture?: DragEventHandler; 559 | onDragExit?: DragEventHandler; 560 | onDragExitCapture?: DragEventHandler; 561 | onDragLeave?: DragEventHandler; 562 | onDragLeaveCapture?: DragEventHandler; 563 | onDragOver?: DragEventHandler; 564 | onDragOverCapture?: DragEventHandler; 565 | onDragStart?: DragEventHandler; 566 | onDragStartCapture?: DragEventHandler; 567 | onDrop?: DragEventHandler; 568 | onDropCapture?: DragEventHandler; 569 | onMouseDown?: MouseEventHandler; 570 | onMouseDownCapture?: MouseEventHandler; 571 | onMouseEnter?: MouseEventHandler; 572 | onMouseEnterCapture?: MouseEventHandler; 573 | onMouseLeave?: MouseEventHandler; 574 | onMouseLeaveCapture?: MouseEventHandler; 575 | onMouseMove?: MouseEventHandler; 576 | onMouseMoveCapture?: MouseEventHandler; 577 | onMouseOut?: MouseEventHandler; 578 | onMouseOutCapture?: MouseEventHandler; 579 | onMouseOver?: MouseEventHandler; 580 | onMouseOverCapture?: MouseEventHandler; 581 | onMouseUp?: MouseEventHandler; 582 | onMouseUpCapture?: MouseEventHandler; 583 | 584 | // Selection Events 585 | onSelect?: GenericEventHandler; 586 | onSelectCapture?: GenericEventHandler; 587 | 588 | // Touch Events 589 | onTouchCancel?: TouchEventHandler; 590 | onTouchCancelCapture?: TouchEventHandler; 591 | onTouchEnd?: TouchEventHandler; 592 | onTouchEndCapture?: TouchEventHandler; 593 | onTouchMove?: TouchEventHandler; 594 | onTouchMoveCapture?: TouchEventHandler; 595 | onTouchStart?: TouchEventHandler; 596 | onTouchStartCapture?: TouchEventHandler; 597 | 598 | // Pointer Events 599 | onPointerOver?: PointerEventHandler; 600 | onPointerOverCapture?: PointerEventHandler; 601 | onPointerEnter?: PointerEventHandler; 602 | onPointerEnterCapture?: PointerEventHandler; 603 | onPointerDown?: PointerEventHandler; 604 | onPointerDownCapture?: PointerEventHandler; 605 | onPointerMove?: PointerEventHandler; 606 | onPointerMoveCapture?: PointerEventHandler; 607 | onPointerUp?: PointerEventHandler; 608 | onPointerUpCapture?: PointerEventHandler; 609 | onPointerCancel?: PointerEventHandler; 610 | onPointerCancelCapture?: PointerEventHandler; 611 | onPointerOut?: PointerEventHandler; 612 | onPointerOutCapture?: PointerEventHandler; 613 | onPointerLeave?: PointerEventHandler; 614 | onPointerLeaveCapture?: PointerEventHandler; 615 | onGotPointerCapture?: PointerEventHandler; 616 | onGotPointerCaptureCapture?: PointerEventHandler; 617 | onLostPointerCapture?: PointerEventHandler; 618 | onLostPointerCaptureCapture?: PointerEventHandler; 619 | 620 | // UI Events 621 | onScroll?: UIEventHandler; 622 | onScrollCapture?: UIEventHandler; 623 | 624 | // Wheel Events 625 | onWheel?: WheelEventHandler; 626 | onWheelCapture?: WheelEventHandler; 627 | 628 | // Animation Events 629 | onAnimationStart?: AnimationEventHandler; 630 | onAnimationStartCapture?: AnimationEventHandler; 631 | onAnimationEnd?: AnimationEventHandler; 632 | onAnimationEndCapture?: AnimationEventHandler; 633 | onAnimationIteration?: AnimationEventHandler; 634 | onAnimationIterationCapture?: AnimationEventHandler; 635 | 636 | // Transition Events 637 | onTransitionEnd?: TransitionEventHandler; 638 | onTransitionEndCapture?: TransitionEventHandler; 639 | } 640 | 641 | interface HTMLAttributes extends preact.PreactHTMLAttributes, DOMAttributes { 642 | // Standard HTML Attributes 643 | accept?: string; 644 | acceptCharset?: string; 645 | accessKey?: string; 646 | action?: string; 647 | allowFullScreen?: boolean; 648 | allowTransparency?: boolean; 649 | alt?: string; 650 | async?: boolean; 651 | autocomplete?: string; 652 | autofocus?: boolean; 653 | autoPlay?: boolean; 654 | capture?: boolean; 655 | cellPadding?: number | string; 656 | cellSpacing?: number | string; 657 | charSet?: string; 658 | challenge?: string; 659 | checked?: boolean; 660 | class?: string; 661 | className?: string; 662 | cols?: number; 663 | colSpan?: number; 664 | content?: string; 665 | contentEditable?: boolean; 666 | contextMenu?: string; 667 | controls?: boolean; 668 | controlsList?: string; 669 | coords?: string; 670 | crossOrigin?: string; 671 | data?: string; 672 | dateTime?: string; 673 | default?: boolean; 674 | defer?: boolean; 675 | dir?: string; 676 | disabled?: boolean; 677 | download?: any; 678 | draggable?: boolean; 679 | encType?: string; 680 | form?: string; 681 | formAction?: string; 682 | formEncType?: string; 683 | formMethod?: string; 684 | formNoValidate?: boolean; 685 | formTarget?: string; 686 | frameBorder?: number | string; 687 | headers?: string; 688 | height?: number | string; 689 | hidden?: boolean; 690 | high?: number; 691 | href?: string; 692 | hrefLang?: string; 693 | for?: string; 694 | httpEquiv?: string; 695 | icon?: string; 696 | id?: string; 697 | inputMode?: string; 698 | integrity?: string; 699 | is?: string; 700 | keyParams?: string; 701 | keyType?: string; 702 | kind?: string; 703 | label?: string; 704 | lang?: string; 705 | list?: string; 706 | loop?: boolean; 707 | low?: number; 708 | manifest?: string; 709 | marginHeight?: number; 710 | marginWidth?: number; 711 | max?: number | string; 712 | maxLength?: number; 713 | media?: string; 714 | mediaGroup?: string; 715 | method?: string; 716 | min?: number | string; 717 | minLength?: number; 718 | multiple?: boolean; 719 | muted?: boolean; 720 | name?: string; 721 | noValidate?: boolean; 722 | open?: boolean; 723 | optimum?: number; 724 | pattern?: string; 725 | placeholder?: string; 726 | playsInline?: boolean; 727 | poster?: string; 728 | preload?: string; 729 | radioGroup?: string; 730 | readOnly?: boolean; 731 | rel?: string; 732 | required?: boolean; 733 | role?: string; 734 | rows?: number; 735 | rowSpan?: number; 736 | sandbox?: string; 737 | scope?: string; 738 | scoped?: boolean; 739 | scrolling?: string; 740 | seamless?: boolean; 741 | selected?: boolean; 742 | shape?: string; 743 | size?: number; 744 | sizes?: string; 745 | slot?: string; 746 | span?: number; 747 | spellcheck?: boolean; 748 | src?: string; 749 | srcset?: string; 750 | srcDoc?: string; 751 | srcLang?: string; 752 | srcSet?: string; 753 | start?: number; 754 | step?: number | string; 755 | style?: any; 756 | summary?: string; 757 | tabIndex?: number; 758 | target?: string; 759 | title?: string; 760 | type?: string; 761 | useMap?: string; 762 | value?: string | string[] | number; 763 | width?: number | string; 764 | wmode?: string; 765 | wrap?: string; 766 | 767 | // RDFa Attributes 768 | about?: string; 769 | datatype?: string; 770 | inlist?: any; 771 | prefix?: string; 772 | property?: string; 773 | resource?: string; 774 | typeof?: string; 775 | vocab?: string; 776 | } 777 | 778 | interface IntrinsicElements { 779 | // HTML 780 | a: HTMLAttributes; 781 | abbr: HTMLAttributes; 782 | address: HTMLAttributes; 783 | area: HTMLAttributes; 784 | article: HTMLAttributes; 785 | aside: HTMLAttributes; 786 | audio: HTMLAttributes; 787 | b: HTMLAttributes; 788 | base: HTMLAttributes; 789 | bdi: HTMLAttributes; 790 | bdo: HTMLAttributes; 791 | big: HTMLAttributes; 792 | blockquote: HTMLAttributes; 793 | body: HTMLAttributes; 794 | br: HTMLAttributes; 795 | button: HTMLAttributes; 796 | canvas: HTMLAttributes; 797 | caption: HTMLAttributes; 798 | cite: HTMLAttributes; 799 | code: HTMLAttributes; 800 | col: HTMLAttributes; 801 | colgroup: HTMLAttributes; 802 | data: HTMLAttributes; 803 | datalist: HTMLAttributes; 804 | dd: HTMLAttributes; 805 | del: HTMLAttributes; 806 | details: HTMLAttributes; 807 | dfn: HTMLAttributes; 808 | dialog: HTMLAttributes; 809 | div: HTMLAttributes; 810 | dl: HTMLAttributes; 811 | dt: HTMLAttributes; 812 | em: HTMLAttributes; 813 | embed: HTMLAttributes; 814 | fieldset: HTMLAttributes; 815 | figcaption: HTMLAttributes; 816 | figure: HTMLAttributes; 817 | footer: HTMLAttributes; 818 | form: HTMLAttributes; 819 | h1: HTMLAttributes; 820 | h2: HTMLAttributes; 821 | h3: HTMLAttributes; 822 | h4: HTMLAttributes; 823 | h5: HTMLAttributes; 824 | h6: HTMLAttributes; 825 | head: HTMLAttributes; 826 | header: HTMLAttributes; 827 | hr: HTMLAttributes; 828 | html: HTMLAttributes; 829 | i: HTMLAttributes; 830 | iframe: HTMLAttributes; 831 | img: HTMLAttributes; 832 | input: HTMLAttributes; 833 | ins: HTMLAttributes; 834 | kbd: HTMLAttributes; 835 | keygen: HTMLAttributes; 836 | label: HTMLAttributes; 837 | legend: HTMLAttributes; 838 | li: HTMLAttributes; 839 | link: HTMLAttributes; 840 | main: HTMLAttributes; 841 | map: HTMLAttributes; 842 | mark: HTMLAttributes; 843 | menu: HTMLAttributes; 844 | menuitem: HTMLAttributes; 845 | meta: HTMLAttributes; 846 | meter: HTMLAttributes; 847 | nav: HTMLAttributes; 848 | noscript: HTMLAttributes; 849 | object: HTMLAttributes; 850 | ol: HTMLAttributes; 851 | optgroup: HTMLAttributes; 852 | option: HTMLAttributes; 853 | output: HTMLAttributes; 854 | p: HTMLAttributes; 855 | param: HTMLAttributes; 856 | picture: HTMLAttributes; 857 | pre: HTMLAttributes; 858 | progress: HTMLAttributes; 859 | q: HTMLAttributes; 860 | rp: HTMLAttributes; 861 | rt: HTMLAttributes; 862 | ruby: HTMLAttributes; 863 | s: HTMLAttributes; 864 | samp: HTMLAttributes; 865 | script: HTMLAttributes; 866 | section: HTMLAttributes; 867 | select: HTMLAttributes; 868 | slot: HTMLAttributes; 869 | small: HTMLAttributes; 870 | source: HTMLAttributes; 871 | span: HTMLAttributes; 872 | strong: HTMLAttributes; 873 | style: HTMLAttributes; 874 | sub: HTMLAttributes; 875 | summary: HTMLAttributes; 876 | sup: HTMLAttributes; 877 | table: HTMLAttributes; 878 | tbody: HTMLAttributes; 879 | td: HTMLAttributes; 880 | textarea: HTMLAttributes; 881 | tfoot: HTMLAttributes; 882 | th: HTMLAttributes; 883 | thead: HTMLAttributes; 884 | time: HTMLAttributes; 885 | title: HTMLAttributes; 886 | tr: HTMLAttributes; 887 | track: HTMLAttributes; 888 | u: HTMLAttributes; 889 | ul: HTMLAttributes; 890 | "var": HTMLAttributes; 891 | video: HTMLAttributes; 892 | wbr: HTMLAttributes; 893 | 894 | //SVG 895 | svg: SVGAttributes; 896 | animate: SVGAttributes; 897 | circle: SVGAttributes; 898 | clipPath: SVGAttributes; 899 | defs: SVGAttributes; 900 | desc: SVGAttributes; 901 | ellipse: SVGAttributes; 902 | feBlend: SVGAttributes; 903 | feColorMatrix: SVGAttributes; 904 | feComponentTransfer: SVGAttributes; 905 | feComposite: SVGAttributes; 906 | feConvolveMatrix: SVGAttributes; 907 | feDiffuseLighting: SVGAttributes; 908 | feDisplacementMap: SVGAttributes; 909 | feFlood: SVGAttributes; 910 | feGaussianBlur: SVGAttributes; 911 | feImage: SVGAttributes; 912 | feMerge: SVGAttributes; 913 | feMergeNode: SVGAttributes; 914 | feMorphology: SVGAttributes; 915 | feOffset: SVGAttributes; 916 | feSpecularLighting: SVGAttributes; 917 | feTile: SVGAttributes; 918 | feTurbulence: SVGAttributes; 919 | filter: SVGAttributes; 920 | foreignObject: SVGAttributes; 921 | g: SVGAttributes; 922 | image: SVGAttributes; 923 | line: SVGAttributes; 924 | linearGradient: SVGAttributes; 925 | marker: SVGAttributes; 926 | mask: SVGAttributes; 927 | path: SVGAttributes; 928 | pattern: SVGAttributes; 929 | polygon: SVGAttributes; 930 | polyline: SVGAttributes; 931 | radialGradient: SVGAttributes; 932 | rect: SVGAttributes; 933 | stop: SVGAttributes; 934 | symbol: SVGAttributes; 935 | text: SVGAttributes; 936 | tspan: SVGAttributes; 937 | use: SVGAttributes; 938 | } 939 | } 940 | } 941 | -------------------------------------------------------------------------------- /preact-source/preact.js: -------------------------------------------------------------------------------- 1 | import { h, h as createElement } from "./h"; 2 | import { cloneElement } from "./clone-element"; 3 | import { Component } from "./component"; 4 | import { render } from "./render"; 5 | import { rerender } from "./render-queue"; 6 | import options from "./options"; 7 | 8 | function createRef() { 9 | return {}; 10 | } 11 | 12 | export default { 13 | // h 方法 和 createElement 是同一个方法 14 | h, 15 | createElement, 16 | cloneElement, 17 | createRef, 18 | Component, 19 | render, 20 | rerender, 21 | options 22 | }; 23 | 24 | export { 25 | h, 26 | createElement, 27 | cloneElement, 28 | createRef, 29 | Component, 30 | render, 31 | rerender, 32 | options 33 | }; 34 | -------------------------------------------------------------------------------- /preact-source/render-queue.js: -------------------------------------------------------------------------------- 1 | import options from "./options"; 2 | import { defer } from "./util"; 3 | import { renderComponent } from "./vdom/component"; 4 | 5 | /** 6 | * 待渲染队列 7 | * @type {Array} 8 | */ 9 | let items = []; 10 | 11 | /** 12 | * 组件存入渲染队列 13 | * @param {import('./component').Component} component The component to rerender 14 | */ 15 | export function enqueueRender(component) { 16 | // component._dirty为false时才会将组件放入待渲染队列中,然后就将 component._dirty 设置为 true 17 | // 这样就能防止一个组件多次render 18 | if ( 19 | !component._dirty && 20 | (component._dirty = true) && 21 | // 仅有一次放在render队列中 22 | items.push(component) == 1 23 | ) { 24 | // 异步的执行render,要执行render方法的component中的_dirty设为true 25 | (options.debounceRendering || defer)(rerender); 26 | } 27 | } 28 | 29 | /** 30 | * 逐一取出组件,一次调用 renderComponent 函数 31 | */ 32 | export function rerender() { 33 | let p; 34 | while ((p = items.pop())) { 35 | if (p._dirty) renderComponent(p); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /preact-source/render.js: -------------------------------------------------------------------------------- 1 | import { diff } from "./vdom/diff"; 2 | 3 | /** 4 | * Render JSX into a `parent` Element. 5 | * @param {import('./vnode').VNode} vnode h 函数生成的虚拟dom 6 | * @param {import('./dom').PreactElement} parent 该虚拟 dom 生成的真实 dom 将要挂载的父节点 7 | * @param {import('./dom').PreactElement} [merge] 待更新的真实dom,首次渲染则为null 8 | * `merge` 9 | * @public 10 | * 11 | * @example 12 | * // render a div into : 13 | * render(

hello!
, document.body); 14 | * 15 | * @example 16 | * // render a "Thing" component into #foo: 17 | * const Thing = ({ name }) => { name }; 18 | * render(, document.querySelector('#foo')); 19 | */ 20 | export function render(vnode, parent, merge) { 21 | return diff(merge, vnode, {}, false, parent, false); 22 | } 23 | -------------------------------------------------------------------------------- /preact-source/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 可以用 Object.assign 3 | * @param {*} obj 4 | * @param {*} props 5 | */ 6 | export function extend(obj, props) { 7 | for (let i in props) obj[i] = props[i]; 8 | return obj; 9 | } 10 | 11 | /** 12 | * 对第一个参数 ref 进行类型判断 13 | * 如果是一个执行函数,则执行 ref 这个函数,value就是其参数 14 | * 如果是一个字面量,就追加一个 current 属性,并将value赋值 15 | * @param {*} ref 16 | * @param {*} value 17 | */ 18 | export function applyRef(ref, value) { 19 | if (ref) { 20 | if (typeof ref == "function") ref(value); 21 | else ref.current = value; 22 | } 23 | } 24 | 25 | /** 26 | * defer是一个用于异步执行的函数变量 27 | * promise或者是定时器 28 | * 在某种浏览器中,上述的三元未必成立,所以就做了这个判断 29 | * 而且,promise 是 microtask,在事件循环的最末尾处,触发的事件比 settimeout快 30 | */ 31 | export const defer = 32 | typeof Promise == "function" 33 | ? Promise.resolve().then.bind(Promise.resolve()) 34 | : setTimeout; 35 | -------------------------------------------------------------------------------- /preact-source/vdom/component-recycler.js: -------------------------------------------------------------------------------- 1 | import { Component } from "../component"; 2 | 3 | /** 4 | * 保留一些组件,以便以后使用 5 | * @type {Component[]} 6 | * @private 7 | */ 8 | export const recyclerComponents = []; 9 | 10 | /** 11 | * 创建组件实例 12 | * 参数props与context分别对应的是组件的中属性和context 13 | * Components. 14 | * @param {function} Ctor Ctor组件则是需要创建的组件类型(函数或者是类) 15 | * @param {object} props 组件属性 16 | * @param {object} context 上下文环境 17 | * @returns {import('../component').Component} 18 | */ 19 | export function createComponent(Ctor, props, context) { 20 | let inst, 21 | i = recyclerComponents.length; 22 | 23 | /** 24 | * class App extends Component{} 25 | * 如果组件是向上面那样创建的,继承 Component 父类,则App的实例就会又render方法 26 | * 27 | * 这就解释了就算没有继承 Component 父类且通过函数创建的无状态函数组件(PFC). 28 | */ 29 | if (Ctor.prototype && Ctor.prototype.render) { 30 | // inst 是 组件实例,将props和context传入 31 | inst = new Ctor(props, context); 32 | /** 33 | * 如果没有给父级构造函数super传入props和context, 34 | * 那么inst中的props和context的属性为undefined, 35 | * 通过强制调用Component.call(inst, props, context)可以给inst中props、context进行初始化赋值。 36 | */ 37 | Component.call(inst, props, context); 38 | } else { 39 | /** 40 | * PFC类型组件处理 41 | * 1. 实例化 Component 类 42 | * 2. 实例化对象的构造器指向传入的PFC组件函数 43 | * 3. 添加render方法 44 | */ 45 | inst = new Component(props, context); 46 | // constructor 如果不赋值,则默认指向 Component 类,重新赋值了,则指向了新的对象 47 | inst.constructor = Ctor; 48 | inst.render = doRender; 49 | } 50 | 51 | while (i--) { 52 | // 回收组件中存在之前创建过的 PFC 组件 53 | if (recyclerComponents[i].constructor === Ctor) { 54 | // nextBase 属性记录的是该组件之前渲染的真实dom 55 | inst.nextBase = recyclerComponents[i].nextBase; 56 | // 在组件回收站中删除这个组件 57 | recyclerComponents.splice(i, 1); 58 | return inst; 59 | } 60 | } 61 | // 返回这个组件实例 62 | return inst; 63 | } 64 | 65 | /** 66 | * 该方法,会将传入的函数 Ctor 的返回的 vnode 作为结果返回 67 | * 68 | * @param {*} props 69 | * @param {*} state 70 | * @param {*} context 71 | */ 72 | function doRender(props, state, context) { 73 | return this.constructor(props, context); 74 | } 75 | -------------------------------------------------------------------------------- /preact-source/vdom/component.js: -------------------------------------------------------------------------------- 1 | import { 2 | SYNC_RENDER, 3 | NO_RENDER, 4 | FORCE_RENDER, 5 | ASYNC_RENDER, 6 | ATTR_KEY 7 | } from "../constants"; 8 | import options from "../options"; 9 | import { extend, applyRef } from "../util"; 10 | import { enqueueRender } from "../render-queue"; 11 | import { getNodeProps } from "./index"; 12 | import { 13 | diff, 14 | mounts, 15 | diffLevel, 16 | flushMounts, 17 | recollectNodeTree, 18 | removeChildren 19 | } from "./diff"; 20 | import { createComponent, recyclerComponents } from "./component-recycler"; 21 | import { removeNode } from "../dom/index"; 22 | 23 | /** 24 | * 为组件实例对象添加 props 属性 25 | * @param {import('../component').Component} component 目标组件 26 | * @param {object} props 新的组件 27 | * @param {number} renderMode Render options - specifies how to re-render the component 28 | * @param {object} context The new context 29 | * @param {boolean} mountAll Whether or not to immediately mount all components 30 | */ 31 | export function setComponentProps( 32 | component, 33 | props, 34 | renderMode, 35 | context, 36 | mountAll 37 | ) { 38 | // 首先判断组件状态是否可用 39 | if (component._disable) return; 40 | // 状态锁,先把组件设置为不可用,等待更新完后再设置为可用 41 | component._disable = true; 42 | // ref 属性 43 | component.__ref = props.ref; 44 | // 组件唯一键值 45 | component.__key = props.key; 46 | // 移除这两个属性 47 | delete props.ref; 48 | delete props.key; 49 | 50 | // getDerivedStateFromProps 静态方法 51 | if (typeof component.constructor.getDerivedStateFromProps === "undefined") { 52 | if (!component.base || mountAll) { 53 | if (component.componentWillMount) component.componentWillMount(); 54 | } else if (component.componentWillReceiveProps) { 55 | component.componentWillReceiveProps(props, context); 56 | } 57 | } 58 | 59 | // context比对 60 | if (context && context !== component.context) { 61 | if (!component.prevContext) component.prevContext = component.context; 62 | component.context = context; 63 | } 64 | 65 | // 属性比对 66 | if (!component.prevProps) component.prevProps = component.props; 67 | component.props = props; 68 | // 设置为可用 69 | component._disable = false; 70 | 71 | // render 模式 72 | if (renderMode !== NO_RENDER) { 73 | if ( 74 | renderMode === SYNC_RENDER || 75 | options.syncComponentUpdates !== false || 76 | !component.base 77 | ) { 78 | renderComponent(component, SYNC_RENDER, mountAll); 79 | } else { 80 | enqueueRender(component); 81 | } 82 | } 83 | 84 | // 返回组件的真实实例dom 85 | applyRef(component.__ref, component); 86 | } 87 | 88 | /** 89 | * 渲染组件,及调用生命周期的钩子函数 90 | * @param {import('../component').Component} component 待渲染的组件 91 | * @param {number} [renderMode] render mode, see constants.js for available options. 92 | * @param {boolean} [mountAll] Whether or not to immediately mount all components 93 | * @param {boolean} [isChild] ? 94 | * @private 95 | */ 96 | export function renderComponent(component, renderMode, mountAll, isChild) { 97 | if (component._disable) return; 98 | 99 | // 最新的状态属性 100 | let props = component.props, 101 | state = component.state, 102 | context = component.context, 103 | // 组件上一个状态组件实例属性 104 | previousProps = component.prevProps || props, 105 | previousState = component.prevState || state, 106 | previousContext = component.prevContext || context, 107 | // 如果有真实dom存在,则是更新,没有则是第一次渲染 108 | isUpdate = component.base, 109 | // 从缓存中取出上一次暄软或者是组件回收之前同类型的组件实例 110 | nextBase = component.nextBase, 111 | initialBase = isUpdate || nextBase, 112 | // 组件实例中的_component属性表示的组件的子组件 113 | // 仅仅只有当组件返回的是组件时(也就是当前组件为高阶组件),才会存在 114 | initialChildComponent = component._component, 115 | // 变量skip用来标志是否需要跳过更新的过程 116 | skip = false, 117 | snapshot = previousContext, 118 | rendered, 119 | inst, 120 | cbase; 121 | 122 | // 生命周期静态属性 getDerivedStateFromProps 123 | if (component.constructor.getDerivedStateFromProps) { 124 | state = extend( 125 | extend({}, state), 126 | component.constructor.getDerivedStateFromProps(props, state) 127 | ); 128 | component.state = state; 129 | } 130 | 131 | /** 132 | * 如果 component.base 存在,则isUpdate字段为true,那就说明组件渲染之前是有真实dom的,属性更新操作 133 | * 首先要将组件中原来的 props,state,context这三个属性都换成 previousProps、previousState、previousContext 134 | * 为什么要换成之前的状态属性,因为在 shouldComponentUpdate componentWillUpdate 这两个生命周期中,组件的状态还是之前的 135 | * 136 | * 如果renderMode不是强制刷新,且component.shouldComponentUpdate函数返回值为false时, 137 | * 则表示要跳过此次刷新过程,更新标志skip为true 138 | * 139 | * 如果component.shouldComponentUpdate不存在获取返回的是ture,则判断执行 component.componentWillUpdate 函数 140 | * 141 | * 最后,组件实例的props、state、context替换成最新的状态 142 | * 143 | */ 144 | if (isUpdate) { 145 | component.props = previousProps; 146 | component.state = previousState; 147 | component.context = previousContext; 148 | if ( 149 | renderMode !== FORCE_RENDER && 150 | component.shouldComponentUpdate && 151 | component.shouldComponentUpdate(props, state, context) === false 152 | ) { 153 | skip = true; 154 | } else if (component.componentWillUpdate) { 155 | component.componentWillUpdate(props, state, context); 156 | } 157 | component.props = props; 158 | component.state = state; 159 | component.context = context; 160 | } 161 | 162 | // 组件实例中的prevProps、prevState、prevContext的属性全部设置为null 163 | component.prevProps = component.prevState = component.prevContext = component.nextBase = null; 164 | // 状态锁 只有_dirty为false才会被放入更新队列,然后_dirty会被置为true,这样组件实例就不会被多次放入更新队列 165 | component._dirty = false; 166 | 167 | /** 168 | * 组件更新,首先执行组件实例的render方法,就是react组件类中的render函数 169 | * 170 | */ 171 | if (!skip) { 172 | // 执行render函数的返回值rendered则是组件实例对应的虚拟dom元素(VNode) 173 | rendered = component.render(props, state, context); 174 | 175 | /** 176 | * 存在 component.getChildContext 函数 177 | * 执行 component.getChildContext 函数,返回子组件的context 178 | * 子组件的 context 属性会覆盖父组件的context属性 179 | */ 180 | if (component.getChildContext) { 181 | context = extend(extend({}, context), component.getChildContext()); 182 | } 183 | 184 | /** 185 | * 在组件更新获取当前组件实例更新前,获取dom更新前的状态 186 | */ 187 | if (isUpdate && component.getSnapshotBeforeUpdate) { 188 | snapshot = component.getSnapshotBeforeUpdate( 189 | previousProps, 190 | previousState 191 | ); 192 | } 193 | 194 | // childComponent 返回虚拟dom的类型,没错,后面又要判断这个类型了 195 | let childComponent = rendered && rendered.nodeName, 196 | toUnmount, 197 | base; 198 | 199 | /** 200 | * 如果这个组件的类型是一个function,则说明是高阶组件 201 | */ 202 | if (typeof childComponent === "function") { 203 | // 获取虚拟dom的属性以及默认属性 204 | let childProps = getNodeProps(rendered); 205 | // 初始化子组件值 component._component 存在 206 | inst = initialChildComponent; 207 | 208 | /** 209 | * 子组件值存在,且子组件的构造函数指向了 rendered && rendered.nodeName 的高阶函数 210 | * 并且key值也是相同的 211 | */ 212 | if ( 213 | inst && 214 | inst.constructor === childComponent && 215 | childProps.key == inst.__key 216 | ) { 217 | // 同步的方式递归更新子组件的状态属性 218 | setComponentProps(inst, childProps, SYNC_RENDER, context, false); 219 | } else { 220 | toUnmount = inst; 221 | // 创建子组件实例 222 | component._component = inst = createComponent( 223 | childComponent, 224 | childProps, 225 | context 226 | ); 227 | // 子组件之前渲染的实例 228 | inst.nextBase = inst.nextBase || nextBase; 229 | // 子组件所对应的父组件 230 | inst._parentComponent = component; 231 | // 不渲染,只为设置实例的属性 232 | setComponentProps(inst, childProps, NO_RENDER, context, false); 233 | // 递归调用,同步 render 234 | renderComponent(inst, SYNC_RENDER, mountAll, true); 235 | } 236 | // 组件的所对应的真实dom 237 | base = inst.base; 238 | } else { 239 | // nodename 不是 function类型 240 | // initialBase来自于initialBase = isUpdate || nextBase 241 | // initialBase等于isUpdate 242 | // nextBase = component.nextBase 243 | // 即,cbase 就是上次组件渲染的内容 244 | // 如果组件实例存在缓存 nextBase 245 | cbase = initialBase; 246 | 247 | // component._component,缓存中子组件,用来存储之后需要卸载的组件 248 | toUnmount = initialChildComponent; 249 | if (toUnmount) { 250 | // cbase对应的是之前的组件的dom节点 251 | // component._component = null的目的就是切断之前组件间的父子关系 252 | cbase = component._component = null; 253 | } 254 | 255 | /** 256 | * initialBase 存在且 renderMode 方式为同步渲染 257 | */ 258 | if (initialBase || renderMode === SYNC_RENDER) { 259 | if (cbase) cbase._component = null; 260 | /** 261 | * 调用diff方法,第一个参数,之前渲染的真实dom 262 | * rendered 就是 需要渲染的虚拟dom 263 | * context 上下文属性 264 | * mountAll || !isUpdate 是否更新 265 | * 缓存中nextBase 上次渲染的dom节点的父节点 266 | * componentRoot 为true表示的是当前diff是以组件中render函数的渲染内容的形式调用,也可以说当前的渲染内容是属于组件类型的 267 | * 268 | * 返回diff本次渲染后的真实dom节点 269 | */ 270 | base = diff( 271 | cbase, 272 | rendered, 273 | context, 274 | mountAll || !isUpdate, 275 | initialBase && initialBase.parentNode, 276 | true 277 | ); 278 | } 279 | } 280 | 281 | /** 282 | * 之前存在的nextBase,渲染之前dom存在,且渲染后的dom与它不同,且子组件也不同 283 | */ 284 | if (initialBase && base !== initialBase && inst !== initialChildComponent) { 285 | // 父节点 286 | let baseParent = initialBase.parentNode; 287 | // 父节点存在且新渲染的dom和这个父节点不同 288 | if (baseParent && base !== baseParent) { 289 | // 父级的DOM元素中将之前的DOM节点替换成当前对应渲染的DOM节点 290 | baseParent.replaceChild(base, initialBase); 291 | // 子组件不存在 292 | if (!toUnmount) { 293 | // 缓存中子组件字段设置为null 294 | initialBase._component = null; 295 | // 回收组件 296 | recollectNodeTree(initialBase, false); 297 | } 298 | } 299 | } 300 | 301 | if (toUnmount) { 302 | // 卸载组件 303 | unmountComponent(toUnmount); 304 | } 305 | 306 | // base属性指向新渲染的dom 307 | component.base = base; 308 | 309 | if (base && !isChild) { 310 | let componentRef = component, 311 | t = component; 312 | while ((t = t._parentComponent)) { 313 | (componentRef = t).base = base; 314 | } 315 | base._component = componentRef; 316 | base._componentConstructor = componentRef.constructor; 317 | } 318 | } 319 | 320 | if (!isUpdate || mountAll) { 321 | mounts.push(component); 322 | } else if (!skip) { 323 | // 生命周期 component.componentDidUpdate 函数调用 324 | if (component.componentDidUpdate) { 325 | component.componentDidUpdate(previousProps, previousState, snapshot); 326 | } 327 | if (options.afterUpdate) options.afterUpdate(component); 328 | } 329 | 330 | while (component._renderCallbacks.length) 331 | component._renderCallbacks.pop().call(component); 332 | 333 | if (!diffLevel && !isChild) flushMounts(); 334 | } 335 | 336 | /** 337 | * 338 | * @param {*} dom 旧真实dom 339 | * @param {*} vnode 虚拟dom 340 | * @param {*} context 上下文 341 | * @param {*} mountAll 342 | */ 343 | export function buildComponentFromVNode(dom, vnode, context, mountAll) { 344 | // 取得附上真实DOM上的组件实例,注意:dom._component 属性缓存的是这个真实dom是由哪个虚拟dom渲染的。 345 | // 变量c就是原真实dom由哪个组件渲染的,就是原组件的缓存(组件过大会不会造成内存溢出呢) 346 | let c = dom && dom._component, 347 | originalComponent = c, 348 | oldDom = dom, 349 | // 判断原dom节点对应的组件类型与虚拟dom的元素类型是否相同 350 | isDirectOwner = c && dom._componentConstructor === vnode.nodeName, 351 | isOwner = isDirectOwner, 352 | // 获取虚拟dom节点的属性值 353 | props = getNodeProps(vnode); 354 | 355 | /** 356 | * while 循环 c 这个组件,首先将c上的 _parentComponent 属性赋值给c,条件不达,就再次赋值,一直往上找 357 | * _parentComponent属性缓存的就是该组件父类组件实例 358 | * { 359 | * _parentComponent: { 360 | * _parentComponent: { 361 | * _parentComponent: { 362 | * _parentComponent: {} 363 | * } 364 | * } 365 | * } 366 | * } 367 | */ 368 | while (c && !isOwner && (c = c._parentComponent)) { 369 | // 如果组件类型变了,一直向上遍历;看类型是否相同,直到找到与之同类型的组件实例,让isOwner为true为止 370 | isOwner = c.constructor === vnode.nodeName; 371 | } 372 | 373 | if (c && isOwner && (!mountAll || c._component)) { 374 | setComponentProps(c, props, ASYNC_RENDER, context, mountAll); 375 | dom = c.base; 376 | } else { 377 | // 当虚拟dom和原dom节点的元素类型不一致 378 | if (originalComponent && !isDirectOwner) { 379 | // 移除旧的dom元素 380 | unmountComponent(originalComponent); 381 | dom = oldDom = null; 382 | } 383 | /** 384 | * 创建新的组件实例(typeof vnode.nodeName === function) 385 | * 这个组件实例结构: 386 | * { 387 | * _dirty: true, 388 | context: {}, 389 | props: {}, 390 | state: {}, 391 | _renderCallbacks: [], 392 | constructor: [Function: Ctor], 393 | render: [Function: doRender] } 394 | nextBase?: dom 395 | * } 396 | */ 397 | c = createComponent(vnode.nodeName, props, context); 398 | if (dom && !c.nextBase) { 399 | // 对这个实例对象的 nextBase 重新赋值,将原dom赋值给这个属性 400 | // 这个属性就是为了能基于此DOM元素进行渲染,从缓存中读取 401 | c.nextBase = dom; 402 | // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229: 403 | // 将 oldDom 销毁 404 | oldDom = null; 405 | } 406 | // 为这个刚生成的组件实例对象 c 添加 props 属性 407 | setComponentProps(c, props, SYNC_RENDER, context, mountAll); 408 | // 将这个组件实例对象的缓存 base 替换 dom 这个值 409 | dom = c.base; 410 | 411 | // 如果oldDom 和 dom 不是同一个,则对 oldDom 进行销毁和回收 412 | if (oldDom && dom !== oldDom) { 413 | oldDom._component = null; 414 | recollectNodeTree(oldDom, false); 415 | } 416 | } 417 | 418 | return dom; 419 | } 420 | 421 | /** 422 | * 移除这个组件并回收它 423 | * @param {import('../component').Component} component 组件 424 | * @private 425 | */ 426 | export function unmountComponent(component) { 427 | if (options.beforeUnmount) options.beforeUnmount(component); 428 | // 回溯这个组件渲染的实例dom 429 | let base = component.base; 430 | // 这个新增字段标示组件已经被禁用了 431 | component._disable = true; 432 | // 调用组件销毁钩子函数 433 | if (component.componentWillUnmount) component.componentWillUnmount(); 434 | // 将缓存中的实例删除 435 | component.base = null; 436 | // 回溯组件的子组件 437 | let inner = component._component; 438 | // 存在则销毁 439 | if (inner) { 440 | // 递归销毁 441 | unmountComponent(inner); 442 | } else if (base) { 443 | // 如果base节点是preact创建,则调用ref函数,卸载传入null字段 444 | if (base[ATTR_KEY] != null) applyRef(base[ATTR_KEY].ref, null); 445 | // 将base节点缓存在nextBase属性中 446 | component.nextBase = base; 447 | // 将base节点从父节点中删除 448 | removeNode(base); 449 | // 保存这个组件,以后会用到的 450 | recyclerComponents.push(component); 451 | // 卸载base节点所有的子元素 452 | removeChildren(base); 453 | } 454 | // 组件__ref属性函数调用null 455 | applyRef(component.__ref, null); 456 | } 457 | -------------------------------------------------------------------------------- /preact-source/vdom/diff.js: -------------------------------------------------------------------------------- 1 | import { ATTR_KEY } from "../constants"; 2 | import { isSameNodeType, isNamedNode } from "./index"; 3 | import { buildComponentFromVNode } from "./component"; 4 | import { createNode, setAccessor } from "../dom/index"; 5 | import { unmountComponent } from "./component"; 6 | import options from "../options"; 7 | import { applyRef } from "../util"; 8 | import { removeNode } from "../dom/index"; 9 | 10 | /** 11 | * Queue of components that have been mounted and are awaiting componentDidMount 12 | * @type {Array} 13 | */ 14 | // 用于收集那些等待被调用componentDidMount回调的组件 15 | export const mounts = []; 16 | 17 | /** 18 | * 记录递归层次 19 | * 这个值很奇怪,我发现代码里根本没有做多层次的递归调用,只有0和1,曾一度怀疑我看代码是不是老眼昏花了。 20 | * 在 diff 方法中,这个变量只做了一次增,一次减 21 | */ 22 | export let diffLevel = 0; 23 | 24 | // 判定挂载的父类DOM树是否为SVG 25 | let isSvgMode = false; 26 | 27 | /** 28 | * 一个全局的标志位,在diff方法中,这个标志位如果要为 true ,就必须更新的真实dom必须存在且这个dom并不是preact创建的 29 | * hydrating = dom != null && !(ATTR_KEY in dom); 30 | * ATTR_KEY 对应常量 __preactattr_,只要 preact 创建的 dom,都会包含这个属性 31 | * 这个属性用来存储 props 等缓存信息 32 | */ 33 | let hydrating = false; 34 | 35 | /** 36 | * 批量触发componentDidMount与afterMount 37 | */ 38 | export function flushMounts() { 39 | let c; 40 | while ((c = mounts.shift())) { 41 | if (options.afterMount) options.afterMount(c); 42 | if (c.componentDidMount) c.componentDidMount(); 43 | } 44 | } 45 | 46 | /** 47 | * 将虚拟dom创建或者更新真实dom,渲染至页面 48 | * @param {import('../dom').PreactElement} dom 待更新的旧真实dom,首次渲染则为null 49 | * @param {import('../vnode').VNode} vnode 目标虚拟dom 50 | * @param {object} context 上下文属性 51 | * @param {boolean} mountAll Whether or not to immediately mount all components 52 | * @param {Element} parent 虚拟 dom 挂载的父级节点 53 | * @param {boolean} componentRoot ? 54 | * @returns {import('../dom').PreactElement} 返回真实 dom 55 | * @private 56 | */ 57 | export function diff(dom, vnode, context, mountAll, parent, componentRoot) { 58 | // diffLevel为 0 时表示第一次进入diff函数 59 | if (!diffLevel++) { 60 | // 第一次 diff 会判定当前的DOM树是否为SVG 61 | isSvgMode = parent != null && parent.ownerSVGElement !== undefined; 62 | 63 | // 首次渲染设置标志位 64 | hydrating = dom != null && !(ATTR_KEY in dom); 65 | } 66 | 67 | // 更新 真实dom 或返回新的 真实dom 68 | let ret = idiff(dom, vnode, context, mountAll, componentRoot); 69 | 70 | // 将渲染的真实 dom 挂载到父类节点上 71 | if (parent && ret.parentNode !== parent) parent.appendChild(ret); 72 | 73 | // diffLevel回减到0说明已经要结束diff的调用 ,当diff结束了之后,触发钩子函数 74 | if (!--diffLevel) { 75 | // diff 结束,将标志位设置为false 76 | hydrating = false; 77 | // 触发 componentDidMount 78 | if (!componentRoot) flushMounts(); 79 | } 80 | 81 | return ret; 82 | } 83 | 84 | /** 85 | * Internals of `diff()`, separated to allow bypassing diffLevel / mount flushing. 86 | * @param {import('../dom').PreactElement} dom A DOM node to mutate into the shape of a `vnode` 87 | * @param {import('../vnode').VNode} vnode A VNode (with descendants forming a tree) representing the desired DOM structure 88 | * @param {object} context The current context 89 | * @param {boolean} mountAll Whether or not to immediately mount all components 90 | * @param {boolean} [componentRoot] ? 91 | * @private 92 | */ 93 | function idiff(dom, vnode, context, mountAll, componentRoot) { 94 | // 定义一个输出变量,将原始dom赋值给它(如果没有更新,直接就输出了) 95 | let out = dom, 96 | prevSvgMode = isSvgMode; 97 | 98 | // 虚拟dom为null和boolean,就赋值空字符 99 | if (vnode == null || typeof vnode === "boolean") vnode = ""; 100 | 101 | // 为字符传和数字类型时,转换成文本节点 102 | if (typeof vnode === "string" || typeof vnode === "number") { 103 | /** 104 | * 当原始 dom 存在,且是一个文本类型,存在splitText方法属性,且拥有父类节点(文本类型的节点) 105 | *

hello

106 | * 这个dom中,文本类型的值就是 hello ,而它的父类节点是 p 标签 107 | */ 108 | if ( 109 | dom && 110 | dom.splitText !== undefined && 111 | dom.parentNode && 112 | // 文本类型是不存在指向子组件的 _component 属性,所以dom._component始终为undefined 113 | (!dom._component || componentRoot) 114 | ) { 115 | // 这是文本类型的比对,如果不同,则将新的文本值,覆盖到原始dom上 116 | if (dom.nodeValue != vnode) { 117 | dom.nodeValue = vnode; 118 | } 119 | } else { 120 | // 不是文本节点或者旧dom不存在,替换之前的节点,回收之前的节点 121 | out = document.createTextNode(vnode); 122 | if (dom) { 123 | // 原始 dom 存在,且存在父节点,则基于父节点, 124 | if (dom.parentNode) dom.parentNode.replaceChild(out, dom); 125 | // 只卸载生命周期,不删除节点 126 | recollectNodeTree(dom, true); 127 | } 128 | } 129 | // preact 创建的dom,都会有这个属性 130 | out[ATTR_KEY] = true; 131 | 132 | // 输出文本类型的dom 133 | return out; 134 | } 135 | 136 | /** 137 | * 当 vnodeName 是 function 类型 138 | */ 139 | let vnodeName = vnode.nodeName; 140 | if (typeof vnodeName === "function") { 141 | return buildComponentFromVNode(dom, vnode, context, mountAll); 142 | } 143 | 144 | /** 145 | * VNode 是一个组件类型,就是h函数的执行结果 146 | * { 147 | nodeName:"div",//标签名或者函数(自定义组件)节点的类型, div span p ... 148 | children:[], //子组件组成的数组,每一项也是一个vnode 149 | key:"", //key 150 | attributes:{} //jsx的属性 151 | * } 152 | */ 153 | 154 | // svg的处理 155 | isSvgMode = 156 | vnodeName === "svg" 157 | ? true 158 | : vnodeName === "foreignObject" 159 | ? false 160 | : isSvgMode; 161 | 162 | vnodeName = String(vnodeName); 163 | // 原 dom 不存在或者 原dom存在但是虚拟dom的元素类型与之不通,则按照虚拟dom重新创建一个 164 | if (!dom || !isNamedNode(dom, vnodeName)) { 165 | // 创建一个新的dom 166 | out = createNode(vnodeName, isSvgMode); 167 | /** 168 | * 原先dom已经存在于页面上的情况 169 | * 如果原先的dom节点中存在自元素,则将他们全部移到新元素中 170 | * 如果原先dom节点存在父类元素,则直接将原来的dom替换成新的元素,挂载在这个父类节点上 171 | */ 172 | if (dom) { 173 | // 循环子元素,全部移到这个新元素下 174 | while (dom.firstChild) out.appendChild(dom.firstChild); 175 | // 替换旧dom,挂载在这个父类节点上 176 | if (dom.parentNode) dom.parentNode.replaceChild(out, dom); 177 | // 递归地回收(或者卸载)节点及其后代节点 178 | // 在调用之前我们已经将其在父元素中进行替换,所以这里是不需要进行调用的函数removeNode再进行删除该节点的,所以第二个参数是true 179 | recollectNodeTree(dom, true); 180 | } 181 | } 182 | 183 | /** 184 | * 开始处理子元素 185 | * fc 真实dom的第一个子元素 186 | * props 通过preact创建的真实dom中的属性[ATTR_KEY]的值 187 | * vchildren 虚拟dom中的子元素数组 188 | */ 189 | let fc = out.firstChild, 190 | props = out[ATTR_KEY], 191 | vchildren = vnode.children; 192 | 193 | // props 处理逻辑 194 | if (props == null) { 195 | props = out[ATTR_KEY] = {}; 196 | // 将真实dom中的一些属性也追加到props数组中 197 | for (let a = out.attributes, i = a.length; i--; ) 198 | props[a[i].name] = a[i].value; 199 | } 200 | 201 | /** 202 | * 下面的这个条件语句,一一分解: 203 | * !hydrating: 是preact创建的 204 | * vchildren && vchildren.length === 1 虚拟dom中存在children字段,且不止一个 205 | * typeof vchildren[0] === "string" 第一个子组件类型为字符类型 206 | * fc != null && fc.splitText !== undefined && fc.nextSibling == null 文本类型,且无其他节点 207 | */ 208 | if ( 209 | !hydrating && 210 | vchildren && 211 | vchildren.length === 1 && 212 | typeof vchildren[0] === "string" && 213 | fc != null && 214 | fc.splitText !== undefined && 215 | fc.nextSibling == null 216 | ) { 217 | // 文本类型值比对时,直接替换 218 | if (fc.nodeValue != vchildren[0]) { 219 | fc.nodeValue = vchildren[0]; 220 | } 221 | } 222 | // 子节点存在且不为文本类型时,执行深层diff 223 | else if ((vchildren && vchildren.length) || fc != null) { 224 | innerDiffNode( 225 | out, 226 | vchildren, 227 | context, 228 | mountAll, 229 | hydrating || props.dangerouslySetInnerHTML != null 230 | ); 231 | } 232 | 233 | // 将props和atrributes从VNode中应用到DOM元素 234 | diffAttributes(out, vnode.attributes, props); 235 | 236 | // 恢复之前的SVG模式 237 | isSvgMode = prevSvgMode; 238 | 239 | return out; 240 | } 241 | 242 | /** 243 | * 虚拟dom子组件的深层diff方法 244 | * @param {*} dom 虚拟dom所定义的真实dom 245 | * @param {*} vchildren diff的虚拟子元素 246 | * @param {*} context 环境上下文 247 | * @param {*} mountAll 248 | * @param {*} isHydrating 是否由preact创建 249 | */ 250 | function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) { 251 | let originalChildren = dom.childNodes, 252 | children = [], 253 | keyed = {}, 254 | keyedLen = 0, 255 | min = 0, 256 | len = originalChildren.length, 257 | childrenLen = 0, 258 | vlen = vchildren ? vchildren.length : 0, 259 | j, 260 | c, 261 | f, 262 | vchild, 263 | child; 264 | 265 | // Build up a map of keyed children and an Array of unkeyed children: 266 | if (len !== 0) { 267 | for (let i = 0; i < len; i++) { 268 | let child = originalChildren[i], 269 | props = child[ATTR_KEY], 270 | key = 271 | vlen && props 272 | ? child._component 273 | ? child._component.__key 274 | : props.key 275 | : null; 276 | if (key != null) { 277 | keyedLen++; 278 | keyed[key] = child; 279 | } else if ( 280 | props || 281 | (child.splitText !== undefined 282 | ? isHydrating 283 | ? child.nodeValue.trim() 284 | : true 285 | : isHydrating) 286 | ) { 287 | children[childrenLen++] = child; 288 | } 289 | } 290 | } 291 | 292 | if (vlen !== 0) { 293 | for (let i = 0; i < vlen; i++) { 294 | vchild = vchildren[i]; 295 | child = null; 296 | 297 | // attempt to find a node based on key matching 298 | let key = vchild.key; 299 | if (key != null) { 300 | if (keyedLen && keyed[key] !== undefined) { 301 | child = keyed[key]; 302 | keyed[key] = undefined; 303 | keyedLen--; 304 | } 305 | } 306 | // attempt to pluck a node of the same type from the existing children 307 | else if (min < childrenLen) { 308 | for (j = min; j < childrenLen; j++) { 309 | if ( 310 | children[j] !== undefined && 311 | isSameNodeType((c = children[j]), vchild, isHydrating) 312 | ) { 313 | child = c; 314 | children[j] = undefined; 315 | if (j === childrenLen - 1) childrenLen--; 316 | if (j === min) min++; 317 | break; 318 | } 319 | } 320 | } 321 | 322 | // morph the matched/found/created DOM child to match vchild (deep) 323 | child = idiff(child, vchild, context, mountAll); 324 | 325 | f = originalChildren[i]; 326 | if (child && child !== dom && child !== f) { 327 | if (f == null) { 328 | dom.appendChild(child); 329 | } else if (child === f.nextSibling) { 330 | removeNode(f); 331 | } else { 332 | dom.insertBefore(child, f); 333 | } 334 | } 335 | } 336 | } 337 | 338 | // remove unused keyed children: 339 | if (keyedLen) { 340 | for (let i in keyed) 341 | if (keyed[i] !== undefined) recollectNodeTree(keyed[i], false); 342 | } 343 | 344 | // remove orphaned unkeyed children: 345 | while (min <= childrenLen) { 346 | if ((child = children[childrenLen--]) !== undefined) 347 | recollectNodeTree(child, false); 348 | } 349 | } 350 | 351 | /** 352 | * 递归地回收(或者卸载)节点及其后代节点 353 | * @param {*} node 354 | * @param {*} unmountOnly 如果为`true`,仅仅触发卸载的生命周期,跳过删除 355 | */ 356 | export function recollectNodeTree(node, unmountOnly) { 357 | let component = node._component; 358 | if (component) { 359 | // 如果节点包含children的子组件,需要卸载这个子组件 360 | unmountComponent(component); 361 | } else { 362 | // 如果 preact 创建的组件,且设置了 ref,且 ref是一个function时,在卸载的时候,需要传一个null作为参数作为回调。 363 | if (node[ATTR_KEY] != null) applyRef(node[ATTR_KEY].ref, null); 364 | // 如果不是 preact 创建的组件,或者 设置了删除字段,则调用删除节点方法 365 | if (unmountOnly === false || node[ATTR_KEY] == null) { 366 | // 从父节点中将该子节点删除 367 | removeNode(node); 368 | } 369 | // 递归删除子节点 370 | removeChildren(node); 371 | } 372 | } 373 | 374 | /** 375 | * 回收/卸载所有的子元素 376 | * 在方法内部使用 recollectNodeTree(node, true); 377 | * 递归调用 378 | * 注意,这里使用 .lastChild而不是使用 .firstChild,是因为访问节点的代价更低。(工匠精神哈!) 379 | * @param {*} node 380 | */ 381 | export function removeChildren(node) { 382 | node = node.lastChild; 383 | while (node) { 384 | let next = node.previousSibling; 385 | recollectNodeTree(node, true); 386 | node = next; 387 | } 388 | } 389 | 390 | /** 391 | * diff VNode和原页面dom之间的属性 392 | * @param {*} dom 已经通过diff之后的真实dom 393 | * @param {*} attrs 虚拟dom中的属性值 394 | * @param {*} old 通过preact创建的真实dom中,属性[ATTR_KEY]中保存的值和真实dom中已有属性值的组合 395 | */ 396 | function diffAttributes(dom, attrs, old) { 397 | let name; 398 | 399 | // 遍历真实dom中的所有属性,判断该属性是否在虚拟dom中也有,如果没有,则设置其为undefined 400 | for (name in old) { 401 | if (!(attrs && attrs[name] != null) && old[name] != null) { 402 | setAccessor(dom, name, old[name], (old[name] = undefined), isSvgMode); 403 | } 404 | } 405 | 406 | // 遍历虚拟dom中的属性 407 | for (name in attrs) { 408 | // 1.如果虚拟dom中的某个属性不是children或者innerHTML 409 | // 2.且该属性不在old dom中,那说明是虚拟dom新增的属性 或者 如果name是value或者checked属性(表单), 410 | // attrs[name] 与 dom[name] 不同,或者不是value或者checked属性,则和old[name]属性不同,则将dom上的属性更新 411 | if ( 412 | name !== "children" && 413 | name !== "innerHTML" && 414 | (!(name in old) || 415 | attrs[name] !== 416 | (name === "value" || name === "checked" ? dom[name] : old[name])) 417 | ) { 418 | setAccessor(dom, name, old[name], (old[name] = attrs[name]), isSvgMode); 419 | } 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /preact-source/vdom/index.js: -------------------------------------------------------------------------------- 1 | import { extend } from "../util"; 2 | 3 | /** 4 | * Check if two nodes are equivalent. 5 | * @param {import('../dom').PreactElement} node DOM Node to compare 6 | * @param {import('../vnode').VNode} vnode Virtual DOM node to compare 7 | * @param {boolean} [hydrating=false] If true, ignores component constructors 8 | * when comparing. 9 | * @private 10 | */ 11 | export function isSameNodeType(node, vnode, hydrating) { 12 | if (typeof vnode === "string" || typeof vnode === "number") { 13 | return node.splitText !== undefined; 14 | } 15 | if (typeof vnode.nodeName === "string") { 16 | return !node._componentConstructor && isNamedNode(node, vnode.nodeName); 17 | } 18 | return hydrating || node._componentConstructor === vnode.nodeName; 19 | } 20 | 21 | /** 22 | * 判断虚拟dom中的元素类型与原先dom元素的类型是否相同 23 | * @param {*} node 已经存在页面上的旧dom 24 | * @param {*} nodeName 虚拟dom中的元素类型 25 | */ 26 | export function isNamedNode(node, nodeName) { 27 | return ( 28 | node.normalizedNodeName === nodeName || 29 | node.nodeName.toLowerCase() === nodeName.toLowerCase() 30 | ); 31 | } 32 | 33 | /** 34 | * Reconstruct Component-style `props` from a VNode. 35 | * Ensures default/fallback values from `defaultProps`: 36 | * Own-properties of `defaultProps` not present in `vnode.attributes` are added. 37 | * @param {import('../vnode').VNode} vnode The VNode to get props for 38 | * @returns {object} The props to use for this VNode 39 | */ 40 | 41 | /** 42 | * 获取虚拟dom的属性以及默认属性 43 | * @param {*} vnode 44 | */ 45 | export function getNodeProps(vnode) { 46 | let props = extend({}, vnode.attributes); 47 | props.children = vnode.children; 48 | 49 | let defaultProps = vnode.nodeName.defaultProps; 50 | if (defaultProps !== undefined) { 51 | for (let i in defaultProps) { 52 | if (props[i] === undefined) { 53 | props[i] = defaultProps[i]; 54 | } 55 | } 56 | } 57 | 58 | return props; 59 | } 60 | -------------------------------------------------------------------------------- /preact-source/vnode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Virtual DOM Node 3 | * 虚拟 dom 的结构,通过h /(createElement())方法会返回这个类的实例 4 | * { 5 | nodeName:"div",//标签名或者函数(自定义组件) 6 | children:[], //子组件组成的数组,每一项也是一个vnode 7 | key:"", //key 8 | attributes:{} //jsx的属性 9 | } 10 | 拥有子组件的嵌套类型: 11 | { 12 | nodeName:"div",//标签名或者函数(自定义组件) 13 | children:[ 14 | { 15 | nodeName:"div", 16 | children:[], 17 | key:"", 18 | attributes:{} 19 | } 20 | ], 21 | key:"", //key 22 | attributes:{} //jsx的属性 23 | } 24 | 25 | 26 | 27 | nodeName存在三种情况: 28 | - 文本类型,

hello

,其中 hello 就是文本类型 29 | - 字符串,普通标签类型 就是div,span,p等等的html标签 30 | - function,当嵌套组件时,h(HelloJSX, null),这个函数执行后的虚拟dom 31 | { 32 | nodeName: HelloJSX, // HelloJSX 是一个function 33 | children: [], 34 | key: '', 35 | attributes: {} 36 | } 37 | 38 | */ 39 | export const VNode = function VNode() {}; 40 | -------------------------------------------------------------------------------- /react-redux/components/Context.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const ReactReduxContext = React.createContext(null) 4 | 5 | export default ReactReduxContext 6 | -------------------------------------------------------------------------------- /react-redux/components/Provider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { ReactReduxContext } from './Context' 4 | 5 | class Provider extends Component { 6 | constructor(props) { 7 | super(props) 8 | 9 | const { store } = props 10 | 11 | this.state = { 12 | storeState: store.getState(), 13 | store 14 | } 15 | } 16 | 17 | componentDidMount() { 18 | this._isMounted = true 19 | this.subscribe() 20 | } 21 | 22 | componentWillUnmount() { 23 | if (this.unsubscribe) this.unsubscribe() 24 | 25 | this._isMounted = false 26 | } 27 | 28 | componentDidUpdate(prevProps) { 29 | if (this.props.store !== prevProps.store) { 30 | if (this.unsubscribe) this.unsubscribe() 31 | 32 | this.subscribe() 33 | } 34 | } 35 | 36 | subscribe() { 37 | const { store } = this.props 38 | 39 | this.unsubscribe = store.subscribe(() => { 40 | const newStoreState = store.getState() 41 | 42 | if (!this._isMounted) { 43 | return 44 | } 45 | 46 | this.setState(providerState => { 47 | // If the value is the same, skip the unnecessary state update. 48 | if (providerState.storeState === newStoreState) { 49 | return null 50 | } 51 | 52 | return { storeState: newStoreState } 53 | }) 54 | }) 55 | 56 | // Actions might have been dispatched between render and mount - handle those 57 | const postMountStoreState = store.getState() 58 | if (postMountStoreState !== this.state.storeState) { 59 | this.setState({ storeState: postMountStoreState }) 60 | } 61 | } 62 | 63 | render() { 64 | const Context = this.props.context || ReactReduxContext 65 | 66 | return ( 67 | 68 | {this.props.children} 69 | 70 | ) 71 | } 72 | } 73 | 74 | Provider.propTypes = { 75 | store: PropTypes.shape({ 76 | subscribe: PropTypes.func.isRequired, 77 | dispatch: PropTypes.func.isRequired, 78 | getState: PropTypes.func.isRequired 79 | }), 80 | context: PropTypes.object, 81 | children: PropTypes.any 82 | } 83 | 84 | export default Provider 85 | -------------------------------------------------------------------------------- /react-redux/components/connectAdvanced.js: -------------------------------------------------------------------------------- 1 | import hoistStatics from 'hoist-non-react-statics' 2 | import invariant from 'invariant' 3 | import React, { Component, PureComponent } from 'react' 4 | import { isValidElementType, isContextConsumer } from 'react-is' 5 | 6 | import { ReactReduxContext } from './Context' 7 | 8 | const stringifyComponent = Comp => { 9 | try { 10 | return JSON.stringify(Comp) 11 | } catch (err) { 12 | return String(Comp) 13 | } 14 | } 15 | 16 | export default function connectAdvanced( 17 | /* 18 | selectorFactory is a func that is responsible for returning the selector function used to 19 | compute new props from state, props, and dispatch. For example: 20 | 21 | export default connectAdvanced((dispatch, options) => (state, props) => ({ 22 | thing: state.things[props.thingId], 23 | saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)), 24 | }))(YourComponent) 25 | 26 | Access to dispatch is provided to the factory so selectorFactories can bind actionCreators 27 | outside of their selector as an optimization. Options passed to connectAdvanced are passed to 28 | the selectorFactory, along with displayName and WrappedComponent, as the second argument. 29 | 30 | Note that selectorFactory is responsible for all caching/memoization of inbound and outbound 31 | props. Do not use connectAdvanced directly without memoizing results between calls to your 32 | selector, otherwise the Connect component will re-render on every state or props change. 33 | */ 34 | selectorFactory, 35 | // options object: 36 | { 37 | // the func used to compute this HOC's displayName from the wrapped component's displayName. 38 | // probably overridden by wrapper functions such as connect() 39 | getDisplayName = name => `ConnectAdvanced(${name})`, 40 | 41 | // shown in error messages 42 | // probably overridden by wrapper functions such as connect() 43 | methodName = 'connectAdvanced', 44 | 45 | // REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of 46 | // calls to render. useful for watching in react devtools for unnecessary re-renders. 47 | renderCountProp = undefined, 48 | 49 | // determines whether this HOC subscribes to store changes 50 | shouldHandleStateChanges = true, 51 | 52 | // REMOVED: the key of props/context to get the store 53 | storeKey = 'store', 54 | 55 | // REMOVED: expose the wrapped component via refs 56 | withRef = false, 57 | 58 | // use React's forwardRef to expose a ref of the wrapped component 59 | forwardRef = false, 60 | 61 | // the context consumer to use 62 | context = ReactReduxContext, 63 | 64 | // additional options are passed through to the selectorFactory 65 | ...connectOptions 66 | } = {} 67 | ) { 68 | invariant( 69 | renderCountProp === undefined, 70 | `renderCountProp is removed. render counting is built into the latest React dev tools profiling extension` 71 | ) 72 | 73 | invariant( 74 | !withRef, 75 | 'withRef is removed. To access the wrapped instance, use a ref on the connected component' 76 | ) 77 | 78 | const customStoreWarningMessage = 79 | 'To use a custom Redux store for specific components, create a custom React context with ' + 80 | "React.createContext(), and pass the context object to React Redux's Provider and specific components" + 81 | ' like: . ' + 82 | 'You may also pass a {context : MyContext} option to connect' 83 | 84 | invariant( 85 | storeKey === 'store', 86 | 'storeKey has been removed and does not do anything. ' + 87 | customStoreWarningMessage 88 | ) 89 | 90 | const Context = context 91 | 92 | return function wrapWithConnect(WrappedComponent) { 93 | if (process.env.NODE_ENV !== 'production') { 94 | invariant( 95 | isValidElementType(WrappedComponent), 96 | `You must pass a component to the function returned by ` + 97 | `${methodName}. Instead received ${stringifyComponent( 98 | WrappedComponent 99 | )}` 100 | ) 101 | } 102 | 103 | const wrappedComponentName = 104 | WrappedComponent.displayName || WrappedComponent.name || 'Component' 105 | 106 | const displayName = getDisplayName(wrappedComponentName) 107 | 108 | const selectorFactoryOptions = { 109 | ...connectOptions, 110 | getDisplayName, 111 | methodName, 112 | renderCountProp, 113 | shouldHandleStateChanges, 114 | storeKey, 115 | displayName, 116 | wrappedComponentName, 117 | WrappedComponent 118 | } 119 | 120 | const { pure } = connectOptions 121 | 122 | let OuterBaseComponent = Component 123 | 124 | if (pure) { 125 | OuterBaseComponent = PureComponent 126 | } 127 | 128 | function makeDerivedPropsSelector() { 129 | let lastProps 130 | let lastState 131 | let lastDerivedProps 132 | let lastStore 133 | let lastSelectorFactoryOptions 134 | let sourceSelector 135 | 136 | return function selectDerivedProps( 137 | state, 138 | props, 139 | store, 140 | selectorFactoryOptions 141 | ) { 142 | if (pure && lastProps === props && lastState === state) { 143 | return lastDerivedProps 144 | } 145 | 146 | if ( 147 | store !== lastStore || 148 | lastSelectorFactoryOptions !== selectorFactoryOptions 149 | ) { 150 | lastStore = store 151 | lastSelectorFactoryOptions = selectorFactoryOptions 152 | sourceSelector = selectorFactory( 153 | store.dispatch, 154 | selectorFactoryOptions 155 | ) 156 | } 157 | 158 | lastProps = props 159 | lastState = state 160 | 161 | const nextProps = sourceSelector(state, props) 162 | 163 | lastDerivedProps = nextProps 164 | return lastDerivedProps 165 | } 166 | } 167 | 168 | function makeChildElementSelector() { 169 | let lastChildProps, lastForwardRef, lastChildElement, lastComponent 170 | 171 | return function selectChildElement( 172 | WrappedComponent, 173 | childProps, 174 | forwardRef 175 | ) { 176 | if ( 177 | childProps !== lastChildProps || 178 | forwardRef !== lastForwardRef || 179 | lastComponent !== WrappedComponent 180 | ) { 181 | lastChildProps = childProps 182 | lastForwardRef = forwardRef 183 | lastComponent = WrappedComponent 184 | lastChildElement = ( 185 | 186 | ) 187 | } 188 | 189 | return lastChildElement 190 | } 191 | } 192 | 193 | class Connect extends OuterBaseComponent { 194 | constructor(props) { 195 | super(props) 196 | invariant( 197 | forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey], 198 | 'Passing redux store in props has been removed and does not do anything. ' + 199 | customStoreWarningMessage 200 | ) 201 | this.selectDerivedProps = makeDerivedPropsSelector() 202 | this.selectChildElement = makeChildElementSelector() 203 | this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind( 204 | this 205 | ) 206 | } 207 | 208 | indirectRenderWrappedComponent(value) { 209 | // calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this` 210 | return this.renderWrappedComponent(value) 211 | } 212 | 213 | renderWrappedComponent(value) { 214 | invariant( 215 | value, 216 | `Could not find "store" in the context of ` + 217 | `"${displayName}". Either wrap the root component in a , ` + 218 | `or pass a custom React context provider to and the corresponding ` + 219 | `React context consumer to ${displayName} in connect options.` 220 | ) 221 | const { storeState, store } = value 222 | 223 | let wrapperProps = this.props 224 | let forwardedRef 225 | 226 | if (forwardRef) { 227 | wrapperProps = this.props.wrapperProps 228 | forwardedRef = this.props.forwardedRef 229 | } 230 | 231 | let derivedProps = this.selectDerivedProps( 232 | storeState, 233 | wrapperProps, 234 | store, 235 | selectorFactoryOptions 236 | ) 237 | 238 | return this.selectChildElement( 239 | WrappedComponent, 240 | derivedProps, 241 | forwardedRef 242 | ) 243 | } 244 | 245 | render() { 246 | const ContextToUse = 247 | this.props.context && 248 | this.props.context.Consumer && 249 | isContextConsumer() 250 | ? this.props.context 251 | : Context 252 | 253 | return ( 254 | 255 | {this.indirectRenderWrappedComponent} 256 | 257 | ) 258 | } 259 | } 260 | 261 | Connect.WrappedComponent = WrappedComponent 262 | Connect.displayName = displayName 263 | 264 | if (forwardRef) { 265 | const forwarded = React.forwardRef(function forwardConnectRef( 266 | props, 267 | ref 268 | ) { 269 | return 270 | }) 271 | 272 | forwarded.displayName = displayName 273 | forwarded.WrappedComponent = WrappedComponent 274 | return hoistStatics(forwarded, WrappedComponent) 275 | } 276 | 277 | return hoistStatics(Connect, WrappedComponent) 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /react-redux/connect/connect.js: -------------------------------------------------------------------------------- 1 | import connectAdvanced from '../components/connectAdvanced' 2 | import shallowEqual from '../utils/shallowEqual' 3 | import defaultMapDispatchToPropsFactories from './mapDispatchToProps' 4 | import defaultMapStateToPropsFactories from './mapStateToProps' 5 | import defaultMergePropsFactories from './mergeProps' 6 | import defaultSelectorFactory from './selectorFactory' 7 | 8 | /* 9 | connect is a facade over connectAdvanced. It turns its args into a compatible 10 | selectorFactory, which has the signature: 11 | 12 | (dispatch, options) => (nextState, nextOwnProps) => nextFinalProps 13 | 14 | connect passes its args to connectAdvanced as options, which will in turn pass them to 15 | selectorFactory each time a Connect component instance is instantiated or hot reloaded. 16 | 17 | selectorFactory returns a final props selector from its mapStateToProps, 18 | mapStateToPropsFactories, mapDispatchToProps, mapDispatchToPropsFactories, mergeProps, 19 | mergePropsFactories, and pure args. 20 | 21 | The resulting final props selector is called by the Connect component instance whenever 22 | it receives new props or store state. 23 | */ 24 | 25 | function match(arg, factories, name) { 26 | for (let i = factories.length - 1; i >= 0; i--) { 27 | const result = factories[i](arg) 28 | if (result) return result 29 | } 30 | 31 | return (dispatch, options) => { 32 | throw new Error( 33 | `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${ 34 | options.wrappedComponentName 35 | }.` 36 | ) 37 | } 38 | } 39 | 40 | function strictEqual(a, b) { 41 | return a === b 42 | } 43 | 44 | // createConnect with default args builds the 'official' connect behavior. Calling it with 45 | // different options opens up some testing and extensibility scenarios 46 | export function createConnect({ 47 | connectHOC = connectAdvanced, 48 | mapStateToPropsFactories = defaultMapStateToPropsFactories, 49 | mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, 50 | mergePropsFactories = defaultMergePropsFactories, 51 | selectorFactory = defaultSelectorFactory 52 | } = {}) { 53 | return function connect( 54 | mapStateToProps, 55 | mapDispatchToProps, 56 | mergeProps, 57 | { 58 | pure = true, 59 | areStatesEqual = strictEqual, 60 | areOwnPropsEqual = shallowEqual, 61 | areStatePropsEqual = shallowEqual, 62 | areMergedPropsEqual = shallowEqual, 63 | ...extraOptions 64 | } = {} 65 | ) { 66 | const initMapStateToProps = match( 67 | mapStateToProps, 68 | mapStateToPropsFactories, 69 | 'mapStateToProps' 70 | ) 71 | const initMapDispatchToProps = match( 72 | mapDispatchToProps, 73 | mapDispatchToPropsFactories, 74 | 'mapDispatchToProps' 75 | ) 76 | const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') 77 | 78 | return connectHOC(selectorFactory, { 79 | // used in error messages 80 | methodName: 'connect', 81 | 82 | // used to compute Connect's displayName from the wrapped component's displayName. 83 | getDisplayName: name => `Connect(${name})`, 84 | 85 | // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes 86 | shouldHandleStateChanges: Boolean(mapStateToProps), 87 | 88 | // passed through to selectorFactory 89 | initMapStateToProps, 90 | initMapDispatchToProps, 91 | initMergeProps, 92 | pure, 93 | areStatesEqual, 94 | areOwnPropsEqual, 95 | areStatePropsEqual, 96 | areMergedPropsEqual, 97 | 98 | // any extra options args can override defaults of connect or connectAdvanced 99 | ...extraOptions 100 | }) 101 | } 102 | } 103 | 104 | export default createConnect() 105 | -------------------------------------------------------------------------------- /react-redux/connect/mapDispatchToProps.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux' 2 | import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' 3 | 4 | export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) { 5 | return typeof mapDispatchToProps === 'function' 6 | ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps') 7 | : undefined 8 | } 9 | 10 | export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) { 11 | return !mapDispatchToProps 12 | ? wrapMapToPropsConstant(dispatch => ({ dispatch })) 13 | : undefined 14 | } 15 | 16 | export function whenMapDispatchToPropsIsObject(mapDispatchToProps) { 17 | return mapDispatchToProps && typeof mapDispatchToProps === 'object' 18 | ? wrapMapToPropsConstant(dispatch => 19 | bindActionCreators(mapDispatchToProps, dispatch) 20 | ) 21 | : undefined 22 | } 23 | 24 | export default [ 25 | whenMapDispatchToPropsIsFunction, 26 | whenMapDispatchToPropsIsMissing, 27 | whenMapDispatchToPropsIsObject 28 | ] 29 | -------------------------------------------------------------------------------- /react-redux/connect/mapStateToProps.js: -------------------------------------------------------------------------------- 1 | import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' 2 | 3 | export function whenMapStateToPropsIsFunction(mapStateToProps) { 4 | return typeof mapStateToProps === 'function' 5 | ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') 6 | : undefined 7 | } 8 | 9 | export function whenMapStateToPropsIsMissing(mapStateToProps) { 10 | return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined 11 | } 12 | 13 | export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing] 14 | -------------------------------------------------------------------------------- /react-redux/connect/mergeProps.js: -------------------------------------------------------------------------------- 1 | import verifyPlainObject from '../utils/verifyPlainObject' 2 | 3 | export function defaultMergeProps(stateProps, dispatchProps, ownProps) { 4 | return { ...ownProps, ...stateProps, ...dispatchProps } 5 | } 6 | 7 | export function wrapMergePropsFunc(mergeProps) { 8 | return function initMergePropsProxy( 9 | dispatch, 10 | { displayName, pure, areMergedPropsEqual } 11 | ) { 12 | let hasRunOnce = false 13 | let mergedProps 14 | 15 | return function mergePropsProxy(stateProps, dispatchProps, ownProps) { 16 | const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps) 17 | 18 | if (hasRunOnce) { 19 | if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps)) 20 | mergedProps = nextMergedProps 21 | } else { 22 | hasRunOnce = true 23 | mergedProps = nextMergedProps 24 | 25 | if (process.env.NODE_ENV !== 'production') 26 | verifyPlainObject(mergedProps, displayName, 'mergeProps') 27 | } 28 | 29 | return mergedProps 30 | } 31 | } 32 | } 33 | 34 | export function whenMergePropsIsFunction(mergeProps) { 35 | return typeof mergeProps === 'function' 36 | ? wrapMergePropsFunc(mergeProps) 37 | : undefined 38 | } 39 | 40 | export function whenMergePropsIsOmitted(mergeProps) { 41 | return !mergeProps ? () => defaultMergeProps : undefined 42 | } 43 | 44 | export default [whenMergePropsIsFunction, whenMergePropsIsOmitted] 45 | -------------------------------------------------------------------------------- /react-redux/connect/selectorFactory.js: -------------------------------------------------------------------------------- 1 | import verifySubselectors from './verifySubselectors' 2 | 3 | export function impureFinalPropsSelectorFactory( 4 | mapStateToProps, 5 | mapDispatchToProps, 6 | mergeProps, 7 | dispatch 8 | ) { 9 | return function impureFinalPropsSelector(state, ownProps) { 10 | return mergeProps( 11 | mapStateToProps(state, ownProps), 12 | mapDispatchToProps(dispatch, ownProps), 13 | ownProps 14 | ) 15 | } 16 | } 17 | 18 | export function pureFinalPropsSelectorFactory( 19 | mapStateToProps, 20 | mapDispatchToProps, 21 | mergeProps, 22 | dispatch, 23 | { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } 24 | ) { 25 | let hasRunAtLeastOnce = false 26 | let state 27 | let ownProps 28 | let stateProps 29 | let dispatchProps 30 | let mergedProps 31 | 32 | function handleFirstCall(firstState, firstOwnProps) { 33 | state = firstState 34 | ownProps = firstOwnProps 35 | stateProps = mapStateToProps(state, ownProps) 36 | dispatchProps = mapDispatchToProps(dispatch, ownProps) 37 | mergedProps = mergeProps(stateProps, dispatchProps, ownProps) 38 | hasRunAtLeastOnce = true 39 | return mergedProps 40 | } 41 | 42 | function handleNewPropsAndNewState() { 43 | stateProps = mapStateToProps(state, ownProps) 44 | 45 | if (mapDispatchToProps.dependsOnOwnProps) 46 | dispatchProps = mapDispatchToProps(dispatch, ownProps) 47 | 48 | mergedProps = mergeProps(stateProps, dispatchProps, ownProps) 49 | return mergedProps 50 | } 51 | 52 | function handleNewProps() { 53 | if (mapStateToProps.dependsOnOwnProps) 54 | stateProps = mapStateToProps(state, ownProps) 55 | 56 | if (mapDispatchToProps.dependsOnOwnProps) 57 | dispatchProps = mapDispatchToProps(dispatch, ownProps) 58 | 59 | mergedProps = mergeProps(stateProps, dispatchProps, ownProps) 60 | return mergedProps 61 | } 62 | 63 | function handleNewState() { 64 | const nextStateProps = mapStateToProps(state, ownProps) 65 | const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) 66 | stateProps = nextStateProps 67 | 68 | if (statePropsChanged) 69 | mergedProps = mergeProps(stateProps, dispatchProps, ownProps) 70 | 71 | return mergedProps 72 | } 73 | 74 | function handleSubsequentCalls(nextState, nextOwnProps) { 75 | const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) 76 | const stateChanged = !areStatesEqual(nextState, state) 77 | state = nextState 78 | ownProps = nextOwnProps 79 | 80 | if (propsChanged && stateChanged) return handleNewPropsAndNewState() 81 | if (propsChanged) return handleNewProps() 82 | if (stateChanged) return handleNewState() 83 | return mergedProps 84 | } 85 | 86 | return function pureFinalPropsSelector(nextState, nextOwnProps) { 87 | return hasRunAtLeastOnce 88 | ? handleSubsequentCalls(nextState, nextOwnProps) 89 | : handleFirstCall(nextState, nextOwnProps) 90 | } 91 | } 92 | 93 | // TODO: Add more comments 94 | 95 | // If pure is true, the selector returned by selectorFactory will memoize its results, 96 | // allowing connectAdvanced's shouldComponentUpdate to return false if final 97 | // props have not changed. If false, the selector will always return a new 98 | // object and shouldComponentUpdate will always return true. 99 | 100 | export default function finalPropsSelectorFactory( 101 | dispatch, 102 | { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } 103 | ) { 104 | const mapStateToProps = initMapStateToProps(dispatch, options) 105 | const mapDispatchToProps = initMapDispatchToProps(dispatch, options) 106 | const mergeProps = initMergeProps(dispatch, options) 107 | 108 | if (process.env.NODE_ENV !== 'production') { 109 | verifySubselectors( 110 | mapStateToProps, 111 | mapDispatchToProps, 112 | mergeProps, 113 | options.displayName 114 | ) 115 | } 116 | 117 | const selectorFactory = options.pure 118 | ? pureFinalPropsSelectorFactory 119 | : impureFinalPropsSelectorFactory 120 | 121 | return selectorFactory( 122 | mapStateToProps, 123 | mapDispatchToProps, 124 | mergeProps, 125 | dispatch, 126 | options 127 | ) 128 | } 129 | -------------------------------------------------------------------------------- /react-redux/connect/verifySubselectors.js: -------------------------------------------------------------------------------- 1 | import warning from '../utils/warning' 2 | 3 | function verify(selector, methodName, displayName) { 4 | if (!selector) { 5 | throw new Error(`Unexpected value for ${methodName} in ${displayName}.`) 6 | } else if ( 7 | methodName === 'mapStateToProps' || 8 | methodName === 'mapDispatchToProps' 9 | ) { 10 | if (!selector.hasOwnProperty('dependsOnOwnProps')) { 11 | warning( 12 | `The selector for ${methodName} of ${displayName} did not specify a value for dependsOnOwnProps.` 13 | ) 14 | } 15 | } 16 | } 17 | 18 | export default function verifySubselectors( 19 | mapStateToProps, 20 | mapDispatchToProps, 21 | mergeProps, 22 | displayName 23 | ) { 24 | verify(mapStateToProps, 'mapStateToProps', displayName) 25 | verify(mapDispatchToProps, 'mapDispatchToProps', displayName) 26 | verify(mergeProps, 'mergeProps', displayName) 27 | } 28 | -------------------------------------------------------------------------------- /react-redux/connect/wrapMapToProps.js: -------------------------------------------------------------------------------- 1 | import verifyPlainObject from '../utils/verifyPlainObject' 2 | 3 | export function wrapMapToPropsConstant(getConstant) { 4 | return function initConstantSelector(dispatch, options) { 5 | const constant = getConstant(dispatch, options) 6 | 7 | function constantSelector() { 8 | return constant 9 | } 10 | constantSelector.dependsOnOwnProps = false 11 | return constantSelector 12 | } 13 | } 14 | 15 | // dependsOnOwnProps is used by createMapToPropsProxy to determine whether to pass props as args 16 | // to the mapToProps function being wrapped. It is also used by makePurePropsSelector to determine 17 | // whether mapToProps needs to be invoked when props have changed. 18 | // 19 | // A length of one signals that mapToProps does not depend on props from the parent component. 20 | // A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and 21 | // therefore not reporting its length accurately.. 22 | export function getDependsOnOwnProps(mapToProps) { 23 | return mapToProps.dependsOnOwnProps !== null && 24 | mapToProps.dependsOnOwnProps !== undefined 25 | ? Boolean(mapToProps.dependsOnOwnProps) 26 | : mapToProps.length !== 1 27 | } 28 | 29 | // Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction, 30 | // this function wraps mapToProps in a proxy function which does several things: 31 | // 32 | // * Detects whether the mapToProps function being called depends on props, which 33 | // is used by selectorFactory to decide if it should reinvoke on props changes. 34 | // 35 | // * On first call, handles mapToProps if returns another function, and treats that 36 | // new function as the true mapToProps for subsequent calls. 37 | // 38 | // * On first call, verifies the first result is a plain object, in order to warn 39 | // the developer that their mapToProps function is not returning a valid result. 40 | // 41 | export function wrapMapToPropsFunc(mapToProps, methodName) { 42 | return function initProxySelector(dispatch, { displayName }) { 43 | const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) { 44 | return proxy.dependsOnOwnProps 45 | ? proxy.mapToProps(stateOrDispatch, ownProps) 46 | : proxy.mapToProps(stateOrDispatch) 47 | } 48 | 49 | // allow detectFactoryAndVerify to get ownProps 50 | proxy.dependsOnOwnProps = true 51 | 52 | proxy.mapToProps = function detectFactoryAndVerify( 53 | stateOrDispatch, 54 | ownProps 55 | ) { 56 | proxy.mapToProps = mapToProps 57 | proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) 58 | let props = proxy(stateOrDispatch, ownProps) 59 | 60 | if (typeof props === 'function') { 61 | proxy.mapToProps = props 62 | proxy.dependsOnOwnProps = getDependsOnOwnProps(props) 63 | props = proxy(stateOrDispatch, ownProps) 64 | } 65 | 66 | if (process.env.NODE_ENV !== 'production') 67 | verifyPlainObject(props, displayName, methodName) 68 | 69 | return props 70 | } 71 | 72 | return proxy 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /react-redux/index.js: -------------------------------------------------------------------------------- 1 | import Provider from './components/Provider' 2 | import connectAdvanced from './components/connectAdvanced' 3 | import { ReactReduxContext } from './components/Context' 4 | import connect from './connect/connect' 5 | 6 | export { Provider, connectAdvanced, ReactReduxContext, connect } 7 | -------------------------------------------------------------------------------- /react-redux/utils/isPlainObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 和redux的工具方法一致,判断当前对象是否是纯对象 3 | * 一般用来判断action 4 | * () => { 5 | * type: string, 6 | * payload: {} 7 | * } 8 | * @param {*} obj 9 | */ 10 | export default function isPlainObject(obj) { 11 | if (typeof obj !== "object" || obj === null) return false; 12 | 13 | let proto = Object.getPrototypeOf(obj); 14 | if (proto === null) return true; 15 | 16 | let baseProto = proto; 17 | while (Object.getPrototypeOf(baseProto) !== null) { 18 | baseProto = Object.getPrototypeOf(baseProto); 19 | } 20 | 21 | return proto === baseProto; 22 | } 23 | -------------------------------------------------------------------------------- /react-redux/utils/shallowEqual.js: -------------------------------------------------------------------------------- 1 | const hasOwn = Object.prototype.hasOwnProperty; 2 | 3 | /** 4 | * 判断两个对象是否相等且是否同一个引用地址 5 | */ 6 | function is(x, y) { 7 | if (x === y) { 8 | return x !== 0 || y !== 0 || 1 / x === 1 / y; 9 | } else { 10 | return x !== x && y !== y; 11 | } 12 | } 13 | 14 | export default function shallowEqual(objA, objB) { 15 | if (is(objA, objB)) return true; 16 | 17 | if ( 18 | typeof objA !== "object" || 19 | objA === null || 20 | typeof objB !== "object" || 21 | objB === null 22 | ) { 23 | return false; 24 | } 25 | 26 | const keysA = Object.keys(objA); 27 | const keysB = Object.keys(objB); 28 | 29 | if (keysA.length !== keysB.length) return false; 30 | 31 | for (let i = 0; i < keysA.length; i++) { 32 | if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { 33 | return false; 34 | } 35 | } 36 | 37 | return true; 38 | } 39 | -------------------------------------------------------------------------------- /react-redux/utils/verifyPlainObject.js: -------------------------------------------------------------------------------- 1 | import isPlainObject from "./isPlainObject"; 2 | import warning from "./warning"; 3 | 4 | /** 5 | * 判定纯对象异常方法 6 | * 在redux中,没有这样的优化 7 | * 值得学习 8 | * @param {*} value 9 | * @param {*} displayName 10 | * @param {*} methodName 11 | */ 12 | export default function verifyPlainObject(value, displayName, methodName) { 13 | if (!isPlainObject(value)) { 14 | warning( 15 | `${methodName}() in ${displayName} must return a plain object. Instead received ${value}.` 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /react-redux/utils/warning.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints a warning in the console if it exists. 3 | * 4 | * @param {String} message The warning message. 5 | * @returns {void} 6 | */ 7 | export default function warning(message) { 8 | /* eslint-disable no-console */ 9 | if (typeof console !== 'undefined' && typeof console.error === 'function') { 10 | console.error(message) 11 | } 12 | /* eslint-enable no-console */ 13 | try { 14 | // This error was thrown as a convenience so that if you enable 15 | // "break on all exceptions" in your console, 16 | // it would pause the execution at this line. 17 | throw new Error(message) 18 | /* eslint-disable no-empty */ 19 | } catch (e) {} 20 | /* eslint-enable no-empty */ 21 | } 22 | -------------------------------------------------------------------------------- /react-redux/utils/wrapActionCreators.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from "redux"; 2 | 3 | /** 4 | * 这个方法类似 redux-thunk ,它是一个高阶函数,返回值是一个高阶函数,接收的参数就是 action 的执行方法 dispatch 5 | * @param {*} actionCreators 生成 action 方法 6 | * let actionCreators = () => { 7 | * type: string, 8 | * payload: {} 9 | * } 10 | */ 11 | export default function wrapActionCreators(actionCreators) { 12 | return dispatch => bindActionCreators(actionCreators, dispatch); 13 | } 14 | -------------------------------------------------------------------------------- /redux-source/applyMiddleware.js: -------------------------------------------------------------------------------- 1 | import compose from "./compose"; 2 | 3 | /** 4 | * 中间件模块,当createStore方法有enhancer参数时,实现中间件模块 5 | * 6 | * @param {...any} middlewares 7 | */ 8 | export default function applyMiddleware(...middlewares) { 9 | return createStore => (...args) => { 10 | const store = createStore(...args); 11 | let dispatch = () => { 12 | throw new Error( 13 | "Dispatching while constructing your middleware is not allowed. " + 14 | "Other middleware would not be applied to this dispatch." 15 | ); 16 | }; 17 | // 这是一个简易的 store 对象,关键是在中间件内部,会用到这个dispatch和state 18 | const middlewareAPI = { 19 | getState: store.getState, 20 | // 传入一个初始 dispatch 21 | dispatch: (...args) => dispatch(...args) 22 | }; 23 | const chain = middlewares.map(middleware => middleware(middlewareAPI)); 24 | dispatch = compose(...chain)(store.dispatch); 25 | 26 | return { 27 | ...store, 28 | dispatch 29 | }; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /redux-source/bindActionCreators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 函数式的高级写法 3 | * @param {*} actionCreator 4 | * @param {*} dispatch 5 | */ 6 | // const bindActionCreator = (actionCreator, dispatch) => (...args) => dispatch(actionCreator.apply(this, args)); 7 | 8 | /** 9 | * 这个函数的主要作用就是返回一个函数,当我们调用返回的这个函数的时候, 10 | * 就会自动的dispatch对应的action 11 | * @param {*} actionCreator 12 | * @param {*} dispatch 13 | */ 14 | function bindActionCreator(actionCreator, dispatch) { 15 | return function() { 16 | return dispatch(actionCreator.apply(this, arguments)); 17 | }; 18 | } 19 | 20 | /** 21 | * bindActionCreators是redux提供的一个辅助方法, 22 | * 能够让我们以方法的形式来调用action。同时,自动dispatch对应的action。 23 | * @param {Function or Object} actionCreators 24 | * @param {Function} dispatch 25 | */ 26 | export default function bindActionCreators(actionCreators, dispatch) { 27 | if (typeof actionCreators === "function") { 28 | return bindActionCreator(actionCreators, dispatch); 29 | } 30 | 31 | if (typeof actionCreators !== "object" || actionCreators === null) { 32 | throw new Error( 33 | `bindActionCreators expected an object or a function, instead received ${ 34 | actionCreators === null ? "null" : typeof actionCreators 35 | }. ` + 36 | `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` 37 | ); 38 | } 39 | 40 | const boundActionCreators = {}; 41 | for (const key in actionCreators) { 42 | // 单个action生成方法 43 | const actionCreator = actionCreators[key]; 44 | if (typeof actionCreator === "function") { 45 | boundActionCreators[key] = bindActionCreator(actionCreator, dispatch); 46 | } 47 | } 48 | return boundActionCreators; 49 | } 50 | -------------------------------------------------------------------------------- /redux-source/combineReducers.js: -------------------------------------------------------------------------------- 1 | import ActionTypes from "./utils/actionTypes"; 2 | import warning from "./utils/warning"; 3 | import isPlainObject from "./utils/isPlainObject"; 4 | 5 | /** 6 | * 经过reducer,返回值不能为undefined 7 | * @param {*} key state树中对应的reducer键值 8 | * @param {*} action 9 | */ 10 | function getUndefinedStateErrorMessage(key, action) { 11 | const actionType = action && action.type; 12 | const actionDescription = 13 | (actionType && `action "${String(actionType)}"`) || "an action"; 14 | 15 | return ( 16 | `Given ${actionDescription}, reducer "${key}" returned undefined. ` + 17 | `To ignore an action, you must explicitly return the previous state. ` + 18 | `If you want this reducer to hold no value, you can return null instead of undefined.` 19 | ); 20 | } 21 | 22 | /** 23 | * 24 | * @param {state} inputState 当前state树 25 | * @param {*} reducers 26 | * @param {*} action 27 | * @param {*} unexpectedKeyCache 28 | */ 29 | function getUnexpectedStateShapeWarningMessage( 30 | inputState, 31 | reducers, 32 | action, 33 | unexpectedKeyCache 34 | ) { 35 | const reducerKeys = Object.keys(reducers); 36 | const argumentName = 37 | action && action.type === ActionTypes.INIT 38 | ? "preloadedState argument passed to createStore" 39 | : "previous state received by the reducer"; 40 | 41 | if (reducerKeys.length === 0) { 42 | return ( 43 | "Store does not have a valid reducer. Make sure the argument passed " + 44 | "to combineReducers is an object whose values are reducers." 45 | ); 46 | } 47 | // state是不是纯对象 48 | if (!isPlainObject(inputState)) { 49 | return ( 50 | `The ${argumentName} has unexpected type of "` + 51 | {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + 52 | `". Expected argument to be an object with the following ` + 53 | `keys: "${reducerKeys.join('", "')}"` 54 | ); 55 | } 56 | // 过滤出state树中无法识别的,无定义reducer的key 57 | const unexpectedKeys = Object.keys(inputState).filter( 58 | // 判定条件:reducers中不存在 这个 key 且 unexpectedKeyCache变量中也不存在 59 | key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key] 60 | ); 61 | 62 | unexpectedKeys.forEach(key => { 63 | // 此处要注意下,这个地方是内部函数的一个副作用,在此处赋值,会对函数外的引用变量发生改变 64 | // 对,又是闭包 65 | unexpectedKeyCache[key] = true; 66 | }); 67 | 68 | if (action && action.type === ActionTypes.REPLACE) return; 69 | 70 | if (unexpectedKeys.length > 0) { 71 | return ( 72 | `Unexpected ${unexpectedKeys.length > 1 ? "keys" : "key"} ` + 73 | `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` + 74 | `Expected to find one of the known reducer keys instead: ` + 75 | `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.` 76 | ); 77 | } 78 | } 79 | 80 | /** 81 | * 确认reducer是否是合法的reducer,即返回的state是不是undefined,如果是undefined,则是非法reducer 82 | * 83 | * 1. 在初始化阶段,reducer 传入的 state 值是 undefined,此时,需要返回初始state,且初始state不能为undefined 84 | * 2. 当传入不认识的 actionType 时, reducer(state, {type}) 返回的不能是undefined 85 | * 3. redux/ 这个 namespace 下的action 不应该做处理,直接返回 currentState 就行 (谁运气这么差会去用这种actionType...) 86 | */ 87 | function assertReducerShape(reducers) { 88 | Object.keys(reducers).forEach(key => { 89 | const reducer = reducers[key]; 90 | const initialState = reducer(undefined, { type: ActionTypes.INIT }); 91 | 92 | if (typeof initialState === "undefined") { 93 | throw new Error( 94 | `Reducer "${key}" returned undefined during initialization. ` + 95 | `If the state passed to the reducer is undefined, you must ` + 96 | `explicitly return the initial state. The initial state may ` + 97 | `not be undefined. If you don't want to set a value for this reducer, ` + 98 | `you can use null instead of undefined.` 99 | ); 100 | } 101 | 102 | if ( 103 | typeof reducer(undefined, { 104 | type: ActionTypes.PROBE_UNKNOWN_ACTION() 105 | }) === "undefined" 106 | ) { 107 | throw new Error( 108 | `Reducer "${key}" returned undefined when probed with a random type. ` + 109 | `Don't try to handle ${ 110 | ActionTypes.INIT 111 | } or other actions in "redux/*" ` + 112 | `namespace. They are considered private. Instead, you must return the ` + 113 | `current state for any unknown actions, unless it is undefined, ` + 114 | `in which case you must return the initial state, regardless of the ` + 115 | `action type. The initial state may not be undefined, but can be null.` 116 | ); 117 | } 118 | }); 119 | } 120 | 121 | /** 122 | * 合并reducer 123 | * 接收参数: 124 | * { 125 | * todo: (state, action) => { 126 | * ... 127 | * return state; 128 | * }, 129 | * ... 130 | * } 131 | * @param {*} reducers 132 | * 133 | * 返回值是一个纯函数,接收当前 state树和action,这个纯函数返回更新后的 state 树 134 | * @return (state, action) => state 135 | */ 136 | export default function combineReducers(reducers) { 137 | const reducerKeys = Object.keys(reducers); 138 | const finalReducers = {}; 139 | for (let i = 0; i < reducerKeys.length; i++) { 140 | const key = reducerKeys[i]; 141 | 142 | if (process.env.NODE_ENV !== "production") { 143 | if (typeof reducers[key] === "undefined") { 144 | warning(`No reducer provided for key "${key}"`); 145 | } 146 | } 147 | // 把非function的reducer过滤掉,存入变量finalReducers 148 | if (typeof reducers[key] === "function") { 149 | finalReducers[key] = reducers[key]; 150 | } 151 | } 152 | const finalReducerKeys = Object.keys(finalReducers); 153 | 154 | // 定义变量存储 state树中的无效key(reducer中不存在) 155 | let unexpectedKeyCache; 156 | if (process.env.NODE_ENV !== "production") { 157 | unexpectedKeyCache = {}; 158 | } 159 | 160 | let shapeAssertionError; 161 | try { 162 | // 校验reducer是否合法 163 | assertReducerShape(finalReducers); 164 | } catch (e) { 165 | shapeAssertionError = e; 166 | } 167 | 168 | /** 169 | * 最终返回的函数,接收两个参数,在createStore方法中会调用,state为当前树,action为传递的操作 170 | * @return state 经过 action 改变后的 state 树 171 | */ 172 | return function combination(state = {}, action) { 173 | // 抛出不合法的 reducer 校验 174 | if (shapeAssertionError) { 175 | throw shapeAssertionError; 176 | } 177 | 178 | if (process.env.NODE_ENV !== "production") { 179 | const warningMessage = getUnexpectedStateShapeWarningMessage( 180 | state, 181 | finalReducers, 182 | action, 183 | unexpectedKeyCache 184 | ); 185 | if (warningMessage) { 186 | warning(warningMessage); 187 | } 188 | } 189 | // 是否有改变判定字段 190 | let hasChanged = false; 191 | const nextState = {}; 192 | for (let i = 0; i < finalReducerKeys.length; i++) { 193 | // state树中的各个reducer对应的值 194 | const key = finalReducerKeys[i]; 195 | // 单个reducer 196 | const reducer = finalReducers[key]; 197 | // 此处是state树中对应的状态值,其key,就是reducers传入combineRefucers所对应各个reducer的key值 198 | const previousStateForKey = state[key]; 199 | // 经过reducer,返回改变后的模块state值 200 | const nextStateForKey = reducer(previousStateForKey, action); 201 | // reducer的返回值不能为undefined 202 | if (typeof nextStateForKey === "undefined") { 203 | const errorMessage = getUndefinedStateErrorMessage(key, action); 204 | throw new Error(errorMessage); 205 | } 206 | // 找到对应的key值,把改变后的模块state重新赋值 207 | nextState[key] = nextStateForKey; 208 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey; 209 | } 210 | return hasChanged ? nextState : state; 211 | }; 212 | } 213 | -------------------------------------------------------------------------------- /redux-source/compose.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 从右至左来组合多个函数 3 | * 最右边的参数可以接收多个参数,因为它将为由此产生的函数提供签名 4 | * 5 | * 从右至左把接收到的函数合成后的最终函数 6 | * compose(f,g,h) 类似于 (...args) => f(g(h(...args))) 7 | * 8 | * @param {...any} funcs 9 | */ 10 | export default function compose(...funcs) { 11 | if (funcs.length === 0) { 12 | return arg => arg; 13 | } 14 | 15 | if (funcs.length === 1) { 16 | return funcs[0]; 17 | } 18 | 19 | return funcs.reduce((a, b) => (...args) => a(b(...args))); 20 | } 21 | -------------------------------------------------------------------------------- /redux-source/createStore.js: -------------------------------------------------------------------------------- 1 | import $$observable from "symbol-observable"; 2 | 3 | import ActionTypes from "./utils/actionTypes"; 4 | import isPlainObject from "./utils/isPlainObject"; 5 | 6 | /** 7 | * 8 | * @param {Function} reducer 纯函数,接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。 9 | * 10 | * @param {any} [preloadedState] 参数是可选的, 用于设置 state 初始状态。 11 | * 12 | * @param {Function} [enhancer] 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。 13 | * 这与 middleware 相似,它也允许你通过复合函数改变 store 接口 14 | * 15 | * @returns {Store} A Redux store that lets you read the state, dispatch actions 16 | * and subscribe to changes. 17 | */ 18 | export default function createStore(reducer, preloadedState, enhancer) { 19 | /***** 参数校验阶段 *****/ 20 | // preloadedState和enhancer不能同时为function 21 | if ( 22 | (typeof preloadedState === "function" && typeof enhancer === "function") || 23 | (typeof enhancer === "function" && typeof arguments[3] === "function") 24 | ) { 25 | throw new Error( 26 | "It looks like you are passing several store enhancers to " + 27 | "createStore(). This is not supported. Instead, compose them " + 28 | "together to a single function." 29 | ); 30 | } 31 | 32 | /** 33 | * preloadedState是一个函数,没有enhancer参数时,preloadedState赋值给enhancer 34 | */ 35 | if (typeof preloadedState === "function" && typeof enhancer === "undefined") { 36 | enhancer = preloadedState; 37 | preloadedState = undefined; 38 | } 39 | 40 | // enhancer存在且必须是一个function 41 | if (typeof enhancer !== "undefined") { 42 | if (typeof enhancer !== "function") { 43 | throw new Error("Expected the enhancer to be a function."); 44 | } 45 | 46 | /** 47 | * 此种情况创建的store如下 48 | * const store = createStore(rootReducer, applyMiddleware(...middlewares)) 49 | * 或者 createStore(reducer,preState,applyMiddleware(thunk)) 50 | * 由此可见 enhancer 为 applyMiddleware 执行时返回的高阶函数 51 | * 此后就进入了 applyMiddleware 模块内部的逻辑 52 | */ 53 | return enhancer(createStore)(reducer, preloadedState); 54 | } 55 | 56 | if (typeof reducer !== "function") { 57 | throw new Error("Expected the reducer to be a function."); 58 | } 59 | 60 | // 保存了当前的reducer函数,该reducer函数可以被动态替换掉 61 | let currentReducer = reducer; 62 | // 保存了当前的state数据 63 | let currentState = preloadedState; 64 | // 保存了当前注册的函数列表 65 | let currentListeners = []; 66 | // 获取当前监听器的一个副本(相同的引用) 67 | let nextListeners = currentListeners; 68 | // 是否正在dispatch一个action 69 | let isDispatching = false; 70 | 71 | /** 72 | * 如果nextListeners和currentListeners具有相同的引用, 73 | * 则获取一份当前事件监听器集合的一个副本保存到nextListeners中 74 | * [].slice(),克隆数组的方法,这两个数组就是不会同一个引用了 75 | */ 76 | function ensureCanMutateNextListeners() { 77 | if (nextListeners === currentListeners) { 78 | nextListeners = currentListeners.slice(); 79 | } 80 | } 81 | 82 | /** 83 | * 返回当前state树,以获取当前状态 84 | */ 85 | function getState() { 86 | // dispatch 方法未触发 action 前,无法获取当前 state 树 87 | if (isDispatching) { 88 | throw new Error( 89 | "You may not call store.getState() while the reducer is executing. " + 90 | "The reducer has already received the state as an argument. " + 91 | "Pass it down from the top reducer instead of reading it from the store." 92 | ); 93 | } 94 | return currentState; 95 | } 96 | 97 | /** 98 | * 定义监听器,将回调函数存入到内存栈中 99 | * @param {*} listener 监听者 100 | */ 101 | function subscribe(listener) { 102 | if (typeof listener !== "function") { 103 | throw new Error("Expected the listener to be a function."); 104 | } 105 | // 如果redux正在触发action,则会抛出异常 106 | if (isDispatching) { 107 | throw new Error( 108 | "You may not call store.subscribe() while the reducer is executing. " + 109 | "If you would like to be notified after the store has been updated, subscribe from a " + 110 | "component and invoke store.getState() in the callback to access the latest state. " + 111 | "See https://redux.js.org/api-reference/store#subscribe(listener) for more details." 112 | ); 113 | } 114 | // 当调用退订方式时,会调用这个字段判别是否在监听者列表中 115 | let isSubscribed = true; 116 | // 调用这个方法把当前事件监听器列表克隆到nextListeners列表 117 | ensureCanMutateNextListeners(); 118 | // 压栈传入的监听者 119 | nextListeners.push(listener); 120 | 121 | /** 122 | * 取消监听方法 123 | * let listener = store.subscribe(() => {}) 124 | * listener.unsubscribe() 125 | */ 126 | return function unsubscribe() { 127 | if (!isSubscribed) { 128 | return; 129 | } 130 | 131 | if (isDispatching) { 132 | throw new Error( 133 | "You may not unsubscribe from a store listener while the reducer is executing. " + 134 | "See https://redux.js.org/api-reference/store#subscribe(listener) for more details." 135 | ); 136 | } 137 | // 字段锁 138 | isSubscribed = false; 139 | 140 | // 此处调用这个方法的意义就是确保 当前监听列表以及副本的数据一致性 141 | ensureCanMutateNextListeners(); 142 | // 在 nextListeners 列表中排除这个监听列表 143 | const index = nextListeners.indexOf(listener); 144 | // 从事件监听器集合中删除这个事件监听器 145 | nextListeners.splice(index, 1); 146 | }; 147 | } 148 | 149 | /** 150 | * 核心方法 151 | * 触发action的函数:每次触发一个action,currentListeners中的所有函数都要执行一遍 152 | */ 153 | function dispatch(action) { 154 | // 通过 isPlainObject方法判断的对象,具体实现可以查看 utils 工具函数中对这个方法的解释 155 | if (!isPlainObject(action)) { 156 | throw new Error( 157 | "Actions must be plain objects. " + 158 | "Use custom middleware for async actions." 159 | ); 160 | } 161 | // action对象按照一定的格式,必须包含type字段 162 | if (typeof action.type === "undefined") { 163 | throw new Error( 164 | 'Actions may not have an undefined "type" property. ' + 165 | "Have you misspelled a constant?" 166 | ); 167 | } 168 | 169 | if (isDispatching) { 170 | throw new Error("Reducers may not dispatch actions."); 171 | } 172 | 173 | // isDispatching 等待currentReducer的返回值之后,设置状态为false 174 | try { 175 | // 巧用闭包 176 | isDispatching = true; 177 | // reducer函数经过action后返回新的state树 178 | // 注意,这里的 state 是整个应用的 state 树 179 | currentState = currentReducer(currentState, action); 180 | } finally { 181 | // reducer函数执行完成后,将isDispatching恢复成false,方便下次action的触发 182 | isDispatching = false; 183 | } 184 | 185 | // 定义监听者列表 186 | // nextListeners: 保存这次dispatch后,需要触发的所有事件监听器的列表 187 | // currentListeners: 保存一份nextListeners列表的副本 188 | const listeners = (currentListeners = nextListeners); 189 | for (let i = 0; i < listeners.length; i++) { 190 | const listener = listeners[i]; 191 | // 调用所有的事件监听器 192 | listener(); 193 | } 194 | 195 | // 正因为有这个返回值,才会有后来的中间件机制 196 | return action; 197 | } 198 | 199 | /** 200 | * 替换 `store` 当前用来计算 `state` 的 `reducer` 201 | * 适用场景 202 | * @param {*} nextReducer 新的reducer 203 | */ 204 | function replaceReducer(nextReducer) { 205 | if (typeof nextReducer !== "function") { 206 | throw new Error("Expected the nextReducer to be a function."); 207 | } 208 | // 函数副作用,替换了当前使用的 currentReducer 209 | currentReducer = nextReducer; 210 | // 替换结束后,重新初始化 211 | dispatch({ type: ActionTypes.REPLACE }); 212 | } 213 | 214 | /** 215 | * 一个及其简单的 observable 实现,可以学习 216 | * source$ = store.observable() 217 | * source$.subscribe({ 218 | * next: (state) => { 219 | * console.log(state); 220 | * } 221 | * }) 222 | * 223 | */ 224 | function observable() { 225 | // 首先将 subscribe 方法引用赋值于 outerSubscribe变量 226 | const outerSubscribe = subscribe; 227 | return { 228 | /** 229 | * 订阅方法 230 | * @param {*} observer 订阅者 231 | * observer 包含一个next方法,该方法参数传递当前 state 树 232 | */ 233 | subscribe(observer) { 234 | if (typeof observer !== "object" || observer === null) { 235 | throw new TypeError("Expected the observer to be an object."); 236 | } 237 | 238 | function observeState() { 239 | if (observer.next) { 240 | observer.next(getState()); 241 | } 242 | } 243 | 244 | observeState(); 245 | // 提供退订方法 246 | const unsubscribe = outerSubscribe(observeState); 247 | return { unsubscribe }; 248 | }, 249 | 250 | [$$observable]() { 251 | return this; 252 | } 253 | }; 254 | } 255 | 256 | /** 257 | * store对象创建的时候,内部会主动调用dispatch({ type: ActionTypes.INIT })来对内部状态进行初始化。 258 | * 同时,reducer就会被调用进行初始化。 259 | */ 260 | dispatch({ type: ActionTypes.INIT }); 261 | 262 | return { 263 | dispatch, 264 | subscribe, 265 | getState, 266 | replaceReducer, 267 | [$$observable]: observable 268 | }; 269 | } 270 | -------------------------------------------------------------------------------- /redux-source/index.js: -------------------------------------------------------------------------------- 1 | import createStore from "./createStore"; 2 | import combineReducers from "./combineReducers"; 3 | import bindActionCreators from "./bindActionCreators"; 4 | import applyMiddleware from "./applyMiddleware"; 5 | import compose from "./compose"; 6 | import warning from "./utils/warning"; 7 | import __DO_NOT_USE__ActionTypes from "./utils/actionTypes"; 8 | 9 | /** 10 | * 定义了一个空方法 isCrushed(), 11 | * 主要是验证在非生产环境下 Redux 是否被压缩(因为在生产环境下,空方法会被kill的,那么 (isCrushed.name !== 'isCrushed') 就是 true), 12 | * 如果被压缩会给开发者一个 warn 提示)。 13 | */ 14 | function isCrushed() {} 15 | 16 | if ( 17 | process.env.NODE_ENV !== "production" && 18 | typeof isCrushed.name === "string" && 19 | isCrushed.name !== "isCrushed" 20 | ) { 21 | warning( 22 | 'You are currently using minified code outside of NODE_ENV === "production". ' + 23 | "This means that you are running a slower development build of Redux. " + 24 | "You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify " + 25 | "or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) " + 26 | "to ensure you have the correct code for your production build." 27 | ); 28 | } 29 | 30 | export { 31 | createStore, 32 | combineReducers, 33 | bindActionCreators, 34 | applyMiddleware, 35 | compose, 36 | __DO_NOT_USE__ActionTypes 37 | }; 38 | -------------------------------------------------------------------------------- /redux-source/utils/actionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These are private action types reserved by Redux. 3 | * For any unknown actions, you must return the current state. 4 | * If the current state is undefined, you must return the initial state. 5 | * Do not reference these action types directly in your code. 6 | */ 7 | 8 | /** 9 | * 随机字符串,36进制,取6位 10 | */ 11 | const randomString = () => 12 | Math.random() 13 | .toString(36) 14 | .substring(7) 15 | .split("") 16 | .join("."); 17 | 18 | const ActionTypes = { 19 | INIT: `@@redux/INIT${randomString()}`, 20 | REPLACE: `@@redux/REPLACE${randomString()}`, 21 | PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}` 22 | }; 23 | 24 | export default ActionTypes; 25 | -------------------------------------------------------------------------------- /redux-source/utils/isPlainObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断一个函数是否是简单对象,可以理解为简单的json对象,不是promise 3 | * @param {any} obj 目标对象 4 | * @returns {boolean} 5 | */ 6 | export default function isPlainObject(obj) { 7 | if (typeof obj !== "object" || obj === null) return false; 8 | 9 | let proto = obj; 10 | // 追溯这个对象的原型链,直到尽头null 11 | while (Object.getPrototypeOf(proto) !== null) { 12 | proto = Object.getPrototypeOf(proto); 13 | } 14 | // 如果这个一个对象的原型链上的开始和结尾都相同,则推断是 plain object 15 | return Object.getPrototypeOf(obj) === proto; 16 | } 17 | -------------------------------------------------------------------------------- /redux-source/utils/warning.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints a warning in the console if it exists. 3 | * 4 | * @param {String} message The warning message. 5 | * @returns {void} 6 | */ 7 | export default function warning(message) { 8 | /* eslint-disable no-console */ 9 | if (typeof console !== 'undefined' && typeof console.error === 'function') { 10 | console.error(message) 11 | } 12 | /* eslint-enable no-console */ 13 | try { 14 | // This error was thrown as a convenience so that if you enable 15 | // "break on all exceptions" in your console, 16 | // it would pause the execution at this line. 17 | throw new Error(message) 18 | } catch (e) {} // eslint-disable-line no-empty 19 | } 20 | --------------------------------------------------------------------------------