文章目录
useState为什么不能放在判断里,从问题深入到源码,盘它
1.useState基本使用和特性
- 更加轻量的状态管理,每个useState都会维护一个单独的状态
- 当状态变更时,会触发更新,将数据更新。
- useState必须放在顶层,并且不能包含在任何判断里
function App() {
const [J,setJ] =React.useState(0)
const [Y,setY] =React.useState(0)
return (
<div className="App">
stateJ:{J}
<br />
<button onClick={()=>setJ(val=>val+1)}>更新J</button>
</div>
);
}
2.封装实现轻量化useState
1.实现state,setState
- 我们先定义一下一个常量state,和更改数据方法setState
- 由于setState
支持函数回调
,我们需要做下函数的判断,为函数时,将最新state传入回调,并将回调执行,反之亦然 - 再通过useState将变量和方法return出去。这样就实现了内部自己的状态
const intialValue =null
function useState(val){
let state_ = intialValue;
state_=val;
const setState_ =(funcOrOther)=>{
typeof(funcOrOther) === 'function' ? funcOrOther(state_) : state_=funcOrOther;
updateDom()
}
return [state_, setState_]
}
2.实现更新数据
- 虽然数据变了,视图并未更新,我们模拟下试图更新,每次setState完成后,重新render。
- 视图还未变化,原因是每次重新执行useState都会重新初始化,那么我们需要模拟一个外界存储下每次的变量。
let num= 0
const intialValue =null
function useState(val){
let state_ = intialValue;
state_= num || val;
const setState_ =(funcOrOther)=>{
typeof(funcOrOther) === 'function' ? state_=funcOrOther(state_) : state_=funcOrOther;
num=state_
updateDom()
}
return [state_, setState_]
}
function updateDom(name) {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById(name|| 'root')
);
}
updateDom('root')
3.缓存state的值并触发事件
- 这样对于多个useState和显然是不合理的,我们尝试用对象存储,但是对象存储是无序的,不能识别是哪个useState执行,那么我们只能用数组了
- 点击后发现还是没变更,原因是step没有清空,一直在累加,在更新时清空下;
- 改写下,state_Arr存储值,step,标记步数
let step =0;
let state_Arr=[]
const intialValue =null
function useState(val){
const currentIndex = step
state_Arr[currentIndex] =state_Arr[currentIndex] || val
const setState_ = newValue => {
state_Arr[currentIndex] = typeof(newValue) === 'function'? newValue(state_Arr[currentIndex]) : newValue
updateDom()
}
step++
return [state_Arr[currentIndex],setState_]
}
function updateDom(name) {
step =0
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById(name || 'root')
);
}
4.usestate函数不能被判断,且必须在顶层的真正原因
- 我们加个判断试下,显然当条件不成立时,setY是找不到的,也就是就会报错。这样就很好理解他为啥不能加判断了
- 放在顶层也是这个道理
function App() {
const [J,setJ] =useState(0)
let Y,setY;
if(J%2 ==0){
[Y,setY] =useState(10)
}
console.log(J,Y,'render');
return (
<div className="App">
stateJ:{J}
stateK:{Y}
<br />
<button onClick={()=>{setJ(val=>val+1)}}>更新J</button>
<button onClick={()=>{setY(val=>val+1)}}>更新Y</button>
</div>
);
}
5.完整实现代码
function App() {
const [J,setJ] =useState(0)
let Y,setY;
if(J%2 ==0){
[Y,setY] =useState(10)
}
console.log(J,Y,'render');
return (
<div className="App">
stateJ:{J}
stateK:{Y}
<br />
<button onClick={()=>{setJ(val=>val+1)}}>更新J</button>
<button onClick={()=>{setY(val=>val+1)}}>更新Y</button>
</div>
);
}
let step =0;
let state_Arr=[]
const intialValue =null
function useState(val){
const currentIndex = step
state_Arr[currentIndex] =state_Arr[currentIndex] || val
const setState_ = newValue => {
state_Arr[currentIndex] = typeof(newValue) === 'function'? newValue(state_Arr[currentIndex]) : newValue
updateDom()
}
step++
return [state_Arr[currentIndex],setState_]
}
function updateDom(name) {
step =0
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById(name || 'root')
);
}
updateDom('root')
3.进阶的useState
1.React 虚拟dom节点挂载state_Arr和步数step
- 目前代码公用了全局的状态和步数,所以我们需要每个组件拥有自己的状态和步数
- react会将每个组件处理成一个DOM虚拟节点,我们就需要将
state_Arr
和step
挂载在虚拟DOM上,然后按照顺序进行更新- 顺便补充下虚拟dom的过程,从数据变更到最终渲染。咱们的方法就是挂载在dom节点上的
2.useState的setState_是否能增加一个类似class组件setState({},callback)完成之后的回调?
- 两个修改同一属性连续的setState方法,将只执行最后的。显然这样很不友好
- 我们虽然可以通过外面套个定0的定时器解决。但是setState,提供了第二个参数setState(value,finishCallback)
class App_ extends React.PureComponent {
state = {
count: 1,
};
onClick = () => {
console.log(this.state,'sbefore');
this.setState({count: this.state.count + 1,});
console.log(this.state,'middle');
this.setState({count: this.state.count + 1,});
console.log(this.state,'after');
};
onClickCb = () => {
console.log(this.state,'sbefore');
this.setState({count: this.state.count + 1,},()=>{
console.log(this.state,'middle');
this.setState({count: this.state.count + 1,});
});
console.log(this.state,'after');
};
render() {
const { count } = this.state;
return (
<div>
<span>{count}</span>
<button onClick={this.onClick}>点击</button>
<button onClick={this.onClickCb}>点击</button>
</div>
);
}
}
解决方案
- 在我们封装的函数里,在更新完数据后,在触发下穿进去的函数
- 或者利用useRef,拿到真实都dom节点,然后再根据原生方法
function useState(val){
const currentIndex = step
state_Arr[currentIndex] =state_Arr[currentIndex] || val
const setState_ = (newValue,endChangeCallback) => {
state_Arr[currentIndex] = typeof(newValue) === 'function'? newValue(state_Arr[currentIndex]) : newValue;
// 模仿 setState增加回调
endChangeCallback(state_Arr[currentIndex])
updateDom()
}
step++
return [state_Arr[currentIndex],setState_]
}
4.源码剖析
1.前置知识:为什么函数组件比类组件更加轻量,以及判断是否为class函数
- 模拟calss函数编译过程,可以看到,需要:
new 一个新实例,再执行实例的render方法
// A 类
class A extends React.compont {
render(){
renurn <div>A</div>
}
}
// React 内部编译
const instance = new A(props); // A {}
const result = instance.render(); // <div>A</div>
- 模拟函数组件实现过程,函数组件只需要将函数执行即可
function B() {
return <div>B</div>
}
const result = B(props); // <div>B</div>
- react 会通过isReactComponent方法去检查是否是class组件
console.log(A.prototype.isReactComponent)
2.前置知识:React Fiber进程详细切片并高优时间插入执行
- 概念:react 的闲杂所有渲染都由fiber来调度,fiber是
比线程还细的控制粒度,旨在对渲染过程做更精细的tiao'zheng
- 原因:stack reconciler 渲染是
从顶层向下的递归mount/update,无法中断,持续占用主线程,相应渲染就可能无法及时处理,且渲染过程中无优先级而言
, - 解决问题:
- 高耗时任务切片,虽然对任务进行切片分成多个小任务,但是
总时间并未改变
- 每个切片任务执行完,会将线程主动权交给react,由react去调度下个执行任务(线程可断)
- react会先检查否有需要高优的紧急任务队列,清空高优队列后。再继续执行(拥有优先级)
- 依赖进化:fiber的这种机制,显然需要更多的上下文去支持,原有的vDom Tree 已经难以满足,于是fiber tree 应运而生。树更新的过程就是根据输入数据以及现有的数据构造出新的fiber tree 。核心就是,
fiber的节点上,一个key,这个key用来存储节点最终状态的state,react会根据当前组件最新的state,然后复制给组件,在执行render
3.前置知识:setState 为什么能调用React 渲染器进行更新
- 概念:React 渲染器拥有一个独立与目标平台的特性,因为react体系的复杂性和目标平台的多样性,除少数自定义组件外,大多数实现都在渲染器中
- 原因:尽管setState1 定义在react内部,因为它能读取react dom 设置的this.updater,然react DOM 来安排并处理更新,所以才能调用更新dom
comPonent.setState=fnnction(partState,callback,'setState'){
this.updater.enqueueSetState(this,partState,callback,'setState')
}
4.解开面纱
1.进入usestate 源码,感觉熟悉,先调用一个函数,来存值并把方法暴露出来
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
2.resolveDispatcher函数干了什么?我们发现他只是将
ReactCurrentDispatcher.current的值赋给了dispatcher
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
3.结合4-3的setState,useState调用的是
ReactCurrentDispatcher.current.useState
,也就是类似内部的this.upoder方法,来更新的
var ReactCurrentDispatcher={
current:null
}
4.beginWork函数中,根据每个fiber节点上的tag值来走不同的逻辑来加载或者更新组件
function beginWork({workInProgress}) {
........
switch(workInProgress.tag){
case IndeterminateComponent:{...}
case FunctionComponent:{...}
case ClassComponent:{...}
}
}
5.hook对象挂载在memorizedStated上来存储函数组建的state
export type Hook = {
memoizedState: any, // 用来记录当前的useState应该返回的结果的
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null, // 缓存对列,存储多次更新行为
next: Hook | null, // 指向下一次的useState对应hook对象
};
5.学无止境,永远不要停止前进
- 我们从面试题入手,自己模拟实现
- 进一步自己升华,对功能进行补充与思考
- 回归到源码,我们浅层看了一眼,补充了自己的大脑
- 我们如今的繁华,不过是站在巨人的臂膀上探索,不盲目自大,也不妄自菲薄,愿我们今后成为自己的巨人!