useState原理

首先創(chuàng)建一個App組件槽畔,加入一個按鈕和點擊后顯示的值num,在按鈕上綁定click事件,每次點擊网棍,num++

function App() {
  console.log('---app run again----')
  const [num, setNum] = useState(0)
  console.log('---render----')
  console.log(`num:${num}`)
  return (
    <div className='App'>
      <p>{num}</p>
      <p>
        <button
          onClick={() => {
            setNum(num + 1)
            console.log(num)
          }}
        >
          +1
        </button>
      </p>
    </div>
  )
}
image.png

在首次渲染的時候調(diào)用App() ---> 運行render() ---> 生成虛擬dom ---> 作用于真實dom<br />用戶點擊button ---> 調(diào)用App() --->調(diào)用setNum(n+1) ---> 運行render() ---> dom diff ---> 作用于真實dom

每次調(diào)用App的時候袜炕,useState都會執(zhí)行。<br />


image.png

state是異步的

在控制臺中讹堤,我們可以看到祭刚,打印的num并不是頁面上顯示的結(jié)果,這是因為react中state的更新是異步的丛肮。當(dāng)我們setState后赡磅,react并不會立即將值做出改變,而是將其暫時放入pedding隊列中宝与。react會合并多個state焚廊,然后只render 一次。

<a name="4fH49"></a>

useState 實現(xiàn)

const newUseState = intialValue => {
  let state = intialValue
  console.log('newUseState run...')
  const setState = newValue => {
    state = newValue
    reRender()
  }
  return [state, setState]
}
const reRender = () => {
  ReactDOM.render(<App />, document.getElementById('root'))
}

此時我們习劫,我們在App中使用newUseState

  console.log('---app run again----')
  const [num, setNum] = newUseState(0)
  console.log('---render----')
  console.log(`num:${num}`)

但是咆瘟,發(fā)現(xiàn)什么用都沒有,num 一直是0


image.png

這是由于每次App()調(diào)用后诽里,num就被初始化為0袒餐,如果不想每次調(diào)用App后被初始化,可以在newUseState外邊定義一個臨時變量來存放set之后的值.

let _state = null
const newUseState = intialValue => {
  _state = _state === null ? intialValue : _state
  console.log('newUseState run...')
  const setState = newValue => {
    _state = newValue
    reRender()
  }
  return [_state, setState]
}

此時谤狡,點擊+1后灸眼,num就做出更新。


image.png

如果有兩個 newUseState

 const [num, setNum] = newUseState(0)
 const [m, setM] = newUseState(20)

此時外部變量_state 存放的num墓懂,會被后面的maxNum覆蓋幢炸,變?yōu)?20.

改變newUseState類型

1. 使_state為對象

let _state = { num:0, m:20 }

但是使用newUseState(0)的時候,我們無法知道賦值給的是num還是maxNum

2. 使_state為數(shù)組

let _state = [0, 20]

此時拒贱,我們的newUseState也要進行修改

let _state = []
let index = 0
const newUseState = intialValue => {
  const currentIndex = index
  _state[currentIndex] =
    _state[currentIndex] === undefined ? intialValue : _state[currentIndex]
  console.log('newUseState run...')
  const setState = newValue => {
    _state[currentIndex] = newValue
    console.log('---after-set----')
    console.log(_state)
    reRender()
  }
  index++
  return [_state[currentIndex], setState]
}

我們把m也放到頁面上

function App() {
  console.log('---app run again----')
  const [num, setNum] = newUseState(0)
  const [m, setM] = newUseState(20)
  console.log('---render----')
  console.log(`num:${num}`)
  return (
    <div className='App'>
      <p>{num}</p>
      <p>
        <button
          onClick={() => {
            setNum(num + 1)
            console.log(`num++`)
            console.log(num)
          }}
        >
          num+1
        </button>
      </p>

      <p>{m}</p>
      <p>
        <button
          onClick={() => {
            setM(m + 1)
            console.log(`m++`)
            console.log(m)
          }}
        >
          m+1
        </button>
      </p>
    </div>
  )
}

但是宛徊,此時點擊按鈕不生效


image.png

是由于每次render運行的時候,index還保存著上次的值逻澳,導(dǎo)致數(shù)組變長闸天。應(yīng)該在render函數(shù)觸發(fā)前將index的值變?yōu)?.

const reRender = () => {
  index = 0
  ReactDOM.render(<App />, document.getElementById('root'))
}

此時,達到了我們想要的效果斜做。

image.png

newUseState 使用數(shù)組的'缺陷'

之前苞氮,我們使用數(shù)組和外部變量index,實現(xiàn)了多個newUseState瓤逼,使得組件中能夠使用多個state笼吟。但是實際上還有一些不是那么方便的地方。

