← Назад к вопросам

Как синхронизировать разные вкладки?

2.2 Middle🔥 151 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проблема синхронизации вкладок

Когда пользователь открывает несколько вкладок одного приложения, данные могут быть несинхронизированы. Пользователь меняет настройки в одной вкладке, но в другой вкладке они не обновляются.

Способ 1: Storage Event (localStorage/sessionStorage)

// Неправильно: изменения в одной вкладке не видны в другой
function Settings() {
  const [theme, setTheme] = useState("light");
  
  const handleThemeChange = (newTheme) => {
    setTheme(newTheme);
    localStorage.setItem("theme", newTheme);
    // Другие вкладки не узнают об этом!
  };
  
  return <button onClick={() => handleThemeChange("dark")}>Switch</button>;
}

// Правильно: слушаем Storage Event
function Settings() {
  const [theme, setTheme] = useState(() => {
    return localStorage.getItem("theme") || "light";
  });
  
  useEffect(() => {
    const handleStorageChange = (event) => {
      if (event.key === "theme" && event.newValue) {
        setTheme(event.newValue);
      }
    };
    
    window.addEventListener("storage", handleStorageChange);
    
    return () => {
      window.removeEventListener("storage", handleStorageChange);
    };
  }, []);
  
  const handleThemeChange = (newTheme) => {
    setTheme(newTheme);
    localStorage.setItem("theme", newTheme);
    // Storage Event триггеритсяся в других вкладках!
  };
  
  return <button onClick={() => handleThemeChange("dark")}>Switch</button>;
}

Способ 2: BroadcastChannel API (современный способ)

class TabSyncManager {
  constructor(channelName = "app-sync") {
    this.channel = new BroadcastChannel(channelName);
    this.listeners = new Map();
  }
  
  subscribe(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
    
    if (this.listeners.get(event).length === 1) {
      this.channel.addEventListener("message", (msg) => {
        if (msg.data.type === event) {
          this.listeners.get(event).forEach(cb => cb(msg.data.payload));
        }
      });
    }
  }
  
  publish(event, payload) {
    this.channel.postMessage({
      type: event,
      payload
    });
  }
}

const syncManager = new TabSyncManager("app-sync");

function UserProfile() {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    syncManager.subscribe("user-updated", (updatedUser) => {
      setUser(updatedUser);
    });
  }, []);
  
  const updateUserData = async (newData) => {
    const response = await fetch(`/api/user`, {
      method: "PUT",
      body: JSON.stringify(newData)
    });
    
    const updatedUser = await response.json();
    setUser(updatedUser);
    syncManager.publish("user-updated", updatedUser);
  };
  
  return (
    <div>
      {user && (
        <>
          <h1>{user.name}</h1>
          <button onClick={() => updateUserData({ name: "Alice" })}>
            Update
          </button>
        </>
      )}
    </div>
  );
}

Способ 3: Сочетание Storage Event + Server Updates

class UserSyncService {
  constructor() {
    this.callbacks = [];
  }
  
  subscribe(callback) {
    this.callbacks.push(callback);
  }
  
  initialize() {
    window.addEventListener("storage", (event) => {
      if (event.key === "user-state" && event.newValue) {
        const newUser = JSON.parse(event.newValue);
        this.notifySubscribers(newUser);
      }
    });
  }
  
  async updateUser(userId, updates) {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(updates)
      });
      
      const updatedUser = await response.json();
      localStorage.setItem("user-state", JSON.stringify(updatedUser));
      this.notifySubscribers(updatedUser);
      
      return updatedUser;
    } catch (error) {
      console.error("Error updating user:", error);
    }
  }
  
  notifySubscribers(user) {
    this.callbacks.forEach(cb => cb(user));
  }
}

const userSyncService = new UserSyncService();
userSyncService.initialize();

function UserProfile() {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    userSyncService.subscribe(setUser);
    
    const savedUser = localStorage.getItem("user-state");
    if (savedUser) {
      setUser(JSON.parse(savedUser));
    }
  }, []);
  
  const handleUpdate = async (newData) => {
    await userSyncService.updateUser(user.id, newData);
  };
  
  if (!user) return <div>Loading...</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <button onClick={() => handleUpdate({ name: "New Name" })}>
        Update Profile
      </button>
    </div>
  );
}

Способ 4: URL Syncing (для простых случаев)

function useUrlSync(key, initialValue) {
  const [value, setValue] = useState(() => {
    const params = new URLSearchParams(window.location.search);
    return params.get(key) || initialValue;
  });
  
  useEffect(() => {
    const handlePopState = () => {
      const params = new URLSearchParams(window.location.search);
      setValue(params.get(key) || initialValue);
    };
    
    window.addEventListener("popstate", handlePopState);
    
    return () => {
      window.removeEventListener("popstate", handlePopState);
    };
  }, [key, initialValue]);
  
  const updateValue = (newValue) => {
    setValue(newValue);
    
    const params = new URLSearchParams(window.location.search);
    params.set(key, newValue);
    window.history.pushState(null, "", `?${params.toString()}`);
  };
  
  return [value, updateValue];
}

function FilteredList() {
  const [sortBy, setSortBy] = useUrlSync("sort", "name");
  
  return (
    <div>
      <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
        <option value="name">By Name</option>
        <option value="date">By Date</option>
      </select>
    </div>
  );
}

Сравнение методов

  • localStorage + Storage Event: просто, поддержка всех браузеров, но медленно
  • BroadcastChannel: современно, быстро, отличная поддержка
  • SharedWorker: мощно, для сложных сценариев
  • URL Syncing: просто, подходит для состояния в URL