千峰React:组件与逻辑封装(下)
ahooks处理钩子
三个关于生命周期的钩子
负责初始化
负责卸载
判断是否已经卸载
进行对bool值的切换
import { useBoolean, useDynamicList } from "ahooks";
function App() {
const [state,{toggle,setTrue,setFalse}]=useBoolean(true)
return (
<div>
<button onClick={toggle}>toggle</button>
<button onClick={setTrue}>setTrue</button>
<button onClick={setFalse}>setFalse</button>
{state+''}
</div>
)
}
export default App
toggle可以切换state的状态,setFalse可以设置为false,setTrue可以设置为true
可以实现类似的效果
import { useToggle, useDynamicList } from "ahooks";
function App() {
const [state,{toggle,setLeft,setRight}]=useToggle(true)
return (
<div>
<button onClick={toggle}>toggle</button>
<button onClick={setLeft}>setLeft</button>
<button onClick={setRight}>setRight</button>
{state+''}
</div>
)
}
export default App
其实useBoolean是在useToggle的基础上实现的,useBoolean主要是针对bool值实现的,useToggle是可以在其他值上实现的
只不过useToggle的参数里,第一个是true,第二个就默认是false,所以也可以设置成别的值
const [state, { toggle, setLeft, setRight }] = useToggle('left','right')
还有别的hooks,以后用到了单独讲
处理防抖值就是取输入结束后的一定时间内的值
比如这个就是等输入结束后500ms
节流也是类似的
管理map,可以添加、删除、重置
useSet也一样
优化状态频繁更新的性能,和useState差不多
例如一个异步程序在组件里,异步程序还没完成,组件就被卸载了,这时候就会引发安全问题
import { useSafeState } from 'ahooks';
import React, { useEffect, useState } from 'react';
const Child = () => {
const [value, setValue] = useSafeState<string>();
useEffect(() => {
setTimeout(() => {
setValue('data loaded from server');
}, 5000);
}, []);
const text = value || 'Loading...';
return <div>{text}</div>;
};
export default () => {
const [visible, setVisible] = useState(true);
return (
<div>
<button onClick={() => setVisible(false)}>Unmount</button>
{visible && <Child />}
</div>
);
};
-
如果在 5 秒内点击 Unmount 按钮,
visible
被设置为false
,Child
组件被卸载。 -
由于
useSafeState
的作用,即使定时器触发,setValue
也不会执行,避免了组件卸载后仍然更新状态的错误。
可以打破闭包(穿透作用域)的影响
一般新更新的值不会在下一轮更新,但是useGetState可以解决这个问题,每次打印的都是最新值
可以重置state,和useState一样
ahooks处理Effect钩子
只在依赖项更新时执行的意思在下面代码的体现就是每点击一次按钮,才执行useUpdateEffect,初次不执行
import { useState } from "react"
import { useUpdateEffect } from "ahooks";
function App() {
const [state, setState] = useState(0)
useUpdateEffect(() => {
console.log('useUpdateEffect')
})
const handleClick = () => {
setState(state+1)
}
return (
<div>
<button onClick={handleClick}></button>
</div>
)
}
export default App
这个更是一样的,只不过是针对于useLayoutEffect
之前我们说useEffect里面不能直接写async,但是useAsyncEffect里面可以写
function mockCheck(): Promise<boolean> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 3000);
});
}
useEffect为什么要有防抖能力?因为useEffect拆解组件里的副作用,如果触发组件频繁,useEffect就要频繁执行
import { useDebounceEffect } from 'ahooks';
import React, { useState } from 'react';
export default () => {
const [value, setValue] = useState('hello');
const [records, setRecords] = useState<string[]>([]);
useDebounceEffect(
() => {
setRecords((val) => [...val, value]);
},
[value],
{
wait: 1000,
},
);
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Typed value"
style={{ width: 280 }}
/>
<p style={{ marginTop: 16 }}>
<ul>
{records.map((record, index) => (
<li key={index}>{record}</li>
))}
</ul>
</p>
</div>
);
};
只获取最后的输入值
除了Effect的防抖,还提供了函数的防抖
不理解为什么提供了这么多防抖函数,学长们说不同部分的防抖需要不同的函数,输入框、按钮、副作用需要不同的防抖
我要跳过这章
ahooks处理Dom钩子
基本都是通过ref去操作dom的
跟普通的onClick没什么区别,但是多了几个参数
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
target | DOM 节点或者 ref | (() => Element) | Element | React.MutableRefObject<Element> | Window | Document | window |
capture | 可选项,listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。 | boolean | false |
once | 可选项,listener 在添加之后最多只调用一次。如果是 true,listener 会在其被调用之后自动移除。 | boolean | false |
passive | 可选项,设置为 true 时,表示 listener 永远不会调用 preventDefault() 。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。 | boolean | false |
enable | 可选项,是否开启监听。 | boolean | true |
这个是监视目标元素外的点击事件
检测监听页面是否可见,例如最小化的时候就会消失
拖拽和释放,拖住元素和释放元素,一般是配合使用
可以拖拽图片、链接、文件等
控制表单
这里的value就相当于target.value一样,其实就是多了个target.value的封装
import React from 'react';
import { useEventTarget } from 'ahooks';
export default () => {
const [value, { reset, onChange }] = useEventTarget({ initialValue: 'this is initial value' });
return (
<div>
<input value={value} onChange={onChange} style={{ width: 200, marginRight: 20 }} />
<button type="button" onClick={reset}>
reset
</button>
</div>
);
};
动态注入css或js
把path扔进去,就可以生成在页面里,使用路径里的样式
设置图标
点击按钮进入全屏/退出全屏/切换全屏
检测监听的dom元素上是否有鼠标悬停
监听dom改变,一旦dom被改变就触发
监听dom图片是否在可视区
监听按键,也可以组合按键
监听长按按键
监听鼠标位置
监听响应式变化,根据不同的分辨率可以执行不同的内容
监听元素滚动的位置
\
手写ahooks的useUpdateEffect
import { useEffect, useState } from 'react'
import { useRef } from 'react'
function useUpdateEffect(effect, deps) {
const isMounted = useRef(false)
useEffect(() => {
//卸载的时候会走
return () => {
//重置isMounted的状态
isMounted.current = false
}
},[])
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true
}else{
effect()
}
},deps)
}
function App() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count+1)
}
useEffect(() => {
console.log(123)
}, [count])
useUpdateEffect(() => {
console.log(456)
}, [count])
return (
<div>
<button onClick={handleClick}>点击</button>
</div>
)
}
export default App
手写ahooks的useBoolean+useHover
import {useState } from 'react'
function useBoolean(defaultValue) {
const [state, setState] = useState(defaultValue)
const toggle = () => setState((state) => !state)
const setTrue = () => setState(true)
const setFalse = () => setState(false)
return [state, { toggle, setTrue, setFalse }]
}
function App() {
const [state,{toggle,setTrue,setFalse}] = useBoolean(true)
return (
<div>
<button onClick={toggle}>点击1</button>
<button onClick={setTrue}>点击2</button>
<button onClick={setFalse}>点击3</button>
{ state+''}
</div>
)
}
export default App
import { useEffect, useState } from 'react'
import { useRef } from 'react'
function useBoolean(defaultValue) {
const [state, setState] = useState(defaultValue);
const setTrue = () => setState(true);
const setFalse = () => setState(false);
const toggle = () => setState(!state); // 添加 toggle 函数
return [state, { toggle, setTrue, setFalse }];
}
function useHover(target) {
const [state, { setTrue, setFalse }] = useBoolean(false); // 默认值为 false
useEffect(() => {
const node = target.current;
if (node) {
node.addEventListener('mouseenter', setTrue);
node.addEventListener('mouseleave', setFalse);
}
return () => {
if (node) {
node.removeEventListener('mouseenter', setTrue);
node.removeEventListener('mouseleave', setFalse);
}
};
}, [target]); // 依赖 target
return state; // 返回 state
}
function App() {
const ref = useRef(null);
const isHover = useHover(ref); // 使用 useHover 的返回值
const [state, { toggle, setTrue, setFalse }] = useBoolean(true); // 解构 useBoolean 的返回值
return (
<div>
<button onClick={toggle}>切换状态</button>
<button onClick={setTrue}>设置为 true</button>
<button onClick={setFalse}>设置为 false</button>
<p>当前状态: {state + ''}</p>
<div ref={ref}>{isHover ? '移入' : '移出'}</div>
</div>
);
}
export default App;
自己实现好麻烦啊。。
案例
import React, { useRef, useState } from 'react'
import { Button, Space, Tour } from 'antd' // 导入 Tour 组件
import { EllipsisOutlined } from '@ant-design/icons'
function App() {
const [open, setOpen] = useState(true);
const ref1 = useRef(null)
const ref2 = useRef(null)
const ref3 = useRef(null)
const steps = [
{
title: 'Upload File',
description: 'Put your files here.',
cover: (
<img
alt="tour.png"
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
/>
),
target: () => ref1.current, // 返回 ref1.current
},
{
title: 'Save',
description: 'Save your changes.',
target: () => ref2.current, // 返回 ref2.current
},
{
title: 'Other Actions',
description: 'Click to see other actions.',
target: () => ref3.current, // 返回 ref3.current
},
]
return (
<div style={{ margin: '100px' }}>
<Space>
<Button ref={ref1}>Upload</Button>
<Button ref={ref2} type="primary">
Save
</Button>
<Button ref={ref3} icon={<EllipsisOutlined />} />
</Space>
<Tour
open={open}
steps={steps} // 使用 steps
onClose={() => {
setOpen(false); // 修正语法错误
}}
/>
</div>
)
}
export default App;
漫游式引导界面
再实现一个效果是首次显示引导,后续不引导,解决办法是把上次的状态存储起来
const [open, setOpen] = useLocalStorageState('tour', { defaultValue:true});