Skip to content

Commit

Permalink
Merge branch 'main' into benchmarks-repo
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewiggins committed Mar 29, 2024
2 parents ba4e877 + d3d57db commit 21f018b
Show file tree
Hide file tree
Showing 17 changed files with 223 additions and 98 deletions.
16 changes: 9 additions & 7 deletions compat/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,16 @@ declare namespace React {
) => preact.VNode<any>;
export function isValidElement(element: any): boolean;
export function isFragment(element: any): boolean;
export function isMemo(element: any): boolean;
export function findDOMNode(
component: preact.Component | Element
): Element | null;

export abstract class PureComponent<P = {}, S = {}, SS = any> extends preact.Component<
P,
S
> {
export abstract class PureComponent<
P = {},
S = {},
SS = any
> extends preact.Component<P, S> {
isPureReactComponent: boolean;
}

Expand Down Expand Up @@ -174,9 +176,9 @@ declare namespace React {

export type ComponentPropsWithRef<
C extends ComponentType<any> | keyof JSXInternal.IntrinsicElements
> = C extends (new(props: infer P) => Component<any, any>)
? PropsWithoutRef<P> & RefAttributes<InstanceType<C>>
: ComponentProps<C>;
> = C extends new (props: infer P) => Component<any, any>
? PropsWithoutRef<P> & RefAttributes<InstanceType<C>>
: ComponentProps<C>;

export function flushSync<R>(fn: () => R): R;
export function flushSync<A, R>(fn: (a: A) => R, a: A): R;
Expand Down
17 changes: 17 additions & 0 deletions compat/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ function isFragment(element) {
return isValidElement(element) && element.type === Fragment;
}

/**
* Check if the passed element is a Memo node.
* @param {*} element The element to check
* @returns {boolean}
*/
function isMemo(element) {
return (
!!element &&
!!element.displayName &&
(typeof element.displayName === 'string' ||
element.displayName instanceof String) &&
element.displayName.startsWith('Memo(')
);
}

/**
* Wrap `cloneElement` to abort if the passed element is not a valid element and apply
* all vnode normalizations.
Expand Down Expand Up @@ -215,6 +230,7 @@ export {
Fragment,
isValidElement,
isFragment,
isMemo,
findDOMNode,
Component,
PureComponent,
Expand Down Expand Up @@ -263,6 +279,7 @@ export default {
isValidElement,
isElement,
isFragment,
isMemo,
findDOMNode,
Component,
PureComponent,
Expand Down
37 changes: 37 additions & 0 deletions compat/test/browser/isMemo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createElement as preactCreateElement, Fragment } from 'preact';
import React, { createElement, isMemo, memo } from 'preact/compat';

describe('isMemo', () => {
it('should check return false for invalid arguments', () => {
expect(isMemo(null)).to.equal(false);
expect(isMemo(false)).to.equal(false);
expect(isMemo(true)).to.equal(false);
expect(isMemo('foo')).to.equal(false);
expect(isMemo(123)).to.equal(false);
expect(isMemo([])).to.equal(false);
expect(isMemo({})).to.equal(false);
});

it('should detect a preact memo', () => {
function Foo() {
return <h1>Hello World</h1>;
}
let App = memo(Foo);
expect(isMemo(App)).to.equal(true);
});

it('should not detect a normal element', () => {
function Foo() {
return <h1>Hello World</h1>;
}
expect(isMemo(Foo)).to.equal(false);
});

it('should detect a preact vnode as false', () => {
expect(isMemo(preactCreateElement(Fragment, {}))).to.equal(false);
});

it('should detect a compat vnode as false', () => {
expect(isMemo(React.createElement(Fragment, {}))).to.equal(false);
});
});
5 changes: 4 additions & 1 deletion compat/test/browser/useSyncExternalStore.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,10 @@ describe('useSyncExternalStore', () => {
await act(() => {
store.set(1);
});
assertLog([1, 1, 'Reset back to 0', 0, 0]);
// Preact logs differ from React here cuz of how we do rerendering. We
// rerender subtrees and then commit effects so Child2 never sees the
// update to 1 cuz Child1 rerenders and runs its layout effects first.
assertLog([1, /*1,*/ 'Reset back to 0', 0, 0]);
expect(container.textContent).to.equal('00');
});

