React Hooks /不必要的组件渲染

时间:2020-02-17 18:31:01

标签: reactjs

我有不必要的渲染问题 基本上,我有一个主题开关的钩子,但是每次我在文本区域中键入内容时,我都会不必要地渲染主题:

enter image description here

基本上我的几乎所有组件都将使用我的主题,所以我认为这可能是有问题的

我的代码:

export default function App() {
  const theme = useTheme();
  const [text, setText] = useState('');
  const handleClick = () => {
    setText('');
  };
  return (
    <ThemeProvider theme={theme}>
      <GlobalStyle />
      <div className="App">
        <Header />
        <button
          css={css`
            background: red;
            width: 100px;
            height: 50px;
            border-radius: 10px;
          `}
          onClick={() =>
            theme.setTheme(
              theme.type === 'dark' ? { type: 'light' } : { type: 'dark' },
            )
          }
        >
          a
        </button>
        <textarea
          onKeyPress={e => {
            if (e.key === 'Enter') {
              e.preventDefault();
            }
            if (e.key === 'Enter') {
              handleClick();
            }
          }}
          value={text}
          onChange={e => setText(e.target.value)}
          placeholder="Digite aqui sua mensagem"
        />
      </div>
    </ThemeProvider>

我的钩子:

export default function useTheme(defaultTheme = lightTheme) {
  const [theme, _setTheme] = useState(getInitialTheme);

  function getInitialTheme() {
    const savedTheme = localStorage.getItem('theme');
    if (savedTheme === 'dark' || savedTheme === 'light') {
      return JSON.parse(savedTheme) === 'dark' ? darkTheme : defaultTheme;
    } else {
      return defaultTheme;
    }
  }

  useEffect(() => {
    localStorage.setItem('theme', JSON.stringify(theme.type));
  }, [theme]);

  return {
    ...theme,
    setTheme: ({ setTheme, ...theme }) => {
      if (theme.type === 'dark') {
        return _setTheme(darkTheme);
      } else {
        return _setTheme(lightTheme);
      }
    },
  };
}

和使用主题的组件:

const Top = props => {
  console.log(props.theme);
  return (
    <HeaderTop>
      <div></div>
    </HeaderTop>
  );
};

const Header = () => {
  const Theme = useTheme();
  return (
    <Container theme={Theme}>
      <Content theme={Theme} flexdirection={'column'}>
        <Top theme={Theme} />
      </Content>
    </Container>
  );
};

1 个答案:

答案 0 :(得分:0)

我已经更新了codesandbox for you here

您的代码存在一些问题:


将主题传递给ThemeProvider

在您的useTheme钩子中,您正在传播theme对象并返回它:

// useTheme.js
return {
  ...theme,
  setTheme: ({ setTheme, ...theme }) => {
    if (theme.type === "dark") {
      return _setTheme(darkTheme);
    } else {
      return _setTheme(lightTheme);
    }
  }
}

// App.js
const theme = useTheme();
<ThemeProvider theme={theme}>

ThemeProvider需要一个简单的对象,因此您应该直接将theme钩子中的useTheme传递出去而不散布,并将其传递给提供者:

// useTheme.js
return {
  theme, // don't spread
  setTheme: ({ setTheme, ...theme }) => {
    if (theme.type === "dark") {
      return _setTheme(darkTheme);
    } else {
      return _setTheme(lightTheme);
    }
  }
}

// App.js
const { theme } = useTheme();
<ThemeProvider theme={theme}>

我还将您的useTheme钩子重命名为useAppTheme,因为它将与您在其他所有组件中使用的Emotion的useTheme钩子相混淆。


在您的App组件中设置状态

App组件是您应用的顶层,因此,如果重新发布,则整个应用也会重新发布。您想避免这种情况,因为使用React的重点是不必每次发生任何更改都重新渲染整个应用程序。

我从您的App组件中删除了任何状态,现在看起来像这样:

/** @jsx jsx */
import { useEffect } from "react";
import { jsx } from "@emotion/core";
import { ThemeProvider } from "emotion-theming";
import GlobalStyle from "./globalStyle";
import Page from "./page";
import useAppTheme from "./useAppTheme";

export default function App() {
  const { theme } = useAppTheme();

  useEffect(() => {
    console.log("rendering app");
  });

  return (
    <ThemeProvider theme={theme}>
      <GlobalStyle />
      <Page />
    </ThemeProvider>
  );
}

我将useEffect留在了那里,以记录App的渲染时间-您只会看到初始化时发生一次。

我将所有逻辑移到了新组件Page上,该组件将在状态更改时重新呈现:

/** @jsx jsx */
import { useState, useEffect } from "react";
import { jsx, css } from "@emotion/core";
import Header from "./header";
import { useTheme } from "emotion-theming";

export default function Page() {
  const theme = useTheme();
  const [text, setText] = useState("");
  const handleClick = () => {
    setText("");
  };

  useEffect(() => {
    console.log("rendering page");
  });

  return (
    <div className="App">
      <Header />
      <textarea
        onKeyPress={e => {
          if (e.key === "Enter") {
            e.preventDefault();
          }
          if (e.key === "Enter") {
            handleClick();
          }
        }}
        value={text}
        onChange={e => setText(e.target.value)}
        placeholder="Digite aqui sua mensagem"
      />
    </div>
  );
}

您会看到,Page组件每次更改时都会重新呈现。这是正确的行为。通常,您应该将本地状态存储在组件树的尽头,所以只有最小的组件才能重新渲染。


主题挂钩重新呈现

您一次text挂接被反复调用,因为您在重新渲染(useTheme)的组件中调用了useTheme。这是预期的。

现在,所有状态都已从App的{​​{1}}中删除(现在重命名为App的情况仅在useTheme组件渲染时被调用一次。

您不应在任何子组件中调用useAppTheme。您可以通过调用Emotion的App钩子从useAppTheme中获取主题。

如果您想更改当前主题,那么更改主题的组件可以调用您的ThemeProvider钩子,但是要知道,每次该组件重新渲染时,您的useTheme钩子都会被调用

我已经移动了将主题设置为useAppTheme组件的按钮:

useAppTheme