import React, { useState, useEffect, memo } from "react"; import ReactDOM from "react-dom";
/* Whenever component re-renders, does it mean that React re-renders the real DOM each time? No, React only updates the part of the UI that changed. A render is scheduled by React each time the state of a component is modified. For example, updating state via the setState hook will not happen immediately but React will execute it at the best possible moment.
But calling the render function has some side-effects even if the real DOM is not re-rendered: - the code inside the function is executed each time, which can be time-consuming depending on its content - the diffing algorithm is executed for each component to be able to determine if the UI needs to be updated */
function A() { console.log("A"); return <B />; }
function B() { console.log("B"); return <C />; }
function C() { console.log("C"); return null; }
function D() { console.log("D"); return null; }
/* Each state change in the parent component triggers a re-rendering of the child components even if they did not receive any props.
We can prevent children components from this needless rendering but wraping them inside `React.memo`. They then child won't render on state change of parent. They will re-render only when props passed to them change. Even after wrapping in `memo` they will always render on change of their internal state.
However when child component re-renders then its parent component is not re-rendered. */
import React, { useState, useEffect, memo } from "react"; import ReactDOM from "react-dom";
/* Whenever component re-renders, does it mean that React re-renders the real DOM each time? No, React only updates the part of the UI that changed. A render is scheduled by React each time the state of a component is modified. For example, updating state via the setState hook will not happen immediately but React will execute it at the best possible moment.
But calling the render function has some side-effects even if the real DOM is not re-rendered: - the code inside the function is executed each time, which can be time-consuming depending on its content - the diffing algorithm is executed for each component to be able to determine if the UI needs to be updated */
function A() { console.log("A"); return <B />; }
function B() { console.log("B"); return <C />; }
function C() { console.log("C"); return null; }
function D() { console.log("D"); return null; }
/* Each state change in the parent component triggers a re-rendering of the child components even if they did not receive any props.
We can prevent children components from this needless rendering but wraping them inside `React.memo`. They then child won't render on state change of parent. They will re-render only when props passed to them change. Even after wrapping in `memo` they will always render on change of their internal state.
However when child component re-renders then its parent component is not re-rendered. */
/* Ans: "App" // first render before useEffect() worked "A" // first render "B" // first render "C" // first render "D" // first render "App" // second render after useEffect() worked "A" // second render -> no B because of memo() -> no C because no B "D" // second render */
import React, { useState, useEffect } from "react"; import ReactDOM from "react-dom";
function A({ children }) { console.log("A"); const [state, setState] = useState(0); useEffect(() => { setState((state) => state + 1); }, []); /* Here even on state change of `A` parent component, child components will not re-render. When the parent state changes, parent component re-renders. But it still has the same children prop it got last time, so React doesn’t visit that subtree. And as a result, child component doesn’t re-render. Thus there are two ways to prevent child components from re-rendering. - wrapping them in `memeo` - passing them as `children` prop */ return children; }
import React, { useState, memo, createContext, useEffect, useContext} from 'react' import ReactDOM from 'react-dom'
const MyContext = createContext(0);
function B() { const count = useContext(MyContext) console.log('B') return null } // When the second time render comes, A would not be re-render again for the memo // method, but the computed value contains component B, which called useContext // with a modified value of MyContext. so B would re-render, answer below // "App" "A" "B" "C" "App" "B" "C" const A = memo(() => { console.log('A') return <B/> })
function C() { console.log('C') return null } function App() { // state is change. This same state is passed in // context. So all those compoonets that use this context and // their children will re-render const [state, setState] = useState(0) useEffect(() => { setState(state => state + 1) }, []) console.log('App') return <MyContext.Provider value={state}> <A/> <C/> </MyContext.Provider> }
import React, { Suspense } from 'react' import ReactDOM from 'react-dom'
const resource = (() => { let data = null let status = 'pending' let fetcher = null return { get() { if (status === 'ready') { return data } if (status === 'pending') { fetcher = new Promise((resolve, reject) => { setTimeout(() => { data = 1 status = 'ready' resolve() }, 100) }) status = 'fetching' }
throw fetcher } } })()
function A() { console.log('A1') const data = resource.get() console.log('A2') return <p>{data}</p> }
function Fallback() { console.log('fallback') return null }
import React, { Suspense } from 'react' import ReactDOM from 'react-dom'
const resource = (() => { let data = null let status = 'pending' let fetcher = null return { get() { if (status === 'ready') { return data } if (status === 'pending') { fetcher = new Promise((resolve, reject) => { setTimeout(() => { data = 1 status = 'ready' resolve() }, 100) }) status = 'fetching' }
throw fetcher } } })()
function A() { console.log('A1') const data = resource.get() console.log('A2') return <p>{data}</p> }
function Fallback() { console.log('fallback') return null }
import React, { Suspense } from 'react' import ReactDOM from 'react-dom'
const resource = (() => { let data = null let status = 'pending' let fetcher = null return { get() { if (status === 'ready') { return data } if (status === 'pending') { fetcher = new Promise((resolve, reject) => { setTimeout(() => { data = 1 status = 'ready' resolve() }, 100) }) status = 'fetching' }
throw fetcher } } })()
function A() { console.log('A1') const data = resource.get() console.log('A2') return <p>{data}</p> }
function Fallback() { console.log('fallback') return null }
Suspense component will be rendered when a child ran into async render (but other children will be rendered first) and all children in Suspense will be re-rendered after async resolve
import React, { Suspense } from 'react' import ReactDOM from 'react-dom'
const resource = (() => { let data = null let status = 'pending' let fetcher = null return { get() { if (status === 'ready') { return data } if (status === 'pending') { fetcher = new Promise((resolve, reject) => { setTimeout(() => { data = 1 status = 'ready' resolve() }, 100) }) status = 'fetching' }
throw fetcher } } })()
function A() { console.log('A1') const data = resource.get() console.log('A2') return <p>{data}</p> }
function Fallback() { console.log('fallback') return null }
When re render from state occurs, we rerender A and all consumers of the context. Theres no need to rerender the other children. So only B is rerendered on the context change
"App" // Initial rendering cycle doesn't run any clean up. "useLayoutEffect" "useEffect 1" "useEffect 2" "App" // Re-render "useLayoutEffect cleanup" // useLayoutEffect is first to be cleaned up and immediately executed. "useLayoutEffect" "useEffect 1 cleanup" // Regular useEffects are grouped, cleaned up and then executed for the second rendering cycle. "useEffect 2 cleanup" "useEffect 1" "useEffect 2"
//undefined - because on initial render ref is //set to null and null?.textContent evaluates //to undefined
//“1” because during initial render current of //ref is set to the first div with textContent //value 1, this value is preserved/cached //when //useEffect/setState triggers a re-render
first render, both component will be rendered “A” “B” second render, since children in A doesn’t have any props change, B will return memorized render result directly, not do re-render, hence only A get rendered “A”
Here’s what I understood from those articles, still not 100% sure, but hope can give you some ideas
Essentially, when App render
1 2 3
<A> <B /> </A>
jsx will create a new React element that pass to A
1 2 3 4 5 6
{ type: A, props: { children: { memorized B } } }
Note, A’s props.children is a new object, so even you memorized A, it still re-rendered
when react render down to B, since B’s props didn’t change, so it returned memoized result (even it’s a new React Element to A, it’s rendering result is the same one)
ReactDOM.render(<App/>, document.getElementById('root')) // click the button userEvent.click(screen.getByText('click me'))
答案
1 2 3 4
"render 0" "handler" "render 1" "handler 0"
详细解析
FlushSync flushes the entire tree and actually forces complete re-rendering for updates that happen inside of a call, so you should use it very sparingly. This way it doesn’t break the guarantee of internal consistency between props, state, and refs.
"render 0": logged the first time the DOM is rendered
Then the click event happens
"handler": logged when the handler for the click fires
"render 1": logged first because flushSync causes the DOM to synchronously get re-rendered with the new state
"handler 0": logged after flushSync, but using the state value that was available when this function first initialized