This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
@@ -0,0 +1,8 @@
type Updater<T> = (updater: T | ((origin: T) => T)) => void;
/**
* Similar to `useState` but will use props value if provided.
* From React 18, we do not need safe `useState` since it will not throw for unmounted update.
* This hooks remove the `onChange` & `postState` logic since we only need basic merged state logic.
*/
export default function useControlledState<T>(defaultStateValue: T | (() => T), value?: T): [T, Updater<T>];
export {};
@@ -0,0 +1,21 @@
import { useState } from 'react';
import useLayoutEffect from "./useLayoutEffect";
/**
* Similar to `useState` but will use props value if provided.
* From React 18, we do not need safe `useState` since it will not throw for unmounted update.
* This hooks remove the `onChange` & `postState` logic since we only need basic merged state logic.
*/
export default function useControlledState(defaultStateValue, value) {
const [innerValue, setInnerValue] = useState(defaultStateValue);
const mergedValue = value !== undefined ? value : innerValue;
useLayoutEffect(mount => {
if (!mount) {
setInnerValue(value);
}
}, [value]);
return [
// Value
mergedValue,
// Update function
setInnerValue];
}
@@ -0,0 +1,3 @@
/** As `React.useEffect` but pass origin value in callback and not need care deps length change. */
declare function useEffect(callback: (prevDeps: any[]) => void, deps: any[]): void;
export default useEffect;
+13
View File
@@ -0,0 +1,13 @@
import * as React from 'react';
/** As `React.useEffect` but pass origin value in callback and not need care deps length change. */
function useEffect(callback, deps) {
const prevRef = React.useRef(deps);
React.useEffect(() => {
if (deps.length !== prevRef.current.length || deps.some((dep, index) => dep !== prevRef.current[index])) {
callback(prevRef.current);
}
prevRef.current = deps;
});
}
export default useEffect;
@@ -0,0 +1,2 @@
declare const useEvent: <T extends (...args: any[]) => any>(callback: T) => undefined extends T ? (...args: Parameters<NonNullable<T>>) => ReturnType<NonNullable<T>> | undefined : T;
export default useEvent;
+8
View File
@@ -0,0 +1,8 @@
import * as React from 'react';
const useEvent = callback => {
const fnRef = React.useRef(callback);
fnRef.current = callback;
const memoFn = React.useCallback((...args) => fnRef.current?.(...args), []);
return memoFn;
};
export default useEvent;
+13
View File
@@ -0,0 +1,13 @@
import * as React from 'react';
/** @private Note only worked in develop env. Not work in production. */
export declare function resetUuid(): void;
/**
* Generate a valid HTML id from prefix and key.
* Sanitizes the key by replacing invalid characters with hyphens.
* @param prefix - The prefix for the id
* @param key - The key from React element, may contain spaces or invalid characters
* @returns A valid HTML id string
*/
export declare function getId(prefix: string, key: React.Key): string;
declare const _default: (id?: string) => string;
export default _default;
+73
View File
@@ -0,0 +1,73 @@
import * as React from 'react';
function getUseId() {
// We need fully clone React function here to avoid webpack warning React 17 do not export `useId`
const fullClone = {
...React
};
return fullClone.useId;
}
let uuid = 0;
/** @private Note only worked in develop env. Not work in production. */
export function resetUuid() {
if (process.env.NODE_ENV !== 'production') {
uuid = 0;
}
}
/**
* Generate a valid HTML id from prefix and key.
* Sanitizes the key by replacing invalid characters with hyphens.
* @param prefix - The prefix for the id
* @param key - The key from React element, may contain spaces or invalid characters
* @returns A valid HTML id string
*/
export function getId(prefix, key) {
// React.Key can be string | number, convert to string first
const keyStr = String(key);
// Valid id characters: letters, digits, hyphen, underscore, colon, period
// Replace all invalid characters (including spaces) with hyphens to preserve length
const sanitizedKey = keyStr.replace(/[^a-zA-Z0-9_.:-]/g, '-');
return `${prefix}-${sanitizedKey}`;
}
const useOriginId = getUseId();
export default useOriginId ?
// Use React `useId`
function useId(id) {
const reactId = useOriginId();
// Developer passed id is single source of truth
if (id) {
return id;
}
// Test env always return mock id
if (process.env.NODE_ENV === 'test') {
return 'test-id';
}
return reactId;
} :
// Use compatible of `useId`
function useCompatId(id) {
// Inner id for accessibility usage. Only work in client side
const [innerId, setInnerId] = React.useState('ssr-id');
React.useEffect(() => {
const nextId = uuid;
uuid += 1;
setInnerId(`rc_unique_${nextId}`);
}, []);
// Developer passed id is single source of truth
if (id) {
return id;
}
// Test env always return mock id
if (process.env.NODE_ENV === 'test') {
return 'test-id';
}
// Return react native id or inner id
return innerId;
};
@@ -0,0 +1,4 @@
import * as React from 'react';
declare const useLayoutEffect: (callback: (mount: boolean) => void | VoidFunction, deps?: React.DependencyList) => void;
export declare const useLayoutUpdateEffect: typeof React.useEffect;
export default useLayoutEffect;
@@ -0,0 +1,30 @@
import * as React from 'react';
import canUseDom from "../Dom/canUseDom";
/**
* Wrap `React.useLayoutEffect` which will not throw warning message in test env
*/
const useInternalLayoutEffect = process.env.NODE_ENV !== 'test' && canUseDom() ? React.useLayoutEffect : React.useEffect;
const useLayoutEffect = (callback, deps) => {
const firstMountRef = React.useRef(true);
useInternalLayoutEffect(() => {
return callback(firstMountRef.current);
}, deps);
// We tell react that first mount has passed
useInternalLayoutEffect(() => {
firstMountRef.current = false;
return () => {
firstMountRef.current = true;
};
}, []);
};
export const useLayoutUpdateEffect = (callback, deps) => {
useLayoutEffect(firstMount => {
if (!firstMount) {
return callback();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
};
export default useLayoutEffect;
@@ -0,0 +1 @@
export default function useMemo<Value, Condition = any[]>(getValue: () => Value, condition: Condition, shouldUpdate: (prev: Condition, next: Condition) => boolean): Value;
+9
View File
@@ -0,0 +1,9 @@
import * as React from 'react';
export default function useMemo(getValue, condition, shouldUpdate) {
const cacheRef = React.useRef({});
if (!('value' in cacheRef.current) || shouldUpdate(cacheRef.current.condition, condition)) {
cacheRef.current.value = getValue();
cacheRef.current.condition = condition;
}
return cacheRef.current.value;
}
@@ -0,0 +1,13 @@
type Updater<T> = (updater: T | ((origin: T) => T), ignoreDestroy?: boolean) => void;
/**
* @deprecated Please use `useControlledState` instead if not need support < React 18.
* Similar to `useState` but will use props value if provided.
* Note that internal use rc-util `useState` hook.
*/
export default function useMergedState<T, R = T>(defaultStateValue: T | (() => T), option?: {
defaultValue?: T | (() => T);
value?: T;
onChange?: (value: T, prevValue: T) => void;
postState?: (value: T) => T;
}): [R, Updater<T>];
export {};
@@ -0,0 +1,58 @@
import useEvent from "./useEvent";
import { useLayoutUpdateEffect } from "./useLayoutEffect";
import useState from "./useState";
/** We only think `undefined` is empty */
function hasValue(value) {
return value !== undefined;
}
/**
* @deprecated Please use `useControlledState` instead if not need support < React 18.
* Similar to `useState` but will use props value if provided.
* Note that internal use rc-util `useState` hook.
*/
export default function useMergedState(defaultStateValue, option) {
const {
defaultValue,
value,
onChange,
postState
} = option || {};
// ======================= Init =======================
const [innerValue, setInnerValue] = useState(() => {
if (hasValue(value)) {
return value;
} else if (hasValue(defaultValue)) {
return typeof defaultValue === 'function' ? defaultValue() : defaultValue;
} else {
return typeof defaultStateValue === 'function' ? defaultStateValue() : defaultStateValue;
}
});
const mergedValue = value !== undefined ? value : innerValue;
const postMergedValue = postState ? postState(mergedValue) : mergedValue;
// ====================== Change ======================
const onChangeFn = useEvent(onChange);
const [prevValue, setPrevValue] = useState([mergedValue]);
useLayoutUpdateEffect(() => {
const prev = prevValue[0];
if (innerValue !== prev) {
onChangeFn(innerValue, prev);
}
}, [prevValue]);
// Sync value back to `undefined` when it from control to un-control
useLayoutUpdateEffect(() => {
if (!hasValue(value)) {
setInnerValue(value);
}
}, [value]);
// ====================== Update ======================
const triggerChange = useEvent((updater, ignoreDestroy) => {
setInnerValue(updater, ignoreDestroy);
setPrevValue([mergedValue], ignoreDestroy);
});
return [postMergedValue, triggerChange];
}
@@ -0,0 +1,6 @@
/**
* Hook to detect if the user is on a mobile device
* Notice that this hook will only detect the device type in effect, so it will always be false in server side
*/
declare const useMobile: () => boolean;
export default useMobile;
+16
View File
@@ -0,0 +1,16 @@
import { useState } from 'react';
import isMobile from "../isMobile";
import useLayoutEffect from "./useLayoutEffect";
/**
* Hook to detect if the user is on a mobile device
* Notice that this hook will only detect the device type in effect, so it will always be false in server side
*/
const useMobile = () => {
const [mobile, setMobile] = useState(false);
useLayoutEffect(() => {
setMobile(isMobile());
}, []);
return mobile;
};
export default useMobile;
+14
View File
@@ -0,0 +1,14 @@
type Updater<T> = T | ((prevValue: T) => T);
export type SetState<T> = (nextValue: Updater<T>,
/**
* Will not update state when destroyed.
* Developer should make sure this is safe to ignore.
*/
ignoreDestroy?: boolean) => void;
/**
* Same as React.useState but `setState` accept `ignoreDestroy` param to not to setState after destroyed.
* We do not make this auto is to avoid real memory leak.
* Developer should confirm it's safe to ignore themselves.
*/
declare const useSafeState: <T>(defaultValue?: T | (() => T)) => [T, SetState<T>];
export default useSafeState;
+24
View File
@@ -0,0 +1,24 @@
import * as React from 'react';
/**
* Same as React.useState but `setState` accept `ignoreDestroy` param to not to setState after destroyed.
* We do not make this auto is to avoid real memory leak.
* Developer should confirm it's safe to ignore themselves.
*/
const useSafeState = defaultValue => {
const destroyRef = React.useRef(false);
const [value, setValue] = React.useState(defaultValue);
React.useEffect(() => {
destroyRef.current = false;
return () => {
destroyRef.current = true;
};
}, []);
function safeSetState(updater, ignoreDestroy) {
if (ignoreDestroy && destroyRef.current) {
return;
}
setValue(updater);
}
return [value, safeSetState];
};
export default useSafeState;
@@ -0,0 +1,9 @@
type Updater<T> = T | ((prevValue: T) => T);
export type SetState<T> = (nextValue: Updater<T>) => void;
/**
* Same as React.useState but will always get latest state.
* This is useful when React merge multiple state updates into one.
* e.g. onTransitionEnd trigger multiple event at once will be merged state update in React.
*/
declare function useSyncState<T>(defaultValue?: T): [get: () => T, set: SetState<T>];
export default useSyncState;
@@ -0,0 +1,20 @@
import * as React from 'react';
import useEvent from "./useEvent";
/**
* Same as React.useState but will always get latest state.
* This is useful when React merge multiple state updates into one.
* e.g. onTransitionEnd trigger multiple event at once will be merged state update in React.
*/
function useSyncState(defaultValue) {
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
const currentValueRef = React.useRef(defaultValue);
const getValue = useEvent(() => {
return currentValueRef.current;
});
const setValue = useEvent(updater => {
currentValueRef.current = typeof updater === 'function' ? updater(currentValueRef.current) : updater;
forceUpdate();
});
return [getValue, setValue];
}
export default useSyncState;