问题描述
当没有任何东西用于触发重新渲染组件时,我很难弄清楚发生了什么.
Events.js
当我从它呈现一次的 Event.js
中删除 useState()
时,组件呈现两次,但我需要收下.当我在 Event 组件中使用 useEffect()
时,第四次呈现.
我只是保留了虚拟数据给你填补空白,并试图删除 React.memo
,没有任何反应.我认为问题出在 Event.js
组件上.我也在使用 Context API,但第四次渲染太多了.
App.js
中的 useEffect 正在从 localStorage 获取一些值,我无法直接访问该值,因为默认情况下该值未定义
这里的沙盒代码:
您的应用运行良好.它正在渲染它应该的样子.众所周知:
React 组件会在其 props 或 state 发生变化时重新渲染.
- 初始道具/状态 -->渲染 -->DOM 更新 -->安装
- 道具/状态已更改-->渲染 -->DOM 更新 -->更新...等等
在下面的示例中,它渲染了2 次strong>,这是正确的:
- 第一个(第一个 console.log)是由于初始渲染,state 为
[]
- 第二个(第二个 console.log)是由于 state 更改(由 useEffect 引起)到
['apple', 'banana']
function Events() {const [allEvents, setAllEvents] = React.useState([]);console.log('事件渲染', allEvents);useEffect(() => {setAllEvents(['apple', 'banana']);}, []);返回<>事件</>;}
关于使用 React.memo:
React.memo 只检查道具 变化.如果封装在 React.memo 中的函数组件在其实现中有 useState 或 useContext Hook,当状态或上下文改变时,它仍然会重新渲染.
由于状态的变化,您不能使用React.memo
跳过重新渲染.您只能优化以跳过由 props 更改引起的重新渲染.
但在上面的例子中,你没有从父组件传递的道具,唯一传递给 Events
的道具是那些由 react-router 即路由道具.所以,没有必要使用 React.memo.
这是沙盒,检查console.logs.您只会看到 3 个日志:应用程序渲染"、具有初始状态的事件渲染"、具有新状态的事件渲染".
如果我们从 index.html 中删除 StrictMode, 并在组件中添加以下 console.logs :
App.js -->console.log('App 渲染')Eventets.js -->console.log('事件渲染', allEvents, isLoading)//(这里的allEvents和isLoading是状态变量)
然后访问 http://localhost:3000,我们看到 1 个日志:
应用呈现
现在点击事件",我们看到 3 个日志:
1: Event Rendered, [], true2:事件渲染,[{}, ... 54 项],真3:事件渲染,[{}, ... 54 项],false
这是正确的行为(参考上面写的生命周期顺序):
- 第一个日志:使用初始状态渲染 (
[]
,true
) - 第二个日志:使用新的 allEvents (
54 items
) 和旧的 isLoading (true
) 进行渲染 - 第三个日志:使用旧的 allEvents(
54 项
)和新的 isLoading(false
)进行渲染
以下是现在要问的正确问题:
问题 1:
为什么第 2 次和第 3 次渲染(日志)是分开的,不应该将它们批处理(合并)并一起应用,因为它们是在同一个函数中编写的?
fetch('url').then(() => {//... 代码在这里setAllEvents([...事件])设置加载(假)})
答案:
不,它们不会在上面的代码中批处理.正如 Dan Abramov 所述:
这是实现细节,可能会在未来版本中更改.
在当前版本中,如果您在 React 事件处理程序中,它们将被批处理.React 批处理在 React 事件处理程序期间完成的所有 setState,并在退出自己的浏览器事件处理程序之前应用它们.
在当前版本中,事件处理程序之外的几个 setStates(例如在网络响应中)将不会被批处理.所以在这种情况下你会得到两次重新渲染.
存在一个用于强制批处理的临时 API.如果你写 ReactDOM.unstable_batchedUpdates(() => { this.fn1(); });
那么这两个调用都将被批处理.但我们希望在未来移除这个 API,而是默认批量处理所有内容.
因此,您可以编写(然后在 fetch 中),如果您愿意,它将节省 1 个渲染:
ReactDOM.unstable_batchedUpdates(() => {setAllEvents([...事件])设置加载(假)})
问题 2:
上面引用的 React 事件处理程序是什么?
答案: foo
在下面的例子中.这 2 个集合状态将被批处理.
const foo = () =>{设置所有事件([{_id:'5ede5af03915bc469a9d598e',标题:'jfklsd',},])设置加载(假)}<button onClick={foo}>CLICK</button>
问题 3:
它更新 HTML DOM 的次数是否与它呈现的一样多(打印 console.log)?
答案: 否.React 在更新真实 DOM 之前比较计算出的虚拟 DOM,因此只有那些更新 UI 所需的更改才会应用于真实 DOM.
问题 4:
为什么使用 StrictMode
时渲染会翻倍?
答案: 是的,StrictMode
会故意重复调用render"以及其他一些用于检测副作用的生命周期方法.严格模式检查仅在开发模式下运行;它们不会影响生产构建.
I'm having a really hard time to figure out what's happening when there is nothing being used to trigger re-render the component.
Events.js
Component renders twice when I remove the useState()
from the Event.js
it renders once, but I need to keep it. when I use useEffect()
inside Event components, renders fourth time.
I just kept the dummy data to give you to fill the emptiness and tried to remove React.memo
, nothing happens. the problem is with the Event.js
component I believe. I'm also using the Context API, but forth time rendering is too much.
useEffect inside App.js
is getting some value from the localStorage, I can't access that direct 'cause the value is undefined by default
sandbox code here: https://codesandbox.io/s/event-manager-reactjs-nbz8z?file=/src/Pages/Events/Events.jsThe Events.js
file is located on /Pages/Events/Events.js
example code is below
Event.js ( child component )
function Events() {
// Sate Managing
const [allEvents, setAllEvents] = React.useState(null);
console.log('Rendering EventsJs', allEvents);
React.useEffect(() => {
setAllEvents(['apple', 'banana']);
}, []);
return (
<div className="events">
{ console.log('Event Rendered.js =>') }
</div>
)
}
export default React.memo(Events, (prevProps, nextProps) => {
return true;
} );
App.js ( parent component )
import { BrowserRouter, Route, Redirect } from 'react-router-dom';
function App() {
const [userId, setUserId] = React.useState(null);
React.useEffect(() => {
setUserId(1);
}, []);
// Login
return (
<BrowserRouter>
<Navigation />
<Route path='/events' component={Events} />
{console.log('App Rendered')}
</BrowserRouter>
);
}
export default App;
Error:
Your app is working fine. It is rendering as it should. As we know:
And react component lifecycle order is:
- Initial props/state --> render --> DOM update --> mounted
- props/state changed --> render --> DOM update --> updated ... so on
In the example below, it is rendering 2 times and that's correct:
- First one (first console.log) is due to initial render with state as
[]
- Second one (second console.log) is due to state change (caused by useEffect) to
['apple', 'banana']
function Events() {
const [allEvents, setAllEvents] = React.useState([]);
console.log('Event Rendered', allEvents);
useEffect(() => {
setAllEvents(['apple', 'banana']);
}, []);
return <>Events</>;
}
About using React.memo:
You can not skip re-render using React.memo
due to change in state. You can only optimize to skip re-rendering caused by change in props.
But in the example above, you don't have props passed from the parent component, the only props passed to Events
are those passed by react-router i.e. route props. So, there is no need to use React.memo.
Here is sandbox, check the console.logs. You will see only 3 logs: "App render", "Event render with initial state", "Event render with new state".
EDIT:
If we remove StrictMode from index.html, and add below console.logs in components:
App.js --> console.log('App rendered')
Evenets.js --> console.log('Event rendered', allEvents, isLoading) // (allEvents and isLoading are state variables here)
And go to http://localhost:3000, we see 1 log:
App Rendered
Now click on "Events", we see 3 logs:
1: Event Rendered, [], true
2: Event Rendered, [{}, ... 54 items], true
3: Event Rendered, [{}, ... 54 items], false
which is correct behavior (refer lifecycles order written above):
- 1st log: render with initial state (
[]
,true
) - 2nd log: render with new allEvents (
54 items
) and old isLoading (true
) - 3rd log: render with old allEvents (
54 items
) and new isLoading (false
)
Below are the right questions to ask now:
Question1:
Why 2nd and 3rd render (log) are separate, should not they be batched (merged) and applied together as they are written in the same function?
fetch('url').then(() => {
// ... code here
setAllEvents([...events])
setLoading(false)
})
Answer:
No, they will not be batched in above code. As explained by Dan Abramov:
So, you can write (inside fetch's then), if you want, it will save 1 render:
ReactDOM.unstable_batchedUpdates(() => {
setAllEvents([...events])
setLoading(false)
})
Question2:
What's React event handler in above quote?
Answer: foo
in example below. These 2 set states will be batched.
const foo = () => {
setAllEvents([
{ _id: '5ede5af03915bc469a9d598e', title: 'jfklsd', },
])
setLoading(false)
}
<button onClick={foo}>CLICK</button>
Question3:
Does it update HTML DOM as many times as it renders (prints console.log)?
Answer: No. React compares calculated virtual DOMs before updating real DOM, so only those changes are applied to real DOM which are required to update the UI.
Question4:
Why was rendering doubled when we use StrictMode
?
Answer: Yes, StrictMode
will intentionally double invoke "render" and some other lifecycle methods to detect side-effects. Strict mode checks are run in development mode only; they do not impact the production build.
这篇关于React 组件使用 useState 渲染两次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!