咱们在平常写React代码的时候,通常状况是用不到Refs这个东西,由于咱们并不直接操做底层DOM元素,而是在render函数里去编写咱们的页面结构,由React来组织DOM元素的更新。node
凡事总有例外,总会有一些很奇葩的时候咱们须要直接去操做页面的真实DOM,这就要求咱们有直接访问真实DOM的能力,而Refs就是为咱们提供了这样的能力。数组
看这个名字也知道,Refs实际上是提供了一个对真实DOM(组件)的引用,咱们能够经过这个引用直接去操做DOM(组件)app
上面有提到,咱们通常状况下是不须要用到这个东西,那具体何时才会用到呢? 看官方建议:ide
- Managing focus, text selection, or media playback. - Triggering imperative animations. - Integrating with third-party DOM libraries.
简单的来讲就是处理DOM元素的focus,文本的选择或者媒体的播放等,以及处罚强制动画或者同第三方DOM库集成的时候。函数
也就是React没法控制局面的时候,就须要直接操做Refs了。动画
咱们通常都是经过一个回调函数的方式,把当前组件的DOM绑定到一个实例变量上,像下面这样:this
class AutoFocusTextInput extends React.Component { constructor(props) { super(props); this.textInput = null; } componentDidMount() { this.textInput.focusTextInput(); } render() { return ( <CustomTextInput ref={ele => { this.textInput = ele}} /> ); } }
在上面的代码中,咱们先声明一个值为null的textInput变量,而后在ref中以回调的方式将组件DOM赋值给textInput。而后就能够经过 this.textInput.focus()这样的性质来直接调用CustomTextInput这个组件的实例方法。rest
可是这个方式有如下两个不太好:code
由于在每次渲染中React都会建立一个新的函数实例。所以,React 须要清理旧的 ref 而且设置新的。
经过将 ref 的回调函数定义成类的绑定函数的方式能够避免上述问题,component
React V16版本新增一个API:React.createRef(); 经过这个API,咱们能够先建立一个ref变量,而后再将这个变量赋值给组件声明中ref属性就行了。
具体看代码:
class CustomTextInput extends React.Component { constructor(props) { super(props); // create a ref to store the textInput DOM element this.textInput = React.createRef(); this.focusTextInput = this.focusTextInput.bind(this); } focusTextInput() { // Explicitly focus the text input using the raw DOM API // Note: we're accessing "current" to get the DOM node this.textInput.current.focus(); } render() { // tell React that we want to associate the <input> ref // with the `textInput` that we created in the constructor return ( <div> <input type="text" ref={this.textInput} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
在上面的代码中,咱们先经过 React.createRef();建立一个ref,并赋值给组件属性textInput(this.textInput),而后在render函数中经过ref={this.textInput}的方式将ref和input这个真实DOM联系起来, 这样就能够经过 this.textInput.current.focus();的方式来直接操做input元素的方法。
在V16版本前,咱们能够直接经过变量访问元素的方法,在V16后,咱们须要经过 this.textInput.current,即真实的DOM是经过current属性来引用的。
若是经过 createRef()这个API赋值给组件的ref,那么引用的就是组件实例;若是是DOM元素,那引用的天然的就是DOM元素了。。
前面咱们说到,在V16版本以前,咱们想要父组件拿到子组件的ref,须要经过一些特殊的方法,V16版本以后,React提供了一种原生的方式来完成这种操做。
这就涉及到React新增的另外一个API: React.forwardRef(), 经过接受一个函数,来传递refs,具体以下:
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); // You can now get a ref directly to the DOM button: const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>;
上面的有点绕,简单来讲,就是咱们建立一个引用,原本是给外面的FancyButton组件的,可是由于React.forwardRef的处理,这个引用被传递给了内部的button元素。这样ref.current的引用由原本的FancyButton实例传递到了button元素自己。
HOC(higher-order components)高阶组件,简单的说,就是经过组件包裹的方式来提到代码复用,高阶组件就是一个函数,且该函数接受一个组件做为参数,并返回一个新的组件。
如下是一个生成高阶组件的函数:
function logProps(WrappedComponent) { class LogProps extends React.Component { render() { return <WrappedComponent {...this.props} />; } } return LogProps; }
logProps是函数,接受一个组件参数,返回一个包裹参数组件的logProps组件。
下面是用法:
class FancyButton extends React.Component { focus() { // ... } // ... } // Rather than exporting FancyButton, we export LogProps. // It will render a FancyButton though. export default logProps(FancyButton);
咱们先声明一个FancyButton的组件,而后将其做为参数传入logProps函数,最后获得的实际上是一个LogProps组件。
接下来咱们使用refs:
import FancyButton from './FancyButton'; const ref = React.createRef(); // The FancyButton component we imported is the LogProps HOC. // Even though the rendered output will be the same, // Our ref will point to LogProps instead of the inner FancyButton component! // This means we can't call e.g. ref.current.focus() <FancyButton label="Click Me" handleClick={handleClick} ref={ref} />;
咱们经过文件引入FancyButton(其实引入的是LogProps组件)而后createRef并指向FancyButton。 本意是但愿引入真正的FancyButton组件,实际上引用的是 外层包裹组件LogProps组件。
咱们能够经过如下改造来完善代码:
function logProps(Component) { class LogProps extends React.Component { render() { const {forwardedRef, ...rest} = this.props; // Assign the custom prop "forwardedRef" as a ref return <Component ref={forwardedRef} {...rest} />; } } // Note the second param "ref" provided by React.forwardRef. // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef" // And it can then be attached to the Component. return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; }); }
如面的代码所示,咱们修改了高阶组件logProps函数的实现方式,在内部组件LogProps的render方法中,给被包裹组件(做为参数传入的组件)添加了来自props的ref。
最终返回的也是一个React.forwardRef处理过的组件,这个组件将ref传递到内部的props中去。
这样,但咱们经过logProps(FancyButton)函数调用的时候,其实返回的是一个通过React.forwardRef处理的组件, 当经过
<FancyButton label="Click Me" handleClick={handleClick} ref={ref} />;
去添加ref的时候, 这个ref其实直接添加到了内部的LogProps组件的forwardedRef属性上,而后在LogProps组件内部,又经过props属性的方式被赋值了 被包裹组件(做为参数的组件,也就是FancyButton组件)。这个传递其实通过了三次。。。。
总的来讲,高阶组件的ref实际上是经过React.forwardRef技术将ref传递到包裹组件logProps上,而后有经过属性传递 传递到真正的FancyButton组件上,两次传递才完成。。。。