1. 只能按順序調(diào)用

在第一次渲染的時候霸旗,第一個值是num贷帮,第二個值是m,那么當(dāng)App()再次被調(diào)用的時候诱告,下一次撵枢,還得保持這個順序,否則就會出錯。先把之前App的代碼微做修改

function App() {
  console.log('---app run again----')
  const [num, setNum] = newUseState(0)
  let m, setM
  if (num % 2 === 0) {
    ;[m, setM] = newUseState(20)
  }
  console.log('---render----')
  console.log(`num:${num}`)
  console.log(`m:${m}`)
  return (
    <div className='App'>
      <p>{num}</p>
      <p>
        <button
          onClick={() => {
            setNum(num + 1)
            console.log(`num++`)
            console.log(num)
          }}
        >
          num+1
        </button>
      </p>

      <p>{m}</p>
      <p>
        <button
          onClick={() => {
            setM(m + 1)
            console.log(`m++`)
            console.log(m)
          }}
        >
          m+1
        </button>
      </p>
    </div>
  )
}

初始化的時候锄禽,m就為undefined

image.png

再次點擊m+1就會報錯


image.png

我們再次將newUseState 換成 React.useState

image.png

此時編輯器就會提示useState被有條件的調(diào)用潜必,hooks必須按照完全一樣的順序渲染。

2.App使用了useState,其他組件用什么

react為每個組件創(chuàng)建了memorisedState和index沃但,并且將其放在對應(yīng)的虛擬dom上磁滚,這樣,假如App()有m宵晚,Example()也可以擁有m垂攘,不會重復(fù)。

1.創(chuàng)建Example組件坝疼,包含和App同樣的m

function Example() {
  const [num, setNum] = useState(0)
  return (
    <>
      <p>examples: {num}</p>
      <p>
        <button
          onClick={() => {
            setNum(num + 1)
            console.log(`num++`)
            console.log(num)
          }}
        >
          example num+1
        </button>
      </p>
    </>
  )
}

2.修改App的return

return (
    <div className='App'>
      <p>{num}</p>
      <p>
        <button
          onClick={() => {
            setNum(num + 1)
            console.log(`num++`)
            console.log(num)
          }}
        >
          num+1
        </button>
      </p>

      <p>{m}</p>
      <p>
        <button
          onClick={() => {
            setM(m + 1)
            console.log(`m++`)
            console.log(m)
          }}
        >
          m+1
        </button>
      </p>

      <div>
        <Example />
      </div>
    </div>
  )

我們點擊各自的num+1搜贤,互不干擾


image.png

useState的set方法每次set的都是不同的值(相當(dāng)于set的分身)

我們創(chuàng)建一個+1 button還有一個log button

function App() {
  const [num, setNum] = useState(0)
  const log = () =>
    setTimeout(() => {
      console.log(`num:${num}`)
    }, 2000)
  return (
    <div className='App'>
      <p>{num}</p>
      <p>
        <button
          onClick={() => {
            setNum(num + 1)
          }}
        >
          +1
        </button>
        <button onClick={log}>log now</button>
      </p>
    </div>
  )
}

當(dāng)我們先點+1谆沃,然后再點log,此時num進行了+1操作钝凶,2秒后打出的num=1也是預(yù)期的結(jié)果


image.png
image.png

但是當(dāng)我們先點擊log,由于是延時2秒觸發(fā)唁影,我們點下2次+1耕陷,此時打出的num竟然是0

image.png
image.png

這是由于當(dāng)num=0時,我們觸發(fā)了log据沈,但是它兩秒后執(zhí)行l(wèi)og(num=0).當(dāng)我們先點+1哟沫,然后在點log時,我們兩秒后觸發(fā)的是log(num=1).set操作的相當(dāng)于每次都是一個副本锌介。

image.png
image.png

解決方法1

1.使用useRef貫穿整個周期
function App() {
  const numRef = useRef(0)
  const log = () =>
    setTimeout(() => {
      console.log(`num:${numRef.current}`)
    }, 2000)
  return (
    <div className='App'>
      <p>{numRef.current}</p>
      <p>
        <button
          onClick={() => {
            numRef.current++
          }}
        >
          +1
        </button>
        <button onClick={log}>log now</button>
      </p>
    </div>
  )
}
image.png
image.png

此時無論先點log還是先點+1嗜诀,都能得到我們預(yù)期的結(jié)果。但是此時孔祸,頁面上的num仍然是0隆敢,因為useRef不會觸發(fā)render函數(shù)。react更傾向于函數(shù)式崔慧,它希望每次操作的并不是同一個值拂蝎,這點有別于vue。

