最近在项目中实践了一下 react hooks,结合本身的经验,整理了一些文档,这里记录一下java
useState
这个应该是咱们最经常使用的 hook 了,用来代替 class 组件中的 setState
基础用法:const [state, setState] = useState(initialState)
;
react
须要注意的点【查看代码】
redux
const [count, setCont] = useState(0);
setCount(count+1)
setCount((prevCount) => prevCount + 1)
// 上面用法一般使用在,我无法保证我获取的 count 是最新的值的状况下,好比在 useCallback 中
const fn = useCallback(() => {
console.log(count); // 这里的count 会一直是初始化的那个count,这个时候若是用 setCount(count + 1) 就会出现问题
setCount((prevCount) => prevCount + 1); // 这样就不会有问题
}, []) // 这里最好传递要用到的依赖,不然会被缓存,获取不到最新的值
复制代码
// 若是是class 组件,每次调用setState 都会触发render,它不会对先后的值进行比较
const useFocusUpdate = () => {
const [ignore, update] = useState(0);
return () => {
update(ignore + 1);
};
};
复制代码
useEffect
基础用法: useEffect(fn, [deps])
【后面的Fn, deps,均指的此处,Fn2指的是 Fn的返回函数】
默认状况下useEffect 接受的函数 Fn 会在 render 以后执行,Fn 执行后,若是返回了一个函数Fn2,那么Fn2,在执行下一个 effect 以前,即上一个 effect 销毁时数组
export default function App() {
console.log('start')
const [count, setCount] = useState(0);
useEffect(() => {
console.log('log on effect', count);
return () => {
console.log('log on effecct return fn', count);
}
}, [count])
console.log('render')
return (
<div className="App"> <h1>{count}</h1> <h2 onClick={() => setCount(count + 1)}>Add Count</h2> </div>
);
}
// 打印顺序
// start render log on effect 0
// 点击 Add Count 触发 count 更新,打印顺序
// start render log on effecct return fn 0 log on effect 1
复制代码
须要注意的点缓存
// 这样会形成死循环,由于每一次从新 render handle 就会被从新建立,useEffect 每次都会检测到变化,每次就会执行
function App() {
const [count, setCount] = useState(0);
const handle = () => { setCount(count + 1) };
useEffect(() => {
handle();
}, [fn])
return (
<div className="App"> </div>
);
}
复制代码
useEffect 常常被用到的使用场景微信
模拟 DidMount
useEffect(fn, []) 传入一个空数组便可,fn 会在首次 render 后执行,以后不再会执行,fn 返回的函数会在组件销毁时执行,即为 willUnmountapp
用来监变量变化,例如dom
// 当 city 出现变化后,咱们从新开始拉取 城市信息,城市下面的运营信息,城市下面的POI等信息
useEffect(() => {
if (cityId !== null) {
fetchCityInfo();
fetchCategoryList();
fetchOperationResource();
fetchPoiList();
}
}, [cityId]);
复制代码
useCallback
把内联回调函数及依赖项数组做为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
ide
为何会有这个hook呢?由于前面说的,组件更新的时候,组件内的内联函数都会被从新建立,若是咱们将这个内联函数变成依赖,或者传递给某个子组件,都会致使一些性能问题。模块化
应用场景
// 这种写法,若是 App 须要re-render,Children 也会出现 re-render,由于handle这个函数每次都被从新被建立了
function App() {
const handle = () => { ... }
return (
<div className="App">
<Children onClick={handle}/>
</div>
);
}
function App() {
const handle = useCallback(fn, [deps]); // fn 中若是用到了某个state,须要放在deps中,不然拿到的 state 不是最新的
return (
<div className="App">
<Children onClick={handle}/>
</div>
);
}
复制代码
useMemo
把“建立”函数和依赖项数组做为参数传入 useMemo,它仅会在某个依赖项改变时才从新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
useCallback(fn, deps) 至关于 useMemo(() => fn, deps)
应用场景
function App() {
// computeExpensiveValue 是一个很是复杂的计算函数,这样作了后,只要依赖的 a 不发生变化,
// computeExpensiveValue是不会重复执行的,value是一个 memoized 值。
const value = useMemo(() => computeExpensiveValue(a), [a]);
return (
<div className="App"> {value} </div>
);
}
复制代码
// 这种写法,若是 App 须要re-render,Children 也会出现 re-render,由于 Children 的 style 这个值一直都被从新建立
function App({ height }) {
return (
<div className="App">
<Children style={{ height }}/>
</div>
);
}
// 用 useMemo 来记忆,减小 Children 的 re-render
function App({ height }) {
const childrenStyle = useMemo(() => ({ height }), [height])
return (
<div className="App">
<Children style={childrenStyle}/>
</div>
);
}
复制代码
useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
基本用法跟注意点【查看代码】
// 注意 须要首次渲染结束后,ref.current 上面才有对应的 dom。若是 dom 被移除了对应的 ref.currnt 就会变成 null
export default function App() {
const [show, setVisible] = useState(true);
const ref = useRef(null);
const listRef = useRef([]);
useEffect(() => {
console.log(listRef);
}, [ref2])
return (
<div className="App"> {show && <div ref={ref}>dom1</div>} {ary.map((num, index) => <div key={num} ref={(_ref) => { listRef.current[index] = _ref; }}>{num}</div>)} <div onClick={() => { console.log(ref.current); setVisible(!show) }}>click me</div> </div>
);
}
复制代码
export default function App() {
const ref2 = useRef(0);
useEffect(() => {
// ref2 改变后 并不会从新触发此函数
console.log(ref2);
}, [ref2])
return (
<div className="App"> <div onClick={() => { ref2.current = Math.random(); }}> change ref</div> </div>
);
}
复制代码
export default function App() {
const hadSendMc = useRef(false);
return (
<div className="App"> <div onClick={() => { if(hadSendMc.current){ return; } console.log('send mc') hadSendMc.current = true; }}>send mc</div> </div>
复制代码
useImperativeHandle
useImperativeHandle 可让你在使用 ref 时自定义暴露给父组件的实例值。在大多数状况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一块儿使用。
我在项目中只用到了一次,主要是给父组件暴露自定义的方法,而不是一股脑的直接返回ref,房子父组件操做ref致使问题
使用示例
const Video = ({ ... }: PropTypes, ref: any) => {
const videoRef: any = useRef(null);
const playVideo = () => {...};
const handlePlayBtnClick = () => {...};
useImperativeHandle(ref, () => ({
play: playVideo,
pause: () => {
if (videoRef.current) {
videoRef.current.pause();
setVideoVisible(false);
}
},
}));
return (
<div className={styles.container}> <video ref={videoRef}> <source src={videoUrl} type="video/mp4" /> </video> </div> ); }; export default React.memo(React.forwardRef(Video)); 复制代码
后续若是使用了,我会同步补上
useContext (这个我没有用过,略)
useReducer (这个我没有用过,略)
useLayoutEffect (这个我没有用过,略)
useDebugValue (这个我没有用过,略)
使用 React.memo 去优化
因为 Function Component 组件没有相似 shouldComponentUpdate 这样的方法,也无法继承 PureComponent,咱们就可使用 React.memo去作优化
注意:
React.memo仅影响 props 变动。若是函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会从新渲染。
使用示例:React.memo(MyComponent, areEqual);
默认状况下其只会对复杂对象作浅层对比,若是你想要控制对比过程,那么请将自定义的比较函数经过第二个参数传入来实现。
利用 useCallback 跟 useMemo 去缓存数据,具体能够看上面的示例
若是使用了 react-redux,能够经过 useSelect 的第二个参数去作优化
使用示例:const result : any = useSelector(selector : Function, areEqual? : Function)
针对具体的业务具体开发,笔者这里只是抛砖迎玉
export const usePageView = (pageId) => {
const hadSendPv = useRef(false);
useEffect(() => {
if (hadSendPv.current) {
return;
}
sendPv();
}, [pageId]);
};
复制代码
export const useIntersectionObserver = (cb: ObserveCallback, ref?: any, startObserver = true) => {
const io: any = useMemo(() => new IntersectionObserver((IntersectionObserverEntryList: any) => cb(IntersectionObserverEntryList, io)), [cb]);
const observerRef: any = useRef(ref || null);
useEffect(() => {
if (!observerRef.current || !startObserver) {
return;
}
if (observerRef.current instanceof Array) {
const eleList = observerRef.current;
eleList.forEach((ele: any) => {
io.observe(ele);
});
return () => {
eleList.forEach((ele: any) => {
io.unobserve(ele);
});
};
}
const ele = observerRef.current;
io.observe(ele);
return () => {
io.unobserve(ele);
};
}, [io, startObserver]);
return observerRef;
};
// 使用方法
function App({ height }) {
const observerHandle = useCallback(() => { ... }, []);
const observerRef = useIntersectionObserver(observerHandle)
return (
<div className="App" ref={observerRef}> <Children style={{ height }}/> </div> ); } 复制代码
const environment = { ... };
export const useWechatShare = (shareInfo: any) => {
if ((environment.isMini() || environment.isWx) && shareInfo.shareUrl) {
// 微信分享设置
}
}, [shareInfo, environment]);
};
复制代码
export const useScroll = (handle: ScrollHandle, threshHold: number = 250) => {
const handleWrapper = useCallback(throttle(handle, threshHold), [handle]);
const containerId = 'containerId';
useEffect(() => {
let listerEle: any = window;
if (containerId) {
listerEle = document.getElementById(containerId);
}
console.log('useScroll 注入成功');
if (listerEle) {
listerEle.addEventListener('scroll', handleWrapper);
return () => {
listerEle.removeEventListener('scroll', handleWrapper);
};
}
}, [handleWrapper, containerId]);
}
复制代码
用 react hooks 模拟 class 的各个生命周期