升级至React 16后findDOMNode失效问题求助
ReactDOM.findDOMNode的变化及React Toolbox适配方案 首先,先明确React 16对ReactDOM.findDOMNode的几个关键调整,这些是导致你遇到问题的核心原因:
React 16中findDOMNode的主要变化
- 函数组件不再支持:
findDOMNode只能用于类组件实例,若尝试在函数组件上调用它,会直接返回null(严格模式下还会抛出警告)。你的隔离测试用的是类组件所以没问题,但要确认React Toolbox的Ripple组件内部是否嵌套了函数组件导致实例丢失。 - 多节点返回场景下返回
null:React 16允许组件返回数组或Fragment(无包裹元素的多节点),此时findDOMNode(this)会返回null——因为它无法确定要指向哪个节点。如果Ripple组件的render方法返回了多个未被容器包裹的DOM元素,这就会触发这个问题。 - StrictMode下警告增强:虽然不会直接返回
null,但StrictMode会对findDOMNode的使用发出更严格的警告,不过这不是你遇到null的根本原因。
针对React Toolbox Ripple组件的修复思路
结合你提供的Ripple组件代码(第88行调用findDOMNode(this)),这里给出几个具体的修复方案:
1. 直接给根DOM元素添加ref,替换findDOMNode
这是最直接的解决方案,彻底避免依赖findDOMNode的实例指向:
// 修改Ripple组件的代码 class Ripple extends Component { constructor(props) { super(props); this.rootRef = React.createRef(); // 使用React 16.3+的createRefAPI } getDescriptor(x, y) { // 直接通过ref获取DOM节点 const domNode = this.rootRef.current; if (!domNode) return null; // 增加容错判断,避免空指针 const { left, top, height, width } = domNode.getBoundingClientRect(); const { rippleCentered: centered, rippleSpread: spread } = this.props; return { left: centered ? 0 : x - left - (width / 2), top: centered ? 0 : y - top - (height / 2), width: width * spread, }; } render() { // 给根DOM元素绑定ref return ( <div ref={this.rootRef} {...this.props}> {/* 原Ripple组件的内容 */} </div> ); } }
如果需要兼容React 16.3之前的版本,可以改用回调ref:
class Ripple extends Component { constructor(props) { super(props); this.rootRef = null; this.setRootRef = (node) => { this.rootRef = node; }; } getDescriptor(x, y) { const domNode = this.rootRef; // ... 后续逻辑同上 } render() { return ( <div ref={this.setRootRef} {...this.props}> {/* 原内容 */} </div> ); } }
2. 检查Ripple组件的render返回值
如果Ripple组件的render方法返回的是数组或Fragment(无容器包裹的多节点),需要用一个容器元素(比如<div>)把所有子元素包裹起来,这样findDOMNode(this)就能正确指向这个容器节点:
// 原错误写法(返回数组) render() { return [ <span key="1">...</span>, <span key="2">...</span> ]; } // 修复后(包裹容器) render() { return ( <div> <span>...</span> <span>...</span> </div> ); }
3. 确认调用时机,避免在未挂载时调用getDescriptor
虽然你的场景是点击触发,但要确保getDescriptor是在组件componentDidMount之后被调用的。如果Ripple组件内部有在componentWillMount或初始化阶段调用该方法的逻辑,就会导致findDOMNode返回null——因为此时DOM还未挂载完成。
4. 临时替代方案
如果暂时不想修改React Toolbox源码,可以考虑用其他已适配React 16的波纹组件替代,比如Material-UI的Ripple组件,或者自己实现一个简单的波纹效果,避免依赖未升级的第三方组件。
为什么你的隔离测试能正常工作?
你的隔离测试中,Yeah组件返回的是单个<div>元素,且是类组件,所以findDOMNode(this)能正确指向这个DOM节点。而React Toolbox的Ripple组件可能存在上述的多节点返回、函数组件嵌套或者ref未正确绑定的问题,导致在React 16下返回null。
内容的提问来源于stack exchange,提问作者Mythli

