我有不必要的渲染问题 基本上,我有一个主题开关的钩子,但是每次我在文本区域中键入内容时,我都会不必要地渲染主题:
基本上我的几乎所有组件都将使用我的主题,所以我认为这可能是有问题的
我的代码:
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>
);
};
答案 0 :(得分:0)
我已经更新了codesandbox for you here。
您的代码存在一些问题:
在您的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
组件是您应用的顶层,因此,如果重新发布,则整个应用也会重新发布。您想避免这种情况,因为使用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