react-hook浅谈
Opened this issue · 0 comments
很久之前react面试都会问道class组件跟function组件有什么区别,很多人都会首先讲到,function组件没有生命周期函数,没有state。
当然由于这两个特别重要的特性,导致了react使用者使用的最多的还是class组件,但是这与作者或者设计者的**还是有点出入,官方是很推荐使用function组件的。
经过官方不懈努力,以及突发奇想的妙计,在16.8.0版本以后,我们终于可以使用hook了。(hook官方介绍)
具体的api,本人建议还是直接去查阅,首先要通读每一个,搞清楚每一个的使用场景,然后实际项目时候,刚开始还是要仔细阅读一遍。
原因如下:
1、第一次仔细通篇阅读,理解意思,这样将来项目中遇到某个场景的时候,知道是有这个api的,不会自己只用那几个自己懂的。
2、百炼锤金,没看一次,也许收获会不一样。
3、官方文档几乎交代了常用的应用场景。
为什么我会一发不可收拾的爱上hook?
首先,除了给与function组件一些神奇的react特性以外,我们还会发现,在以往的class组件,很多包含自己的状态,然后随之产生的逻辑,几乎难以复用,高阶组件以及render props的解决方案,又会使代码难以理解,以及后期维护成本的增加。
那么hook就有这么神奇的魔力么?是的!
我们可以自定义很多通用的逻辑,然后当成自定义hook。
1、例如:我想实现一个hook,可以满足,用来判断一个元素是否被hover
import { useState, useRef } from 'react'
export function useHover() {
const [value, setValue] = useState(false)
const ref = useRef(null)
const handleMouseOver = () => setValue(true)
const handleMouseOut = () => setValue(false)
useEffect(
() => {
const node = ref.current
if (node) {
node.addEventListener("mouseover", handleMouseOver)
node.addEventListener("mouseout", handleMouseOut)
return () => {
node.removeEventListener("mouseover", handleMouseOver)
node.removeEventListener("mouseout", handleMouseOut)
}
}
},
[] // Recall only if ref changes
)
return [ref, value]
}
这样,当我们要对任意函数式组件里某个元素hover状态监听时,只要引用useHover,将返回的ref挂到要监听的元素上,就能实时获取到当前组件的hover状态。
2、又或者我们像对一系列可能同级,可能跨级的组件,都想使用同一个方法,或者组件。以前我们会第一个想到context,或者挂到顶层prop,无限传。
hook可以有个超能力。来个demo
const visibleContext = createContext()
function useProviderVisible() {
const [visible, setVisible] = useState(false)
const [list, setList] = useState(null)
const close = () => {
setList(null)
setVisible(false)
}
return {
visible,
setVisible,
close
}
}
export function VisibleProvider({ children }) {
const viVal = useProviderVisible()
return (
<visibleContext.Provider value={viVal}>{children}</visibleContext.Provider>
)
}
export function useVisible() {
return useContext(visibleContext)
}
这样在我们就有了个visible是否显示的小壳子,哪里需要包哪里,我们还暴露出了,想给外部用户得到的方法跟状态。是不是超棒!
3、useRef组合也很棒,hook本身的api并不是很神奇,重要的是发现他们的深处,组合他们,会发现一个完美而又神奇的新世界!冲鸭!
贴几个常用的:
export function useWindowSize() {
const isClient = typeof window === "object"
const getSize = useCallback(() => {
return {
width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined
}
}, [isClient])
const [windowSize, setWindowSize] = useState(getSize)
useEffect(() => {
if (!isClient) {
return false
}
function handleResize() {
setWindowSize(getSize())
}
window.addEventListener("resize", handleResize)
return () => window.removeEventListener("resize", handleResize)
}, [getSize, isClient]) // Empty array ensures that effect is only run on mount and unmount
return windowSize
}
export function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value]) // Only re-run if value changes
return ref.current
}
// 倒计时
export default function useCountDown() {
const ref = useRef();
const [num, setNum] = useState(0);
const stop = () => {
if (ref.current) {
clearTimeout(ref.current);
}
};
const start = targetNum => {
setNum(targetNum);
};
const reStart = targetNum => {
if (ref.current) {
clearTimeout(ref.current);
setNum(targetNum);
}
};
const reset = () => {
if (ref.current) {
clearTimeout(ref.current);
setNum(0);
}
};
useEffect(() => {
if (num > 0) {
const newNum = num - 1
const iTimer = setTimeout(() => setNum(newNum), 1000);
ref.current = iTimer;
}
return () => {
if (ref.current) {
clearTimeout(ref.current);
}
};
}, [num]);
return {
stop,
start,
reStart,
reset,
isRunning: num > 0,
num
};
}
const FormContext = createContext(null)
function useFormProvider() {
const [telForm, setTelForm] = useState(null)
const [psdForm, setPsdForm] = useState(null)
const [state, setState] = useState('tel')
return { telForm, setTelForm, psdForm, setPsdForm, state, setState }
}
export function useForm() {
return useContext(FormContext)
}
export function FormProvider({ children }) {
const formValues = useFormProvider()
return <FormContext.Provider value={formValues}>{children}</FormContext.Provider>
}
嘿嘿,还有好多,不贴了,自己去组合吧!