当前位置: 首页 > 科技观察

React源码中如何实现受控组件

时间:2023-03-14 09:42:00 科技观察

今天我们将从一个框架开发者的角度来谈谈如何实现受控组件。React中一个简单的受控组件如下:functionApp(){const[num,updateNum]=React.useState(0);constonChange=({target:{value}})=>{updateNum(value);}return()}在onChange中,会更新num,将num作为valueprop传递给,达到控制value的目的。如果你被要求设计,你会做什么?相信大部分同学的第一个想法就是:把数值道具和其他属性道具一样对待就好了。我们知道React内部运行有3个阶段:schedulescheduleupdate阶段diff算法render阶段DOM运行commit阶段假设我们要在onChange中触发update改变className,我们只需要记录要改变的className在render阶段,并在commit阶段执行相应的addClassDOM操作。同理,如果我们想在onChange中触发更新改变值,只需要在render阶段记录要改变的值,在commit阶段执行相应的inputDOM.setAttribute('value',value)操作即可.这个逻辑很流畅。那么事实上呢?直接更改值className的问题只是inputDOM上的一个普通属性。该值与光标在输入框中的位置有关。如果我们直接修改值,属性改变后input的光标输入位置也会丢失,光标会跳到输入框的末尾。想想把1234改成12534,1234-->12534需要先把光标位置移到2,再输入5,如果setAttribute('value','12534'),那么光标就不会停留在5后面而是跳转到4.那么React是怎么解决这个问题的呢?以不受控的形式实现受控组件你没有看错,React以不受控的形式实现了受控组件的逻辑。简单来说,与commit阶段className的受控更新不同,value是完全不受控的,只有在必要的时候才会更新。因为一旦值被更新,光标位置就会丢失。我们稍微修改一下demo,输入是受控组件,值一直为1:输入2,其实会先显示12,然后删除2。只是删除过程是同步的,所以好像输入框里永远只有1。因此,与React不同,对其他组件props的更新将经历schedule-render-commit过程。对于input、textarea、select,React有一个独立的更新路径,这个路径触发的更新称为discreteUpdate。该路径的工作流程如下:首先在不受控的表单中更新表单DOM开始同步优先级的更新更新的值不会像提交阶段的其他props一样作用于DOM调用restoreStateOfTarget方法比较DOM如果实际值(即第1步不受控的值)和第3步更新后的值相同,退出,如果不同则用第3步的值更新DOM,这两个值在什么情况下会一样吗?我们正常的受控组件都是同样的情况:inputvalue={num}onChange={onChange}/>)}这两个值在什么情况下会不同呢?上面的demo中,虽然是受控的,但是并没有调用updateNum更新值:functionApp(){constnum=1;return()}这种情况下,第1步是不受控的值变成了12,第3步控制的值还是1,所以最后DOM值会更新为1。综上所述,我们可以看到,要实现一个完整的前端框架,细节是非常多的。为了实现受控组件,需要从整体更新流程中分离出来,独立实现一套流程。