Expand Down
2 changes: 1 addition & 1 deletion devtools/src/devtools.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { options, Fragment, Component } from 'preact';

export function initDevTools() {
if (typeof window != 'undefined' && window.__PREACT_DEVTOOLS__) {
window.__PREACT_DEVTOOLS__.attachPreact('10.19.6', options, {
window.__PREACT_DEVTOOLS__.attachPreact('10.20.1', options, {
Fragment,
Component
});
Expand Down
12 changes: 8 additions & 4 deletions hooks/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ import { ErrorInfo, PreactContext, Ref as PreactRef } from '../..';

type Inputs = ReadonlyArray<unknown>;

export type StateUpdater<S> = (value: S | ((prevState: S) => S)) => void;
export type Dispatch<A> = (value: A) => void;
export type StateUpdater<S> = S | ((prevState: S) => S);

/**
* Returns a stateful value, and a function to update it.
* @param initialState The initial value (or a function that returns the initial value)
*/
export function useState<S>(initialState: S | (() => S)): [S, StateUpdater<S>];
export function useState<S>(
initialState: S | (() => S)
): [S, Dispatch<StateUpdater<S>>];

export function useState<S = undefined>(): [
S | undefined,
StateUpdater<S | undefined>
Dispatch<StateUpdater<S | undefined>>
];

export type Reducer<S, A> = (prevState: S, action: A) => S;
export type Dispatch<A> = (action: A) => void;

/**
* An alternative to `useState`.
*
Expand Down
4 changes: 2 additions & 2 deletions hooks/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function getHookState(index, type) {

/**
* @template {unknown} S
* @param {import('./index').StateUpdater<S>} [initialState]
* @param {import('./index').Dispatch<import('./index').StateUpdater<S>>} [initialState]
* @returns {[S, (state: S) => void]}
*/
export function useState(initialState) {
Expand All @@ -179,7 +179,7 @@ export function useState(initialState) {
* @template {unknown} S
* @template {unknown} A
* @param {import('./index').Reducer<S, A>} reducer
* @param {import('./index').StateUpdater<S>} initialState
* @param {import('./index').Dispatch<import('./index').StateUpdater<S>>} initialState
* @param {(initialState: any) => void} [init]
* @returns {[ S, (state: S) => void ]}
*/
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "preact",
"amdName": "preact",
"version": "10.19.6",
"version": "10.20.1",
"private": false,
"description": "Fast 3kb React-compatible Virtual DOM library.",
"main": "dist/preact.js",
Expand Down
34 changes: 10 additions & 24 deletions src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { assign } from './util';
import { diff, commitRoot } from './diff/index';
import options from './options';
import { Fragment } from './create-element';
import { EMPTY_ARR, MODE_HYDRATE } from './constants';
import { MODE_HYDRATE } from './constants';

/**
* Base Component class. Provides `setState()` and `forceUpdate()`, which
Expand Down Expand Up @@ -120,22 +120,23 @@ export function getDomSibling(vnode, childIndex) {
* Trigger in-place re-rendering of a component.
* @param {Component} component The component to rerender
*/
function renderComponent(component, commitQueue, refQueue) {
function renderComponent(component) {
let oldVNode = component._vnode,
oldDom = oldVNode._dom,
parentDom = component._parentDom;
commitQueue = [],
refQueue = [];

if (parentDom) {
if (component._parentDom) {
const newVNode = assign({}, oldVNode);
newVNode._original = oldVNode._original + 1;
if (options.vnode) options.vnode(newVNode);

diff(
parentDom,
component._parentDom,
newVNode,
oldVNode,
component._globalContext,
parentDom.ownerSVGElement !== undefined,
component._parentDom.ownerSVGElement !== undefined,
oldVNode._flags & MODE_HYDRATE ? [oldDom] : null,
commitQueue,
oldDom == null ? getDomSibling(oldVNode) : oldDom,
Expand All @@ -145,14 +146,11 @@ function renderComponent(component, commitQueue, refQueue) {

newVNode._original = oldVNode._original;
newVNode._parent._children[newVNode._index] = newVNode;

newVNode._nextDom = undefined;
commitRoot(commitQueue, newVNode, refQueue);

if (newVNode._dom != oldDom) {
updateParentDomPointers(newVNode);
}

return newVNode;
}
}

Expand Down Expand Up @@ -222,33 +220,21 @@ const depthSort = (a, b) => a._vnode._depth - b._vnode._depth;
/** Flush the render queue by rerendering all queued components */
function process() {
let c;
let commitQueue = [];
let refQueue = [];
let root;
rerenderQueue.sort(depthSort);
// 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 ((c = rerenderQueue.shift())) {
if (c._dirty) {
let renderQueueLength = rerenderQueue.length;
root = renderComponent(c, commitQueue, refQueue) || root;
// If this WAS the last component in the queue, run commit callbacks *before* we exit the tight loop.
// This is required in order for `componentDidMount(){this.setState()}` to be batched into one flush.
// Otherwise, also run commit callbacks if the render queue was mutated.
if (renderQueueLength === 0 || rerenderQueue.length > renderQueueLength) {
commitRoot(commitQueue, root, refQueue);
refQueue.length = commitQueue.length = 0;
root = undefined;
renderComponent(c);
if (rerenderQueue.length > renderQueueLength) {
// When i.e. rerendering a provider additional new items can be injected, we want to
// keep the order from top to bottom with those new items so we can handle them in a
// single pass
rerenderQueue.sort(depthSort);
} else if (root) {
if (options._commit) options._commit(root, EMPTY_ARR);
}
}
}
if (root) commitRoot(commitQueue, root, refQueue);
process._rerenderCount = 0;
}

Expand Down
4 changes: 4 additions & 0 deletions src/diff/children.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ export function diffChildren(
childVNode._flags & INSERT_VNODE ||
oldVNode._children === childVNode._children
) {
if (!newDom && oldVNode._dom == oldDom) {
oldDom = getDomSibling(oldVNode);
}
oldDom = insert(childVNode, oldDom, parentDom);
} else if (
typeof childVNode.type == 'function' &&
Expand Down Expand Up @@ -241,6 +244,7 @@ function constructNewChildrenArray(newParentVNode, renderResult, oldChildren) {
if (oldVNode._dom == newParentVNode._nextDom) {
newParentVNode._nextDom = getDomSibling(oldVNode);
}

unmount(oldVNode, oldVNode, false);

// Explicitly nullify this position in oldChildren instead of just
Expand Down
7 changes: 4 additions & 3 deletions src/diff/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ export function diff(
* @param {VNode} root
*/
export function commitRoot(commitQueue, root, refQueue) {
root._nextDom = undefined;

for (let i = 0; i < refQueue.length; i++) {
applyRef(refQueue[i], refQueue[++i], refQueue[++i]);
}
Expand Down Expand Up @@ -577,7 +579,6 @@ export function unmount(vnode, parentVNode, skipRemove) {
}

r.base = r._parentDom = null;
vnode._component = undefined;
}

if ((r = vnode._children)) {
Expand All @@ -586,7 +587,7 @@ export function unmount(vnode, parentVNode, skipRemove) {
unmount(
r[i],
parentVNode,
skipRemove || typeof vnode.type !== 'function'
skipRemove || typeof vnode.type != 'function'
);
}
}
Expand All @@ -598,7 +599,7 @@ export function unmount(vnode, parentVNode, skipRemove) {

// Must be set to `undefined` to properly clean up `_nextDom`
// for which `null` is a valid value. See comment in `create-element.js`
vnode._parent = vnode._dom = vnode._nextDom = undefined;
vnode._component = vnode._parent = vnode._dom = vnode._nextDom = undefined;
}

/** The `.render()` method for a PFC backing instance. */
Expand Down
Loading

0 comments on commit 21f018b

Please sign in to comment.