Open libin1991 opened 5 years ago
Vladimir Horowitz - Schumann来自大转转FE*00:00*02:34
高阶部件是一种用于复用组件逻辑的高级技术,它并不是 React API的一部分,而是从React 演化而来的一种模式。 具体地说,高阶组件就是一个接收一个组件并返回另外一个新组件的函数!
这是官方文档说的,我没有截全,因为后面的解释会造成误解,但简单讲高阶组件(函数)就好比一个加工厂,同样的,屏幕、cpu、扬声器、电池,小米手机工厂组装完就是小米手机,魅族手机组装完就是魅族手机,基本材料都是相同的,不同工厂(高阶组件)有不同的实现及产出,当然这个工厂(高阶组件)也可能是针对某个基本材料的处理。 总之产出的结果拥有了输入组件不具备的功能,输入的组件可以是一个组件的实例,也可以是一个组件类,还可以是一个无状态组件的函数。
随着项目越来越复杂,开发过程中,多个组件需要某个功能,而且这个功能和页面并没有关系,所以也不能简单的抽取成一个新的组件,但是如果让同样的逻辑在各个组件里各自实现,无疑会导致重复的代码。比如页面有三种弹窗一个有title,一个没有,一个又有右上角关闭按钮,除此之外别无它样,你总不能整好几个弹窗组件吧,这里除了tilte,关闭按钮其他的就可以做为上面说的基本材料。
代理方式
操纵prop
访问ref(不推荐)
抽取状态
包装组件
继承方式
操纵生命周期
1. importReact from 'react' 2. functionHocRemoveProp(WrappedComponent){ 3. returnclassWrappingComPonent extends React.Component{ 4. render(){ 5. const{ user,...otherProps }=this.props; 6. return<WrappedComponent{...otherProps}/> 7. } 8. } 9. } 10. exportdefaultHocRemoveProp;
接下来我把简化了写法,把匿名函数去掉,同时换成箭头函数
1. importReact from 'react' 2. constHocAddProp=(WrappedComponent,uid)=> 3. class extends React.Component{ 4. render(){ 5. const newProps ={ 6. uid, 7. }; 8. return<WrappedComponent{...this.props} {...newProps} /> 9. } 10. } 11. exportdefaultHocAddProp;
上面HocRemoveProp高阶组件中,所做的事情和输入组件WrappedComponent功能一样,只是忽略了名为user的prop。也就是说,如果WrappedComponent能处理名为user的prop,这个高阶组件返回的组件则完全无视这个prop。
1. const{ user,...otherProps }=this.props;
这是一个利用es6语法技巧,经过上面的语句,otherProps里面就有this.props中所有的字段除了user. 假如我们现在不希望某个组件接收user的prop,那么我们就不要直接使用这个组件,而是把这个组件作为参数传递给HocRemoveProp,然后我们把这个函数的返回结果当作组件来使用 两个高阶组件的使用方法:
1. const newComponent =HocRemoveProp(SampleComponent); 2. const newComponent =HocAddProp(SampleComponent,'1111111');
也可以利用decorator语法糖这样使用
1. importReact,{Component} from 'React'; 2. @HocRemoveProp 3. classSampleComponent extends Component{ 4. render(){} 5. } 6. exportdefaultSampleComponent;
将所有的状态的管理交给外面的容器组件,这个模式就是 抽取状态 外面的容器就是这个高阶组件
1. constHocContainer=(WrappedComponent)=> 2. class extends React.Component{ 3. constructor(props){ 4. super(props) 5. this.state ={ 6. name:'' 7. } 8. } 9. onNameChange =(event)=>{ 10. this.setState({ 11. name: event.target.value 12. }) 13. } 14. render(){ 15. const newProps ={ 16. name:{ 17. value:this.state.name, 18. onChange:this.onNameChange 19. } 20. } 21. return<WrappedComponent{...this.props}{...newProps}/> 22. } 23. } 1. @HocContainer 2. classSampleComponent extends React.Component{ 3. render(){ 4. return<input name="name"{...this.props.name}/> 5. } 6. }
这样当我们在使用这个已经被包裹的input组件(SampleComponent)时候 它的值就被放在了HocContainer高阶组件中,当很多这样的input组件都用这个HocContainer高阶组件时,那么它们的值都将保存在这个HocContainer高阶组件中
1. constHocStyleComponent=(WrappedComponent, style)=> 2. class extends React.Component{ 3. render(){ 4. return( 5. <div style={style}> 6. <WrappedComponent{...this.props}{...newProps}/> 7. </div> 8. ) 9. } 10. }
这样使用
1. importHocStyleComponent from './HocStyleComponent'; 2. const colorSytle ={color:'#ff5555'} 3. const newComponent =HocStyleComponent(SampleComponent, colorSytle);
-代理方式的生命周期的过程类似于堆栈调用:
1. didmount 一> HOC didmount 一>(HOCs didmount)一>(HOCs will unmount)一>HOC will unmount一>unmount
1. constMyContainer=(WrappedComponent)=> 2. class extends WrappedComponent{ 3. render(){ 4. return super.render(); 5. } 6. }
这个例子很简单,相当于把WrappedComponent组件的render方法,
通过super.render()方法吐到了MyContainer 中,可以顺序调用。
继承方式的生命周期的过程类似队列调用:
1. didmount 一> HOC didmount 一>(HOCs didmount)一>will unmount一>HOC will unmount一>(HOCs will unmount)
1.代理方式下WrappedComponent会经历一个完整的生命周期,产生的新组件和参数组件是两个不同的组件,一次渲染,两个组件都会经历各自的生命周期,
2.而在继承方式下,产生的新组件和参数组件合二为一,super.render只是生命周期中的函数,变成一个生命周期。
来看下面的例子你就会明白了。
首先创建一个高阶,在创建一个使用高阶组件的组件,也就是是输入组件,最后我在改变这个输入组件props
1. import* as React from 'react'; 2. constHocComponent=(WrappedComponent)=> 3. classMyContainer extends WrappedComponent{ 4. render(){ 5. if(this.props.time &&this.state.success){ 6. return super.render() 7. } 8. return<div>倒计时完成了...</div> 9. } 10. }
这个高阶组件会直接读取输入组件中的props,state,然后控制了输入组件的render展示 只有在props.time和state.success同时为真的时候才会展示
1. import* as React from 'react'; 2. importHocComponent from './HocComponent' 3. @HocComponent 4. classDemoComponent extends React.Component{ 5. constructor(props){ 6. super(props); 7. this.state ={ 8. success:true, 9. }; 10. } 11. render(){ 12. return <div>我是一个组件</div> 13. } 14. } 15. exportdefaultDemoComponent;
然后调用,递减time数值直到变为0最后页面的效果就是,当然他不是循环的。先展示”我是一个组件“,我设置了两秒,之后展示”倒计时完成“
由此可以看出高阶组件也可以控制state
但是最好要限制这样做,可能会让WrappedComponent组件内部状态变得一团糟。建议可以通过重新命名state,以防止混淆。
1. constHOCPropsComponent=(WrappedComponent)=> 2. class extends WrappedComponent{ 3. render(){ 4. const elementsTree = super.render(); 5. let newProps ={ 6. color:(elementsTree && elementsTree.type ==='div')?'#fff':'#ff5555' 7. }; 8. const props =Object.assign({}, elementsTree.props, newProps) 9. const newElementsTree =React.cloneElement(elementsTree, props, elementsTree.props.children) 10. return newElementsTree 11. } 12. }
这样就传入了新的props
1. React.cloneElement( element,[props],[...children]) 2. 参数:TYPE(ReactElement),[PROPS(object)],[CHILDREN(ReactElement)
克隆并返回一个新的 ReactElement ,新返回的元素会保留有旧元素的 props、ref、key,也会集成新的 props。
1. class addProps extends React.Component{ 2. render(){ 3. const newProps ='uid' 4. returnthis.props.children(newProps) 5. } 6. }
使用方式
1. <addProps> 2. { 3. (argument)=><div>{argument}</div> 4. } 5. </addProps>
感觉很方便,但是每次渲染都会重新定义一个新的函数,如果不想的话就不要定义匿名函数,
1. showUid(argument){ 2. return<div>{argument}</div> 3. }
recompose是一个很流行的库,它提供了很多很有用的高阶组件(小工具),而且也可以优雅的组合它们。
我们有这样一个组件
1. constProfile=({ user })=>( 2. <div> 3. <div>Username:{user.username}</div> 4. <div>Age:{user.age}</div> 5. </div> 6. )
如果想要改变组件接口来接收单个 prop 而不是整个用户对象,可以用 recompose 提供的高 阶组件 flattenProp 来实现。
1. constProfile=({ username,age })=>( 2. <div> 3. <div>Username:{username}</div> 4. <div>Age:{age}</div> 5. </div> 6. )
const ProfileWithFlattenUser = flattenProp('user')(Profile); 现在我们希望同时使用多个高阶组件:一个用于扁平化处理用户 prop,另一个用于重命名用 户对象的单个 prop。 此时 recompose 库提供的 compose 函数就派上用场了。
1. const enhance = compose( 2. flattenProp('user'), 3. renameProp('username','name') 4. )
然后按照以下方式将它应用于原有组件:
1. constEnhancedProfile= enhance(Profile)
还可以将 compose 函数用 在我们自己的高阶组件上,甚至结合使用都可以:
1. const enhance = compose( 2. flattenProp('user'), 3. renameProp('username','name'), 4. withInnerWidth 5. )
我们将从Recompose库中使用withStateHandlers高阶组件。 它将允许我们将组件状态与组件本身隔离开来。 我们将使用它为电子邮件,密码和确认密码字段添加表单状态,以及上述字段的事件处理程序。
1. import{ withStateHandlers, compose } from "recompose"; 2. const initialState ={ 3. email:{ value:""}, 4. password:{ value:""}, 5. confirmPassword:{ value:""} 6. }; 7. const onChangeEmail = props => event =>({ 8. email:{ 9. value: event.target.value, 10. isDirty:true 11. } 12. }); 13. const onChangePassword = props => event =>({ 14. password:{ 15. value: event.target.value, 16. isDirty:true 17. } 18. }); 19. const onChangeConfirmPassword = props => event =>({ 20. confirmPassword:{ 21. value: event.target.value, 22. isDirty:true 23. } 24. }); 25. const withTextFieldState = withStateHandlers(initialState,{ 26. onChangeEmail, 27. onChangePassword, 28. onChangeConfirmPassword 29. }); 30. exportdefault withTextFieldState;
withStateHandlers它接受初始状态和包含状态处理程序的对象。调用时,每个状态处理程序将返回新的状态。
好了,很辛苦也很感谢你能看到这里,关于recompose介绍就到这里了,喜欢的朋友可以深入研究recompose其它的方法和源码。
作者 | 邹存洋
Vladimir Horowitz - Schumann来自大转转FE*00:00*02:34
概况:
什么是高阶组件?
这是官方文档说的,我没有截全,因为后面的解释会造成误解,但简单讲高阶组件(函数)就好比一个加工厂,同样的,屏幕、cpu、扬声器、电池,小米手机工厂组装完就是小米手机,魅族手机组装完就是魅族手机,基本材料都是相同的,不同工厂(高阶组件)有不同的实现及产出,当然这个工厂(高阶组件)也可能是针对某个基本材料的处理。 总之产出的结果拥有了输入组件不具备的功能,输入的组件可以是一个组件的实例,也可以是一个组件类,还可以是一个无状态组件的函数。
解决什么问题?
随着项目越来越复杂,开发过程中,多个组件需要某个功能,而且这个功能和页面并没有关系,所以也不能简单的抽取成一个新的组件,但是如果让同样的逻辑在各个组件里各自实现,无疑会导致重复的代码。比如页面有三种弹窗一个有title,一个没有,一个又有右上角关闭按钮,除此之外别无它样,你总不能整好几个弹窗组件吧,这里除了tilte,关闭按钮其他的就可以做为上面说的基本材料。
高阶组件总共分为两大类
代理方式
操纵prop
访问ref(不推荐)
抽取状态
包装组件
继承方式
操纵生命周期
操纵prop
代理方式之 操纵prop
删除prop
增加prop
接下来我把简化了写法,把匿名函数去掉,同时换成箭头函数
上面HocRemoveProp高阶组件中,所做的事情和输入组件WrappedComponent功能一样,只是忽略了名为user的prop。也就是说,如果WrappedComponent能处理名为user的prop,这个高阶组件返回的组件则完全无视这个prop。
这是一个利用es6语法技巧,经过上面的语句,otherProps里面就有this.props中所有的字段除了user. 假如我们现在不希望某个组件接收user的prop,那么我们就不要直接使用这个组件,而是把这个组件作为参数传递给HocRemoveProp,然后我们把这个函数的返回结果当作组件来使用 两个高阶组件的使用方法:
也可以利用decorator语法糖这样使用
代理方式之 抽取状态
将所有的状态的管理交给外面的容器组件,这个模式就是 抽取状态 外面的容器就是这个高阶组件
这样当我们在使用这个已经被包裹的input组件(SampleComponent)时候 它的值就被放在了HocContainer高阶组件中,当很多这样的input组件都用这个HocContainer高阶组件时,那么它们的值都将保存在这个HocContainer高阶组件中
代理方式之 包装组件
这样使用
-代理方式的生命周期的过程类似于堆栈调用:
在说继承方式之前先看一个例子
这个例子很简单,相当于把WrappedComponent组件的render方法,
通过super.render()方法吐到了MyContainer 中,可以顺序调用。
继承方式的生命周期的过程类似队列调用:
1.代理方式下WrappedComponent会经历一个完整的生命周期,产生的新组件和参数组件是两个不同的组件,一次渲染,两个组件都会经历各自的生命周期,
2.而在继承方式下,产生的新组件和参数组件合二为一,super.render只是生命周期中的函数,变成一个生命周期。
来看下面的例子你就会明白了。
继承方式之 操纵生命周期(渲染劫持)
首先创建一个高阶,在创建一个使用高阶组件的组件,也就是是输入组件,最后我在改变这个输入组件props
这个高阶组件会直接读取输入组件中的props,state,然后控制了输入组件的render展示 只有在props.time和state.success同时为真的时候才会展示
然后调用,递减time数值直到变为0最后页面的效果就是,当然他不是循环的。先展示”我是一个组件“,我设置了两秒,之后展示”倒计时完成“
由此可以看出高阶组件也可以控制state
但是最好要限制这样做,可能会让WrappedComponent组件内部状态变得一团糟。建议可以通过重新命名state,以防止混淆。
继承方式之 操纵prop
这样就传入了新的props
克隆并返回一个新的 ReactElement ,新返回的元素会保留有旧元素的 props、ref、key,也会集成新的 props。
还有一个方式,在传递props上有着强于高阶组件的优势不用关心命名,
使用方式
感觉很方便,但是每次渲染都会重新定义一个新的函数,如果不想的话就不要定义匿名函数,
彩蛋recompose库
recompose是一个很流行的库,它提供了很多很有用的高阶组件(小工具),而且也可以优雅的组合它们。
Step 1 扁平props.
我们有这样一个组件
如果想要改变组件接口来接收单个 prop 而不是整个用户对象,可以用 recompose 提供的高 阶组件 flattenProp 来实现。
const ProfileWithFlattenUser = flattenProp('user')(Profile); 现在我们希望同时使用多个高阶组件:一个用于扁平化处理用户 prop,另一个用于重命名用 户对象的单个 prop。 此时 recompose 库提供的 compose 函数就派上用场了。
然后按照以下方式将它应用于原有组件:
还可以将 compose 函数用 在我们自己的高阶组件上,甚至结合使用都可以:
Step 2 提取输入表单的State
我们将从Recompose库中使用withStateHandlers高阶组件。 它将允许我们将组件状态与组件本身隔离开来。 我们将使用它为电子邮件,密码和确认密码字段添加表单状态,以及上述字段的事件处理程序。
withStateHandlers它接受初始状态和包含状态处理程序的对象。调用时,每个状态处理程序将返回新的状态。
好了,很辛苦也很感谢你能看到这里,关于recompose介绍就到这里了,喜欢的朋友可以深入研究recompose其它的方法和源码。