26
loading...
This website collects cookies to deliver better user experience
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
FancyInput
now.function App() {
const ref = useRef();
const focus = useCallback(() => {
ref.current?.focus();
}, []);
return (
<div>
<FancyInput ref={inputRef} />
<button onClick={focus} />
</div>
);
}
useImperativeHandle()
, what if we just update ref.current
? like below.function FancyInput(props, ref) {
const inputRef = useRef();
ref.current = () => ({
focus: () => {
inputRef.current.focus();
},
});
return <input ref={inputRef} />;
}
FancyInput
only set current
of ref accepted, not cleaning up.ref
changes during renders? Then the old ref would still hold the ref which causes problems since from the usage of <FancyInput ref={inputRef} />
, it should be cleaned.useEffect()
which could help clean things up, so we can try things like this.function FancyInput(props, ref) {
const inputRef = useRef();
useEffect(() => {
ref.current = () => ({
focus: () => {
inputRef.current.focus();
},
});
return () => {
ref.current = null;
};
}, [ref]);
return <input ref={inputRef} />;
}
ref
is RefObject not function ref? Ok then, we need to check that.function FancyInput(props, ref) {
const inputRef = useRef();
useEffect(() => {
if (typeof ref === "function") {
ref({
focus: () => {
inputRef.current.focus();
},
});
} else {
ref.current = () => ({
focus: () => {
inputRef.current.focus();
},
});
}
return () => {
if (typeof ref === "function") {
ref(null);
} else {
ref.current = null;
}
};
}, [ref]);
return <input ref={inputRef} />;
}
useImperativeHandle()
works. Except useImperativeHandle()
is a layout effect, the ref setting happens at same stage of useLayoutEffect()
, soonner than useEffect()
.useImperativeHandle()
is called.function mountImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
return mountEffectImpl(
fiberFlags,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
function updateImperativeHandle<T>(
ref: {| current: T | null |} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null
): void {
// TODO: If deps are provided, should we skip comparing the ref itself?
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
return updateEffectImpl(
UpdateEffect,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps
);
}
function imperativeHandleEffect<T>(
create: () => T,
ref: {| current: T | null |} | ((inst: T | null) => mixed) | null | void
) {
if (typeof ref === "function") {
const refCallback = ref;
const inst = create();
refCallback(inst);
return () => {
refCallback(null);
};
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}
useImperativeHandle()
is no magic, it just wraps the ref setting and cleaning for us, internally it is in the same stage as useLayoutEffect()
so a bit sooner than useEffect()
.