我想显示一个自定义输入组件,然后在单击按钮时调用其方法:
const Parent = () => {
const customInputRef = useRef(null);
const [customInputVisible, setCustomInputVisible] = useState(false);
async function onPress() {
setCustomInputVisible(true);
await resolvePendingChanged(); // customInput is not null and can be accessed
customInputRef.current.customMethod();
}
return (
<View>
<Button onPress={onPress}>Press me!</Button>
{customInputVisible && <CustomInput ref={customInputRef} />}
</View>
);
}
我看到人们使用自定义forceUpdate函数来触发组件更新,但这对我来说并没有真正的帮助。
在 Svelte 中,有一个 “tick”生命周期挂钩,它正是我所需要的。
它返回一个承诺,一旦任何挂起状态就解决 更改已应用到 DOM(或者立即应用,如果没有 待定状态更改)。
React 中是否有相当于 Svelte 的
tick
的工具,如果没有,我该如何在 React 中解决这个问题?
您可以创建一个自定义钩子,使用 callback ref 来设置实际的 ref,并解析一个 Promise:
const { forwardRef, useImperativeHandle, useRef, useState, useCallback, useMemo } = React;
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
customMethod: () => {
inputRef.current.focus();
}
}), []);
return <input ref={inputRef} />;
});
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
const waitForComponent = () => {
const componentRef = useRef(null);
return useMemo(() => {
let deferred = new Deferred();
return {
waitLoad(ref) {
componentRef.current = ref;
if (ref) deferred.resolve();
else deferred = new Deferred(); // create new Promise when ref is null
},
isLoaded: () => deferred.promise,
componentRef
};
}, []);
}
const Parent = () => {
const { waitLoad, componentRef, isLoaded } = waitForComponent();
const [customInputVisible, setCustomInputVisible] = useState(false);
function onPress() {
setCustomInputVisible(visible => !visible);
// use async here - SO snippet doesn't support async await
isLoaded().then(() => componentRef.current.customMethod());
}
return (
<div>
<button onClick={onPress}>Press me!</button>
{customInputVisible && <CustomInput ref={waitLoad} />}
</div>
);
};
ReactDOM.render(
<Parent />,
root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>
一点点进步。
const { forwardRef, useImperativeHandle, useRef, useState, useCallback, useMemo } = React;
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
customMethod: () => {
inputRef.current.focus();
}
}), []);
return <input ref={inputRef} />;
});
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
const useWaitForComponent = () => {
return useMemo(() => {
let deferred = new Deferred();
const waitLoad = (ref)=> {
if (ref) deferred.resolve(ref);
else deferred = new Deferred(); // create new Promise when ref is null
}
const isLoaded = () => deferred.promise
return [waitLoad, isLoaded];
}, []);
}
const Parent = () => {
const [ refButtonWait, isButtonLoaded ] = useWaitForComponent();
const [customInputVisible, setCustomInputVisible] = useState(false);
function onPress() {
setCustomInputVisible(visible => !visible);
// use async here - SO snippet doesn't support async await
isButtonLoaded().then((ref) => ref.customMethod());
}
return (
<div>
<button onClick={onPress}>Press me!</button>
{customInputVisible && <CustomInput ref={refButtonWait} />}
</div>
);
};
ReactDOM.render(
<Parent />,
root
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>