浅析React.useState

213 阅读3分钟

我们在使用react的函数组件时,经常会使用到React.useState,接下来,我会简单的讲一下他的原理。

造一个React.useState

React.useState的原理其实很简单,就是每次arguments[1]执行时,会将值赋给一个变量,然后重新渲染视图。

let newValue
const myUseState = (initialValue) => {
  newValue !== undefined ? newValue = newValue : newValue = initialValue
  const setValue = (computedValue) => {
    newValue = computedValue
    render()
  }
  return ([newValue, setValue])
}

const App = () => {
  const [n, setN] = myUseState(0)
  const addN = () => {
    setN(n + 1)
  }
  return (
    <>
      {n}
      <button onClick={addN}> +1</button>
    </>
  )
}

上面的代码已经可以实现React.useState的部分功能了,但是如果我们有两个数据,那么我们就要简单的修改一下上面的代码

let newValue = []
let index = 0

const myUseState = (initialValue) => {
  let currentIndex = index
  newValue[currentIndex] !== undefined ? newValue[currentIndex] = newValue[currentIndex] : newValue[currentIndex] = initialValue
  const setValue = (computedValue) => {
    newValue[currentIndex] = computedValue
    render()
  }
  index += 1
  return ([newValue[currentIndex], setValue])
}


const App = () => {
  index = 0
  //每次运行时初始化index
  const [n, setN] = myUseState(0)
  const [m, setM] = myUseState(1)
  
  const addN = () => {
    setN(n + 1)
  }
  const addM = () => {
    setM(m + 1)
  }
  return (
    <>
      {n}
      <button onClick={addN}> +1</button>
      <hr/>
      {m}
      <button onClick={addM}> +1</button>
    </>
  )
}

上面的代码通过一个数组,实现了多次调用的功能。


React.useState 的缺点

React.useState 由于运用了数组进数据存储,所以,它依赖index进行定位

当React.useState写在条件语句中时,由于React.useState会重新渲染,所以会导致index被污染,造成数据混乱。

同理,在React.useState中,两次渲染的数据顺序必须相同,否则也会造成数据混乱。

let newValue = []
let index = 0

const myUseState = (initialValue) => {
  let currentIndex = index
  newValue[currentIndex] !== undefined ? newValue[currentIndex] = newValue[currentIndex] : newValue[currentIndex] = initialValue
  const setValue = (computedValue) => {
    newValue[currentIndex] = computedValue
    console.log(currentIndex)
    render()
  }
  index += 1
  return ([newValue[currentIndex], setValue])
}


const App = () => {
  index = 0
  const [n, setN] = myUseState(0)
  let k , setK
  if(n % 2 === 0){
    [k,setK] = myUseState(1)
  }
  //myUseState 被有条件的调用
  //此时,由于myUseState会重新渲染App,
  //所以当k存在时,m的index为2,
  //k不存在时,m的index为1
  //导致了数据的混乱
  
  const [m, setM] = myUseState(2)

  const addN = () => {
    setN(n + 1)
  }
  const addM = () => {
    setM(m + 1)
  }
  return (
    <>
      {n}
      <button onClick={addN}> +1</button>
      <hr/>
      {m}
      <button onClick={addM}> +1</button>
      <hr/>
      {k}
    </>
  )
}

React.useState 每次都会创建出一个新数据

在React函数组件中,每次渲染都会产生一个新的作用域,因此每次setN时,都会在新作用域中产生一个新的n。

const App = () => {
  const [n, setN] = React.useState(0)
  const addN = () => {
    setN(n + 1)
  }
  const log = () => {
    setTimeout(() => {
      console.log(n)
    }, 1000)
  }
  return (
    <>
      {n}
      <button onClick={addN}> +1</button>
      <button onClick={log}> log</button>
    </>
  )
}

上面的代码中,你如果先点击log,在点击+1,你会log出原来的值,而不是 +1 后的值,原因就是log中的n绑定在了旧作用域中。

  • 虽然React并不建议我们去覆盖原先的n,但是我们还是可以这样去做。
  1. 使用React.useRef
// 初始化React.useRef
const nRef = React.useRef(0) // {current:0}

//声明一个方法,手动触发视图更新
const update = React.useState(null)[1]

//每次更新,触发一次update
<button onClick={()=>{nRef.current += 2; update(nRef.current)}}> +1 </button>
  1. 使用React.useContent
const content = React.createContext(null);

function App() {
  const [n, setN] = React.useState(0);
  return (
    <content.Provider value={{n, setN}}>
      {n}
      <Child/>
    </content.Provider>
  );
};

function Child() {
  const {n, setN} = React.useContext(content);
  const addN = () => {
    setN(n + 1);
  };
  return (
    <>
      <button onClick={addN}> +1</button>
    </>
  );
};