如何避免使用钩子在组件中进行不必要的渲染

时间:2020-02-17 22:40:58

标签: reactjs

你好,我有一个主题提供商,其中有两个主题与Emotion provider主题

,我有一个组件头,可以通过useTheme获得该主题:

const Header = () => {
  const Theme = useTheme();
  const [text, setText] = useState('');
  console.log(Theme, 'this is header');
  const handleClick = () => {
    setText('');
  };
  return (
    <Container theme={Theme}>
      <Content theme={Theme} flexdirection={'column'}>
        <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"
        />
        <Top theme={Theme} />
        <NavBar theme={Theme} />
      </Content>
    </Container>
  );
};

然后我将主题作为孩子的道具:

const NavBar = props => {
  console.log(props.theme, 'this is nav bar');
  return (
    <Nav>
      <ul>
        <li>
          <a href="#home">home</a>
        </li>
        <li>
          <a href="#homex">test</a>
        </li>
        <li>
          <a href="#homexv">test</a>
        </li>
      </ul>
    </Nav>
  );
};

但是在此组件中,我有一个文本字段,在键入时会在其中渲染,但是我的主题像gif一样一起渲染:

enter image description here

这是我控制主题的钩子:

export default function useAppTheme(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);
      }
    },
  };
}

和我的主要组成部分:

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

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

  return (
    <ThemeProvider theme={theme}>
      <GlobalStyle />
      <div className="App">
        <Header />
        <button
          css={css`
            background: red;
            width: 100px;
            height: 50px;
            border-radius: 10px;
          `}
          onClick={() =>
            setTheme(
              theme.type === 'dark' ? { type: 'light' } : { type: 'dark' },
            )
          }
        >
          a
        </button>
      </div>
    </ThemeProvider>

1 个答案:

答案 0 :(得分:0)

据我了解,您的代码按预期运行。我想我可以解释如下。

由于状态更新(调用Header onChange),重新呈现了

setText。如果重新渲染了组件,则每个子组件都需要重新渲染(即使道具没有变化)。您可以使用shouldComponentUpdate或成为PureComponentmemoized function(al)组件来退出此重新渲染。如我们所见,NavBar在这里只是一个状态功能组件,它将在每次状态更新时重新呈现。有关详细信息,请参见here

您可以使用React.memo来解决此问题,如下所示:-

const NavBar = React.memo(props => {
  console.log(props.theme, 'this is nav bar');
  return (
    <Nav>
      <ul>
        <li>
          <a href="#home">home</a>
        </li>
        <li>
          <a href="#homex">test</a>
        </li>
        <li>
          <a href="#homexv">test</a>
        </li>
      </ul>
    </Nav>
  );
});

现在,除非实际更改道具,否则不会重新渲染组件。