2.強制更新

function App() {
  const numRef = useRef(0)
  const log = () =>
    setTimeout(() => {
      console.log(`num:${numRef.current}`)
    }, 2000)
  const forceUpdate = useState(null)[1]
  return (
    <div className='App'>
      <p>{numRef.current}</p>
      <p>
        <button
          onClick={() => {
            numRef.current++
            forceUpdate(numRef.current)
          }}
        >
          +1
        </button>
        <button onClick={log}>log now</button>
      </p>
    </div>
  )
}

我們創(chuàng)建一個forceUpdate方法惶室,讓其一開始傳入為null温自,之后每次點擊傳入numRef.current,這時就可以強制render,達到我們的預(yù)期效果

image.png
image.png

之前皇钞,我們使用useRef創(chuàng)建了一個貫穿App組件的變量悼泌,并且通過創(chuàng)建一個無用的state,來達到強制更新組件的目的夹界。但是這樣做券躁,并不是很好。

解決方法2:使用useContext創(chuàng)建貫穿不同組件的變量

首先創(chuàng)建兩個子組件ChildA和ChildB

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>
  );
}

改造下chilA和childB的父組件App

function App() {
  const [theme, setTheme] = React.useState("red");
  return (
      <div className={`App ${theme}`}>
        <p>{theme}</p>
        <div>
          <ChildA />
        </div>
        <div>
          <ChildB />
        </div>
      </div>
  );
}

增加兩個css類

.red button {
  background: red;
  color: white;
  width: 100px;
  line-height: 40px;
  height: 40px;
  border-radius: 4px;
}
.blue button {
  background: blue;
  color: white;
  width: 100px;
  line-height: 40px;
  height: 40px;
  border-radius: 4px;
}
image.png
image.png

此時變成這樣。接下來創(chuàng)建App的context也拜,來傳遞給子組件ChildA和ChildB.

const themeContext = React.createContext(null);

function App() {
  const [theme, setTheme] = React.useState("red");
  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={() =>
          setTimeout(() => {
            setTheme('red')
          }, 2000)
        }
      >
        red
      </button>
    </div>
  )
}

function ChildB() {
  const { setTheme } = React.useContext(themeContext)
  return (
    <div>
      <button
        onClick={() =>
          setTimeout(() => {
            setTheme('blue')
          }, 2000)
        }
      >
        blue
      </button>
    </div>
  )
}

此時以舒,ChildA和ChildB中操作的theme都是通過Context傳過來的,也就是它們修改的都是同一個值慢哈。


image.png
image.png

此時點擊后也就能生效了蔓钟。


image.png
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市卵贱,隨后出現(xiàn)的幾起案子滥沫,更是在濱河造成了極大的恐慌,老刑警劉巖键俱,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兰绣,死亡現(xiàn)場離奇詭異,居然都是意外死亡编振,警方通過查閱死者的電腦和手機缀辩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踪央,“玉大人臀玄,你說我怎么就攤上這事〕澹” “怎么了健无?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長液斜。 經(jīng)常有香客問我累贤,道長,這世上最難降的妖魔是什么少漆? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任臼膏,我火速辦了婚禮,結(jié)果婚禮上检疫,老公的妹妹穿的比我還像新娘讶请。我一直安慰自己,他們只是感情好屎媳,可當(dāng)我...
    茶點故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布夺溢。 她就那樣靜靜地躺著,像睡著了一般烛谊。 火紅的嫁衣襯著肌膚如雪风响。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天丹禀,我揣著相機與錄音状勤,去河邊找鬼鞋怀。 笑死,一個胖子當(dāng)著我的面吹牛持搜,可吹牛的內(nèi)容都是我干的密似。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼葫盼,長吁一口氣:“原來是場噩夢啊……” “哼残腌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贫导,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤抛猫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后孩灯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闺金,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年峰档,在試婚紗的時候發(fā)現(xiàn)自己被綠了败匹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡面哥,死狀恐怖哎壳,靈堂內(nèi)的尸體忽然破棺而出毅待,到底是詐尸還是另有隱情尚卫,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布尸红,位于F島的核電站吱涉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏外里。R本人自食惡果不足惜怎爵,卻給世界環(huán)境...
    茶點故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盅蝗。 院中可真熱鬧鳖链,春花似錦、人聲如沸墩莫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狂秦。三九已至灌侣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裂问,已是汗流浹背侧啼。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工牛柒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痊乾。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓皮壁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哪审。 傳聞我的和親對象是個殘疾皇子闪彼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,587評論 2 350

推薦閱讀更多精彩內(nèi)容