useState的基本用法
function App(){
const [n, setN] = React.useState(0)
return (
<div className='App'>
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
</div>
)
}
- 問自己幾個(gè)問題
1.執(zhí)行setN的時(shí)候會(huì)發(fā)生什么喇澡?n會(huì)變嗎殊校?App()會(huì)重新執(zhí)行嗎?
2.如果App()會(huì)重新執(zhí)行窜醉,那么useState(0)的時(shí)候艺谆,n每次的值會(huì)有不同嗎拜英?
通過console.log就能得到答案!
結(jié)論:n會(huì)變虫给,App()會(huì)重新執(zhí)行侠碧,n每次的值會(huì)有不同,但是n不是被setN改變的药蜻!
分析
- setN
1.setN一定會(huì)修改數(shù)據(jù)x瓷式,將n+1存入x
2.setN一定會(huì)觸發(fā)<App />重新渲染(re-render)
- useState
useState肯定會(huì)從x讀取n的最新值
- x
每個(gè)組件有自己的數(shù)據(jù)x贸典,我們將其命名為state
嘗試自己實(shí)現(xiàn)一個(gè)React.useState
let _state //全局_state用來存儲(chǔ)state的值踱卵,避免重新渲染的時(shí)候被myUseState重置為初始值
const myUseState = initialValue => {
_state = _state === undefined ? initialValue : _state
const setState = (newValue) => {
_state = newValue
render()
}
return [_state, setState]
}
const render = () => {
ReactDOM.render(<App1 />, document.getElementById('root'))
}
function App1(){
const [n, setN] = myUseState(0)
return (
<div className='App'>
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
</div>
)
}
如果一個(gè)組件用了兩個(gè)useState怎么辦?
由于所以數(shù)據(jù)都放在_state里妒挎,所以會(huì)沖突西饵。改進(jìn)思路:把_state做成數(shù)組,比如_state = [0,0]
多個(gè)useState
let _state = [] //全局_state用來存儲(chǔ)state的值庸队,避免重新渲染的時(shí)候被myUseState重置為初始值
let index = 0
const myUseState = initialValue => {
const currentIndex = index
_state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex]
const setState = newValue => {
_state[currentIndex] = newValue
render()
}
index += 1
return [_state[currentIndex], setState]
}
const render = () => {
index = 0
ReactDOM.render(<App1 />, document.getElementById('root'))
}
function App1(){
const [n, setN] = myUseState(0)
const [m, setM] = myUseState(0)
return (
<div className='App'>
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
<p>{m}</p>
<p>
<button onClick={() => setM(m + 1)}>+1</button>
</p>
</div>
)
}
_state數(shù)組方案缺點(diǎn)
- useState調(diào)用順序
若第一次渲染時(shí)n是第一個(gè)闯割,m是第二個(gè),k是第三個(gè)
則第二次渲染時(shí)必須保證順序完全一致
所以React不允許出現(xiàn)如下代碼
function App(){
const [n, setN] = React.useState(0)
let m, setM
if(n % 2 === 1){
/* error:
* React Hook "React.useState" is called conditionally.
* React Hooks must be called in the exact same order in every component render
*原因: 因?yàn)閡seState內(nèi)部原理是把state聲明成一個(gè)數(shù)組宾尚,需要順序一一對(duì)應(yīng)谢澈,如上圖
*/
[m, setM] = React.useState(0)
}
return (
<div className='App'>
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
<p>{m}</p>
<p>
<button onClick={() => setM(m + 1)}>+1</button>
</p>
</div>
)
}
現(xiàn)在的代碼還有一個(gè)問題
- App用了_state和index锥忿,那其他組件用什么?
解決辦法:給每個(gè)組件創(chuàng)建一個(gè)_state和index
- 又有問題:放在全局作用域里重名了怎么辦淹朋?
解決辦法:放在組件對(duì)應(yīng)的虛擬節(jié)點(diǎn)對(duì)象上
總結(jié)
- 每個(gè)函數(shù)組件對(duì)應(yīng)一個(gè)React節(jié)點(diǎn)
- 每個(gè)節(jié)點(diǎn)保存著state和index
- useState會(huì)讀取state[index]
- index 由useState出現(xiàn)的順序決定
- setState會(huì)修改state,并觸發(fā)更新
注意:這里對(duì)React的實(shí)現(xiàn)做了簡(jiǎn)化钉答,React節(jié)點(diǎn)應(yīng)該是FiberNode础芍,_state的真實(shí)名稱是memorizedState数尿,index的實(shí)現(xiàn)則用到了鏈表右蹦,有興趣的可以自行學(xué)習(xí)
閱讀源碼后诊杆,來講講React Hooks是怎么實(shí)現(xiàn)的 - 掘金
n 的分身
function App(){
const [n, setN] = React.useState(0)
const log = () => setTimeout(() => console.log(`n: ${n}`), 3000)
return (
<div className='App'>
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
<button onClick={log}>log</button>
</p>
</div>
)
}
- 兩種操作
1.點(diǎn)擊+1,再點(diǎn)擊log --無bug窥岩,先加1再打印1
2.點(diǎn)擊log宰缤,再點(diǎn)擊+1 --有bug,加1再打印0
問題:為什么log出了舊數(shù)據(jù)
因?yàn)橛卸鄠€(gè)n
貫穿始終的狀態(tài)
- 全局變量
用window.xxx即可朦乏,但是太low
- useRef
useRef不僅可以用于div,還能用于任意數(shù)據(jù)
function App(){
const nRef = React.useRef(0); //{current: 0}
const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
// const [n, setN] = React.useState(null) 簡(jiǎn)化如下
// const setN = React.useState(null)[1] 換個(gè)有意義的變量名如下
const update = React.useState(null)[1] //自己手動(dòng)更新渲染App的關(guān)鍵代碼
return (
<div className="App">
<p>{nRef.current} 這里并不能實(shí)時(shí)更新</p>
<p>
{/*
* 1.nRef.current并不會(huì)重新渲染App氧骤,因?yàn)镽eact是函數(shù)式編程思想,不會(huì)幫你更新刽锤,我們只能自己手動(dòng)更新
* 2.自己造出一個(gè)update(nRef.current)
*/
}
<button onClick={() => {nRef.current += 1; update(nRef.current)}}>+1</button>
<button onClick={log}>log</button>
</p>
</div>
);
}
- useContext
useContext不僅能貫穿始終,還能貫穿不同組件
const themeContext = React.createContext(null)
function App() {
const [theme, setTheme] = React.useState('blue');
return (
<themeContext.Provider value={{theme, setTheme}}>
<div className={`App ${theme}`}>
<p>{theme}</p>
<div>
<ChildA/>
</div>
<div>
<ChildB/>
</div>
</div>
</themeContext.Provider>
);
}
function ChildA() {
const {setTheme} = React.useContext(themeContext)
return (
<div>
<button onClick={() => setTheme('red')}>red</button>
</div>
)
}
function ChildB() {
const {setTheme} = React.useContext(themeContext)
return (
<div>
<button onClick={() => setTheme('blue')}>blue</button>
</div>
)
}
總結(jié)
- 每次重新渲染朦佩,組件函數(shù)就會(huì)執(zhí)行
- 對(duì)應(yīng)的所以state都會(huì)出現(xiàn)【分身】
- 如果你不希望出現(xiàn)分身
- 可以用useRef/useContext等