Ở phần 1, mình render component React ở trong một ứng dụng dùng Solid JS.
Ở phần 2 này mình sẽ giải quyết vấn đề "giao tiếp": làm sao để chuyển data giữa project chính dùng Solid và component React.
Mình sẽ chia làm 2 vấn đề nhỏ hơn:
- Chuyển data từ component React lên app Solid JS chính
- Chuyển data từ app Solid JS chính xuống component React
Ở một project chỉ dùng React hay Solid bình thường, để giao tiếp giữa 2 component mình dùng props. Tuy nhiên ở trường hợp này mình không thể dùng props bình thường để chuyển data giữa một ứng dụng dùng Solid JS vào component React được. Thay vào đó, mình sẽ sử dụng callback.
Ở phần tiếp theo mình sẽ giải quyết vấn đề đầu tiên: chuyển data từ component React đến app Solid JS chính.
Chuyển data từ component React lên app Solid JS chính
Để minh họa làm thế nào để chuyển data từ component React đến app Solid JS chính, hãy cùng xây dựng một ứng dụng "đếm" nhỏ. App Solid JS sẽ hiển thị bộ đếm, còn component React sẽ là button mà khi click vào đó, bộ đếm nằm ở app Solid JS chính sẽ tăng lên 1.
Để làm được điều đó, component React phải "nói" cho ứng dụng Solid JS chính mỗi lần button bị click. App Solid chính sẽ đưa cho app component React function onIncrement
. Mỗi lần app component React thấy button bị click, nó sẽ gọi đến function onIncrement
này.
Chi tiết hơn là thế này:
- Function
mount
bây giờ sẽ nhận thêm một parameter mới: fuctiononIncrement
- App Solid chính sẽ truyền function
onIncrement
vàomount
- Function
mount
sẽ truyền functiononIncrement
này vào component App - Mỗi khi button trong component App bị click, nó sẽ gọi function
onIncrement
đó
Trong file App.tsx
của component React, hãy thêm như sau:
// App.tsx (React app)
interface Props { onIncrement: (amount: number) => any;
} export const App: React.FC<Props> = ({ onIncrement }) => { return <button onClick={() => onIncrement(1)}>Increment from React</button>;
};
Component App bây giờ sẽ nhận props mới là onIncrement
. Mỗi khi button bị click, nó sẽ gọi onIncrement
với giá trị là 1.
Ở file index.tsx
file (của project component React), hãy thêm vào như sau:
// index.tsx (React app)
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App"; interface Callbacks { onIncrement: (amount: number) => any;
} export const mount = (root: HTMLElement, { onIncrement }: Callbacks) => { createRoot(root).render( <StrictMode> <App onIncrement={onIncrement} /> </StrictMode> );
};
Function mount
bây giờ nhận thêm function onIncrement
và truyền nó xuống component App.
Bây giờ hãy build ứng dụng của mình bằng cách chạy script build
script ở project react-component:
pnpm run build
Tiếp theo, ở file App.tsx
ở app Solid chính, hãy chỉnh nó thành như thế này:
// App.tsx (Solid app)
import { mount } from "react-component";
import { createSignal, onMount } from "solid-js"; export const App = () => { let container!: HTMLDivElement; const [value, setValue] = createSignal<number>(0); onMount(() => { mount(container, { onIncrement: (amount) => { setValue((value) => value + amount); }, }); }); return ( <> <div ref={container} /> <div> <div>Count from Solid: {value()}</div> </div> </> );
};
App Solid JS chính bây giờ sẽ truyền vào mount
một function có tên là onIncrement
. Mỗi lần function onIncrement
được gọi, nó sẽ tăng value
lên một khoản là amount
do component React truyền ra.
Ok vậy là xong rồi, bây giờ nếu bạn click vào button "Increment from React", bạn sẽ thấy bộ đếm ở app Solid chính tăng lên 1!
Chuyển data app Solid JS chính xuống component React
Tiếp theo mình sẽ "phát triển" ứng dụng lên một chút bằng cách thêm một <input/>
ở trong app chính Solid JS. <input/>
này cho phép nhập giá trị mà component React dùng để tăng.
Ví dụ, khi bạn nhập 5 vào input
, cái button trong component React sẽ hiển thị thành "Increment 5 from React", và khi bạn click button này, bộ đếm ở ứng dụng Solid chính sẽ tăng lên 5 đơn vị.
Tương tự như cách mà component React "giao tiếp" với ứng dụng Solid chính ở trên, component React sẽ đưa cho ứng dụng Solid chính function setAmount
. Mỗi khi giá trị trong <input/>
thay đổi, ứng dụng Solid chính sẽ gọi hàm setAmount
này.
Đây là các bước cụ thể chi tiết hơn:
- Function
mount
của project component React bây giờ trả về functionsetAmount
- App Solid chính sẽ nhận function
setAmount
đó - Mỗi lần
<input/>
thay đổi giá trị, app Solid này sẽ gọi functionsetAmount
này
Ở file App.tsx
của app Solid chính, hãy chỉnh thành như thế này:
// App.tsx (Solid app)
import { mount, type ReturnedCallbacks } from "react-component";
import { createSignal, onMount } from "solid-js"; export const App = () => { let container!: HTMLDivElement; let setAmount: ReturnedCallbacks["setAmount"] | undefined; const [value, setValue] = createSignal<number>(0); onMount(() => { const callbacks = mount(container, { onIncrement: (amount) => { setValue((value) => value + amount); }, }); setAmount = callbacks.setAmount; }); return ( <> <div ref={container} /> <div> <input onInput={(e) => { const value = parseFloat(e.currentTarget.value); if (!isNaN(value)) { setAmount && setAmount(value); } }} /> <div>Count from Solid: {value()}</div> </div> </> ); };
Function mount
bây giờ sẽ trả về callbacks
là một object trong đó có setAmount
. Mình sẽ gán function setAmount
trong callbacks
này vào function setAmount
được định nghĩa ở đầu component App
. Như thế, mình có thể dùng function setAmount
này trong cả component.
Ngoài ra mình cũng tạo thêm một <input/>
để người dùng có thể nhập amount vào. Mỗi lần người dùng chỉnh sửa giá trị, nó sẽ gọi function setAmount
để update giá trị amount mới trong component React.
*Note: ở trong Solid JS, mình không dùngonChange
mà dùng onInput
Ở file index.tsx
của component React, hãy chỉnh thành thế này:
// src/index.tsx (React component)
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App"; export interface Callbacks { onIncrement: (amount: number) => any;
} export interface ReturnedCallbacks { setAmount: (amount: number) => any;
} export const mount = ( root: HTMLElement, { onIncrement }: Callbacks
): ReturnedCallbacks => { createRoot(root).render( <StrictMode> <App onIncrement={onIncrement} /> </StrictMode> ); return { setAmount: (amount: number) => { // TODO: pass the amount to the App component }, };
};
Function mount
bây giờ sẽ trả về function setAmount
cho app Solid chính để gọi như bạn thấy ở trên.
Nhưng câu hỏi tiếp theo là làm sao để mình truyền giá trị amount
này vào component App? Để làm được việc đó, thực ra mình cần sử dụng store management như kiểu Redux. Tại vì thực tế là mình đang truyền một value cho cả một app viết bằng React chứ không chỉ đơn thuần là 1 component.
Có rất nhiều giải pháp cho store management như Redux, MobX, Nano Stores, Valtio,... Nếu bạn muốn một giải pháp thật nhẹ và tối giản, có lẽ bạn sẽ muốn dùng Nano Stores chẳng hạn. Nhưng ở bài viết này, mình sẽ dùng Redux vì nó quen thuộc nhất với mọi người.
Và đây là hình vẽ minh họa khi mình sử dụng Redux:
Đầu tiên, ở project component React, hãy install Redux:
pnpm install @reduxjs/toolkit react-redux
Ở thư mục src
của component React, mình sẽ tạo file amount-slice.ts
như sau:
// src/amount-slice.ts (React component)
import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "./store"; // Define a type for the slice state
export interface AmountState { value: number;
} // Define the initial state using that type
const initialState: AmountState = { value: 0,
}; const amountSlice = createSlice({ name: "amount", // `createSlice` will infer the state type from the `initialState` argument initialState, reducers: { setAmount: (state, action: PayloadAction<number>) => { state.value = action.payload; }, },
}); export const { setAmount } = amountSlice.actions; // Other code such as selectors can use the imported `RootState` type
export const selectAmount = (state: RootState) => state.amount.value; export default amountSlice.reducer;
Tiếp theo, cũng ở thư mục src
của component React, mình sẽ tạo file store.ts
như sau:
// src/store.ts (React component)
import { configureStore, EnhancedStore, StoreEnhancer, ThunkDispatch, Tuple, UnknownAction,
} from "@reduxjs/toolkit";
import amountSlice, { AmountState } from "./amount-slice"; export const store: Store = configureStore({ reducer: { amount: amountSlice, },
}); // Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>; export type Store = EnhancedStore< { amount: AmountState; }, UnknownAction, Tuple< [ StoreEnhancer<{ dispatch: ThunkDispatch< { amount: AmountState; }, undefined, UnknownAction >; }>, StoreEnhancer ] >
>;
Bây giờ, mình sẽ sử dụng Redux ở trong index.tsx
:
// src/index.tsx (React component)
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
import { Provider } from "react-redux";
import { store } from "./store";
import { setAmount } from "./amount-slice"; export interface Callbacks { onIncrement: (amount: number) => any;
} export interface ReturnedCallbacks { setAmount: (amount: number) => any;
} export const mount = ( root: HTMLElement, { onIncrement }: Callbacks
): ReturnedCallbacks => { createRoot(root).render( <StrictMode> <Provider store={store}> <App onIncrement={onIncrement} /> </Provider> </StrictMode> ); return { setAmount: (amount: number) => { store.dispatch(setAmount(amount)); }, };
};
Để thay đổi store bên ngoài một component React, bạn hãy dùng store.dispatch()
như trên đây.
Bây giờ, ở file App.tsx
của component React mình có thể lấy được giá trị amount ở Redux store:
// src/App.tsx (React component)
import { useSelector } from "react-redux";
import { RootState } from "./store"; interface Props { onIncrement: (amount: number) => any;
} export const App: React.FC<Props> = ({ onIncrement }) => { const amount = useSelector((state: RootState) => state.amount.value); return ( <button onClick={() => onIncrement(amount)}> Increment {amount} from React </button> );
};
Ok vậy là xong phần code rồi. Cuối cùng mình chỉ cần build ứng dụng của mình lên thôi:
pnpm run build
Nếu bạn chạy server (ở app Solid chính) và truy cập vào http://localhost:5173
, bạn sẽ thấy thế này trên màn hình:
Vậy là ứng dụng của mình hoàn chỉnh rồi!
Kết luận
Vậy là bây giờ bạn đã biết làm sao để integrate một component React vào một ứng dụng dùng Solid JS. Nhưng mình nghĩ vẫn còn nhiều thứ phải cân nhắc và nhiều việc phải làm để từ bài viết này tự thiết kế ra giải pháp dành riêng cho project của bạn. Vì vậy, ở phần tiếp theo mình sẽ giải thích về các setting và configuration cơ bản khi build một component React.