useState用法
點(diǎn)擊button會發(fā)生什么错敢?
分析:
setN
setN一定會修改數(shù)據(jù)x, 將n+1存入x
setN一定會觸發(fā)<App/>重新渲染(render)useState
useState肯定會從x讀取n的最新值x
每個組件有自己的數(shù)據(jù)x, 我們將其命名為state
最簡單的useState的實(shí)現(xiàn)
https://codesandbox.io/s/admiring-bash-utvzo
問題:由于我們所有的數(shù)據(jù)都放在_state怜奖,如果一個組件用了兩個useState就會沖突
比如:
const [n, setN] = myUseState(0);
const [m, setM] = myUseState(0);
這樣我們修改一個另一個也會跟著變
改進(jìn)思路:
把_state做成一個對象
比如_state = {n:0, m: 0}
不行,因?yàn)閡seState(0)并不知道變量叫n還是m把_state做成數(shù)組
比如_state = [0,0]
這樣我們每次通過對應(yīng)的索引來修改就可以
let _state = [];
let index = 0;
const myUseState = num => {
// 之所以要把index賦給一個currentIndex變量是因?yàn)椋? // 我們render完后緊接著要對index+1缺脉,這樣就可以調(diào)用幾次useState數(shù)組里面就有多少個state,
// 也就是index就會和state對應(yīng)
const currentIndex = index;
_state[currentIndex] =
_state[currentIndex] === undefined ? num : _state[currentIndex];
const setN = num1 => {
_state[currentIndex] = num1;
render();
};
index += 1;
return [_state[currentIndex], setN];
};
function render() {
// 之所以render前先置為0是因?yàn)槿绻恢?就會一直累加下去筒占,而頁面根本沒有那么多state
index = 0;
ReactDOM.render(<App />, rootElement);
}
function App() {
const [n, setN] = myUseState(0);
const [m, setM] = myUseState(0);
const x = () => {
setN(n + 1);
};
const y = () => {
setM(m + 1);
};
return (
<div>
{n}
<button onClick={x}>+1</button>
<br />
{m}
<button onClick={y}>+1</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
在線運(yùn)行地址:https://codesandbox.io/s/autumn-hooks-resfq
_state數(shù)組方案的缺點(diǎn)
- useState調(diào)用順序
若第一次渲染時n是第一個晚碾,m是第二個,k是第三個妄荔,則第二次渲染時必須保持順序完全一致泼菌,所以我們不能寫成下面這樣
const [n, setN] = myUseState(0);
let m, setM
if (n % 2 === 1) {
[m, setM] = myUseState(0);
}
上面代碼一開始默認(rèn)頁面中只有一個myUseState,然后我們對n操作這時候又變成了兩個啦租,它兩次渲染不一致就會出現(xiàn)問題,所以我們?nèi)绻趓eact中這樣寫就會直接報錯
現(xiàn)在的代碼還有一個問題:
App用了_state和index, 那其他組件用什么荒揣?
解決辦法:給每個組件創(chuàng)建一個_state和index
放在全局作用域里重名了咋辦篷角?
解決辦法:放在組件對應(yīng)的虛擬節(jié)點(diǎn)對象上
總結(jié)
- 每個函數(shù)組件對應(yīng)一個React節(jié)點(diǎn)
- 每個節(jié)點(diǎn)保存著state和index
- useState會讀取state[index]
- index由useState出現(xiàn)的順序決定
- setState會修改state,并觸發(fā)更新
使用useRef和useContext得到一個貫穿始終的狀態(tài)
- useState每次修改都會生成一個新的state
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--> 得到的n的值就是當(dāng)前的值+1
2). 點(diǎn)擊log再點(diǎn)擊+1--> 得到的值就是上一個n的值
原因:因?yàn)橛卸鄠€n
上圖中有兩條線
第一條就是我們正常操作先點(diǎn)+1再點(diǎn)log系任,它會先觸發(fā)setN恳蹲,setN執(zhí)行就出觸發(fā)render,這時候就會第二次渲染App俩滥,生成一個新的n嘉蕾,n是1,再點(diǎn)擊三秒后就會log(1)霜旧;
第二條就是我們先點(diǎn)log错忱,它會三秒后打印出第一次這個n,這時候的n的值還是0挂据,然后點(diǎn)擊+1觸發(fā)render以清,第二次渲染App,生成一個新的n=1崎逃,然后三秒鐘到打印出第一次的n也就是0
總結(jié):我們每次修改state都不會修改當(dāng)前的state掷倔,而是會重新生成一個新的state,舊的state和新的state有可能同時存在个绍,之后舊的state會被垃圾回收掉
在線代碼運(yùn)行:https://codesandbox.io/s/icy-firefly-imvfu
- 如何實(shí)現(xiàn)一個貫穿始終的狀態(tài)
1). 全局變量
用window.xxx即可勒葱,但太low了
2). useRef
useRef不僅可以用于div,還能用于任意數(shù)據(jù)
function App() {
const nRef = React.useRef(0); // React.useRef(0)就返回一個{current: 0}
const log = () => {
setTimeout(() => console.log(`n: ${nRef.current}`), 3000);
};
return (
<div className="App">
<p>{nRef.current}</p>
<p>
<button onClick={() => (nRef.current += 1)}>+1</button>
<button onClick={log}>log</button>
</p>
</div>
);
}
上面的代碼不管是先點(diǎn)+1
還是先點(diǎn)log
結(jié)果都一樣
問題:但是我們視圖里的0并沒有改變巴柿,可控制臺里的n卻一直在變
原因:nRef.current += 1
不會觸發(fā)render
不會重新渲染App
解決辦法:通過修改state強(qiáng)制更新(不推薦使用)
const [n, setN] = React.useState(-1);
<button
onClick={() => {
nRef.current += 1;
setN(nRef.current);
}}
>
+1
</button>
上面其實(shí)我們并沒有用到n凛虽,所以我們可以簡化一下
const update = React.useState(-1)[1];
<button
onClick={() => {
nRef.current += 1;
update(nRef.current);
}}
>
在線代碼運(yùn)行:https://codesandbox.io/s/crazy-mcclintock-1rb3l
3). useContext
useContext不僅能貫穿始終,還能貫穿不同組件
案例:https://codesandbox.io/s/wizardly-grothendieck-ek0i5
總結(jié)
- 每次重新渲染篮洁,組件函數(shù)就會執(zhí)行
- 對應(yīng)的所有state都會出現(xiàn)「分身」
- 如果你不希望出現(xiàn)分身涩维,可以使用useRef/useContext