一道深入 react 骨髓的面试题

458 阅读2分钟

最近在外网看到了一道面试题:「请准确说出下面这段代码在 react@18 中各个 log 语句的打印顺序」

import { useEffect } from "react";  
import { createRoot } from "react-dom/client";  
  
export const App = ({ name }) => {  
    console.log("log:", 5);  
  
    Promise.resolve().then(() => {  
        console.log("log:", 6);  
    });  

    useEffect(() => {  
        console.log("log:", 7);  
    });  

    return (  
        <div>  
            <h1>Hello {name}!</h1>  
            <p>Start editing to see some magic happen :)</p>  
        </div>  
    );  
 };  

const root = createRoot(document.getElementById("root"));  

console.log("log:", 2);  
root.render(<App name="StackBlitz" />);  
console.log("log:", 3);  

Promise.resolve().then(() => {  
    console.log("log:", 4);  
});  

setTimeout(() => {  
    console.log("log:", 1);  
}, 0);

面试官还会接着做进一步的提问,连番拷打候选人:

  1. 先简单说说你的解题思路是什么;

  2. 上面代码中,假如把 setTimeout() 的调用放在了 root.render() 之前,结果会发生改变吗?如果发生了改变,那最新的打印顺序是什么,为什么呢?

  3. 从源码来看,effect 的 create 函数是在一个被调度的 callback 里面去执行的,为什么它还是会比 Promise.then() 的 callback 函数先执行?

  4. 如果把 useEffect() 的调用放在子组件里面,然后 <App >组件再消费这个子组件,像这样:

    import { useEffect } from "react";  
    import { createRoot } from "react-dom/client";
     
    const Child = () => {
       useEffect(() => {
         console.log("log:", 7);
       });
    
       return null;
     };
     
     export const App = ({ name }) => {  
         console.log("log:", 5);  
    
         Promise.resolve().then(() => {  
             console.log("log:", 6);  
         });  
    
         return (  
             <div>  
                 <h1>Hello {name}!</h1>  
                 <p>Start editing to see some magic happen :)</p>
                 <Child />
             </div>  
             );  
     };  
    
     const root = createRoot(document.getElementById("root"));  
    
     console.log("log:", 2);  
     root.render(<App name="StackBlitz" />);  
     console.log("log:", 3);  
    
     Promise.resolve().then(() => {  
         console.log("log:", 4);  
     });  
    
     setTimeout(() => {  
         console.log("log:", 1);  
     }, 0);
    

    结果会发生改变吗?

  5. 如果在 <App> 组件函数加一个超过 5ms 的阻塞操作,结果会是怎么样?像这样:

    import { useEffect } from "react";  
    import { createRoot } from "react-dom/client";
    
    function sleep() {
      const start = performance.now();
      while (performance.now() - start < 10) {
        // block
      }
    }
    
    const Child = () => {
      useEffect(() => {
        console.trace();
        console.log("log:", 7);
      });
    
      return null;
    };
    
    export const App = ({ name }) => {  
        console.log("log:", 5);  
    
        Promise.resolve().then(() => {  
            console.log("log:", 6);  
        });  
    
        sleep(); 
    
        return (  
            <div>  
                <h1>Hello {name}!</h1>  
                <p>Start editing to see some magic happen :)</p>
                <Child />
            </div>  
            );  
    };  
    
    const root = createRoot(document.getElementById("root"));  
    
    console.log("log:", 2);  
    root.render(<App name="StackBlitz" />);  
    console.log("log:", 3);  
    
    Promise.resolve().then(() => {  
        console.log("log:", 4);  
    });  
    
    setTimeout(() => {  
        console.log("log:", 1);  
    }, 0);
    

    那么最后打印结果是怎样的?打印顺序发生了变化了吗?相比于问题四,为什么会有这种变化?

在题目设计者看来,谁如果能够经受得住这五个问题的拷问,并且给出准确答案,谁就是 react 大师级别的人物。经过我的研究,我对此深信不疑。

不知道,有哪些大佬能够准确给出答案呢?