手把手教你实现React数据持久化机制

3,688 阅读3分钟

背景

最近在项目中遇到一个有意思的需求,大致如下:

image.png

如上图红框所示,该列表数据展示的依赖条件总共有四个:两个下拉选择框,一个搜索框和一个分页器。

现有如下需求:

当用户选择或填写了上述条件时,帮助用户记住所操作的内容,当用户再次进入该页面时,保持和离开之前所看到的内容相同。

需求分析

有了需求之后,我们先对这个小需求进行简单的分析。

对于该需求,关键词在于“记住”。毫无疑问,肯定是将搜索条件的值存放在localStorage/sessionStorage,需求上应该没什么问题。

技术设计

需求分析了个七七八八之后,接下来就是技术设计环节。

正常来说,不考虑这个小需求的话,我们应该是这样做:

const [params,setParams]=useState({ "address":"", keyword:"", page:1, pageSize:10 })
// 当用户改变依赖条件(翻页)时执行 setParams({...params, page:2 })

现在由于需要“记住”之前的状态,我们需要将useState中的initialState同步到Storage中去,且在setState的时候对Storage进行同步更新。当读取state时,如果Storage中有值则直接读取Storage中的值,如果没有则读取默认的initialState

具体api设计:

const [ xxxstate,setXxxState]=useStorageState("存储在Storage中的key值",initialState)

技术实现

import { useState } from 'react';
interface Options {
  priority?: 'local' | 'initialValue';
  type?: 'localStorage' | 'sessionStorage';
}

//保持状态一致
const valueToStorageFun = <T>({ type, key, value }: { type: Options['type']; key: string; value: T }) => {
  if (typeof window !== 'undefined') {
    switch (type) {
      case 'localStorage':
        window.localStorage.setItem(key, JSON.stringify(value));
        break;
      case 'sessionStorage':
        window.sessionStorage.setItem(key, JSON.stringify(value));
        break;
      default:
        window.localStorage.setItem(key, JSON.stringify(value));
    }
  }
};

export function useStorageState<T>(key: string, initialValue: T, options?: Options) {
  const { priority = 'local', type = 'localStorage' } = { priority: 'local', type: 'localStorage', ...options };
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === 'undefined') {
      return initialValue;
    }
    try {
      // 根据类型判断从何处读取数据
      let item;
      switch (type) {
        case 'localStorage':
          item = window.localStorage.getItem(key);
          break;
        case 'sessionStorage':
          item = window.sessionStorage.getItem(key);
          break;
        default:
          item = window.localStorage.getItem(key);
      }

      // 解析state并按优先级进行处理
      if (item) {
        switch (priority) {
          case 'local':
            return JSON.parse(item);
          case 'initialValue':
            valueToStorageFun({ key, type, value: initialValue });
            return initialValue;
          default:
            return JSON.parse(item);
        }
      } else {
        return initialValue;
      }
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value: T | ((val: T) => T)) => {
    try {
      //和useState保持相同用法 支持函数和默认值
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      //保存state
      setStoredValue(valueToStore);
      //同步到storage中
      valueToStorageFun({ key, type, value: valueToStore });
    } catch (error) {
      console.log(error);
    }
  };
  return [storedValue, setValue] as const;
}

总结

这里肯定有小伙伴要问了,你写的这个hookahooks中的useLocalStorageState有什么区别呀,不是一样的吗?

眼尖的肯定发现了手动实现useStorageState的参数中有个priority字段,这里是根据业务来决定的。拿上面的业务场景举个简单的例子🌰:

有一些用户操作记录是需要实时同步到URL中的,比如输入框中的keyword,这个时候当其他页面带着keyword跳到该页面的时候,是不能读本地存储的,需要优先读取URL中的参数,这个时候需要将priority设为initialValue

拿京东页面举例:

当用户带着参数跳转到这个商品查找页面时,该页面是需要优先读取URL中的参数

image.png

这里可能有些小伙伴又要问了,你为什么不直接把这些状态全部同步到URL中呢,还存在缓存中这么麻烦?

为什么没有采用这种方式是因为你在该页面的操作记录,是很难传到其他页面的,其他页面往该页面跳转的时候,你也很难知道这个页面之前的状态。

大致就是这些,若有觉得不妥的地方,请大家积极指点😁。