React高阶组件总结
在多个不同的组件中需要用到相同的功能,这个解决方法,通常有Mixin和高阶组件。 Mixin方法例如: //给所有组件添加一个name属性 var defaultMixin = { getDefaultProps: function() { return { name: "Allen" } } } var Component = React.createClass({ mixins: [defaultMixin],render: function() { return <h1>Hello,{this.props.name}</h1> } }) 但是由于Mixin过多会使得组件难以维护,在React ES6中Mixin不再被支持。 高阶组件是一个接替Mixin实现抽象组件公共功能的好方法。高阶组件其实是一个函数,接收一个组件作为参数, 1. 适用范围广,它不需要es6或者其它需要编译的特性,有函数的地方,就有HOC。 高阶组件和Mixin的效果比对: 高阶组件基本形式:const EnhancedComponent = higherOrderComponent(WrappedComponent); 详细如下: function hoc(ComponentClass) { return class HOC extends React.Component { componentDidMount() { console.log("hoc"); } render() { return <ComponentClass /> } } } //使用高阶组件 class ComponentClass extends React.Component { render() { return <div></div> } } export default hoc(MyComponent); //作为装饰器使用 @hoc export default class ComponentClass extends React.Component { //... } 高阶组件有两种常见的用法: 1. 属性代理(Props Proxy): 高阶组件通过ComponentClass的props来进行相关操作 1. 属性代理(Props Proxy) 属性代理有如下4点常见作用: 1. 操作props (1). 操作props 可以对原组件的props进行增删改查,通常是查找和增加,删除和修改的话,需要考虑到不能破坏原组件。 function ppHOC(WrappedComponent) { return class PP extends React.Component { render() { const newProps = { user: currentLoggedInUser } return <WrappedComponent {...this.props} {...newProps}/> } } } (2). 通过refs访问组件实例 可以通过ref回调函数的形式来访问传入组件的实例,进而调用组件相关方法或其他操作。 //WrappedComponent初始渲染时候会调用ref回调,传入组件实例,在proc方法中,就可以调用组件方法 function refsHOC(WrappedComponent) { return class RefsHOC extends React.Component { proc(wrappedComponentInstance) { wrappedComponentInstance.method() } render() { const props = Object.assign({},this.props,{ref: this.proc.bind(this)}) return <WrappedComponent {...props}/> } } } (3). 提取state 你可以通过传入 props 和回调函数把 state 提取出来,类似于 smart component 与 dumb component。更多关于 dumb and smart component。 function ppHOC(WrappedComponent) { return class PP extends React.Component { constructor(props) { super(props) this.state = { name: ‘‘ } this.onNameChange = this.onNameChange.bind(this) } onNameChange(event) { this.setState({ name: event.target.value }) } render() { const newProps = { name: { value: this.state.name,onChange: this.onNameChange } } return <WrappedComponent {...this.props} {...newProps}/> } } } //使用方式如下 @ppHOC class Example extends React.Component { render() { //使用ppHOC装饰器之后,组件的props被添加了name属性,可以通过下面的方法,将value和onChange添加到input上面 //input会成为受控组件 return <input name="name" {...this.props.name}/> } } (4). 包裹WrappedComponent 为了封装样式、布局等目的,可以将WrappedComponent用组件或元素包裹起来。 function ppHOC(WrappedComponent) { return class PP extends React.Component { render() { return ( <div style={{display: ‘block‘}}> <WrappedComponent {...this.props}/> </div> ) } } } 2. 继承反转(Inheritance Inversion) HOC继承了WrappedComponent,意味着可以访问到WrappedComponent的state,props,生命周期和render方法。如果在HOC中定义了 大致形式如下: function ppHOC(WrappedComponent) { return class ExampleEnhance extends WrappedComponent { ... componentDidMount() { super.componentDidMount(); } componentWillUnmount() { super.componentWillUnmount(); } render() { ... return super.render(); } } } 例如,实现一个显示loading的请求。组件中存在网络请求,完成请求前显示loading,完成后再显示具体内容。 function hoc(ComponentClass) { return class HOC extends ComponentClass { render() { if (this.state.success) { return super.render() } return <div>Loading...</div> } } } @hoc export default class ComponentClass extends React.Component { state = { success: false,data: null }; async componentDidMount() { const result = await fetch(...请求); this.setState({ success: true,data: result.data }); } render() { return <div>主要内容</div> } } (1) 渲染劫持 继承反转这种模式,可以劫持被继承class的render内容,进行修改,过滤后,返回新的显示内容。 通过渲染劫持,你可以完成: 在由 render输出的任何 React 元素中读取、添加、编辑、删除 props 注:在 Props Proxy 中不能做到渲染劫持。 例如下面,过滤掉原组件中的ul元素: function hoc(ComponentClass) { return class HOC extends ComponentClass { render() { const elementTree = super.render(); elementTree.props.children = elementTree.props.children.filter((z) => { return z.type !== "ul" && z; } const newTree = React.cloneElement(elementTree); return newTree; } } } @hoc export default class ComponentClass extends React.Component { render() { const divStyle = { width: ‘100px‘,height: ‘100px‘,backgroundColor: ‘red‘ }; return ( <div> <p style={{color: ‘brown‘}}>啦啦啦</p> <ul> <li>1</li> <li>2</li> </ul> <h1>哈哈哈</h1> </div> ) } } (2) 操作state HOC可以读取,编辑和删除WrappedComponent实例的state,可以添加state。不过这个可能会破坏WrappedComponent的state, export function IIHOCDEBUGGER(WrappedComponent) { return class II extends WrappedComponent { render() { return ( <div> <h2>HOC Debugger Component</h2> <p>Props</p> <pre>{JSON.stringify(this.props,null,2)}</pre> <p>State</p><pre>{JSON.stringify(this.state,2)}</pre> {super.render()} </div> ) } } } (3) 条件渲染 当 this.props.loggedIn 为 true 时,这个 HOC 会完全渲染 WrappedComponent 的渲染结果。(假设 HOC 接收到了 loggedIn 这个 prop) function iiHOC(WrappedComponent) { return class Enhancer extends WrappedComponent { render() { if (this.props.loggedIn) { return super.render() } else { return null } } } } (4) 解决WrappedComponent名字丢失问题 用HOC包裹的组件会丢失原先的名字,影响开发和调试。可以通过在WrappedComponent的名字上加一些前缀来作为HOC的名字, //或 class HOC extends ... { static displayName = `HOC(${getDisplayName(WrappedComponent)})` ... } //getDisplayName function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || ‘Component’ } (5) 实际应用 1. mobx-react就是高阶组件是一个实际应用 @observer装饰器将组件包装为高阶组件,传入组件MyComponent后,mobx-react会对其生命周期进行各种处理,并通过 2. 实现一个从localStorage返回记录的功能 //通过多重高阶组件确定key并设定组件 const withStorage = (key) => (WrappedComponent) => { return class extends Component { componentWillMount() { let data = localStorage.getItem(key); this.setState({data}); } render() { return <WrappedComponent data={this.state.data} {...this.props} /> } } } @withStorage(‘data‘) class MyComponent2 extends Component { render() { return <div>{this.props.data}</div> } } @withStorage(‘name‘) class MyComponent3 extends Component { render() { return <div>{this.props.data}</div> } } 3. 实现打点计时功能 (1). Props Proxy方式 //性能追踪:渲染时间打点 export default (Target) => (props)=>{ let func1 = Target.prototype[‘componentWillMount‘] let func2 = Target.prototype[‘componentDidMount‘]//Demo并没有在prototype上定义该方法,func2为undefined,但是并不会有影响,这样做只是为了事先提取出可能定义的逻辑,保持原函数的纯净 let begin,end; Target.prototype[‘componentWillMount‘] = function (...argus){//do not use arrow funciton to bind ‘this‘ object func1.apply(this,argus);//执行原有的逻辑 begin = Date.now(); } Target.prototype[‘componentDidMount‘] = function (...argus){ func2.apply(this,argus);//执行原有的逻辑 end = Date.now(); console.log(Target.name+‘组件渲染时间:‘+(end-begin)+‘毫秒‘) } return <Target {...props}/>//do not forget to pass props to the element of Target } (2) Inheritance Inversion方式 // 另一种HOC的实现方式 Inheritance Inversion export default Target => class Enhancer extends Target { constructor(p){ super(p);//es6 继承父类的this对象,并对其修改,所以this上的属性也被继承过来,可以访问,如state this.end =0; this.begin=0; } componentWillMount(){ super.componentWilMount && super.componentWilMount();// 如果父类没有定义该方法,直接调用会出错 this.begin = Date.now(); } componentDidMount(){ super.componentDidMount && super.componentDidMount(); this.end=Date.now(); console.log(Target.name+‘组件渲染时间‘+(this.end-this.begin)+‘ms‘) } render(){ let ele = super.render();//调用父类的render方法 return ele;//可以在这之前完成渲染劫持 } } ? ? 参考:https://zhuanlan.zhihu.com/p/24776678?group_id=802649040843051008 https://blog.csdn.net/cqm1994617/article/details/54800360 ? https://blog.csdn.net/xiangzhihong8/article/details/73459720 https://segmentfault.com/a/1190000004598113 https://blog.csdn.net/sinat_17775997/article/details/79087977 https://blog.csdn.net/neoveee/article/details/69212146 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |