基础 - ref - 图1

问题


问:如下代码输出结果,解释原因。

  1. export default class Index extends React.Component{
  2. state={ num:0 }
  3. node = null
  4. render(){
  5. return <div >
  6. <div ref={(node)=>{
  7. this.node = node
  8. console.log('此时的参数是什么:', this.node )
  9. }} >ref元素节点</div>
  10. <button onClick={()=> this.setState({ num: this.state.num + 1 }) } >点击</button>
  11. </div>
  12. }
  13. }
  14. // 输出结果:
  15. // 初始化时执行一次,输出:此时的参数是什么:<div>ref元素节点</div>
  16. // 点击按钮时,输出两个结果:此时的参数是什么:null 和 此时的参数是什么:<div>ref元素节点</div>
  17. // 修改为,如下代码:
  18. export default class Index extends React.Component {
  19. state = { num: 0 };
  20. node = null;
  21. getDom = (node) => {
  22. this.node = node;
  23. console.log("此时的参数是什么:", this.node);
  24. };
  25. render() {
  26. return (
  27. <div>
  28. <div ref={this.getDom}>ref元素节点</div>
  29. <button onClick={() => this.setState({ num: this.state.num + 1 })}>
  30. 点击
  31. </button>
  32. </div>
  33. );
  34. }
  35. }
  36. // 输出结果:
  37. // 初始化时执行一次,输出:此时的参数是什么:<div>ref元素节点</div>
  38. // 点击按钮不会执行,原因:ref 执行相同的函数,不会打上 Ref tag ,不会更新 ref 逻辑,表现就是 getDom 不会执行。

原因:每次更新时,给 ref 赋了新的函数,markRef 执行时,判断 current.ref !== ref(右侧 ref 是:workInProgress.ref),就会给 fiber 打上 Ref tag ,在 commit 阶段,就会更新 ref,执行 ref 回调函数。

继续提问:为什么先要置空 ref ???
答:防止内存泄漏。
先置空 ref ,避免在一次更新中,fiber 节点卸载了,但 ref 引用没有卸载,指向了原来的元素或组件。
先说结论:防止内存泄漏
如果 ref 每次绑定一个全新的 对象(Ref.current,callback)上,而不清理对旧的 dom节点 或者 类实例 的引用,则可能会产生内存泄漏。
参考:facebook

  1. <div ref={node => this.node1 = node}>node1 保存 div DOM 节点引用</div>
  2. <div ref={node => this.node2 = node}>node2 保存 div DOM 节点引用</div>
  3. {
  4. false && <div ref={node => this.node2 = node}>
  5. 此出 div DOM 节点已卸载,react 内部调用 safeDeletionRef 后,this.node2 为 null,但是 this.node1 依旧保存着 div DOM 节点的引用。
  6. </div>
  7. }

TODO:这个问题怎么处理字符串的,还是没有理解
问: ref="node"字符串,为什么会按照函数方式来处理?
答:若ref属性是字符串时,React 会自动绑定一个函数来处理ref逻辑。

react-reconciler/src/ReactChildFiber.js

  1. const ref = function(value) {
  2. let refs = inst.refs;
  3. if (refs === emptyRefsObject) {
  4. refs = inst.refs = {};
  5. }
  6. if (value === null) {
  7. delete refs[stringRef];
  8. } else {
  9. refs[stringRef] = value;
  10. }
  11. };

ref="node"最终会被绑定在组件实例的refs属性上。
举例:<div ref="node"></div>ref 函数,在 commitAttachRef 中处理如下:ref(node) // 等同于 inst.refs.node = <div>


问:被ref 标记的 fiber,在fiber每次更新时都会调用commitDetachRefcommitAttachRef吗?
答:不是。只有 ref 更新时,才会调用调用如上方法更新 ref,原始就在如上两个方法的执行时期。


问:什么是 effectTag????