问题
问:如下代码输出结果,解释原因。
export default class Index extends React.Component{state={ num:0 }node = nullrender(){return <div ><div ref={(node)=>{this.node = nodeconsole.log('此时的参数是什么:', this.node )}} >ref元素节点</div><button onClick={()=> this.setState({ num: this.state.num + 1 }) } >点击</button></div>}}// 输出结果:// 初始化时执行一次,输出:此时的参数是什么:<div>ref元素节点</div>// 点击按钮时,输出两个结果:此时的参数是什么:null 和 此时的参数是什么:<div>ref元素节点</div>// 修改为,如下代码:export default class Index extends React.Component {state = { num: 0 };node = null;getDom = (node) => {this.node = node;console.log("此时的参数是什么:", this.node);};render() {return (<div><div ref={this.getDom}>ref元素节点</div><button onClick={() => this.setState({ num: this.state.num + 1 })}>点击</button></div>);}}// 输出结果:// 初始化时执行一次,输出:此时的参数是什么:<div>ref元素节点</div>// 点击按钮不会执行,原因: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
<div ref={node => this.node1 = node}>node1 保存 div DOM 节点引用</div><div ref={node => this.node2 = node}>node2 保存 div DOM 节点引用</div>{false && <div ref={node => this.node2 = node}>此出 div DOM 节点已卸载,react 内部调用 safeDeletionRef 后,this.node2 为 null,但是 this.node1 依旧保存着 div DOM 节点的引用。</div>}
TODO:这个问题怎么处理字符串的,还是没有理解
问: ref="node"字符串,为什么会按照函数方式来处理?
答:若ref属性是字符串时,React 会自动绑定一个函数来处理ref逻辑。
react-reconciler/src/ReactChildFiber.js
const ref = function(value) {let refs = inst.refs;if (refs === emptyRefsObject) {refs = inst.refs = {};}if (value === null) {delete refs[stringRef];} else {refs[stringRef] = value;}};
ref="node"最终会被绑定在组件实例的refs属性上。
举例:<div ref="node"></div>ref 函数,在 commitAttachRef 中处理如下:ref(node) // 等同于 inst.refs.node = <div>。
问:被ref 标记的 fiber,在fiber每次更新时都会调用commitDetachRef、commitAttachRef吗?
答:不是。只有 ref 更新时,才会调用调用如上方法更新 ref,原始就在如上两个方法的执行时期。
问:什么是 effectTag????
