最普通的场景
设置元素的ref,获取引用,调用focus()方法
在不同层级的组件间管理
父组件需要获得子组件的引用,需要传给子组件回调,让父组件能够获得引用。
有列表的场景
一种方法是将所有子组件的ref传给父组件,这样需要一个ref数组。由于数组可能动态变化,不能用useRef hook,只能在class组件内实现。数组变化更新ref。
也可以只创建一个ReactRef。渲染列表时,会被聚焦的那一项被赋予ref,由父组件管理这个引用。
更新状态后如何处理按钮事件
我想实现一个需求:编辑框聚焦时按钮显示“完成”,编辑框失焦时显示“新任务”。看起来很好实现,但面临尴尬的问题:当我点击“完成”时,编辑框已经失焦,其实此时应该显示“新任务”了。所以,真实的逻辑应该加上:如果失焦时按钮没有被点击,那么显示“新任务”,如果因为按钮被聚焦而导致编辑框失焦,那么应该显示“完成”直到鼠标松开。
沿着这个思路非常绕,我想了很久都不知道如何处理。稍微理了一下,决定按钮是否显示的条件,不只有编辑框是否聚集,还有“刚才编辑框是否聚焦”。怎么理解“刚才”呢?难道要通过时间来设计逻辑吗?
我想还是要借助一下时间的逻辑。即blur事件触发后,状态在一定时间后变为false,如果按下的是按钮,则终止这个过程,由按钮变更状态。
如何理解一定时间?它可以是较短的时间。但设想一下,即便是50ms,也意味着在50ms内焦点变化没有任何响应。如果50ms内再次聚焦,问题就大了,因为在focus时触发的状态变化其实被丢弃了,这个概念有点类似数据库的丢失修改。会导致即使处于编辑状态,状态也为“非编辑”。
尽管50ms内失焦再聚焦的概率比较小,我们仍然需要考虑,有没有更好的方案?时间维度上,0ms是最好的,逻辑维度上,Blur事件、Focus事件后立即检查是最好的。其实可以做到,只是我们不能为每个元素都绑定Focus,我们需要在Blur里设置Focus后才能执行的操作。了解过JS异步机制就会知道,点击按钮导致焦点转移,这个过程先运行Blur再运行Focus,但两个操作都被放进了异步队列中,如果我们在Blur中设置setTimeout(()=>{},0),最后的执行顺序是Blur-Focus-setTimeout。我们实现了Focus后立即执行一些操作,却不必为所有元素绑定focus。
剩下的问题是如何判断Focus的元素。这一点DOM提供了activeElement的API,我们只需要为按钮添加某个独特的DOM属性,用于判断就好了。
最后我们整理一下。编辑框聚焦时状态变为Editing,焦点转移时触发编辑框的Blur,然后触发activeEleemnt的Focus,之后执行Blur添加的异步操作,依据activeElement判断状态是否置为NonEditing。如果activeElement是按钮,则不改变状态,等按钮Click触发后再改变。
补充:为什么要这样做?如果不对按钮的Click做特殊处理,那么点按钮的时候“完成”立即变成“新任务”,导致触发的onClick事件不符合预期。