本系列文章會在筆者的部落格繼續連載!Design System 101 感謝大家的閱讀!
稍微繁忙的一天!決定先介紹兩個 Design System 常用的 Hooks useSafeLayoutEffect
以及 useComposedRefs
!
useLayoutEffect
是 React 提供的一個 Hook,跟 useEffect
一樣,都是用來處理 Side Effect 的,不同的是 useLayoutEffect
其執行時間是瀏覽器繪製 DOM 之前,且在 SSR 的時候,會噴出錯誤:
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
主要是因為 useLayoutEffect
不會在 Server Side 執行,這時候就可以使用 useSafeLayoutEffect
來解決這個問題。
而 useSafeLayoutEffect
的實作,概念上很簡單就是將 useLayoutEffect
在 Server Side 的時候,改用 useEffect
來執行。
import {useLayoutEffect, useEffect} from 'react';
const useSafeLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
useComposedRefs
通常是用來合併多個 ref, 而這在 Design System 中是很常見的。
例如有時後我們的需要同時控制組件內部某個元素,並且也能夠讓外部開發者也能控制這個元素。這時候就可以使用 useComposedRefs
來解決這個問題。
舉例來說:
當這個組件 mounted (渲染完成) 的時候,自動 focus 到輸入框上。同時,如果也希望讓其他使用者可以拿到輸入框的值,這時候就可以使用 useComposedRefs
來解決這個問題。
import React, { forwardRef, useEffect, useRef } from 'react';
import { useComposedRefs } from './compose-refs';
const FocusableInput = forwardRef((props, externalRef) => {
const internalRef = useRef();
const composedRef = useComposedRefs(externalRef, internalRef);
useEffect(() => {
if (internalRef.current) {
internalRef.current.focus();
}
}, []);
return <input ref={composedRef} {...props} />;
});
export default () => {
const inputRef = useRef(null);
const handleButtonClick = () => {
if (inputRef.current) {
console.log('輸入框的值是', inputRef.current.value);
}
};
return (
<div>
<FocusableInput ref={inputRef} placeholder="Placeholder" />
<button onClick={handleButtonClick}>Submit</button>
</div>
);
};
import React, { useCallback } from 'react';
function assignRef(ref, value) {
if (ref == null) {
return;
}
if (typeof ref === 'function') {
ref(value);
} else {
Reflect.set(ref, 'current', value);
}
}
export function useComposedRefs(...refs) {
return React.useCallback(
(value) => {
refs.forEach((ref) => {
assignRef(ref, value);
});
},
[refs],
);
}