使用 Redux 進行狀態管理的另一種方法
Redux 經常在 React.js 應用程序中用於管理全局狀態。通常,redux 存儲遵循與應用程序的數據庫架構類似的形狀。例如。對於表 tasks
, 你通常有一個對應的 tasksReducer
.
使用 Redux,您的應用程序數據現在位於兩個位置:前端和後端:
- 在前端,我們需要有一個中心位置來保持數據的一致性。例如。當我們改變
title
task
的屬性 在我們的應用程序中,我們希望此更改直接在顯示此title
的所有反應組件中可見 屬性。如果沒有 redux,應用程序可能仍會意外顯示舊的title
其他組件中的屬性。 - 後端為數據提供實際的單一事實來源。
在構建單頁應用程序時,很多工作都花在使這兩個系統保持同步上: 當你添加一個新的 task
應用程序中的對象,您首先將其添加到您的 redux 商店,因此它在 UI 中可見,您還可以對後端進行 API 調用。當後端 API 調用失敗時,你想再次從你的 redux 存儲中刪除記錄,否則你的本地狀態會與事實來源不同步。
保持這兩個系統(後端和前端)同步從根本上來說是困難的,因為它正在處理分佈式系統問題。
來自多頁應用的靈感
無需為我們項目中的每個表手動編寫此狀態管理代碼,我們可以通過重新思考我們的方法來大大簡化問題。
我們可以從中獲得靈感的一個方向是多頁應用程序。多頁應用程序通常比單頁應用程序簡單得多。多頁面應用程序始終直接在 SQL 數據庫的狀態上呈現。例如。在構建一個簡單的 PHP 應用程序時,您從數據庫中獲取一些數據,然後根據該數據呈現 HTML。沒有像 redux 這樣的第二個系統。這是使多頁面應用程序更易於構建的原因之一。
<?php
// Fetch data
$query = "SELECT * FROM tasks ORDER BY created_at";
$statement = $conn->prepare($query);
$statement->execute();
$tasks = $statement->fetchAll();
// Render HTML
echo "<div>";
echo "<h1>Tasks</h1>";
foreach ($tasks as $task) {
echo "<div>" . htmlspecialchars($task['title']) . "</div>";
}
echo "</div>";
我們可以將這個原則應用到單頁應用嗎?
讓我們試試吧。
從前端查詢
首先,我們需要一種描述查詢的方法。這可能看起來像這樣:
const theQuery = query('tasks').orderBy('createdAt');
與多頁應用程序不同,在我們的單頁應用程序中,視圖需要在底層數據更改時重新呈現。所以我們還需要一種方法,讓服務器在查詢的底層數據庫記錄發生變化時通知客戶端,以便重新渲染組件。
在 React 中,這通常使用 Hook 來解決。假設我們已經構建了一個自定義 useQuery
每當該鉤子返回的數據庫記錄發生更改時,該鉤子就會神奇地刷新。它看起來像這樣:
function Tasks() {
// Fetch data
// and magically keep the data fresh
const tasks = useQuery(query('tasks').orderBy('createdAt'));
// Render
return <div>
<h1>Tasks</h1>
{tasks?.map(task => <div>{task.title}</div>)}
</div>
}
可以看到這個結構跟上面PHP代碼的結構很接近。
useQuery
始終返回最新的數據庫狀態,並在數據庫中的記錄發生更改時自動刷新。有了這個,我們現在實際上已經歸檔了跨應用程序狀態的一致性目標。我們最初打算用 redux 解決的目標。我們現在不是基於 redux 存儲來渲染視圖,而是基於實際的數據庫來渲染視圖。就像好的舊 PHP 一樣。
突變
使用 useQuery
當底層數據更改時自動刷新,我們可以以任何我們想要的方式進行突變。我們可以使用手動 REST Api 調用突變,以及像 createRecord(tableName, record)
這樣的自定義函數 或 updateRecord(tableName, id, patch)
,或微服務。
只要突變寫入數據庫,數據庫更改就會被我們的useQuery
拾取 自動。
精簡後端
我們把上述useQuery
的API思路 和 query
與 Thin Backend 一起工作。 Thin 為您提供了一種簡單的方法,可以讓您的後端只是數據上的一個薄層,同時在前端提供豐富的交互式體驗。
Thin Backend 提供 useQuery
自動訂閱 Postgres 表中的更改並通知任何 useQuery
的鉤子 關於這些變化的電話。為了確保每個用戶的數據安全和私密,我們使用 Postgres 政策僅在您的政策規定的情況下才授予訪問權限。
Thin 還提供了簡單的函數來創建、更新和刪除數據庫記錄:
const task = await createRecord('tasks', { title: 'New task' });
await updateRecord('tasks', task.id, { title: 'Updated title' });
await deleteRecord('tasks', task.id);
以下是使用這些 API 的簡單待辦事項應用程序的外觀:
import { query, createRecord } from 'thin-backend';
import { useQuery } from 'thin-backend-react';
function Tasks() {
// `useQuery` always returns the latest records from the db
const tasks = useQuery(query('tasks').orderBy('createdAt'));
return <div>
{tasks.map(task => <Task task={task} key={task.id} />)}
</div>
}
function Task({ task }) {
return <div>{task.title}</div>
}
function AddTaskButton() {
const handleClick = () => {
const task = { title: window.prompt('Title:') };
createRecord('tasks', task);
}
return <button onClick={handleClick}>Add Task</button>
}
function App() {
// No need for state management libs
// `useQuery` automatically triggers a re-render on new data
return <div>
<Tasks />
<AddTaskButton />
</div>
}
您可以在此處運行該代碼的現場演示。
一旦開始使用上述 API,您將體驗到它可以極大地簡化前端數據庫狀態的管理。最後你甚至可能根本不再需要 redux。
結論
通過基於我們實際的數據庫狀態而不是像 redux 這樣的第二個系統來渲染視圖,我們可以從根本上簡化現代單頁前端的狀態管理。
如果您對此感到好奇,請在 Thin.dev 上嘗試一下。
以下是試用者對 Thin 的評價: