@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
194 lines (193 loc) • 7.91 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getRef = getRef;
exports.render = render;
exports.resolveDOM = resolveDOM;
exports.unmount = unmount;
var ReactDOM = _interopRequireWildcard(require("react-dom"));
var _semiGlobal = _interopRequireDefault(require("./semi-global"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const fullClone = Object.assign({}, ReactDOM);
const legacyRender = fullClone.render;
const legacyUnmount = fullClone.unmountComponentAtNode;
const legacyFindDOMNode = fullClone.findDOMNode;
const {
version
} = ReactDOM;
const mainVersion = Number((version || '').split('.')[0]);
// React 版本兼容性检查
let hasWarnedVersionMismatch = false;
function checkVersionCompatibility() {
var _a;
if (hasWarnedVersionMismatch) {
return;
}
// 如果是 React 19+ 但没有注入 createRoot,在首次使用时会通过 warnCreateRootNotFound 警告
// 如果是 React < 18 但用户注入了 createRoot,给出警告
if (mainVersion < 18 && typeof ((_a = _semiGlobal.default.config) === null || _a === void 0 ? void 0 : _a.createRoot) === 'function') {
hasWarnedVersionMismatch = true;
console.warn(`[Semi UI] createRoot was injected but React version is ${version} (< 18). ` + 'This configuration is unusual and may cause unexpected behavior.');
}
}
function toggleWarning(skip) {
var _a;
const internals = (_a = fullClone.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) !== null && _a !== void 0 ? _a : fullClone.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
if (internals && typeof internals === 'object') {
internals.usingClientEntryPoint = skip;
}
}
/**
* Resolve `createRoot` with 3-level fallback:
* 1. semiGlobal.config.createRoot (user injection, required for React 19)
* 2. fullClone.createRoot (auto-discovery from react-dom default export, works in React 18)
* 3. undefined → triggers console.error guiding user to inject
*/
function resolveCreateRoot() {
var _a;
if (typeof ((_a = _semiGlobal.default.config) === null || _a === void 0 ? void 0 : _a.createRoot) === 'function') {
return _semiGlobal.default.config.createRoot;
}
if (typeof fullClone.createRoot === 'function') {
return fullClone.createRoot;
}
return undefined;
}
let hasWarnedCreateRoot = false;
function warnCreateRootNotFound() {
if (hasWarnedCreateRoot) {
return;
}
hasWarnedCreateRoot = true;
console.error('[Semi UI] createRoot is not available. ' + 'If you are using React 19, please inject createRoot before using Semi components. ' + 'For details, see: https://semi.design/zh-CN/ecosystem/react19\n' + '[Semi UI] createRoot 不可用。' + '如果您正在使用 React 19,请在使用 Semi 组件前注入 createRoot。' + '详情请参阅:https://semi.design/zh-CN/ecosystem/react19');
}
// ========================== Render ==========================
const MARK = '__semi_react_root__';
function render(node, container) {
checkVersionCompatibility();
const createRoot = resolveCreateRoot();
if (createRoot) {
toggleWarning(true);
const root = container[MARK] || createRoot(container);
toggleWarning(false);
root.render(node);
container[MARK] = root;
} else if (legacyRender) {
legacyRender(node, container);
} else {
warnCreateRootNotFound();
}
}
function unmount(container) {
// 优先检查是否有 createRoot 创建的 root,而不是检查 createRoot 是否可用
// 这样可以正确处理:用 legacyRender 渲染后,用户又注入了 createRoot 的情况
if (container[MARK]) {
container[MARK].unmount();
delete container[MARK];
} else if (legacyUnmount) {
legacyUnmount(container);
}
// 如果既没有 root 也没有 legacyUnmount,可能是:
// 1. 容器从未被渲染过
// 2. 容器已经被卸载过了
// 这两种情况都不需要警告,静默处理即可
}
// ======================== findDOMNode ========================
/**
* React 19+ fallback for findDOMNode: traverse React Fiber tree downward
* from a class component instance to find the first DOM element.
*
* Uses React internal Fiber structure (_reactInternals). If React changes
* its internals in future versions, this will safely return null without
* throwing errors, falling back to the warning path in resolveDOM.
*/
function findDOMFromFiber(instance) {
var _a;
try {
const fiber = (_a = instance === null || instance === void 0 ? void 0 : instance._reactInternals) !== null && _a !== void 0 ? _a : instance === null || instance === void 0 ? void 0 : instance._reactInternalFiber;
if (!fiber || typeof fiber !== 'object') {
return null;
}
let node = fiber.child;
let iterations = 0;
const MAX_ITERATIONS = 50;
while (node && iterations < MAX_ITERATIONS) {
iterations++;
// HostComponent: stateNode is a DOM element
if (node.stateNode instanceof Element) {
return node.stateNode;
}
if (node.child) {
node = node.child;
continue;
}
while (node && !node.sibling) {
if (node === fiber) {
return null;
}
node = node.return;
}
if (node && node !== fiber) {
node = node.sibling;
} else {
break;
}
}
} catch (e) {
// Fiber structure may have changed in a future React version; fail silently
}
return null;
}
/**
* React 16/17/18: use ReactDOM.findDOMNode to resolve real DOM from component instance.
* React 19: findDOMNode is removed; traverse Fiber tree to find DOM node.
*
* 注意:findDOMNode 可能返回 Text 节点,但我们只返回 Element 类型以保证类型安全。
*/
function resolveDOM(instance) {
if (!instance) {
return null;
}
// 已经是 Element,直接返回
if (instance instanceof Element) {
return instance;
}
// 尝试使用 findDOMNode (React 16/17/18)
if (legacyFindDOMNode) {
try {
const node = legacyFindDOMNode(instance);
// findDOMNode 可能返回 Text 节点,我们只返回 Element
if (node instanceof Element) {
return node;
}
return null;
} catch (e) {
// findDOMNode 可能在某些情况下抛出错误(如 StrictMode 警告)
return null;
}
}
// React 19 fallback: traverse Fiber tree to find the first DOM element
return findDOMFromFiber(instance);
}
// ========================= getRef ===========================
/**
* React 16/17/18: ref is a top-level property on the element (element.ref).
* React 19: ref is moved into element.props.ref.
*
* 使用版本检测来确定 ref 的位置,避免在 React 18 中错误地读取 props.ref。
*/
function getRef(element) {
var _a, _b, _c;
if (!element) {
return null;
}
// React 19+: ref 在 props 中
if (mainVersion >= 19) {
return (_b = (_a = element.props) === null || _a === void 0 ? void 0 : _a.ref) !== null && _b !== void 0 ? _b : null;
}
// React 16/17/18: ref 在顶层
return (_c = element.ref) !== null && _c !== void 0 ? _c : null;
}