問題代碼
import {Dispatch, FC, SetStateAction, useState} from 'react'import './App.css'const Child: FC<{ m: number, setM: Dispatch<SetStateAction<number>> }> = (props) => {const {m, setM } = propsreturn (<div><button onClick={() => setM(m + 1)}>addM</button><span>child</span><span>{m}</span></div>)
}
const App = () => {const [n, setN] = useState(0)const [m, setM] = useState(0)return (<div><button onClick={() => setN(n + 1)}>addN</button><span>father</span><span>{n}</span><div><Child m={m} setM={setM}/></div></div>)
}export default App
問題描述
上面這個組件現在存在的問題:
當我們在點擊n+1時,會導致App組件重新渲染,然后Child組件雖然不依賴n,但是由于父組件re-render,他自己也會進行無效的re-render
解決方法一 使用Memo
為了減少這種無效的re-render,我們經常會使用memo()去包裹組件,配合useCallback緩存函數,useMemo緩存其他變量來達到緩存組件,減少無效更新的情況。
import {Dispatch, FC, memo, SetStateAction, useCallback, useState} from 'react'import './App.css'const _Child: FC<{ m: number, setM: Dispatch<SetStateAction<number>> }> = (props) => {const {m, setM } = propsconsole.log('child render')return (<div><button onClick={() => setM(m + 1)}>addM</button><span>child</span><span>{m}</span></div>)
}
const Child= memo(_Child)const App = () => {const [n, setN] = useState(0)const [m, setM] = useState(0)return (<div><button onClick={() => setN(n + 1)}>addN</button><span>father</span><span>{n}</span><div><Child m={m} setM={useCallback(setM, [])}/></div></div>)
}
解決方法二 向下移動state
這個解決方法其實就是將組件粒度變得更細。
將state下沉到與之相關的組件中去,也就是將與該狀態相關的組件抽離成一個單獨的組件。
import {useState} from 'react'import './App.css'const Child = () => {const [m, setM] = useState(0)return (<div><button onClick={() => setM(m + 1)}>addM</button><span>child</span><span>{m}</span></div>)
}const Child2 = () => {const [n, setN] = useState(0)return (<><button onClick={() => setN(n + 1)}>addN</button><span>Child2</span><span>{n}</span></>)
}
const App = () => {return (<div><div><Child2/></div><div><Child/></div></div>)
}export default App
解決方法三 內容提升
像上面那種情況,組件可以單獨抽離是因為知道Child組件不依賴n的狀態 代碼如下。
但是如果我們假設是App中的div依賴n呢。這種情況下其實Child組件依然不應該刷新
import {Dispatch, FC, SetStateAction, useState} from 'react'import './App.css'const Child: FC<{ m: number, setM: Dispatch<SetStateAction<number>> }> = (props) => {const {m, setM } = propsreturn (<div><button onClick={() => setM(m + 1)}>addM</button><span>child</span><span>{m}</span></div>)
}
const App = () => {const [n, setN] = useState(0)const [m, setM] = useState(0)return (<div class={n.toString()}> //父組件依賴n<button onClick={() => setN(n + 1)}>addN</button><span>father</span><span>{n}</span><div><Child m={m} setM={setM}/></div></div>)
}export default App
解決上面代碼的問題
將于n相關的組件放到Father中,然后不相關的作為children傳給Father組件。
這樣在Father組件re-render的時候,由于App(父組件)中的組件沒有變化,所以拿到的children依然是上一次的(沒有發生變化的)所以children部分不會re-render。
這樣就避免了無效的刷新
import {FC, HTMLAttributes, useState} from 'react';export default function App() {return (<Father><Child/></Father>);
}// 將內容提升到該父組件中
interface Props extends HTMLAttributes<HTMLDivElement>{}
const Father:FC<Props>=({children})=> {const [n, setN] = useState(0)return (<div><button onClick={() => setN(n + 1)}>addN</button><span>father</span><span>{n}</span><div>{children}</div></div>)
}const Child = () => {const [m, setM] = useState(0)console.log("執行了")return (<div><button onClick={() => setM(m + 1)}>addM</button><span>child</span><span>{m}</span></div>)
}
解決方法四 內容集中
import {useState} from 'react'import './App.css'const App = () => {const [n, setN] = useState(0)const [m, setM] = useState(0)return (<div className={n.toString()}><div><button onClick={() => setN(n + 1)}>addN</button><span>father</span><span>{n}</span></div>// 將內容提升到該父組件中<div><button onClick={() => setM(m + 1)}>addM</button><span>child</span><span>{m}</span></div></div>)
}export default App