david2tdw / blog

学习记录
1 stars 1 forks source link

[React] react组件重复渲染 #216

Open david2tdw opened 2 years ago

david2tdw commented 2 years ago

深入 React 函数组件的 re-render 原理及优化

david2tdw commented 2 years ago

组件本身使用 useState 或 useReducer 更新,引起的 re-render

常规使用:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>React template</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState } = React;
      const Counter = () => {
        console.log("counter render");
        const [count, addCount] = useState(0);

        return (
          <div>
            <div>{count}</div>
            <button
              onClick={() => {
                addCount(count + 1);
              }}
            >
              add
            </button>
          </div>
        );
      };

      ReactDOM.render(<Counter />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

immutation state

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>immutation state</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState } = React;
      const Counter = () => {
        console.log("counter render");
        //我们将上面计数组件中的 state 值改成引用类型, 点击并不会引起 re-render
        const [count, addCount] = useState({
          num: 0,
          time: Date.now(),
        });

        const handleClick = () => {
          count.num++;
          count.time = Date.now();
          addCount(count);
        };
        return (
          <div>
            <div>
              {count.num}, {count.time}
            </div>
            <button onClick={handleClick}>add</button>
          </div>
        );
      };

      ReactDOM.render(<Counter />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

强制更新

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>强制更新</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState } = React;
      const Counter = () => {
        console.log("counter render");
        const [, forceUpdate] = useState({});
        //我们将上面计数组件中的 state 值改成引用类型
        const [count, addCount] = useState({
          num: 0,
          time: Date.now(),
        });

        const handleClick = () => {
          count.num++;
          count.time = Date.now();
          addCount(count);
          forceUpdate({});
        };
        return (
          <div>
            <div>
              {count.num}, {count.time}
            </div>
            <button onClick={handleClick}>add</button>
          </div>
        );
      };

      ReactDOM.render(<Counter />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

父组件更新引起子组件的 re-render 常规使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>常规使用</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState } = React;

      const Hello = ({ name }) => {
        console.log("hello render");
        return <div>hello {name}</div>;
      };
      const App = () => {
        console.log("app render");
        const [count, addCount] = useState(0);

        const handleClick = () => {
          addCount(count + 1);
        };
        return (
          <div>
            <Hello name="react" />
            <button onClick={handleClick}>add</button>
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

父组件更新引起子组件的 re-render - 优化组件设计 将更新部分抽离成单独组件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>将更新部分抽离成单独组件</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState } = React;

      const Counter = () => {
        console.log("counter render");
        const [count, addCount] = useState(0);

        const handleClick = () => {
          addCount(count + 1);
        };

        return <button onClick={handleClick}>add</button>;
      };

      const Hello = ({ name }) => {
        console.log("hello render");
        return <div>hello {name}</div>;
      };

      const App = () => {
        console.log("app render");

        return (
          <div>
            <Hello name="react" />
            <Counter />
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

将不需要 re-render 的部分抽离,以插槽形式渲染(children)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>将不需要 re-render 的部分抽离,以插槽形式渲染</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState } = React;

      const Hello = ({ name }) => {
        console.log("hello render");
        return <div>hello {name}</div>;
      };
      const App = ({ children }) => {
        console.log("app render");
        const [count, addCount] = useState(0);

        const handleClick = () => {
          addCount(count + 1);
        };
        return (
          <div>
            {children}
            <button onClick={handleClick}>add</button>
          </div>
        );
      };

      const Main = () => {
        return (
          <App>
            <Hello name="react" />
          </App>
        );
      };
      ReactDOM.render(<Main />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

React.memo

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>React.memo</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState } = React;

      const Hello = React.memo(({ name }) => {
        console.log("hello render");
        return <div>hello {name}</div>;
      });

      const App = () => {
        console.log("app render");
        const [count, addCount] = useState(0);

        const handleClick = () => {
          addCount(count + 1);
        };
        return (
          <div>
            <Hello name="react" />
            <button onClick={handleClick}>add</button>
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

React.memo 引用类的 props, 添加事件处理的函数

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>React.memo 引用类的 props, 添加事件处理的函数</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState } = React;

      const Hello = React.memo(({ name, onClick }) => {
        console.log("hello render");
        return <div onClick={onClick}>hello {name}</div>;
      });

      const App = () => {
        console.log("app render");
        const [count, addCount] = useState(0);

        const handleClick = () => {
          console.log("hello click");
        };
        return (
          <div>
            <Hello name="react" onClick={handleClick} />
            <button
              onClick={() => {
                addCount(count + 1);
              }}
            >
              add
            </button>
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

useCallback

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>useCallback</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState, useCallback } = React;

      const Hello = React.memo(({ name, onClick }) => {
        console.log("hello render");
        return <div onClick={onClick}>hello {name}</div>;
      });

      const App = () => {
        console.log("app render");
        const [count, addCount] = useState(0);

        const handleClick = useCallback(() => {
          console.log("hello click");
        }, []);

        return (
          <div>
            <Hello name="react" onClick={handleClick} />
            <button
              onClick={() => {
                addCount(count + 1);
              }}
            >
              add
            </button>
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

useCallback use state

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>useCallback use state</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState, useCallback } = React;

      const Hello = React.memo(({ name, onClick }) => {
        console.log("hello render");
        return <div onClick={onClick}>hello {name}</div>;
      });

      const App = () => {
        console.log("app render");
        const [count, addCount] = useState(0);

        const handleClick = useCallback(() => {
          console.log("hello click count" , count);
        }, []);

        return (
          <div>
            <Hello name="react" onClick={handleClick} />
            <button
              onClick={() => {
                addCount(count + 1);
              }}
            >
              add
            </button>
            <div>count is: {count}</div>
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

useCallback use state fix - 会导致闭包问题,子组件重复渲染

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>useCallback use state fix</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState, useCallback } = React;

      const Hello = React.memo(({ name, onClick }) => {
        console.log("hello render");
        return <div onClick={onClick}>hello {name}</div>;
      });

      const App = () => {
        console.log("app render");
        const [count, addCount] = useState(0);

        const handleClick = useCallback(() => {
          console.log("hello click count" , count);
        }, [count]);

        return (
          <div>
            <Hello name="react" onClick={handleClick} />
            <button
              onClick={() => {
                addCount(count + 1);
              }}
            >
              add
            </button>
            <div>count is: {count}</div>
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
david2tdw commented 2 years ago

useRef & useEffect

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>useRef & useEffect</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <style></style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const { useState, useCallback, useRef, useEffect } = React;

      const Hello = React.memo(({ name, onClick }) => {
        console.log("hello render");
        return <div onClick={onClick}>hello {name}</div>;
      });

      const App = () => {
        console.log("app render");
        const [count, addCount] = useState(0);

        const countRef = useRef(count);

        useEffect(() => {
          countRef.current = count;
        }, [count]);

        const handleClick = useCallback(() => {
          console.log("hello click count", countRef.current);
        }, [countRef]);

        return (
          <div>
            <Hello name="react" onClick={handleClick} />
            <button
              onClick={() => {
                addCount(count + 1);
              }}
            >
              add
            </button>
            <div>count is: {count}</div>
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>