react高阶组件
组件间抽象在React组件的构建过程中,常常有这样的场景,有一类功能需要被不同的组件公用,此时,就涉及抽象的话题,在不同设计理念下,有许多的抽象方法,而针对React,我们重点讨论两种:mixin和高阶组件。 mixinmixin的特性一直广泛存在于各种面向对象语言中。比如在Ruby中,include关键词即是mixin。是将一个模块混入到一个另一个模块中,或是一个类中。 为什么编程语言要引入这样一种特性呢? 事实上,包括C++等一些年龄较大的OOP语言,它们都有一个强大但危险的多重继承特性。在现代语言中,为了权衡利弊,大都舍弃了多重继承,只采用单继承,但单继承在实现抽象时有很多不方便的地方,为了弥补缺失,java引入了接口interface。其他一些语言则引入了像mixin的技巧。 封装mixin方法const mixin = function(obj,mixins){ const newObj = obj; newObj.prototype = Object.create(obj.prototype); for(let prop in mixins){ if(mixins.hasOwnProperty(prop)){ newObj.prototype[prop] = mixins[prop]; } } return newObj; } const BigMixin = { fly:()=>{ console.log(‘I can fly‘); } }; const Big = function(){ console.log(‘new big‘); } const FlyBig = mixin(Big,BigMixin); const flyBig = new FlyBig(); //=>‘new big‘ flyBig.fly(); //=> ‘I can fly‘ ? 从上面的代码,我们不难看出,对于广义的mixin方法,就是用赋值的方式将mixin对象里的方法都挂载到原对象上,来实现对对象的混入。 从上述的实现,我们可以联想到 underscore库中的extend 或 lodash库中的 assign方法,或者说ES6中的Object.assign()方法。 在react中使用mixin在官方封装的‘reat-addons-pure-render-mixin‘;在git上没找到相关的有价值的库,应该是react认为mixin是一种反模式形式。 但是发现了react-immutable-render-mixin这样的库。只是很久没维护了,不建议使用 我们可以看到,使用createClass实现的mixin为组件做了两件事。 工具方法:这是mixin的基本功能,如果你想共享一些工具类方法,就可以定义它们,直接在各个组件中使用。 ES6 Classes 与 decorator然而,使用我们推荐的ES6 classes形势构建组件时,它并不支持mixin。React文档中也未能给出解决方法。 要在class的基础上封装mixin,就要说到class的本质。ES6并没有改变js面向对象方法基于原型的本质,不过再次智商提供了一些语法糖。class就是其中之一。 对于是按mixin方法来说,这就没什么不一样了。接下来我们来聊聊另一个语法糖decorator。正巧可以用来实现class的mixin。 decorator 是ES7定义的新特性,与java 的 pre-defined annotation(预定义注解)相似。但与java 的annotation 不同的是,decorator是运用在运行时的方法,在Redux或其他一些应用层框架中,越来越多的使用decorator以实现对组件的修饰。 这样我们就可以使用@mixin。 import {getOwnPropertyDescriptors} from ‘./private/utils‘; const { defineProperty } = Object; function handleClass(target,mixins){ if(!mixins.length){ throw new SyntaxError(‘@mixin() class .....‘) } for(let i=0;i<mixins.length;i++){ const descs = getOwnPropertyDescriptors(mixins[i]); for(const key in descs){ if(!(key in target.prototype)){ defineProperty(target.prototype,key,descs[key]) } } } } export default function mixin(...mixins){ if(typeof mixins[0] == ‘function‘){ return handelClass(mixins[0],[]); } else{ return target=>{ return handleClass(target,mixins); } } } ? 可以看到,源代码十分简单,它将每一个mixin对象的方法都跌价到target 对象的原型上以达到mixin的目的,这样,就可以用@mixin来做多个重用模块的叠加了。 对于react,我们自然可以用上述方法来实现mixin。但不幸的是,社区从0.14版本开始渐渐开始剥离mixin。那么,到底是什么原因导致mixin成为反模式了呢? mixin问题
针对这些困扰,React社区提出来新的方式来取代mixin,那就是高阶组件。 高阶组件(Higher-Order Components)高阶组件(HOC)是 React 中用于重用组件逻辑的高级技术。 HOC 本身不是 React API 的一部分。 它们是从 React 构思本质中浮现出来的一种模式。 具体来说,高阶组件是一个函数,能够接受一个组件并返回一个新的组件。 在我们项目中使用react-redux框架的时候,有一个
?
connect
构建一个简单的hocfunction hello (){ console.log("hello i love react ") } function hoc(fn){ return ()=>{ console.log("first"); fn(); console.log("end"); } } hello = hoc(hello); hello(); ? 实现高阶组件的方法实现高阶组件的方法有如下两种:
接下来我们分别来阐述这两种方法。 属性代理属性代理是我们react中常见高阶组件的实现方法,我们通过一个例子来说明: import React,{Component} from ‘react‘; const MyContainer = (WraooedComponent) => class extends Component { render(){ return <WrappedComponent {...this.props} /> } } ? 从这里看到最重要部分是render 方法中返回了传入 WrappedComponent的React组件。这样,我们就可以通过高阶组件来传递props。这种方法即为属性代理。 自然,我们想要使用MyContainer这个高阶组件就变得非常容易: import React,{Component} from ‘react‘; class MyComponent extends Component{ //... } ? export default MyContainer(MyComponent);
这样组件就可以一层层地作为参数被调用,原始组件就具备了高阶组件对它的修饰。就这么简单,保持单个组件封装性的同时还保留了易用性。当然,我们也可以用decorator来转换。 当使用属性代理构建高阶组件时,调用顺序不同于mixin。上述执行生命周期的过程类似于堆栈调用: didmount ->HOC didmount ->(HOCs didmount)->(HOCs will unmount)->HOC will unmount -> unmount 反向继承另一种构建高阶组件的方法称为反向继承,从字面意思上看,它一定与继承性相关。我们同样来看一个简单的实现。 const MyContainer = (WrappedComponent)=>{ class extends WrappedComponent { render(){ return super.render(); } } } ? 如上代码。高阶组件返回的组件继承于 WrappedComponent 。因为被动地继承了 WrappedComponent,所有的调用都会反向,这也是种方法的由来。 这种方法与属性代理不太一样。它通过继承WrappedComponent来实现,方法可以通过super来顺序调用。因为依赖于继承机制。HOC的调用顺序和队列是一样的。 didmount -> HOC didmount ->(HOCs didmount) -> will unmount ->HOC will unmount ->(HOCs will unmount) 在反向继承方法中,高阶组件可以使用 WrappedComponent 引用,这意味着它可以使用 WrappedComponent 的state 、props。生命周期和render方法。但它不能保证完整的子组件树被解析。它有两个比较大的特点,下面我们展开来讲一讲。 渲染劫持渲染劫持就是指的是高阶组件可以控制 WrappedComponent的渲染过程,并渲染各种各样的结果。我们可以在这个过程中在任何React元素输出的结果中读取、增加、修改、删除props,或读取或修改React元素树,或条件显示。又或者用样式包裹元素树 控制state高阶组件可以读取、修改或删除WrappedComponent实例中的state,如果需要的话,也可以增加state。 组件命名当包裹一个高阶组件时,我们失去了原始 WrappedComponent的displayName,而组件名字是方便我们开发与调试的重要属性。 组件参数有时,我们调用高阶组件需要传入一些参数,这可以用非常简单的方式来实现。 import React from ‘react‘ function HOCFactoryFactory(...params){ return function HOCFactory(WrappedComponent){ return class HOC extends Component{ render(){ return <WrappedComponent {...this.props} /> } } } } ? 当你使用的时候,可以这么写:
//or
这也是利用了函数式编程的特征。可见,在React抽象的过程中,处处可见它的影子。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |