淺析React中的useState

淺析React中的useState

1. 簡(jiǎn)單的 useState 實(shí)現(xiàn)

function App() {    // 簡(jiǎn)單的 +1 案例
    const [n, setN] = React.useState(0)
    return (
        <div className='App'>
            <p>{n}</p>
            <p>
                <button onClick={() => setN(n + 1)}>n+1</button>
            </p>
        </div>
    )
}

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App/>,
    rootElement
);
  1. 首次渲染頁面展示內(nèi)容 0 和 按鈕,會(huì)調(diào)用App函數(shù)
  2. 調(diào)用 App 函數(shù)會(huì)獲得一個(gè)對(duì)象,可以認(rèn)為這個(gè)對(duì)象是一個(gè)虛擬的DOM
  3. 當(dāng)用戶點(diǎn)擊 按鈕時(shí)會(huì)調(diào)用 setN 函數(shù),并且再次調(diào)用 App函數(shù) 渲染App組件
  4. 每次調(diào)用React.useState時(shí)返回的n值應(yīng)該不一樣

嘗試實(shí)現(xiàn) React.useState

let _state = undefined    // 模擬 state

function myUseState(initialValue) {   // 模擬useState
    _state = _state === undefined ? initialValue : _state // 只在第一次使用初始值

    function setState(newValue) {
        _state = newValue
        render()
    }

    return [_state, setState]
}
//   這是對(duì) render 的簡(jiǎn)化
const render = () => ReactDOM.render(<App/>, document.getElementById("root"))

function App() {
    const [n, setN] = myUseState(0)
    return (
        <div className='App'>
            <p>{n}</p>
            <p>
                <button onClick={() => setN(n + 1)}>n+1</button>
            </p>
        </div>
    )
}

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App/>,
    rootElement
);

會(huì)發(fā)現(xiàn)可以實(shí)現(xiàn) n + 1 功能

但是有這樣一個(gè)問題,如果一個(gè)組件用了兩個(gè)useState怎么辦?由于所有數(shù)據(jù)都放在_state,所以會(huì)沖突

const [n,setN] = myUseState(0)
const [m,setM] = myUseState(0)   // _state 會(huì)變成后面的 m
改進(jìn)思路
  • 把_state做成一個(gè)對(duì)象
  • 比如_state ={n: 0, m: 0}
  • 不行,因?yàn)閡seState(0)并不知道變量叫n還是m
  • 把_state做成數(shù)組
  • 比如_state =[0, 0]
  • 貌似可行,我們來試試看
let _state = []   // 存放多個(gè)數(shù)據(jù)
let index = 0     //  數(shù)據(jù)下標(biāo)

function myUseState(initialValue) {
    const currentIndex = index    // 保留當(dāng)前下標(biāo)
    _state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex]   // 只有首次才使用初始值

    function setState(newValue) {     // set函數(shù)
        _state[currentIndex] = newValue
        render()
    }

    index++
    return [_state[currentIndex], setState]
}

const render = () => {
    index = 0     // 每次運(yùn)行 App 函數(shù)之前需要重置index 否則 會(huì)增加_state 數(shù)組長(zhǎng)度
    ReactDOM.render(<App/>, document.getElementById("root"))
}

function App() {
    const [n, setN] = myUseState(0)
    const [m, setM] = myUseState(0)
    return (
        <div className='App'>
            <p>{n}</p>
            <p>
                <button onClick={() => setN(n + 1)}>n+1</button>
            </p>
            <p>{m}</p>
            <p>
                <button onClick={() => setM(m + 1)}>n+1</button>
            </p>
        </div>
    )
}

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App/>,
    rootElement
);
_state 數(shù)組方案的缺點(diǎn)

依賴useState的調(diào)用順序

  • 若第一次渲染時(shí)n是第一個(gè),m是第二個(gè),k是第三個(gè)
  • 則第二次渲染時(shí)必須保證順序完全一致
  • 所以React不允許出現(xiàn)如下代碼,不能在判斷里面使用useState
function App(){
    const [n,setN] = React.useState(0)
    let m,setM
    if(n % 2 === 1){
        [m,setM] = React.useState(0)   // 這句會(huì)報(bào)錯(cuò)
    }
}

代碼還有一個(gè)問題,App用了_state 和 index,那其他組件用什么 ?

答:給每個(gè)組件創(chuàng)建一個(gè)_state 和index

又有問題,放在全局作用域里重名了咋整 ?

答:放在組件對(duì)應(yīng)的虛擬節(jié)點(diǎn)對(duì)象上

2. useState簡(jiǎn)單原理總結(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)則用到了鏈表。

3. useRef 與 useContext

新手 對(duì) 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>
  );
}

當(dāng)我先點(diǎn)擊加一,再 log,與先log立即點(diǎn)擊加一 打印結(jié)果不一樣,后者會(huì)打印舊的值

這是因?yàn)橄赛c(diǎn)擊加一按鈕時(shí)會(huì)觸發(fā) render 函數(shù),相當(dāng)于再次運(yùn)行 App 函數(shù),此時(shí)的 n 為加一后的值;當(dāng)我先點(diǎn)擊log再立即點(diǎn)擊加一時(shí),log函數(shù)中使用的 n 值保留的是舊的值(或者理解為上一個(gè)App函數(shù)中的舊的變量),因此不會(huì)打印新的值,當(dāng)然沒有對(duì)舊值的引用時(shí),舊n會(huì)被垃圾回收掉

那假如我希望有一個(gè) n 能夠貫穿始終,在 React中應(yīng)該怎么辦呢?

  • 使用全局變量

這樣可以,但是太low了

  • useRef

useRef 不僅可以用于div,還能用于任意數(shù)據(jù)

function App() {
  const nRef = React.useRef(0);
  const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
  const uadate = React.useState(null)[1]
  return (
    <div className="App">
      <p>{nRef.current} 這里并不能實(shí)時(shí)更新</p>
      <p>
        <button onClick={() => {nRef.current += 1;update(nRef.current)}>+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}
//  update 為了讓App 重新渲染
  • useContext 不僅能貫穿始終,還能貫穿不同組件

點(diǎn)擊換顏色例子

const themeContext = React.createContext(null);  // 其實(shí)類似全局變量

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>    // themeContext 作用域在這個(gè)標(biāo)簽內(nèi)
  );
}

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)分身
  • 可以用usaRef / useContext等
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ReactDOM.render(<MyComponent/>, document.getElementById("...
    liquan_醴泉閱讀 668評(píng)論 0 0
  • React Hooks Hook是React v16.8的新特性,可以用函數(shù)的形式代替原來的繼承類的形式,可以在不...
    左冬的博客閱讀 877評(píng)論 0 1
  • React Hooks 在了解React Hooks之前, 我們先看一下 Class 和函數(shù)式 的一般寫法 Cla...
    YM雨蒙閱讀 2,967評(píng)論 0 1
  • 前言 自react16.8發(fā)布了正式版hook用法以來,我們公司組件的寫法逐漸由class向函數(shù)式組件+hook的...
    大春春閱讀 4,242評(píng)論 3 7
  • 分析 setState setState 一定會(huì)修改中間變量 state,將 n+1存入 state setSta...
    我是Msorry閱讀 1,231評(píng)論 0 0

友情鏈接更多精彩內(nèi)容