Spaces:
Running
Running
File size: 7,625 Bytes
e8a57cb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | import { assign } from './util';
import { diff, commitRoot } from './diff/index';
import options from './options';
import { Fragment } from './create-element';
import { MODE_HYDRATE, NULL } from './constants';
/**
* Base Component class. Provides `setState()` and `forceUpdate()`, which
* trigger rendering
* @param {object} props The initial component props
* @param {object} context The initial context from parent components'
* getChildContext
*/
export function BaseComponent(props, context) {
this.props = props;
this.context = context;
}
/**
* Update component state and schedule a re-render.
* @this {import('./internal').Component}
* @param {object | ((s: object, p: object) => object)} update A hash of state
* properties to update with new values or a function that given the current
* state and props returns a new partial state
* @param {() => void} [callback] A function to be called once component state is
* updated
*/
BaseComponent.prototype.setState = function (update, callback) {
// only clone state when copying to nextState the first time.
let s;
if (this._nextState != NULL && this._nextState != this.state) {
s = this._nextState;
} else {
s = this._nextState = assign({}, this.state);
}
if (typeof update == 'function') {
// Some libraries like `immer` mark the current state as readonly,
// preventing us from mutating it, so we need to clone it. See #2716
update = update(assign({}, s), this.props);
}
if (update) {
assign(s, update);
}
// Skip update if updater function returned null
if (update == NULL) return;
if (this._vnode) {
if (callback) {
this._stateCallbacks.push(callback);
}
enqueueRender(this);
}
};
/**
* Immediately perform a synchronous re-render of the component
* @this {import('./internal').Component}
* @param {() => void} [callback] A function to be called after component is
* re-rendered
*/
BaseComponent.prototype.forceUpdate = function (callback) {
if (this._vnode) {
// Set render mode so that we can differentiate where the render request
// is coming from. We need this because forceUpdate should never call
// shouldComponentUpdate
this._force = true;
if (callback) this._renderCallbacks.push(callback);
enqueueRender(this);
}
};
/**
* Accepts `props` and `state`, and returns a new Virtual DOM tree to build.
* Virtual DOM is generally constructed via [JSX](https://jasonformat.com/wtf-is-jsx).
* @param {object} props Props (eg: JSX attributes) received from parent
* element/component
* @param {object} state The component's current state
* @param {object} context Context object, as returned by the nearest
* ancestor's `getChildContext()`
* @returns {ComponentChildren | void}
*/
BaseComponent.prototype.render = Fragment;
/**
* @param {import('./internal').VNode} vnode
* @param {number | null} [childIndex]
*/
export function getDomSibling(vnode, childIndex) {
if (childIndex == NULL) {
// Use childIndex==null as a signal to resume the search from the vnode's sibling
return vnode._parent
? getDomSibling(vnode._parent, vnode._index + 1)
: NULL;
}
let sibling;
for (; childIndex < vnode._children.length; childIndex++) {
sibling = vnode._children[childIndex];
if (sibling != NULL && sibling._dom != NULL) {
// Since updateParentDomPointers keeps _dom pointer correct,
// we can rely on _dom to tell us if this subtree contains a
// rendered DOM node, and what the first rendered DOM node is
return sibling._dom;
}
}
// If we get here, we have not found a DOM node in this vnode's children.
// We must resume from this vnode's sibling (in it's parent _children array)
// Only climb up and search the parent if we aren't searching through a DOM
// VNode (meaning we reached the DOM parent of the original vnode that began
// the search)
return typeof vnode.type == 'function' ? getDomSibling(vnode) : NULL;
}
/**
* Trigger in-place re-rendering of a component.
* @param {import('./internal').Component} component The component to rerender
*/
function renderComponent(component) {
if (component._parentDom && component._dirty) {
let oldVNode = component._vnode,
oldDom = oldVNode._dom,
commitQueue = [],
refQueue = [],
newVNode = assign({}, oldVNode);
newVNode._original = oldVNode._original + 1;
if (options.vnode) options.vnode(newVNode);
diff(
component._parentDom,
newVNode,
oldVNode,
component._globalContext,
component._parentDom.namespaceURI,
oldVNode._flags & MODE_HYDRATE ? [oldDom] : NULL,
commitQueue,
oldDom == NULL ? getDomSibling(oldVNode) : oldDom,
!!(oldVNode._flags & MODE_HYDRATE),
refQueue
);
newVNode._original = oldVNode._original;
newVNode._parent._children[newVNode._index] = newVNode;
commitRoot(commitQueue, newVNode, refQueue);
oldVNode._dom = oldVNode._parent = null;
if (newVNode._dom != oldDom) {
updateParentDomPointers(newVNode);
}
}
}
/**
* @param {import('./internal').VNode} vnode
*/
function updateParentDomPointers(vnode) {
if ((vnode = vnode._parent) != NULL && vnode._component != NULL) {
vnode._dom = vnode._component.base = NULL;
vnode._children.some(child => {
if (child != NULL && child._dom != NULL) {
return (vnode._dom = vnode._component.base = child._dom);
}
});
return updateParentDomPointers(vnode);
}
}
/**
* The render queue
* @type {Array<import('./internal').Component>}
*/
let rerenderQueue = [];
/*
* The value of `Component.debounce` must asynchronously invoke the passed in callback. It is
* important that contributors to Preact can consistently reason about what calls to `setState`, etc.
* do, and when their effects will be applied. See the links below for some further reading on designing
* asynchronous APIs.
* * [Designing APIs for Asynchrony](https://blog.izs.me/2013/08/designing-apis-for-asynchrony)
* * [Callbacks synchronous and asynchronous](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)
*/
let prevDebounce;
const defer =
typeof Promise == 'function'
? Promise.prototype.then.bind(Promise.resolve())
: setTimeout;
/**
* Enqueue a rerender of a component
* @param {import('./internal').Component} c The component to rerender
*/
export function enqueueRender(c) {
if (
(!c._dirty &&
(c._dirty = true) &&
rerenderQueue.push(c) &&
!process._rerenderCount++) ||
prevDebounce != options.debounceRendering
) {
prevDebounce = options.debounceRendering;
(prevDebounce || defer)(process);
}
}
/**
* @param {import('./internal').Component} a
* @param {import('./internal').Component} b
*/
const depthSort = (a, b) => a._vnode._depth - b._vnode._depth;
/** Flush the render queue by rerendering all queued components */
function process() {
try {
let c,
l = 1;
// Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary
// process() calls from getting scheduled while `queue` is still being consumed.
while (rerenderQueue.length) {
// Keep the rerender queue sorted by (depth, insertion order). The queue
// will initially be sorted on the first iteration only if it has more than 1 item.
//
// New items can be added to the queue e.g. when rerendering a provider, so we want to
// keep the order from top to bottom with those new items so we can handle them in a
// single pass
if (rerenderQueue.length > l) {
rerenderQueue.sort(depthSort);
}
c = rerenderQueue.shift();
l = rerenderQueue.length;
renderComponent(c);
}
} finally {
rerenderQueue.length = process._rerenderCount = 0;
}
}
process._rerenderCount = 0;
|