PocketBase与前端框架集成:React、Vue、Angular实战

【免费下载链接】pocketbase 开源的实时后端,仅用1个文件实现。 【免费下载链接】pocketbase 项目地址: https://gitcode.com/GitHub_Trending/po/pocketbase

引言:为什么选择PocketBase作为后端解决方案?

还在为复杂的后端架构和数据库配置而头疼?还在为API接口的繁琐开发而烦恼?PocketBase(口袋数据库)作为一款开源的实时后端解决方案,仅用单个文件即可提供完整的数据库管理、用户认证、文件存储和实时订阅功能。本文将深入探讨如何将PocketBase与主流前端框架(React、Vue、Angular)进行无缝集成,助你快速构建全栈应用。

读完本文,你将掌握:

  • ✅ PocketBase核心API架构与功能特性
  • ✅ React应用集成PocketBase的完整实践
  • ✅ Vue.js项目对接PocketBase的最佳方案
  • ✅ Angular应用使用PocketBase的配置指南
  • ✅ 实时数据订阅与状态管理的高级技巧

PocketBase核心架构解析

API端点设计

PocketBase采用RESTful风格的API设计,主要端点包括:

mermaid

认证机制

PocketBase支持多种认证方式:

认证方式 端点 适用场景
密码认证 /api/collections/:collection/auth-with-password 传统用户名密码登录
OAuth2 /api/collections/:collection/auth-with-oauth2 第三方登录集成
Token刷新 /api/collections/:collection/auth-refresh 保持会话活跃
文件访问Token /api/files/token 保护文件访问权限

React集成实战

环境配置与SDK安装

首先安装PocketBase JavaScript SDK:

npm install pocketbase
# 或
yarn add pocketbase

基础配置与初始化

创建PocketBase客户端实例:

import PocketBase from 'pocketbase';

// 初始化客户端
const pb = new PocketBase('http://127.0.0.1:8090');

// 配置自动Token刷新
pb.autoCancellation(false); // 禁用自动请求取消

export default pb;

认证管理组件

创建React认证上下文:

import React, { createContext, useContext, useEffect, useState } from 'react';
import pb from '../lib/pocketbase';

const AuthContext = createContext();

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth必须在AuthProvider内部使用');
  }
  return context;
};

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(pb.authStore.record);
  const [token, setToken] = useState(pb.authStore.token);

  useEffect(() => {
    // 监听认证状态变化
    const unsubscribe = pb.authStore.onChange((token, record) => {
      setToken(token);
      setUser(record);
    });

    return () => unsubscribe();
  }, []);

  const login = async (email, password) => {
    try {
      const authData = await pb.collection('users').authWithPassword(email, password);
      return authData;
    } catch (error) {
      throw new Error(error.message);
    }
  };

  const logout = () => {
    pb.authStore.clear();
  };

  const value = {
    user,
    token,
    login,
    logout,
    isAuthenticated: pb.authStore.isValid
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

CRUD操作封装

创建数据操作Hook:

import { useState, useCallback } from 'react';
import pb from '../lib/pocketbase';

export const useCollection = (collectionName) => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchRecords = useCallback(async (page = 1, perPage = 30, filters = {}) => {
    setLoading(true);
    setError(null);
    
    try {
      const result = await pb.collection(collectionName).getList(page, perPage, {
        filter: Object.entries(filters)
          .map(([key, value]) => `${key} ~ "${value}"`)
          .join(' && '),
        sort: '-created'
      });
      
      setData(result.items);
      return result;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [collectionName]);

  const createRecord = useCallback(async (recordData) => {
    try {
      const record = await pb.collection(collectionName).create(recordData);
      setData(prev => [...prev, record]);
      return record;
    } catch (err) {
      setError(err.message);
      throw err;
    }
  }, [collectionName]);

  const updateRecord = useCallback(async (id, recordData) => {
    try {
      const record = await pb.collection(collectionName).update(id, recordData);
      setData(prev => prev.map(item => item.id === id ? record : item));
      return record;
    } catch (err) {
      setError(err.message);
      throw err;
    }
  }, [collectionName]);

  const deleteRecord = useCallback(async (id) => {
    try {
      await pb.collection(collectionName).delete(id);
      setData(prev => prev.filter(item => item.id !== id));
    } catch (err) {
      setError(err.message);
      throw err;
    }
  }, [collectionName]);

  return {
    data,
    loading,
    error,
    fetchRecords,
    createRecord,
    updateRecord,
    deleteRecord,
    refetch: () => fetchRecords(1, 30)
  };
};

实时数据订阅

实现实时数据更新:

import { useEffect } from 'react';
import pb from '../lib/pocketbase';

export const useRealtime = (collectionName, callback) => {
  useEffect(() => {
    const unsubscribe = pb.collection(collectionName).subscribe('*', (e) => {
      callback(e);
    });

    return () => {
      unsubscribe();
    };
  }, [collectionName, callback]);
};

// 在组件中使用
const MyComponent = () => {
  const [records, setRecords] = useState([]);

  useRealtime('posts', (e) => {
    switch (e.action) {
      case 'create':
        setRecords(prev => [...prev, e.record]);
        break;
      case 'update':
        setRecords(prev => prev.map(item => 
          item.id === e.record.id ? e.record : item
        ));
        break;
      case 'delete':
        setRecords(prev => prev.filter(item => item.id !== e.record.id));
        break;
    }
  });

  // ... 组件其余部分
};

Vue.js集成方案

Vue 3 Composition API集成

创建PocketBase插件:

// plugins/pocketbase.js
import PocketBase from 'pocketbase';
import { ref } from 'vue';

export default {
  install: (app, options) => {
    const pb = new PocketBase(options.baseUrl || 'http://localhost:8090');
    
    const user = ref(pb.authStore.record);
    const token = ref(pb.authStore.token);

    // 监听认证状态变化
    pb.authStore.onChange((newToken, newRecord) => {
      token.value = newToken;
      user.value = newRecord;
    });

    app.provide('$pb', pb);
    app.provide('$user', user);
    app.provide('$token', token);
  }
};

在main.js中配置

import { createApp } from 'vue';
import App from './App.vue';
import pocketbase from './plugins/pocketbase';

const app = createApp(App);

app.use(pocketbase, {
  baseUrl: import.meta.env.VITE_POCKETBASE_URL
});

app.mount('#app');

组合式函数封装

// composables/usePocketBase.js
import { inject } from 'vue';

export const usePocketBase = () => {
  const pb = inject('$pb');
  const user = inject('$user');
  const token = inject('$token');

  const login = async (email, password) => {
    try {
      return await pb.collection('users').authWithPassword(email, password);
    } catch (error) {
      throw new Error(error.message);
    }
  };

  const logout = () => {
    pb.authStore.clear();
  };

  return {
    pb,
    user,
    token,
    login,
    logout,
    isAuthenticated: pb.authStore.isValid
  };
};

// composables/useCollection.js
import { ref } from 'vue';
import { usePocketBase } from './usePocketBase';

export const useCollection = (collectionName) => {
  const { pb } = usePocketBase();
  const data = ref([]);
  const loading = ref(false);
  const error = ref(null);

  const fetchRecords = async (page = 1, perPage = 30, filters = {}) => {
    loading.value = true;
    error.value = null;
    
    try {
      const result = await pb.collection(collectionName).getList(page, perPage, {
        filter: Object.entries(filters)
          .map(([key, value]) => `${key} ~ "${value}"`)
          .join(' && '),
        sort: '-created'
      });
      
      data.value = result.items;
      return result;
    } catch (err) {
      error.value = err.message;
      throw err;
    } finally {
      loading.value = false;
    }
  };

  // ... 其他CRUD操作类似React版本

  return {
    data,
    loading,
    error,
    fetchRecords
  };
};

Vue组件中使用

<template>
  <div>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">错误: {{ error }}</div>
    <div v-else>
      <div v-for="item in data" :key="item.id">
        {{ item.title }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { useCollection } from '../composables/useCollection';

const { data, loading, error, fetchRecords } = useCollection('posts');

// 组件挂载时获取数据
onMounted(() => {
  fetchRecords();
});
</script>

Angular集成指南

安装与配置

首先安装PocketBase SDK:

ng add pocketbase
# 或
npm install pocketbase

创建PocketBase服务

// services/pocketbase.service.ts
import { Injectable } from '@angular/core';
import PocketBase from 'pocketbase';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class PocketBaseService {
  private pb: PocketBase;
  private userSubject = new BehaviorSubject<any>(null);
  private tokenSubject = new BehaviorSubject<string>('');

  public user$ = this.userSubject.asObservable();
  public token$ = this.tokenSubject.asObservable();

  constructor() {
    this.pb = new PocketBase('http://localhost:8090');
    
    // 初始化状态
    this.userSubject.next(this.pb.authStore.record);
    this.tokenSubject.next(this.pb.authStore.token || '');

    // 监听认证状态变化
    this.pb.authStore.onChange((token, record) => {
      this.tokenSubject.next(token || '');
      this.userSubject.next(record);
    });
  }

  get client(): PocketBase {
    return this.pb;
  }

  async login(email: string, password: string): Promise<any> {
    try {
      return await this.pb.collection('users').authWithPassword(email, password);
    } catch (error) {
      throw new Error(error.message);
    }
  }

  logout(): void {
    this.pb.authStore.clear();
  }

  get isAuthenticated(): boolean {
    return this.pb.authStore.isValid;
  }
}

创建数据服务

// services/data.service.ts
import { Injectable } from '@angular/core';
import { PocketBaseService } from './pocketbase.service';
import { BehaviorSubject, catchError, finalize, from, Observable, tap } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private loadingSubject = new BehaviorSubject<boolean>(false);
  private errorSubject = new BehaviorSubject<string>('');

  public loading$ = this.loadingSubject.asObservable();
  public error$ = this.errorSubject.asObservable();

  constructor(private pbService: PocketBaseService) {}

  getCollection<T>(collectionName: string, page = 1, perPage = 30, filters: any = {}): Observable<T[]> {
    this.loadingSubject.next(true);
    this.errorSubject.next('');

    return from(
      this.pbService.client.collection(collectionName).getList(page, perPage, {
        filter: this.buildFilterString(filters),
        sort: '-created'
      })
    ).pipe(
      tap(result => result.items),
      catchError(error => {
        this.errorSubject.next(error.message);
        throw error;
      }),
      finalize(() => this.loadingSubject.next(false))
    );
  }

  private buildFilterString(filters: any): string {
    return Object.entries(filters)
      .filter(([_, value]) => value !== null && value !== undefined && value !== '')
      .map(([key, value]) => `${key} ~ "${value}"`)
      .join(' && ');
  }

  // 其他CRUD操作方法...
}

在组件中使用

// components/post-list.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from '../services/data.service';
import { PocketBaseService } from '../services/pocketbase.service';

@Component({
  selector: 'app-post-list',
  templateUrl: './post-list.component.html',
  styleUrls: ['./post-list.component.css']
})
export class PostListComponent implements OnInit {
  posts: any[] = [];
  loading = false;
  error = '';

  constructor(
    private dataService: DataService,
    private pbService: PocketBaseService
  ) {}

  ngOnInit(): void {
    this.loadPosts();
  }

  loadPosts(): void {
    this.loading = true;
    this.dataService.getCollection('posts').subscribe({
      next: (posts) => {
        this.posts = posts;
        this.error = '';
      },
      error: (error) => {
        this.error = error.message;
      },
      complete: () => {
        this.loading = false;
      }
    });
  }

  // 实时订阅示例
  setupRealtimeSubscription(): void {
    this.pbService.client.collection('posts').subscribe('*', (e) => {
      console.log('实时更新:', e);
      // 处理实时数据更新
    });
  }
}

高级特性与最佳实践

文件上传与管理

// 文件上传示例
const uploadFile = async (file, collectionName, recordId, fieldName) => {
  const formData = new FormData();
  formData.append(fieldName, file);

  try {
    const record = await pb.collection(collectionName).update(recordId, formData);
    return record;
  } catch (error) {
    throw new Error(`文件上传失败: ${error.message}`);
  }
};

// 获取文件URL
const getFileUrl = (record, fileName, thumb = '0x0') => {
  return pb.files.getUrl(record, fileName, { thumb });
};

错误处理与重试机制

// 高级错误处理
const withRetry = async (operation, maxRetries = 3, delay = 1000) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      // 指数退避重试
      await new Promise(resolve => 
        setTimeout(resolve, delay * Math.pow(2, attempt - 1))
      );
    }
  }
};

