useState是React自带的一个方法,用于操作和重新渲染UI
今天我就来刨析以下useState到底是怎么实现的,并且自己构造一个与useState相同的方法myUseState
以实现一个+1的案例为模板
React自带的API useState
const App =()=>{ const [n,setN] = useState(0); return( <div> n:{n} <button onClick={add=>{setN(n+1)}}>+1</button> </div> ) }
const [n,setN] = useState(0);这一句看着是不是很迷惑,即使学了前面的基础知识看到这一句还是有点疑惑
其实useState是一个是函数,0是传入的参数,把传入的参数赋值给了n,而setN也是一个函数
是不是还是有点迷茫???
我们自己构造一个useState函数就可以很好的理解它
自己构造useState函数
首先约定_state就相当于[n,setN]里的n, setState就等于setN_initValue接受调用myUseState传入的参数_state = _state ===undefined?initValue:_state这一句是第一次调用参数时将拿到的参数值赋值给_state;如果不是第一次调用则将最新的_state值赋值给他自己setState就是将拿到的新值进行操作渲染,newState参数就相当于setN(n+1)中的参数n+1;新值变化后就先赋值给_state然后就渲染到页面上return [_state,setState]将得到的新值_state和操作函数setState当作结果给返回n,和setN只不过是接受返回的参数,可以随便定义,但是要与下面使用相匹配
import React from 'react' import ReactDOM from 'react-dom' // initValue是初始值,接收传入的参数n let _state; const myUseState=(initValue)=>{ _state = _state ===undefined?initValue:_state; const setState=(newState)=>{ _state = newState; ReactDOM.render(<App />,document.getElementById('root')); }; return [_state,setState] } const App =()=>{ const [n,setN] = myUseState(0); return( <div> n:{n} <button onClick={add=>{setN(n+1)}}>+1</button> </div> ) } ReactDOM.render(<App />,document.getElementById('root'));
这个时候就有那么一丝丝明悟了
但是这样还是有bug,只能接受和改变一个n,如果再创建一个m, const [n,setN] = myUseState(0),当你点击n+1的时候m也会同时+1
怎样做到接收多个参数呢?点击哪个就让哪个+1;而另一个不变呢?
解决方法:可以使用数组,将多个参数接收;用index判断使用那一个参数
myUseState接收多个参数
把_state变成数组类型,使用index判断改变的是哪一个currentIndex作为中间变量,保存index的值,因为index是变化的(index+=1),如果不使用currentIndex那么_state指向的就是下一个值
在渲染之前要把index归零,否则作为index的下标一直在在+1,会将数组扩大
import React from 'react' import ReactDOM from 'react-dom' // initValue是初始值,接收传入的参数n let _state=[]; let index=0; const myUseState=(initValue)=>{ const currentIndex = index _state[currentIndex] = _state[currentIndex] ===undefined?initValue:_state[currentIndex]; const setState=(newState)=>{ _state[currentIndex] = newState; render(); }; index+=1; return [_state[currentIndex],setState] }; const render=()=>{ index=0 ReactDOM.render(<App />,document.getElementById('root')); } const App =()=>{ const [n,setN] = myUseState(0); const [m,setM] = myUseState(0); return( <> <div> n:{n} <button onClick={()=>{setN(n+1)}}>+1</button> </div> <div> m:{m} <button onClick={()=>{setM(m+1)}}>+1</button> </div> </> ) } ReactDOM.render(<App />,document.getElementById('root'));
这时的setState就比较完善了,但是还是有一些缺点的
_state数组方案缺点:
useState调用顺序:若第一次渲染时n是第一个,m是第二个,k是第三个则第二次渲染时必须保证顺序完全一致;所以不能出现if... else等改变顺序的代码\
总结:
- 每个函数组件对应一个React节点
- 每个节点保存着state和index
- useState会读取state[index]
- index由useState出现的顺序决定setState会修改state,并触发更新
useState注意事项:
1.不可局部更新:
如果state是一个对象,不能更新一部分数据,因为setState不会合并数据,要使用...先将数据赋值过来
2.地址要变:
setState(obj)如果obj地址不变,那么React就认为数据没有变化
3.接受函数:
可以在初始化时(useState)
const[state,setState]= usestate(()=>{ return initialstate })
该函数返回初始state,且只执行一次
setState传入函数
setN( i => i+1 )