useState 原理

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)

  1. 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

  1. 如何實(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓦阐,更是在濱河造成了極大的恐慌蜗侈,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睡蟋,死亡現(xiàn)場離奇詭異踏幻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)戳杀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門该面,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人信卡,你說我怎么就攤上這事隔缀。” “怎么了傍菇?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵猾瘸,是天一觀的道長。 經(jīng)常有香客問我丢习,道長牵触,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任咐低,我火速辦了婚禮揽思,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘见擦。我一直安慰自己钉汗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布锡宋。 她就那樣靜靜地躺著儡湾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪执俩。 梳的紋絲不亂的頭發(fā)上徐钠,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音役首,去河邊找鬼尝丐。 笑死,一個胖子當(dāng)著我的面吹牛衡奥,可吹牛的內(nèi)容都是我干的爹袁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼矮固,長吁一口氣:“原來是場噩夢啊……” “哼失息!你這毒婦竟也來了譬淳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤盹兢,失蹤者是張志新(化名)和其女友劉穎邻梆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绎秒,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浦妄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了见芹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剂娄。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玄呛,靈堂內(nèi)的尸體忽然破棺而出阅懦,到底是詐尸還是另有隱情,我是刑警寧澤把鉴,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布故黑,位于F島的核電站,受9級特大地震影響庭砍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜混埠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一怠缸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钳宪,春花似錦揭北、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至半醉,卻和暖如春疚俱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缩多。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工呆奕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衬吆。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓梁钾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逊抡。 傳聞我的和親對象是個殘疾皇子姆泻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344