myprelude

我是练习时间长达两年半的前端练习生,擅长CTRL。

react实现组件功能的复用

01 Jun 2019 » javascript

作者:myprelude@github
原文链接: https://myprelude.github.io
转载请注明出处,保留原文链接和作者信息。

福特公司为啥会被评为20世界伟大的企业,主要的原因应该归功于他开创了流水线作业,将汽车拆分为轮胎、发动机、车门、车身等零件,每个零件由一匹人负责,最后在将这些零件组装到一起,就做好了一个汽车。因为这个创新极大的提高了汽车的生产效率让福特一举成为汽车龙头。很多事情都是相同的,在前端开发中,将页面拆分为几个小的部分,这样可以在需要的地方直接复用这个部分既提高开发效率又方便我们单元测试。

HOC 高阶组件

这个名字是不是很熟悉,我们前端开发几乎每天都和一个叫‘高阶函数’打交道。比如我们的异步函数Promise就是一个高阶函数,它接受一个函数同时返回一个函数。高阶组件我们大致也可以知道它的定义:函数接受一个组件,返回一个组件。如下:

    function componentHOC(Component){
        return(
            <Component />
        )
    }

上面就是一个简单的高阶组件,看着好像没有用,甚至感觉多此一举。不急听我将它的作用慢慢道来。

作用一:增加生命周期任务

    function componentLogWrapper(Component){
        return class Hoc extends Component{
            componentDidMount(){
                console.log('组件加载成功')
            }
            componentWillUnmount(){
                console.log('组件已经卸载')
            }
            render(){
                <Component />
            }
        }
    }

现在我们给组件添加了日志打印功能,组件每次卸载会在控制台打印日志。看着好像还是比较简单。但是如果我们将打印日志的代码改为ajax请求呢?如下:

    function componentWrapper(Component){
        return class Hoc extends Component{
            constructor(){
                this.state={
                    data:null
                }
            }
            async componentDidMount(){
                cosnt data = await this.fetchData() // 请求数据的异步操作
                this.setState({data})
            }
            render(){
                return <Component data={this.state.data} />
            }
        }
    }

上面我就可以通过componentWrapper来做请求接口的事情,传递进来的组件仅仅做UI展示。如下:

    class UserComponent extends Component{
        render(
            // 做一些边界处理
            this.props.data?
            <div>
                <p>姓名:{this.props.data.nm}</p>
                <p>年龄:{this.props.data.age}</p>
            </div>
            :null
        )
    }
    componentWrapper(UserComponent)

UserComponent组件仅仅做UI展示,不需要去做业务逻辑,这样就能将业务逻辑和视图层分开,以后其他地方需要这个UserComponent组件时,我们只需要调用传入data就可以了。我们甚至可以在UserComponent加入一些逻辑判断对边界条件的处理,让我们的程序更加牢固。

如果还有一个组件需要请求这个接口的数据就可以直接用componentWrapper(Component)就可以了。

如果这个组件还需要增加打印日志在用componentLogWrapper(componentWrapper(Component))包裹下,这样无限包裹下去有点难看,直接用函数柯理化改写下componentLogWrapper()(componentWrapper)(Component)从右往左执行。

作用二:属性代理

    class ComponentUser extends Component{
        render(){
            return (
                <div>
                    <p>姓名:{this.props.data.nm}</p>
                    <p>年龄:{this.props.data.age}</p>
                </div>
            )
        }
    }

    function ComponentHOC (ComponentUser){
        return class InnerComponent extends Component{
            render(){
                const props = Object.assign({},{nm:'填写你的姓名',age:'填写你的年龄'},this.props.data}
                return(
                    <ComponentUser {...props}/>
                )
            }
        }     
    }

ComponentHOC中对props进行简单的加工然后在传递到ComponentUser组件中,给定组件默认值。这里的只是简单展示,实际情况可以看业务需要,根据不同props来配置不同的展示方案。

属性代理的问题:

使用属性代理的时候会导致ref获取不到的问题,原因是react中refs是一个伪属性,React 对它进行了特殊处理。如果你向一个由高级组件创建的组件的元素添加 ref 应用,那么 ref 指向的是最外层容器组件实例的,而不是包裹组件。解决办法如下:

    function ComponentHOC (ComponentUser){
        return class extends Component{
            render(){
                const props = Object.assign({},{nm:'填写你的姓名',age:'填写你的年龄'},this.props.data}
                const getRef = this.props.getRef||function(){}  // 通过回调获取ref
                return(
                    <ComponentUser {...props} ref={(ref)=>{getRef(ref)}}/>
                )
            }
        }     
    }

反向继承

    function ComponentHOC (ComponentUser){
        return class InnerComponent extends ComponentUser{
            render(){
                return super.render()
            }
        }     
    }

直接继承ComponentUser,然后通过super去调用方法,包括生命周期,state,各种function以及一些静态方法。

渲染劫持

假如我们页面已经有一个用户信息组件,但是在有一个个性化页面需要给这个组件加一个用户的图片,怎么才能在不修改修改用户信息组件的前途下,做到最小改动怎么做呢? copy一份重新那是不是太low了。。。通过高阶组件的渲染劫持就可以做到

    function ComponentHOC (ComponentUser){
        return class extends Component{
            render(){
                return(
                    <div>
                        <div><img src='xxxx' /></div>
                        <ComponentUser {...props} ref={(ref)=>{getRef(ref)}}/>
                    </div>
                )
            }
        }     
    }

通过高阶组件包裹后添加上我们需要的节点,就可以做到在无侵入,如果还需要一个边框,我只要在修改下颜色就可以了。是不是很方便。

其实高阶组件就类似于ES7的装饰者模式。在不改变对象或者函数前提下,为对象或者函数填新的功能。

Render Props

    class RenderComponent extends Component{
        constructor(){
            super();
            this.state={}
        }
        render(){
            return(
                <>
                    {this.props.render()}
                </>
            )
        }
    }

    <RenderComponent render={()=>{
        return(
            <div>render props</div>
        )
    }} />

render props就是通过props传递一个render的回调函数,乍一看是不是和this.props.children效果一样。

    class RenderComponent extends Component{
        constructor(){
            super();
            this.state={}
        }
        render(){
            return(
                <>
                    {this.props.children}
                </>
            )
        }
    }

    <RenderComponent>
        <div>render props</div>
    </RenderComponent>

仔细一看,this.props.children是来渲染组件内部的子元素,我们可以控制展示不同的子元素。假如现在我们的RenderComponent有数据要给到展示的子元素怎么出来呢?当然this.props.children是无法做到的。但是render props可以,因为this.props.render()是一个函数我们可以将需要子元素渲染的数据作为参数传递下去:如下

    class RenderComponent extends Component{
        constructor(){
            super();
            this.state={type:'render Prop'}
        }
        render(){
            return(
                <>
                    {this.props.render(this.state)}
                </>
            )
        }
    }

    <RenderComponent render={(state)=>{
        return(
            <div>{state.type}</div>
        )
    }} />

是不是很方便将RenderComponent的state传递给子元素。至于用途呢?当你觉得高阶组件不能解决你的问题的时候你就可以试试Render props。

react Hook 函数组件

react 16.8 以后更新了新的API – Hook,为我们开发提供了一个新的方式。我们将会用单独一篇文章介绍我们HOOK函数组件的开发实践。

总结

编程开发就像学习功夫,官方文档就相当于武功秘籍,全天下的武功秘籍都是一样的。但是只有少数人能成为武林高手,因为他们熟读武林秘籍,经过自己长时间的摸索早已融会贯通可以面对任何困境,甚至在此基础上深化改造自成一派。开发也是这样的一个过程,学会怎么使用Api后要不到尝试,思考怎样用才是最好。总有一天会达到融会贯通的境界。

-->