// 使用示例
const fetchDataWithRetry = () => 
  withRetry(() => pb.collection('posts').getList(1, 10));

性能优化技巧

mermaid

安全最佳实践

认证安全

// Token自动刷新
const setupTokenRefresh = () => {
  setInterval(async () => {
    if (pb.authStore.isValid) {
      try {
        await pb.collection('users').authRefresh();
      } catch (error) {
        console.warn('Token刷新失败:', error);
        pb.authStore.clear();
      }
    }
  }, 15 * 60 * 1000); // 每15分钟刷新一次
};

输入验证与清理

// 数据验证工具
const validateInput = (data, schema) => {
  const errors = {};
  
  Object.keys(schema).forEach(key => {
    const validator = schema[key];
    const value = data[key];
    
    if (!validator(value)) {
      errors[key] = `字段 ${key} 验证失败`;
    }
  });
  
  return Object.keys(errors).length > 0 ? errors : null;
};

// 使用示例
const userSchema = {
  email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
  password: (value) => value && value.length >= 6
};

部署与生产环境配置

环境变量配置

// 环境敏感的配置
const getPocketBaseConfig = () => {
  const env = process.env.NODE_ENV || 'development';
  
  const configs = {
    development: {
      url: 'http://localhost:8090',
      autoCancel: true
    },
    production: {
      url: 'https://api.yourdomain.com',
      autoCancel: false
    },
    test: {
      url: 'http://localhost:8091',
      autoCancel: true
    }
  };
  
  return configs[env];
};

// 初始化配置
const config = getPocketBaseConfig();
const pb = new PocketBase(config.url);
pb.autoCancellation(config.autoCancel);

监控与日志

// 请求拦截器
pb.beforeSend = function (url, options) {
  console.log(`请求: ${url}`, options);
  return { url, options };
};

【免费下载链接】pocketbase 开源的实时后端,仅用1个文件实现。 【免费下载链接】pocketbase 项目地址: https://gitcode.com/GitHub_Trending/po/pocketbase

更多推荐