一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情
前言
在react中,如果两个组件中的部分功能相似,如何复用两组件的相似功能?
render-props
先看图
定义一个有逻辑没有视图的组件
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>}/>
调用这个组件时,传入渲染函数
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 是一个函数,接收要包装的组件,返回增强后的组件
- 高阶组件的命名:
withMousewithRouterwithXXX
原理
高阶组件是一个函数,它在内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过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是最好的方案