useState为什么不能放在判断里,从问题深入到源码,盘它

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_Arrstep挂载在虚拟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.学无止境,永远不要停止前进

  • 我们从面试题入手,自己模拟实现
  • 进一步自己升华,对功能进行补充与思考
  • 回归到源码,我们浅层看了一眼,补充了自己的大脑
  • 我们如今的繁华,不过是站在巨人的臂膀上探索,不盲目自大,也不妄自菲薄,愿我们今后成为自己的巨人!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

家雀安知鸿鹄志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值