React 高阶组件浅析
背景高阶组件的这种写法的诞生来自于社区的实践,目的是解决一些交叉问题(Cross-Cutting Concerns)。而最早时候
官方明显也意识到了使用 高阶函数的定义说到高阶组件,就不得不先简单的介绍一下高阶函数。下面展示一个最简单的高阶函数 const add = (x,y,f) => f(x)+f(y) 当我们调用 x ==> -5 y ==> 6 f ==> abs f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11 用代码验证一下: add(-5,Math.abs); //11 高阶在维基百科的定义如下
高阶组件的定义那么,什么是高阶组件呢?类比高阶函数的定义,高阶组件就是接受一个组件作为参数并返回一个新组件的函数。这里需要注意高阶组件是一个函数,并不是组件,这一点一定要注意。 <!-- more --> 一个简单的高阶组件下面我们来实现一个简单的高阶组件 export default WrappedComponent => class HOC extends Component { render() { return ( <fieldset> <legend>默认标题</legend> <WrappedComponent {...this.props} /> </fieldset> ); } }; 在其他组件中,我们引用这个高阶组件来强化它 export default class Demo extends Component { render() { return ( <div> 我是一个普通组件 </div> ); } } const WithHeaderDemo = withHeader(Demo); 下面我们来看一下 可以看到, 我们改写一下上述的高阶组件代码,增加一个 const getDisplayName = component => component.displayName || component.name || 'Component'; export default WrappedComponent => class HOC extends Component { static displayName = `HOC(${getDisplayName(WrappedComponent)})`; render() { return ( <fieldset> <legend>默认标题</legend> <WrappedComponent {...this.props} /> </fieldset> ); } }; 再次观察
可以看到,该组件原本的名称已经显示在 为高阶组件传参现在,我们的 export default (WrappedComponent,title = '默认标题') => class HOC extends Component { static displayName = `HOC(${getDisplayName(WrappedComponent)})`; render() { return ( <fieldset> <legend>{title}</legend> <WrappedComponent {...this.props} /> </fieldset> ); } }; 之后我们进行调用: const WithHeaderDemo = withHeader(Demo,'高阶组件添加标题'); 此时观察
可以看到,标题已经正确的进行了设置。 当然我们也可以对其进行柯里化: export default (title = '默认标题') => WrappedComponent => class HOC extends Component { static displayName = `HOC(${getDisplayName(WrappedComponent)})`; render() { return ( <fieldset> <legend>{title}</legend> <WrappedComponent {...this.props} /> </fieldset> ); } }; const WithHeaderDemo = withHeader('高阶组件添加标题')(Demo); 常见的HOC 实现方式基于属性代理(Props Proxy)的方式属性代理是最常见的高阶组件的使用方式,上面所说的高阶组件就是这种方式。 export default function GenerateId(WrappedComponent) { return class HOC extends Component { static displayName = `PropsBorkerHOC(${getDisplayName(WrappedComponent)})`; render() { const newProps = { id: Math.random().toString(36).substring(2).toUpperCase() }; return createElement(WrappedComponent,{ ...this.props,...newProps }); } }; } 调用 const PropsBorkerDemo = GenerateId(Demo); 之后我们观察 基于反向继承(Inheritance Inversion)的方式首先来看一个简单的反向继承的例子: export default function (WrappedComponent) { return class Enhancer extends WrappedComponent { static displayName = `InheritanceHOC(${getDisplayName(WrappedComponent)})`; componentWillMount() { // 可以方便地得到state,做一些更深入的修改。 this.setState({ innerText: '我被Inheritance修改了值' }); } render() { return super.render(); } }; } 如你所见返回的高阶组件类( 反向继承允许高阶组件通过 使用高阶组件遇到的问题静态方法丢失当使用高阶组件包装组件,原始组件被容器组件包裹,也就意味着新组件会丢失原始组件的所有静态方法。 Demo.getDisplayName = () => 'Demo'; 之后调用 // 使用高阶组件 const WithHeaderDemo = HOC(Demo); // 调用后的组件是没有 `getDisplayName` 方法的 typeof WithHeaderDemo.getDisplayName === 'undefined' // true 解决这个问题最简单(Yǘ Chǚn)的方法就是,将原始组件的所有静态方法全部拷贝给新组件: export default (title = '默认标题') => (WrappedComponent) => { class HOC extends Component { static displayName = `HOC(${getDisplayName(WrappedComponent)})`; render() { return ( <fieldset> <legend>{title}</legend> <WrappedComponent {...this.props} /> </fieldset> ); } } HOC.getDisplayName = WrappedComponent.getDisplayName; return HOC; }; 这样做,就需要你清楚的知道都有哪些静态方法需要拷贝的。或者你也可是使用hoist-non-react-statics来帮你自动处理,它会自动拷贝所有非React的静态方法: import hoistNonReactStatic from 'hoist-non-react-statics'; export default (title = '默认标题') => (WrappedComponent) => { class HOC extends Component { static displayName = `HOC(${getDisplayName(WrappedComponent)})`; render() { return ( <fieldset> <legend>{title}</legend> <WrappedComponent {...this.props} /> </fieldset> ); } } // 拷贝静态方法 hoistNonReactStatic(HOC,WrappedComponent); return HOC; }; Refs属性不能传递一般来说,高阶组件可以传递所有的props属性给包裹的组件,但是不能传递
同时还强调道:React在任何时候都不建议使用 ref应用 class Demo extends Component { static propTypes = { getRef: PropTypes.func } static getDisplayName() { return 'Demo'; } constructor(props) { super(props); this.state = { innerText: '我是一个普通组件' }; } render() { const { getRef,...props } = this.props; return ( <div ref={getRef} {...props}> {this.state.innerText} </div> ); } } 之后我们进行调用: <WithHeaderDemo getRef={(ref) => { // 该回调函数被作为常规的props属性传递 this.headerDemo = ref; }} /> 虽然这并不是最完美的解决方案,但是 结语这篇文章只是简单的介绍了高阶组件的两种最常见的使用方式:
参考文章:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |