KEY: To reuse stateful logic in react, sharing non-visual logic, 创建与任何UI分离的自定义Hook
复制粘贴是最粗暴的复用行为,其他两种比较不行的复用为:
Render Prop以下的例子是悬停添加Tooltip的实现:
复制粘贴代码实现
// Copy/Pasting //Info.js function Info({ id, size }) { const [hovering, setHovering] = React.useState(false) const mouseOver = () => setHovering(true) const mouseOut = () => setHovering(false) return (<div> <> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <svg onMouseOver={mouseOver} onMouseOut={mouseOut} width={size} ><path></path></svg> </> </div>) } //TrendChart.js function TrendChart({ id, size }) { const [hovering, setHovering] = React.useState(false) const mouseOver = () => setHovering(true) const mouseOut = () => setHovering(false) return (<div> <> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <div className="balabala..." onMouseOver={mouseOver} onMouseOut={mouseOut} width={size}></div> </> </div>) } //DailyChart.js function DailyChart({ id, size }) { const [hovering, setHovering] = React.useState(false) const mouseOver = () => setHovering(true) const mouseOut = () => setHovering(false) return (<div> <> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <div className="cilacila..." onMouseOver={mouseOver} onMouseOut={mouseOut} width={size}></div> </> </div>) }复制成功
使用HOC,接受arguments。
// HOC function WithHover(Component) { return function (props) { const [hovering, setHovering] = React.useState(false) const mouseOver = () => setHovering(true) const mouseOut = () => setHovering(false) return ( <div onMouseOver={mouseOver} onMouseOut={mouseOut}> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <Component {...props} hovering={hovering}></Component> </div> ) } } //调用 const InfoWithHover = withHover(Info) const TrendChartWithHover = withHover(TrendChart) const DailyChartWithHover = withHover(DailyChart) function Info(hovering) { return (<> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <svg></svg> </>) } function TrendChart(hovering) { return (<> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <div className="balabala..."></div> </>) } function DailyChart(hovering) { return (<> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <div className="cilacila..."></div> </>) }复制成功
Render Props使用公共组件,并向公共组件传递render方法。
// Render Props function Hover({ render }) { const [hovering, setHovering] = React.useState(false) const mouseOver = () => setHovering(true) const mouseOut = () => setHovering(false) return ( <div onMouseOver={mouseOver} onMouseOut={mouseOut}> {render(hovering)} </div> ) } //需传递render方法 const InfoWithHover = <Hover render={(hovering) => <Info hovering={hovering} />}></Hover> const TrendChartWithHover = <Hover render={(hovering) => <TrendChart hovering={hovering} />}></Hover> const DailyChartWithHover = <Hover render={(hovering) => <DailyChart hovering={hovering} />}></Hover> function Info(hovering) { return (<> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <svg></svg> </>) } function TrendChart(hovering) { return (<> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <div className="balabala..."></div> </>) } function DailyChart(hovering) { return (<> {hovering === true ? <Tooltip id={id}></Tooltip> : null} <div className="cilacila..."></div> </>) }复制成功
它不与任何UI耦合,可以返回任何它想要的东西。
我们希望useHover的消费者有两条数据,the hovering state & 要添加悬停状态的DOM节点的属性。
// Custom Hook const useHover = (initialValue) => { const [hovering, setHovering] = useHover(!!initialValue) const mouseOver = () => setHovering(true) const mouseOut = () => setHovering(false) const attrs = { onMouseOver: mouseOver, onMouseOut: mouseOut } return [hovering, attrs] } // UI function Info() { const [hovering, attrs] = useHover(false) return ( <> {hovering ? <Tooltip /> : null} < svg {...attrs} /> </> ) } function TrendChart() { const [hovering, attrs] = useHover(false) return ( <> {hovering ? <Tooltip /> : null} <div className="balabala..." {...attrs}></div> </> ) } function DailyChart() { const [hovering, attrs] = useHover(false) return ( <> {hovering ? <Tooltip /> : null} <div className="cilacila..." {...attrs}></div> </> ) }复制成功
依图比较可见,开发者使用更多的自定义Hook,能够很好简化DOM树结构:

useTimeoutIF your timer is ready ? say yes or no.

useSetState实现古老的setState。

(想起了古老的setState是有一个callback的,stackoverflow是这样解决的:
reactjs - How to use setState callback on react hooks - Stack Overflow
reactjs - How to use callback with useState hook in react - Stack Overflow
Usage
const App = () => { const [state, setState] = useStateCallback(0); // same API as useState const handleClick = () => { setState( prev => prev + 1, // second argument is callback, `s` being the *updated* state s => console.log("I am called after setState, state:", s) ); }; return <button onClick={handleClick}>Increment</button>; }复制成功
useStateCallback
function useStateCallback(initialState) { const [state, setState] = useState(initialState); const cbRef = useRef(null); // init mutable ref container for callbacks const setStateCallback = useCallback((state, cb) => { cbRef.current = cb; // store current, passed callback in ref setState(state); }, []); // keep object reference stable, exactly like `useState` useEffect(() => { // cb.current is `null` on initial render, // so we only invoke callback on state *updates* if (cbRef.current) { cbRef.current(state); cbRef.current = null; // reset callback after execution } }, [state]); return [state, setStateCallback]; }复制成功
Further info: React Hooks FAQ: Is there something like instance variables?

