Javascript 惯用的重DOM操作反应(MathJax)
我正在React应用程序中使用MathJax。MathJax带来了很多复杂性:它有自己的并发管理系统,并对React不知道的DOM进行更改。这导致了大量DOM微观管理,在React中通常被认为是反模式的,我想知道我的代码是否可以做得更好 在下面的代码中,Javascript 惯用的重DOM操作反应(MathJax),javascript,reactjs,typescript,mathjax,Javascript,Reactjs,Typescript,Mathjax,我正在React应用程序中使用MathJax。MathJax带来了很多复杂性:它有自己的并发管理系统,并对React不知道的DOM进行更改。这导致了大量DOM微观管理,在React中通常被认为是反模式的,我想知道我的代码是否可以做得更好 在下面的代码中,MJX是一个组件,它接受一个TeX字符串作为输入,并将其馈送到MathJax中RenderGroup是一个方便的组件,它可以跟踪其所有MJX子体何时完成排版 // 从“React”导入*作为React; /*一旦MathJax加载并准备就绪,承诺
MJX
是一个组件,它接受一个TeX字符串作为输入,并将其馈送到MathJax中RenderGroup
是一个方便的组件,它可以跟踪其所有MJX
子体何时完成排版
//
从“React”导入*作为React;
/*一旦MathJax加载并准备就绪,承诺就会解决*/
export const MathJaxReady=新承诺((解析、拒绝)=>{
常量脚本=$(“#js async mathjax”);
如果(!script)返回;
if(window.hasOwnProperty(“MathJax”)){
MathJax.Hub.Register.StartupHook(“结束”,解析);
}否则{
script.addEventListener(“load”,()=>MathJax.Hub.Register.StartupHook(“End”,resolve));
}
});
界面道具扩展React.HTMLAttributes{
显示?:布尔值;
}
导出类MJX扩展了React.Component{
私有resolveReady:()=>void;
DOMELENT:HTMLSPANLENT;
jax:MathJax.ElementJax;
//最初排版后解决的承诺
准备:承诺;
静态defaultProps={
显示:假
}
建造师(道具:道具){
超级(道具);
this.ready=新承诺((解决,拒绝)=>this.resolveReady=解决);
this.Typeset=this.Typeset.bind(this);
}
异步组件didmount(){
等待你做好准备;
这个.排版()
.then(()=>this.jax=MathJax.Hub.getAllJax(this.doElement)[0])
。然后(这个。准备就绪);
}
shouldComponentUpdate(下一步,下一步状态){
/*原来的span已经被MathJax吃掉了,我们自己管理更新*/
const text=this.props.children数组实例?this.props.children.join(“”):this.props.children,
nextText=nextProps.children数组实例?nextProps.children.join(“”):nextProps.children;
//重播机?
if(this.jax&&text!==nextText){
this.jax.Text(nextrops.children);
}
//课程改变了?
if(this.props.className!==nextrops.className){
const classes=this.props.className?this.props.className.split(“”):[],
newClasses=nextrops.className?nextrops.className.split(“”:[];
const add=newClasses.filter(=>!classes.includes()),
remove=classes.filter(=>!newClasses.includes());
用于(移除常数)
this.domeElement.classList.remove(_);
用于(添加常数)
this.domeElement.classList.add(_);
}
//样式属性已更改?
if(JSON.stringify(this.props.style)!==JSON.stringify(nextrops.style)){
Object.keys(this.props.style | |{})
.filter(=>!(nextrops.style | |{}).hasOwnProperty(290;))
.forEach(=>this.props.style[]=null);
Object.assign(this.domeElement.style,nextProps.style);
}
返回false;
}
Typeset():承诺{
返回新承诺((解决、拒绝)=>{
Queue([“Typeset”,MathJax.Hub,this.domeElement]);
队列(解析);
});
}
render(){
const{children,display,…attrs}=this.props;
常量[打开,关闭]=显示?[“\\[”,“\\]”]:[“\\(“,“\\)”];
返回(
this.domeElement=node}>{open+children+close}
);
}
}
//等待一大堆东西被呈现出来
导出类RenderGroup扩展React.Component{
私人承诺:承诺[];
准备:承诺;
componentDidMount(){
this.ready=Promise.all(this.promises).then(()=>{});
}
render(){
this.promises=[];
返回递归映射(this.props.children,node=>{
if(typeof node.type==“函数”&&node.type.prototype实例MJX){
const originalRef=node.ref;
返回React.cloneElement(节点{
参考:(参考:MJX)=>{
如果(!ref)返回;
这个.promises.push(ref.ready);
if(原始参考类型==“函数”){
原始参考(ref);
}else if(originalRef&&typeof originalRef==“对象”){
原始参考电流=参考;
}
}
});
}
返回节点;
});
}
}
//递归React.Children.map
导出函数递归映射(
子项:React.React节点,
fn:(child:React.ReactElement;根据React团队的说法,这是个问题。问题是函数组件没有实例,所以我不知道如何将.ready
公开给父组件,比如Example
。我知道这个场景有一个钩子,但这似乎取决于最终对HTML组件的引用uess在MJX
的情况下,我可以在
上放置一个ref,但这对渲染组
不起作用
强制管理输入是一件痛苦且容易出错的事情。有没有办法恢复React的声明性优点
另外:我还没有弄清楚如何正确地键入recursiveMap
;TypeScript对fn(child)
行很生气。用泛型替换any也不错
我个人没有使用MathJax,但根据我的经验,处理resolveReady
内容的“惯用反应”方式可能是通过上下文传递回调,让孩子们在加载或准备好时通知家长。例如(使用挂钩!):
const LoadingContext=createContext(()=>()=>{});
const LoadingProvider=memo(LoadingContext.Provider);
函数RenderGroup({children}){
常量[areChildrenReady,setAreChildrenReady]=使用状态(false);
const nextchildiref=useRef(0);
const unfinishedChildrenRef=useRef(new Set());
const startLoading=useCallback(()=>{
const childId=nextCh
const LoadingContext = createContext(() => () => {});
const LoadingProvider = memo(LoadingContext.Provider);
function RenderGroup({ children }) {
const [areChildrenReady, setAreChildrenReady] = useState(false);
const nextChildIdRef = useRef(0);
const unfinishedChildrenRef = useRef(new Set());
const startLoading = useCallback(() => {
const childId = nextChildIdRef.current++;
unfinishedChildrenRef.current.add(childId);
setAreChildrenReady(!!unfinishedChildrenRef.current.size);
const finishLoading = () => {
unfinishedChildrenRef.current.delete(childId);
setAreChildrenReady(!!unfinishedChildrenRef.current.size);
};
return finishLoading;
}, []);
useEffect(() => {
if (areChildrenReady) {
// do whatever
}
}, [areChildrenReady]);
return (
<LoadingProvider value={startLoading}>
{children}
</LoadingProvider>
);
}
function ChildComponent() {
const startLoading = useContext(LoadingContext);
useEffect(() => {
const finishLoading = startLoading();
MathJaxReady
.then(anotherPromise)
.then(finishLoading);
}, [startLoading]);
return (
// elements
);
}