title | date | tags | categories | ||
---|---|---|---|---|---|
常用的自定Hooks |
2020-05-20 |
|
|
- Service 请求 Hook 封装
通过 useAsync 我们可以很轻松的获取到 myFunction 的执行状态。
import React, { useState, useEffect, useCallback } from 'react';
// Usage
function App() {
const { execute, pending, value, error } = useAsync(myFunction, false);
return (
<div>
{value && <div>{value}</div>}
{error && <div>{error}</div>}
<button onClick={execute} disabled={pending}>
{!pending ? 'Click me' : 'Loading...'}
</button>
</div>
);
}
// An async function for testing our hook.
// Will be successful 50% of the time.
const myFunction = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const rnd = Math.random() * 10;
rnd <= 5 ? resolve('Submitted successfully 🙌') : reject('Oh no there was an error 😞');
}, 2000);
});
};
// Hook
const useAsync = (asyncFunction, immediate = true) => {
const [pending, setPending] = useState(false);
const [value, setValue] = useState(null);
const [error, setError] = useState(null);
// The execute function wraps asyncFunction and
// handles setting state for pending, value, and error.
// useCallback ensures the below useEffect is not called
// on every render, but only if asyncFunction changes.
const execute = useCallback(() => {
setPending(true);
setValue(null);
setError(null);
return asyncFunction()
.then((response) => setValue(response))
.catch((error) => setError(error))
.finally(() => setPending(false));
}, [asyncFunction]);
// Call execute if we want to fire it right away.
// Otherwise execute can be called later, such as
// in an onClick handler.
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { execute, pending, value, error };
};
export default App;
- 监听事件 Hooks 封装 对于一些事件(鼠标移入移出, 点击)等的封装
import React, { useState, useEffect, useCallback, useRef } from 'react';
// Usage
function App() {
// State for storing mouse coordinates
const [coords, setCoords] = useState({ x: 0, y: 0 });
// Event handler utilizing useCallback ...
// ... so that reference never changes.
const handler = useCallback(
({ clientX, clientY }) => {
// Update coordinates
setCoords({ x: clientX, y: clientY });
},
[setCoords]
);
// Add event listener using our hook
useEventListener('mousemove', handler);
return (
<h1>
The mouse position is ({coords.x}, {coords.y})
</h1>
);
}
// Hook
function useEventListener(eventName, handler, element = window) {
// Create a ref that stores handler
const savedHandler = useRef();
// Update ref.current value if handler changes.
// This allows our effect below to always get latest handler ...
// ... without us needing to pass it in effect deps array ...
// ... and potentially cause effect to re-run every render.
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(
() => {
// Make sure element supports addEventListener
// On
const isSupported = element && element.addEventListener;
if (!isSupported) return;
// Create event listener that calls handler function stored in ref
const eventListener = (event) => savedHandler.current(event);
// Add event listener
element.addEventListener(eventName, eventListener);
// Remove event listener on cleanup
return () => {
element.removeEventListener(eventName, eventListener);
};
},
[eventName, element] // Re-run if eventName or element changes
);
}
export default App;
-
检查子组件因为什么而重新渲染
import React, { useState, useMemo } from 'react'; import ReactDOM from 'react-dom'; import useWhyDidYouUpdate from './use-why-did-you-update'; const Counter = React.memo(props => { useWhyDidYouUpdate('Counter', props); return <div style={props.style}>{props.count}</div>; }); function App() { const [count, setCount] = useState(0); const [userId, setUserId] = useState(0); const counterStyle = useMemo(() => { return { fontSize: '3rem', color: 'red' } }, []) return ( <div> <div className="counter"> <Counter count={count} style={counterStyle} /> <button onClick={() => setCount(count + 1)}>Increment</button> </div> <div className="user"> <img src={`http://i.pravatar.cc/80?img=${userId}`} /> <button onClick={() => setUserId(userId + 1)}>Switch User</button> </div> </div> ); } const rootElement = document.getElementById('root'); ReactDOM.render(<App />, rootElement);
-
根据当前最小分辨率获取当前column数量
import { useEffect, useState } from 'react'; export default function useMedia(queries, values, defaultValue) { // Array containing a media query list for each query const mediaQueryLists = queries.map(q => window.matchMedia(q)); // Function that gets value based on matching media query const getValue = () => { // Get index of first media query that matches const index = mediaQueryLists.findIndex(mql => mql.matches); // Return related value or defaultValue if none return typeof values[index] !== 'undefined' ? values[index] : defaultValue; }; // State and setter for matched value const [value, setValue] = useState(getValue); useEffect( () => { // Event listener callback // Note: By defining getValue outside of useEffect we ensure that it has ... // ... current values of hook args (as this hook only runs on mount/dismount). const handler = () => setValue(getValue); // Set a listener for each media query with above handler as callback. mediaQueryLists.forEach(mql => mql.addListener(handler)); // Remove listeners on cleanup return () => mediaQueryLists.forEach(mql => mql.removeListener(handler)); }, [] // Empty array ensures effect is only run on mount and unmount ); return value; }
const columnCount = useMedia( // Media queries ['(min-width: 1500px)', '(min-width: 1000px)', '(min-width: 600px)'], // Column counts (relates to above media queries by array index) [7, 6, 5], // Default column count 2 );
- 当我们的内容过于长的时候, 如果我们想要锁定滚动条
import { useLayoutEffect } from 'react'; export default function useLockBodyScroll() { useLayoutEffect(() => { // Get original value of body overflow const originalStyle = window.getComputedStyle(document.body).overflow; // Prevent scrolling on mount document.body.style.overflow = 'hidden'; // Re-enable scrolling when component unmounts return () => document.body.style.overflow = originalStyle; }, []); // Empty array ensures effect is only run on mount and unmount }
function Modal({ title, content, onClose }) { useLockBodyScroll(); return ( <div className="modal-overlay" onClick={onClose}> <div className="modal"> <h2>{title}</h2> <p>{content}</p> </div> </div> ); }
- 监听键盘按键的Hook
import { useState, useEffect } from 'react'; export default function useKeyPress(targetKey) { // State for keeping track of whether key is pressed const [keyPressed, setKeyPressed] = useState(false); // If pressed key is our target key then set to true function downHandler({ key }) { if (key === targetKey) { setKeyPressed(true); } } // If released key is our target key then set to false const upHandler = ({ key }) => { if (key === targetKey) { setKeyPressed(false); } }; // Add event listeners useEffect(() => { window.addEventListener('keydown', downHandler); window.addEventListener('keyup', upHandler); // Remove event listeners on cleanup return () => { window.removeEventListener('keydown', downHandler); window.removeEventListener('keyup', upHandler); }; }, []); // Empty array ensures that effect is only run on mount and unmount return keyPressed; }
-
防抖Hook
import { useState, useEffect, useRef } from 'react'; export default function useDebounce(value, delay) { // State and setters for debounced value const [debouncedValue, setDebouncedValue] = useState(value); useEffect( () => { // Update debounced value after delay const handler = setTimeout(() => { setDebouncedValue(value); }, delay); // Cancel the timeout if value changes (also on delay change or unmount) // This is how we prevent debounced value from updating if value is changed ... // .. within the delay period. Timeout gets cleared and restarted. return () => { clearTimeout(handler); }; }, [value, delay] // Only re-call effect if value or delay changes ); return debouncedValue; }
const [searchTerm, setSearchTerm] = useState(''); const debouncedSearchTerm = useDebounce(searchTerm, 500); useEffct(() => { .... }, [debouncedSearchTerm]) return ( <input style={{ width: '100%', fontSize: '2rem', padding: '0.4rem', marginBottom: '10px' }} placeholder="Search Marvel Comics" onChange={e => setSearchTerm(e.target.value)} /> )
- 监听窗口变化
import { useState, useEffect } from 'react'; // Usage function App() { const size = useWindowSize(); return ( <div> {size.width}px / {size.height}px </div> ); } // Hook function useWindowSize() { const isClient = typeof window === 'object'; function getSize() { return { width: isClient ? window.innerWidth : undefined, height: isClient ? window.innerHeight : undefined }; } const [windowSize, setWindowSize] = useState(getSize); useEffect(() => { if (!isClient) { return false; } function handleResize() { setWindowSize(getSize()); } window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // Empty array ensures that effect is only run on mount and unmount return windowSize; }
-
监听鼠标是否在当前元素上
import { useRef, useState, useEffect } from 'react'; // Usage function App() { const [hoverRef, isHovered] = useHover(); return ( <div ref={hoverRef}> {isHovered ? '😁' : '☹️'} </div> ); } // Hook 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); }; } }, [ref.current] // Recall only if ref changes ); return [ref, value]; }
-
使用 useRef 缓存上一次的值
import React, { Fragment, useState, useEffect, useRef } from 'react';
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const Layout = () => {
const initCount = (value = 0) => value;
const [now, setNow] = useState(() => initCount());
const pre = usePrevious(now);
const handleClick = () => {
setNow(1);
};
return (
<Fragment>
<p>now is {now}</p>
<p>pre is {pre}</p>
<button onClick={handleClick}>改变state</button>
</Fragment>
);
};
export default Layout;