- 渲染函数的输入是属性
- 输出是JSX
- 行为是Hooks
那么对于多个组件公共行为(副作用)应该如何封装呢?
公共Scroll事件的封装
可以通过Hooks封装公共的行为,例如滑动、触摸等复杂的事件处理,可以用hooks封装,从而简化使用。
import { UIEventHandler, useEffect, useRef } from "react"
class ScrollDescriptor {
private left: number = 0
private top: number = 0
private scrollHeight: number = 0
private offsetHeight: number = 0
private scrollToBottomHandlers : Function[] = []
public onScrollToBottom(handler : Function){
this.scrollToBottomHandlers.push(handler)
return () => {
this.scrollToBottomHandlers =
this.scrollToBottomHandlers.filter(x => x !== handler)
}
}
private triggerScrollToBottom(){
this.scrollToBottomHandlers.forEach(h => h())
}
public update(
left: number,
top : number,
offsetHeight : number,
scrollHeight : number
) {
this.left = left
this.top = top
this.scrollHeight = scrollHeight
this.offsetHeight = offsetHeight
if(this.bottomReached()) {
this.triggerScrollToBottom()
}
}
public bottomReached() {
return this.top + this.offsetHeight >= this.scrollHeight
}
}
const useScroll = () => {
const scrollInfo = useRef(new ScrollDescriptor())
const scrollHandler : UIEventHandler<HTMLDivElement> = (e) => {
const scroller = e.currentTarget
const left = e.currentTarget.scrollLeft
const top = e.currentTarget.scrollTop
scrollInfo.current.update(left, top, scroller.offsetHeight, scroller.scrollHeight)
}
return {
onScroll : scrollHandler,
info : scrollInfo.current
}
}
export const ScrollerExample = () => {
const {onScroll, info} = useScroll()
useEffect(() => {
const unsub = info.onScrollToBottom(() => {
console.log("bottom reached")
})
return () => {
unsub()
}
}, [])
return (
<div
onScroll={onScroll}
style={{
height: 600,
width: 400,
overflow: "scroll",
}}
>
<div
style={{
height: 800,
width: "100%",
background: "red",
}}
></div>
<div
style={{
height: 800,
width: "100%",
background: "blue",
}}
></div>
<div
style={{
height: 800,
width: "100%",
background: "yellow",
}}
></div>
</div>
)
}
状态封装
可以使用hooks进行状态的封装,例如之前我们实现的`受控` 组件和`非受控`组件的公共逻辑。
import { ChangeEventHandler, useEffect, useState } from "react"
export function useValue<T>({
value,
defaultValue,
onChange,
}: {
value?: T
defaultValue?: T
onChange?: (val : T) => void
}): [T, (val: T) => void] {
const controlled = typeof value !== "undefined"
const [_value, setValue] = useState<T>(
controlled ? value : defaultValue
)
useEffect(() => {
if (controlled && value !== _value) {
setValue(value)
}
}, [value])
useEffect(() => {
if (!controlled && value !== defaultValue) {
onChange && onChange(value)
}
}, [_value])
const setHandler = (val: T) => {
if (!controlled) {
setValue(val)
} else {
onChange && onChange(val)
}
}
return [_value, setHandler]
}
链接外部能力
有时候我们需要链接外部的能力,比如说:使用一个外部的对象。
class SomeBuzObject {
public getList(){
return ([
{...}, {...}, {...}
])
}
public onListChanged(handler :Function) {
// ...
}
}
这样的情况可以将对象放到`memo` 或者`ref` 中。然后再用`useEffect` 监听外部对象的变化,最后设置一个版本变量,用于更新组件。
const useBuz = () =>{
const obj = useMemo(() => new SomeBuzObject(), [])
// or
//const obj = useRef(new SomeBuzObject())
const [, setV] = useState(0)
useEffect(() => {
obj.onListChanged(() => {
setV(x => x + 1)
})
}, [])
return obj
}
const Component = () => {
const obj = useBuz()
// ...
return <div>
{obj.getList().map((item) => {
return <... />
})}
</div>
}
封装业务逻辑
Hooks也可以对于业务逻辑进行封装。
例子:封装分页逻辑
注意用`useEffect` 让page变化成为数据变化的因子,而不是将page看做单纯的一个请求数据。
async function request(path, page){
const resp = await fetch(path + "?" + qs.stringify({page}))
const data = await resp.json()
return data
}
function usePaging(path){
const [page, setPage] = useState(0)
const [list, setList] = useState([])
useEffect(() => {
request(path, page)
.then(json =>{
setList(json.data.list)
})
}, [page])
return {
list,
next : () => setPage(x => x + 1),
prev : () => setPage(x => Math.max(0, x - 1)),
}
}
const SomeComponent = () => {
const {list, next, prev} = usePaging("/products")
// 绘制逻辑
}
1
|