我们在平时使用ref获取一个组件的引用后,就可以直接通过ref调用组件自身的函数,但是对于高阶组件,使用ref调用原始组件的函数就会报错。
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
简而言之:高阶组件是参数为组件,返回值为新组件的函数。
const EnhancedComponent = higherOrderComponent(WrappedComponent);组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
HOC 在 React 的第三方库中很常见,例如 Redux 的 connect 和 Relay 的 createFragmentContainer。
虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。
例子:
定义被connect包装的test类
import {connect} from 'react-redux' import {Component} from 'react' export default class Test extends Component { constructor(props) { super(props); } testFunction =(test='1')=>{ alert(test) } render() { return ( <div>test</div> ) } } function mergeProps(stateProps, dispatchProps, ownProps) { return Object.assign({}, ownProps, stateProps, dispatchProps) } export default connect({}, {},mergeProps,{withRef:true})(Test)在组件加载完成后调用test组件内部testFunction函数
import {Component} from 'react' import Test from './Test' export default class Test2 extends Component { constructor(props) { super(props); } componentDidMount(){ //使用connect提供的getWrappedInstance() this.refs.test.getWrappedInstance().testFunction('test') } render() { return ( <div><Test ref='test' /></div> ) } }借用官网例子
function logProps(WrappedComponent) { class LogProps extends React.Component { componentDidUpdate(prevProps) { console.log('old props:', prevProps); console.log('new props:', this.props); } render() { return <WrappedComponent {...this.props} />; } } return LogProps; }“logProps” HOC 透传(pass through)所有 props 到其包裹的组件,所以渲染结果将是相同的。
class FancyButton extends React.Component { focus() { // ... } // ... } // 我们导出 LogProps,而不是 FancyButton。 // 虽然它也会渲染一个 FancyButton。 export default logProps(FancyButton);有一点需要注意:refs 将不会透传下去。这是因为 ref 不是 prop 属性。就像 key 一样,其被 React 进行了特殊处理。如果你对 HOC 添加 ref,该 ref 将引用最外层的容器组件,而不是被包裹的组件。
这意味着用于我们 FancyButton 组件的 refs 实际上将被挂载到 LogProps 组件:
import FancyButton from './FancyButton'; const ref = React.createRef(); // 我们导入的 FancyButton 组件是高阶组件(HOC)LogProps。 // 尽管渲染结果将是一样的, // 但我们的 ref 将指向 LogProps 而不是内部的 FancyButton 组件! // 这意味着我们不能调用例如 ref.current.focus() 这样的方法 <FancyButton label="Click Me" handleClick={handleClick} ref={ref} />;幸运的是,我们可以使用 React.forwardRef API 明确地将 refs 转发到内部的 FancyButton 组件。React.forwardRef 接受一个渲染函数,其接收 props 和 ref 参数并返回一个 React 节点。
function logProps(Component) { class LogProps extends React.Component { componentDidUpdate(prevProps) { console.log('old props:', prevProps); console.log('new props:', this.props); } render() { const {forwardedRef, ...rest} = this.props; // 将自定义的 prop 属性 “forwardedRef” 定义为 ref return <Component ref={forwardedRef} {...rest} />; } } // 注意 React.forwardRef 回调的第二个参数 “ref”。 // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef” // 然后它就可以被挂载到被 LogProps 包裹的子组件上。 return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; }); }