Leva là một thư viện GUI dành riêng cho React, cung cấp khả năng quản lý trạng thái mạnh mẽ cùng các hook an toàn về kiểu dữ liệu — mà không bắt buộc phải render giao diện mặc định. Bài viết này hướng dẫn cách tích hợp Leva với ba thư viện quản lý trạng thái phổ biến: Redux, Zustand và Recoil.
Kết nối Leva với Zustand
Vì Leva sử dụng Zustand làm nền tảng bên trong, việc tích hợp giữa hai thư viện này rất liền mạch.
Truy cập trực tiếp store của Zustand từ Leva
import { levaStore } from 'leva/headless';
// Lấy toàn bộ dữ liệu
const allData = levaStore.getData();
// Lấy giá trị cụ thể theo đường dẫn
const currentCount = levaStore.get('uiSettings.counter');
// Cập nhật giá trị
levaStore.set({ 'uiSettings.counter': 10 }, false);
// Theo dõi thay đổi
const unsubscribe = levaStore.useStore.subscribe(
(state) => state.data['uiSettings.counter'],
(newValue) => console.log('Giá trị mới:', newValue)
);
Tạo store tùy chỉnh với useCreateStore
const customStore = useCreateStore();
const configValues = useControls({ speed: 5 }, { store: customStore });
const inputList = useLevaInputs(customStore);
Cách này cho phép bạn duy trì nhiều store Leva độc lập, phù hợp với kiến trúc ứng dụng phức tạp.
Kết hợp Leva và Redux
Mặc dù không có hỗ trợ chính thức, bạn vẫn có thể đồng bộ Leva với Redux thông qua middleware hoặc action creators.
Middleware Redux để lắng nghe thay đổi từ Leva
const syncLevaToRedux = (reduxStore) => (next) => (action) => {
const cleanup = levaStore.useStore.subscribe(
(state) => state.data,
(data) => reduxStore.dispatch({ type: 'SYNC_LEVA_STATE', payload: data })
);
const result = next(action);
return () => {
cleanup();
return result;
};
};
Cập nhật Leva từ Redux action
const setLevaValue = (keyPath, value) => (dispatch) => {
levaStore.set({ [keyPath]: value }, false);
dispatch({ type: 'UPDATE_FROM_LEVA', payload: { keyPath, value } });
};
Đồng bộ Leva với Recoil
Recoil quản lý trạng thái dưới dạng "atom", do đó cần tạo hook tùy chỉnh để liên kết hai hệ thống.
Hook đồng bộ hai chiều
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { useLevaInput } from 'leva/headless';
function useSyncRecoilLeva(recoilAtom, levaKey) {
const [recoilVal, setRecoil] = useRecoilState(recoilAtom);
const { value: levaVal, set: setLeva } = useLevaInput(levaKey);
useEffect(() => {
if (recoilVal !== levaVal) setLeva(recoilVal);
}, [recoilVal, setLeva, levaVal]);
useEffect(() => {
if (levaVal !== recoilVal) setRecoil(levaVal);
}, [levaVal, setRecoil, recoilVal]);
return [recoilVal, setRecoil];
}
Sử dụng trong component
const themeAtom = atom({ key: 'themeColor', default: '#ffffff' });
function ThemeEditor() {
const [color] = useSyncRecoilLeva(themeAtom, 'appearance.color');
return <div>Màu hiện tại: {color}</div>;
}
Chế độ Headless – Nền tảng cho mọi tích hợp
Chế độ headless của Leva cung cấp các API chính sau:
useControls: Quản lý trạng thái mà không render UIuseLevaInputs: Truy xuất danh sách input và metadatauseLevaTree: Lấy cấu trúc cây thư mục của controluseLevaInput: Truy cập input theo đường dẫnuseCreateStore: Tạo store độc lập
Ứng dụng thực tế
Tích hợp với React Three Fiber
function AnimatedCube() {
const { scale, rotationSpeed, materialColor } = useControls({
scale: { value: 1, min: 0.1, max: 3 },
rotationSpeed: { value: 0.02, min: 0, max: 0.1 },
materialColor: '#00ffcc'
});
useFrame((_, delta) => {
// Áp dụng giá trị từ Leva vào animation 3D
});
return (
<mesh scale={scale}>
<boxGeometry />
<meshStandardMaterial color={materialColor} />
</mesh>
);
}
Giao diện điều khiển trong WebXR
function XRPanel() {
useControls({
movementSpeed: { value: 2, min: 0.5, max: 5 },
enableGravity: true,
skyColor: '#87CEEB'
});
const inputs = useLevaInputs();
return (
<group position={[0, 1.6, -0.8]}>
{inputs.map(({ path, input }) => (
<XRButton key={path} label={input.label} valuePath={path} />
))}
</group>
);
}