在 hook 组件中拆分时图时需要注意的事项

最近遇到一个bug,问题是出现在react hook 中,为了代码的可读性,我把一个view拆成了多个子 component,其中有一些是 pure component,有一些是含有 usestate 的 stateful component,但是在实践的过程中发现,当父组件使用 setState 更新视图的时候,有些 stateful component 中的 state 会自动还原为初始值,有一些则不会,仔细对比后发现是组件实例化的写法不一样,代码概要如下:

import { useState } from "react";
import CHild from "./Child";
import "./styles.css";

export default function App() {
  const [random, setRandom] = useState(() => Math.random() * 10);

  const RenderPart = function () {
    const [count, setCount] = useState(0);
    return (
      
{count}
); }; const renderOtherPart = function () { const [count, setCount] = useState(0); return (
{count}
); }; return (

{random}

{/* render component via other render function with Component style would refresh the state of child component*/} {/* render component via other render function with function style wouldn't refresh the state neither */} {renderOtherPart()}
); }

使用 Babel 转译后的代码:

  1. 使用 的方式
"use strict";

function CHild() {
  const [count, setCount] = useState(0);
  return /*#__PURE__*/React.createElement("div", null, count, /*#__PURE__*/React.createElement("button", {
    onClick: () => setCount(prev => prev + 1)
  }, "add one"));
}
function App() {
  const [random, setRandom] = useState(() => Math.random() * 10);
  const RenderPart = function () {
    // return CHild();
    return /*#__PURE__*/React.createElement(CHild, null);
  };
  return /*#__PURE__*/React.createElement("div", {
    className: "App"
  }, /*#__PURE__*/React.createElement("h3", null, random), /*#__PURE__*/React.createElement("button", {
    onClick: () => {
      setRandom(Math.random() * 10);
    }
  }, "refresh"), /*#__PURE__*/React.createElement(RenderPart, null));
}
  1. 使用 renderOtherPart 的方式
"use strict";

function CHild() {
  const [count, setCount] = useState(0);
  return /*#__PURE__*/React.createElement("div", null, count, /*#__PURE__*/React.createElement("button", {
    onClick: () => setCount(prev => prev + 1)
  }, "add one"));
}
function App() {
  const [random, setRandom] = useState(() => Math.random() * 10);
  const renderOtherPart = function () {
    // return CHild();
    return /*#__PURE__*/React.createElement(CHild, null);
  };
  return /*#__PURE__*/React.createElement("div", {
    className: "App"
  }, /*#__PURE__*/React.createElement("h3", null, random), /*#__PURE__*/React.createElement("button", {
    onClick: () => {
      setRandom(Math.random() * 10);
    }
  }, "refresh"), renderOtherPart());
}
在线复现代码地址:

通过运行代可以发现,以 这种方式实例化组件会刷新子组件的state,而 {renderChild()} 这种则不会。原因如下:

从 babel 转译的结果上来看,区别在于使用 的时候会多用一个 React.createElement(RenderPart),在 RenderPart 里面才使用了 React.createElement(Child)。相对于使用 {renderOtherPart()} 的方式,则是只使用了一次 React.createElement(Child),并没有中间的那层 RenderPart。

正是因为多出来的这个 RenderPart,因为是在 hook 组件里的,当父组件 setState 的时候,RenderPart 会被重新创建,内存地址改变,在 react 的 diff 的时候判断为删除了旧组件然后又添加了一个新组件,从而触发了更新逻辑。

为了验证这个问题,我把 RenderPart 从 hook 组件中提取到外部,使它保持不变,或者使用 useCallBack 或者 useMemo 来对 RenderPart 缓存起来,结果验证果然不会重新重置子组件的 state。

const RenderPart = function () {
  const [state, setState] = useState(() => {
    console.log("RenderPart initial state");
    return 0;
  });
  // return CHild();
  console.log("renderPart RenderPart");
  return (
    

{state}

); }; export default function App() { const [random, setRandom] = useState(() => Math.random() * 10); return (

{random}

{/* render component via other render function with Component style would refresh the state of child component*/}
); }

或者使用 useCallBack :

function App() {
  const [random, setRandom] = useState(() => Math.random() * 10);

  const RenderPart = useCallback(function () {
    return ;
  }, []);

  return (
    

{random}

{/* render component via other render function with Component style would refresh the state of child component*/}
); }

你可能感兴趣的:(react.jsjsx前端)