/
githubmirror
/
preact
Обзор
Документация
Войти
/
githubmirror
/
preact
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
compat/src/render.js
412 строк
12 KB
Jovi De Croock
Golf: loose equality where the operands make it identical
06 авг 2026, 18:59
06 авг 2026, 18:59
5e96b7a
Код
Авторство
О чём код?
import { render as preactRender, hydrate as preactHydrate, options, toChildArray, Component } from 'preact'; import { useCallback, useContext, useDebugValue, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'preact/hooks'; import { useDeferredValue, useInsertionEffect, useTransition } from './index'; import { assign, IS_NON_DIMENSIONAL } from './util'; export const REACT_ELEMENT_TYPE = Symbol.for('react.element'); const MODE_HYDRATE = 1 << 5; const CAMEL_PROPS = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/; const CAMEL_REPLACE = /[A-Z0-9]/g; const IS_DOM = typeof document != 'undefined'; /** * This is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84 * on a high level this cuts out the warnings, ... and attempts a smaller implementation * @typedef {{ _value: any; _getSnapshot: () => any }} Store */ export function useSyncExternalStore( subscribe, getSnapshot, getServerSnapshot ) { const serverRendering = options._skipEffects || hydrationRoot; const value = serverRendering ? (getServerSnapshot || getSnapshot)() : getSnapshot(); /** * @typedef {{ _instance: Store }} StoreRef * @type {[StoreRef, (store: StoreRef) => void]} */ const [{ _instance }, forceUpdate] = useState({ _instance: { _value: value, _getSnapshot: getSnapshot } }); useLayoutEffect(() => { _instance._value = value; _instance._getSnapshot = getSnapshot; if (didSnapshotChange(_instance)) { forceUpdate({ _instance }); } }, [subscribe, value, getSnapshot]); useEffect(() => { if (didSnapshotChange(_instance)) { forceUpdate({ _instance }); } return subscribe(() => { if (didSnapshotChange(_instance)) { forceUpdate({ _instance }); } }); }, [subscribe]); return value; } /** @type {(inst: Store) => boolean} */ function didSnapshotChange(inst) { try { return !Object.is(inst._value, inst._getSnapshot()); } catch (error) { return true; } } // Input types for which onchange should not be converted to oninput. const onChangeInputType = type => /fil|che|rad/.test(type); // Some libraries like `react-virtualized` explicitly check for this. Component.prototype.isReactComponent = true; // `UNSAFE_*` lifecycle hooks // Preact only ever invokes the unprefixed methods. // Here we provide a base "fallback" implementation that calls any defined UNSAFE_ prefixed method. // - If a component defines its own `componentDidMount()` (including via defineProperty), use that. // - If a component defines `UNSAFE_componentDidMount()`, `componentDidMount` is the alias getter/setter. // - If anything assigns to an `UNSAFE_*` property, the assignment is forwarded to the unprefixed property. // See https://github.com/preactjs/preact/issues/1941 [ 'componentWillMount', 'componentWillReceiveProps', 'componentWillUpdate' ].forEach(key => { Object.defineProperty(Component.prototype, key, { configurable: true, get() { return this['UNSAFE_' + key]; }, set(v) { Object.defineProperty(this, key, { configurable: true, writable: true, value: v }); } }); }); /** * Proxy render() since React returns a Component reference. * @param {import('./internal').VNode} vnode VNode tree to render * @param {import('./internal').PreactElement} parent DOM node to render vnode tree into * @param {() => void} [callback] Optional callback that will be called after rendering * @returns {import('./internal').Component | null} The root component reference or null */ export function render(vnode, parent, callback) { // React destroys any existing DOM nodes, see #1727 // ...but only on the first render, see #1828 if (parent._children == null) { parent.textContent = ''; } preactRender(vnode, parent); if (typeof callback == 'function') callback(); return vnode ? vnode._component : null; } export function hydrate(vnode, parent, callback) { preactHydrate(vnode, parent); if (typeof callback == 'function') callback(); return vnode ? vnode._component : null; } let oldEventHook = options.event; options.event = e => { if (oldEventHook) e = oldEventHook(e); e.persist = () => {}; e.isPropagationStopped = function isPropagationStopped() { return this.cancelBubble; }; e.isDefaultPrevented = function isDefaultPrevented() { return this.defaultPrevented; }; return (e.nativeEvent = e); }; const classNameDescriptorNonEnumberable = { configurable: true, get() { return this.class; } }; function handleDomVNode(vnode) { let props = vnode.props, type = vnode.type, normalizedProps = {}, isNonDashedType = type.indexOf('-') == -1; for (let i in props) { let value = props[i]; if ( (i == 'value' && 'defaultValue' in props && value == null) || // Emulate React's behavior of not rendering the contents of noscript tags on the client. (IS_DOM && i == 'children' && type == 'noscript') || i == 'class' || i == 'className' ) { // Skip applying value if it is null/undefined and we already set // a default value continue; } if (i == 'style' && typeof value == 'object') { let cloned; for (let key in value) { if (typeof value[key] == 'number' && !IS_NON_DIMENSIONAL.test(key)) { if (!cloned) { cloned = value = assign({}, value); } value[key] += 'px'; } } } else if (i == 'defaultValue' && 'value' in props && props.value == null) { // `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined. // `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property. i = 'value'; } else if (i == 'download' && value === true) { // Calling `setAttribute` with a truthy value will lead to it being // passed as a stringified value, e.g. `download="true"`. React // converts it to an empty string instead, otherwise the attribute // value will be used as the file name and the file will be called // "true" upon downloading it. value = ''; } else if (i == 'translate' && value === 'no') { value = false; } else if (i[0] == 'o' && i[1] == 'n') { let lowerCased = i.toLowerCase(); if (lowerCased == 'ondoubleclick') { i = 'ondblclick'; } else if ( lowerCased == 'onchange' && (type == 'input' || type == 'textarea') && !onChangeInputType(props.type) ) { lowerCased = i = 'oninput'; } else if (lowerCased == 'onfocus') { i = 'onfocusin'; } else if (lowerCased == 'onblur') { i = 'onfocusout'; } // Add support for onInput and onChange, see #3561 // if we have an oninput prop already change it to oninputCapture if (lowerCased == 'oninput') { i = lowerCased; if (normalizedProps[i]) { i = 'oninputCapture'; } } } else if (isNonDashedType && CAMEL_PROPS.test(i)) { i = i.replace(CAMEL_REPLACE, '-$&').toLowerCase(); } else if (value === null) { value = undefined; } normalizedProps[i] = value; } if (type == 'select') { // Add support for array select values: <select multiple value={[]} /> if (normalizedProps.multiple && Array.isArray(normalizedProps.value)) { // forEach() always returns undefined, which we abuse here to unset the value prop. normalizedProps.value = toChildArray(props.children).forEach(child => { child.props.selected = normalizedProps.value.indexOf(child.props.value) != -1; }); } // Adding support for defaultValue in select tag if (normalizedProps.defaultValue != null) { normalizedProps.value = toChildArray(props.children).forEach(child => { if (normalizedProps.multiple) { child.props.selected = normalizedProps.defaultValue.indexOf(child.props.value) != -1; } else { child.props.selected = normalizedProps.defaultValue == child.props.value; } }); } } if (props.class && !props.className) { normalizedProps.class = props.class; Object.defineProperty( normalizedProps, 'className', classNameDescriptorNonEnumberable ); } else if (props.className) { normalizedProps.class = normalizedProps.className = props.className; } vnode.props = normalizedProps; } let oldVNodeHook = options.vnode; options.vnode = vnode => { // only normalize props on Element nodes if (typeof vnode.type == 'string') { handleDomVNode(vnode); } else if (typeof vnode.type == 'function') { const shouldApplyRef = 'prototype' in vnode.type && vnode.type.prototype.render; if ('ref' in vnode.props && shouldApplyRef) { vnode.ref = vnode.props.ref; delete vnode.props.ref; } if (vnode.type.defaultProps) { let normalizedProps = assign({}, vnode.props); for (let i in vnode.type.defaultProps) { if (normalizedProps[i] === undefined) { normalizedProps[i] = vnode.type.defaultProps[i]; } } vnode.props = normalizedProps; } } vnode.$$typeof = REACT_ELEMENT_TYPE; if (oldVNodeHook) oldVNodeHook(vnode); }; // Only needed for react-relay let currentComponent, hydrationRoot; const oldBeforeRender = options._render; options._render = function (vnode) { if (oldBeforeRender) { oldBeforeRender(vnode); } if (vnode._flags & MODE_HYDRATE) hydrationRoot = vnode; currentComponent = vnode._component; }; const oldDiffed = options.diffed; /** @type {(vnode: import('./internal').VNode) => void} */ options.diffed = function (vnode) { if (oldDiffed) { oldDiffed(vnode); } const props = vnode.props; const dom = vnode._dom; if ( dom != null && vnode.type == 'textarea' && 'value' in props && props.value !== dom.value ) { dom.value = props.value == null ? '' : props.value; } currentComponent = null; if (hydrationRoot == vnode) hydrationRoot = null; }; /** * Read the value of a Promise (suspending while pending) or a Context. * Unlike other hooks, `use` may be called conditionally. * @template T * @param {(Promise<T> & { status?: string, value?: T, reason?: any }) | import('../../src/internal').PreactContext} resource * @returns {T} */ export const use = resource => { // A Context is a function without a `then`, a thenable has one. if (resource.then) { if (resource.status == 'fulfilled') return resource.value; if (resource.status == 'rejected') throw resource.reason; if (!resource.status) { resource.status = 'pending'; resource.then( value => { resource.status = 'fulfilled'; resource.value = value; }, reason => { resource.status = 'rejected'; resource.reason = reason; } ); } throw resource; } const id = resource._id; const provider = currentComponent._globalContext[id]; if (!provider) return resource._defaultValue; // `use` runs on every render without hook state, while `provider.sub` // wraps `componentWillUnmount` on each call — mark the component so we // only subscribe it once. if (!currentComponent[id]) { currentComponent[id] = true; provider.sub(currentComponent); } return provider.props.value; }; // This is a very very private internal function for React it // is used to sort-of do runtime dependency injection. export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { ReactCurrentDispatcher: { current: { readContext: use, useCallback, useContext, useDebugValue, useDeferredValue, useEffect, useId, useImperativeHandle, useInsertionEffect, useLayoutEffect, useMemo, // useMutableSource, // experimental-only and replaced by uSES, likely not worth supporting useReducer, useRef, useState, useSyncExternalStore, useTransition } } };