我们有一个包含许多遗留 jQuery 组件的代码堆栈。我们正在转向 React,其中一个步骤就是用 React 包装 jQuery。
但是,像这样管理非 React 子组件的状态似乎并不是 useEffects 常见的内容。
es-lint/exhaustive-deps
不喜欢我的任何解决方案。我已经查看了 https://overreacted.io/a-complete-guide-to-useeffect/ 和 React 文档,但我仍然不确定正确的答案是什么。
朴素的函数组件如下所示:
const MyReactFunctionComponent = (props) => {
const element = useRef(null);
const [JQueryComp, setJQueryComp] = useState(null);
const renderJQueryHelper = () => {
// Not 1-1 props match, lot of transformation and helper functions
const JQueryProps = { ...props };
return new myJQueryComponent(JQueryProps, element.current);
};
useEffect(() => {
// only heavy render on first mount
setJQueryComp(renderJQueryHelper());
return () => {
JQueryComp.destroy();
};
}, []); // warn: missing deps 'JQueryComp' and 'renderJQueryHelper'
// call update on every reRender, comp diffs the props itself.
if (JQueryComp) {
JQueryComp.update(props);
}
return <div ref={element} />;
};
理论上,我可以将整个助手移动到 useEffect 中,但这很快就会变得一团糟,我想避免这种情况。遵循各种指南,我找到了这个解决方案,用
useRef
来存储 useCallback
。
const renderJQueryHelper = useCallback(() => { ..., [props]);
const helperRef = useRef(renderJQueryHelper);
useEffect(() => {
setJQueryComp(helperRef.current());
...
这适用于辅助函数,我已经在其他地方使用过它。但它不包括 JQueryComp,我需要能够调用 destroy。它也不能处理我想要更频繁地运行繁重渲染助手的情况,例如 jQuery 组件崩溃,或者其他更复杂的情况。我觉得我一定错过了什么。 我将包含 JQueryComp 的示例实现,以及它在类组件中的外观,它看起来要简单得多。
const myJQueryComponent = (props, element) => {
const $element = $(element);
$element.addClass('my-JQuery-component');
const initialize = () => {
// lots of JQuery code here, attaching containers, event listeners, etc.
// eventually renders other JQuery components
};
const render = () => {
if ($element.children().length > 0) {
$element.trigger('JQuery_COMP_UPDATE', props);
} else {
initialize();
}
};
this.update = _.debounce((newProps) => {
if (newProps.type !== props.type) {
this.destroy();
}
if (!_.isEqual(newProps, props)) {
props = newProps;
render();
}
}, 100);
this.destroy = () => {
$element.trigger('JQuery_COMP_DESTROY').empty();
};
render();
};
class MyReactClassComponent extends React.Component {
renderJQueryHelper() {
// Not 1-1 props match, lot of transformation and helper functions
const JQueryProps = {...props}
return new myJQueryComponent(JQueryProps, this.element);
}
componentDidMount() {
this.JQueryComp = renderJQueryHelper();
}
componentDidUpdate() {
if (!this.JQueryComp) {
// JQuery comp crashed?
this.JQueryComp = renderJQueryHelper
}
this.JQueryComp.update(this.props);
}
componentWillUnmount() {
if (this.JQueryComp) {
this.JQueryComp.destroy();
}
}
render() {
return <div ref={(element) => (this.element = element)} />;
}
}
myJQueryComponent
组件/对象引用可以存储在另一个 React 引用中。
创建第二个 React 引用来保存对myJQueryComponent
useEffect
钩子回调(类似于React类组件的
componentDidMount
生命周期方法)来实例化
myJQueryComponent
并返回清理函数(类似于类组件的
componentWillUnmount
生命周期方法)在组件卸载时销毁当前
myJQueryComponent
对象。使用第二个 useEffect
props
值随时间变化,并用作触发更新 myJQueryComponent
对象的依赖项(类似于类组件的
componentDidUpdate
生命周期方法) .
const MyReactFunctionComponent = (props) => {
const elementRef = useRef(null);
const jQueryCompRef = useRef();
useEffect(() => {
const jQueryProps = { ...props };
jQueryCompRef.current = new myJQueryComponent(
JQueryProps,
elementRef.current
);
return () => {
jQueryCompRef.current.destroy();
};
// NOTE: mounting effect only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
jQueryCompRef.current.update(props);
}, [props]);
return <div ref={element} />;
};
如果上面的方法不太有效,并且 React 仍然需要一点点才能知道它应该重新渲染,那么您可以在必要时强制组件重新渲染。
示例:
const useForceRerender = () => {
const [, setState] = useState(false);
// useCallback is used to memoize a stable callback
return useCallback(() => setState(c => !c), []);
};
const MyReactFunctionComponent = (props) => {
const elementRef = useRef(null);
const jQueryCompRef = useRef();
const forceRerender = useForceRerender();
useEffect(() => {
const jQueryProps = { ...props };
jQueryCompRef.current = new myJQueryComponent(
JQueryProps,
elementRef.current
);
return () => {
jQueryCompRef.current.destroy();
};
// NOTE: mounting effect only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
jQueryCompRef.current.update(props);
forceRerender();
}, [forceRerender, props]);
return <div ref={element} />;
};