我们在使用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,但是我们还是可以这样去做。
- 使用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>
- 使用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>
</>
);
};