react中组件如何复用

195 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

前言

在react中,如果两个组件中的部分功能相似,如何复用两组件的相似功能?

render-props

先看图

image-20211209150730512.png

定义一个有逻辑没有视图的组件

import React, { Component } from 'react'

type Props = {
    // render: (state: State) => React.ReactElement
}
type State = {
    x: number,
    y: number
}
export default class Mouse extends Component<Props,State> {
    state = {
        x:0, y:0
    }
    move =  (e: MouseEvent) => {
        console.log("move")
        this.setState({
            x: e.pageX,
            y: e.pageY
        })
    }
    componentDidMount() {
        document.addEventListener('mousemove', this.move)
    }
    componentWillUnmount() {
        document.removeEventListener('mousemove',this.move)
    }
    render() {
        return this.props.render(this.state)
    }
}

调用这个组件时,传入静态视图

<Mouse render={<div>xxxx</div>}/>

image-20211209163443703.png

调用这个组件时,传入渲染函数

image-20211209163559473.png

render-props使用children

children代替render属性

注意:并不是该模式叫 render props 就必须使用名为render的prop,实际上可以使用任意名称的prop

使用 children属性 代替 render 属性

// Mouse组件内部:
render() {
    return this.props.children(this.state)
}

使用Mouse组件-直接写UI

<Mouse>
	{({x, y}) => <p>鼠标的位置是 {x},{y}</p> }
</Mouse>

使用Mouse组件-传入组件

<Mouse>
       {(state)=><组件 {...state}/>}
</Mouse>

此时,在 组件 内部,通过props来接收数据

render-props-嵌套

假如有一个scroll.js的类组件,它没有视图功能,内部有监听滚动条的逻辑:

import React, { Component } from 'react'
 
export default class Scroll extends Component  {
    state = {
        left:0, top:0
    }
    scroll = () => {
        console.log("scroll")
        this.setState({
            left: window.scrollX,
            top: window.scrollY
        })
    }
    componentDidMount() {
        window.addEventListener('scroll', this.scroll)
    }
    componentWillUnmount() {
        window.removeEventListener('scroll',this.scroll)
    }
    render() {
        // return this.props.render(this.state)
        return this.props.children(this.state)
    }
}
<Scroll>
    {
        scroll =>(<Mouse>
            {(mouse)=><div><p>滚动条{scroll.top}, {scroll.left}</p>
                      <p>鼠标{mouse.x}, {mouse.y}</p></div>}
        </Mouse>)
    }
</Scroll>

具体解决方案:

  • <Mouse ui={<div>xxxx</div>}
  • <Mouse render={(参数) => <div>xx 参数xx</div>}/> this.props.render(this.state)
  • <Mouse>{(参数) => <div>xx 参数xx</div>}}</Mouse> this.props.children(this.state)

高阶组件HOC

高阶组件

  • HOC,Higher-Order Component 是一个函数,接收要包装的组件,返回增强后的组件
  • 高阶组件的命名: withMouse withRouter withXXX

原理

高阶组件是一个函数,它在内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件

const CatWithMouse = withMouse(Cat)
const PositionWithMOuse = withMouse(Position)

useMouse.js

import React from "react"

export default function useMouse(Base) {
    class Mouse extends React.Component {
        state = {
            x:0, y:0
        }
        move =  (e) => {
            console.log("move")
            this.setState({
                x: e.pageX,
                y: e.pageY
            })
        }
        componentDidMount() {
            document.addEventListener('mousemove', this.move)
        }
        componentWillUnmount() {
            document.removeEventListener('mousemove',this.move)
        }
        render() {
            console.log(this.props)
            // return this.props.render(this.state)
            return <Base {...this.state}  {...this.props} />
        }
    }

    return Mouse
}

使用步骤

  • 创建一个函数,名称约定以 with 开头
  • 指定函数参数(作为要增强的组件) 传入的组件只能渲染基本的UI
  • 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
  • 在内部创建的组件的render中,需要渲染传入的基本组件,增强功能,通过props的方式给基本组件传值
  • 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
// 创建组件
const MousePosition = withMouse(Position)

// 渲染组件
<MousePosition />

HOC-嵌套使用

withScroll

import React from "react"

export default function withScroll(Base) {
    class Scroll extends React.Component {
        state = {
            left:0, top:0
        }
        scroll = () => {
            console.log("scroll")
            this.setState({
                left: window.scrollX,
                top: window.scrollY
            })
        }
        componentDidMount() {
            window.addEventListener('scroll', this.scroll)
        }
        componentWillUnmount() {
            window.removeEventListener('scroll',this.scroll)
        }
 

        render() {
            console.log(this.props)
            // return this.props.render(this.state)
            return <Base {...this.state}  {...this.props} />
        }
    }
    return Scroll
}

com

import img from '../assets/01.png'
import React, { Component } from 'react'

export default class Com extends Component {
    render () {
        return (
            <div> 
                top:{this.props.top}
                <img src={img} style={{position:"absolute", left: this.props.x, top: this.props.y}} alt='展示'/> 
            </div>
        )
    }
}

嵌套使用

const ImageClass2 = withScroll(withMouse(ImageClass))
export default class index extends Component {
    render() {
        return (
            <div>
                <ImageClass2 />
            </div>
        )
    }
}

自定义hooks

import { useState, useEffect } from "react"
export default function useMouse() {
    const [mouse, setMouse] = useState({x:0,y:0})
    const move =  (e) => {
        setMouse({
            x: e.pageX,
            y: e.pageY
        })
    }
    useEffect(() => {
        document.addEventListener('mousemove',  move)
        return () => {
            document.removeEventListener('mousemove', move)
        }
    }, [])
    
    return mouse
}
import { useState, useEffect } from "react"
export default function useScroll() {
    const [scroll, setScroll] = useState({left:0,top:0})
    const move =  (e) => {
        setScroll({
            left: window.scrollX,
            top: window.scrollY
        })
    }
    useEffect(() => {
        window.addEventListener('scroll', move)
        return () => {
            window.removeEventListener('scroll', move)
        }
    }, [])
    

    return scroll
}

com

import img from '../assets/01.png'
import React, { Component } from 'react'

export default class ImageClass extends Component {
    render () {
        return (
            <div> 
                top:{this.props.top}
                <img src={img} style={{position:"absolute", left: this.props.x, top: this.props.y}} alt='展示'/> 
            </div>
        )
    }
}
import useMouse from './useMouse'
import useScroll from './useScroll'
import Com from './Com'

export default  function Index () {
    const mouse = useMouse()
    const scroll = useScroll()
    return (
        <div>
            <Com {...mouse}  {...scroll}/>
        </div>
    )
}

目前hooks是最好的方案