React 16开始ref的写法有了点变化,但作用不变。虽然ref并不是React的推荐写法,也破坏了数据驱动的思路,但不要试ref为洪水猛兽,优先解决问题。
- 使用ref
- 回调ref
- 传递ref
使用ref
用React.createRef()创建ref,使用ref中的current属性访问节点:
import React, { Component } from 'react'; class RefDemo extends Component { constructor(props) { super(props); this.textInput = React.createRef(); // 使用 React.createRef() 创建 refs } focusTextInput = () => { this.textInput.current.focus(); // 使用 ref 中的 current 属性进行访问 }; render() { // React 会在组件加载时将 DOM 传入 current 属性,在卸载时则会改回 null // ref 的更新会发生在 componentDidMount 或 componentDidUpdate 之前 return ( <div> <input type="text" ref={this.textInput} /> <input type="button" value="聚焦输入框" onClick={this.focusTextInput} /> </div> ); } }
也可以调用children的ref:
import React, { Component } from 'react'; import RefDemo from '../Ref'; class ParentRef extends Component { constructor(props) { super(props); this.textInput = React.createRef(); } componentDidMount() { this.textInput.current.focusTextInput(); } render() { return ( <RefDemo ref={this.textInput} /> ); } }
参照官网,ref对函数式组件的写法上有点约束,项目中多是类组件。当然类组件本身就是函数式组件的语法糖,当不需要生命周期钩子方法或state时,用函数式组件更高效,代码量更少。道理是这么说,but who care~,前端业务特性决定了,你今天写的代码,很快就会成为乐色。优先考虑开发效率,和规范团队的板式代码风格,尽量写类组件吧。
回调ref
React.createRef()创建的是ref对象,并不是真实的Dom节点的引用,所以你需要用current属性获取真实的Dom节点引用。
当然React16之前的回调ref(callback ref)的方式仍旧支持,回调ref的方式直接存储真实的Dom节点的引用:
import React, { Component } from 'react'; export default class CallbackRefDemo extends React.Component { constructor(props) { super(props); this.textInput = null; } setTextInputRef = (ele) => { // 回调 ref this.textInput = ele; }; focusTextInput = () => { if (this.textInput) { this.textInput.focus(); // 直接使用原生 API } }; componentDidMount() { this.focusTextInput(); } render() { // 使用 ref 的回调将 text 输入框的 DOM 节点存储到 React return ( <div> <input type="text" ref={this.setTextInputRef} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
React16之前还有一种string类型的ref,这个可能会废弃,不推荐这样写了。
传递ref
可以使用传递ref方式(forwarding refs),在父组件中访问子组件中DOM,例如聚焦DOM元素,获取子组件中某DOM的位置等。
用React.forwardRef((props, ref) => node)来创建子组件,通过参数ref可以指向具体的DOM节点。
父组件:
import React, { Component } from 'react'; import FancyButton from "./child"; export default class FrowardingRef extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } componentDidMount() { this.myRef.current.focus(); // 操作子组件的 DOM } render() { return ( <FancyButton ref={this.myRef}>子组件</FancyButton> ); } }
子组件:
import React from 'react'; // 用 React.forwardRef 创建子组件,参数为 props 和 ref,返回一个 React 组件 // 从父组件传递下来的 ref 绑定到具体 DOM 上 const FancyButton = React.forwardRef((props, ref) => ( <button className="fancybutton" ref={ref}> {props.children} </button> )); export default FancyButton;
日常开发中常用class组件,因此父子组件间传递ref要用HOC封装一下。本质的原因是ref并不是props,无法像普通props属性一样往下传递,ref更像是一种key,HOC的作用就是传递这个key。
子组件:
import React from 'react'; import SubSubComponent from './subSubComponent'; class SubComponent extends React.Component { render () { const { forwardedRef, } = this.props; return ( <SubSubComponent ref={forwardedRef}} /> ); }; function WithRef (Comp) { return React.forwardRef((props, ref) => { return <Comp {...props} forwardedRef={ref} />; }); } export default WithRef(MarketSubPlanImportModal);
另一种获取子组件的DOM的方法是,使用findDOMNode(),简单粗暴但是不推荐。