我有一个使用redux和redux-persist的Typescript项目。是否可以使用Typescript进行redux-persist迁移配置?主要的困难在于:如果你有一个Typescript类型的root redux状态,它通常/总是代表你的状态的最新版本(不一定是之前持久化的版本)。在迁移的上下文中,在知道数据与状态的最新版本不匹配的情况下,如何以类型化方式表示从持久性中读取的数据?以下是一些详细信息:
下面是redux-persist用于数据迁移的Typescript API:
export interface MigrationManifest {
[key: string]: (state: PersistedState) => PersistedState;
}
export interface PersistedState { _persist?: PersistState }
export interface PersistState { version: number; rehydrated: boolean; }提供一个MigrationManifest,它的键是版本号,值是接收持久化状态并返回新持久化状态的函数。由于PersistedState只是一个接口,似乎您可以有一个传入类型并返回一个不同的类型(在数据模式发生更改的情况下)。
因此,假设我有以下类型作为我的根持久存储类型。它符合PersistedState接口:
type RootState = {
name: string,
address: Address,
_persist?: PersistState
}
type Address = {
street: string
city: string
}在将来的某个时候,我会更新我的模型以:
type RootState = {
name: string,
address: Address,
_persist?: PersistState
}
type Address = {
vacant: boolean
}我需要提供一个类似如下的迁移:
const manifest: MigrationManifest = {
1: (state: PersistedState) => {
const persistedState = state as ???
const migratedState: RootState = migrateState(persistedState)
return migratedState
}
}我正在努力为传入的状态获取一个类型(我对???的强制转换是这样的)。在这样一个简单的示例中,维护我的状态的每个版本的记录,根据需要导入和使用它们是很容易的:
import { RootState as RootState_v0 } from 'types_v0.ts'
const manifest: MigrationManifest = {
1: (state: PersistedState) => {
const persistedState = state as RootState_v0
const migratedState: RootState = migrateState(persistedState)
return migratedState
}
}实际上,这并不是那么简单;我有一个复杂的状态树,并不是所有的状态树都定义在一个集中且易于版本控制的位置。
我可以想出一个解决方案,但我不知道是否可行,那就是以某种方式创建我的RootState类型的一个版本,其中所有的中间类型别名和接口名称都是“分解的”。类似于:
type RootState_v0 = {
name: string,
address: {
street: string
city: string
},
_persist?: {
version: number,
rehydrated: boolean
}
}如果我能以某种自动的方式创建它,那么在迁移中保存和使用它将变得容易和方便。
你知道这是否可能,或者关于如何在react-redux迁移中有效使用Typescript的任何其他建议?
发布于 2018-08-02 12:07:12
在某些情况下,您必须指定哪些内容在哪些版本中具有哪些类型。但是您可以使用查找类型和条件类型来以分散的方式更容易地构建数据结构。下面是一个例子:
interface MigrationManifest {
[key: string]: (state: PersistedState) => PersistedState;
}
interface PersistedState { _persist?: PersistState }
interface PersistState { version: number; rehydrated: boolean; }
//////
type VERSION_3 = 3;
type VERSION_2 = VERSION_3 | 2;
type VERSION_1 = VERSION_2 | 1;
type VERSION_0 = VERSION_1 | 0;
interface TypeBundleBase {
root: {};
address: {};
phoneNumber: {};
}
interface RootV0<B extends TypeBundleBase> {
name: string;
address: B["address"];
phoneNumber: B["phoneNumber"];
_persist?: PersistState;
}
interface ContactsV2<B extends TypeBundleBase> {
address: B["address"];
phoneNumber: B["phoneNumber"];
}
interface RootV2<B extends TypeBundleBase> {
name: string;
contacts: ContactsV2<B>;
_persist?: PersistState;
}
interface AddressV0<B extends TypeBundleBase> {
street: string;
city: string;
}
interface AddressV1<B extends TypeBundleBase> {
vacant: boolean;
}
type PhoneNumberV0<B extends TypeBundleBase> = string;
interface PhoneNumberV3<B extends TypeBundleBase> {
countryCode: string;
countryPhoneNumber: string;
}
interface Types<V extends VERSION_0, B extends TypeBundleBase> {
root: [V] extends [VERSION_2] ? RootV2<B> : RootV0<B>;
address: [V] extends [VERSION_1] ? AddressV1<B> : AddressV0<B>;
phoneNumber: [V] extends [VERSION_3] ? PhoneNumberV3<B> : PhoneNumberV0<B>;
}
interface FixedTypes<V extends VERSION_0> extends Types<V, FixedTypes<V>> { }
type Root<V extends VERSION_0> = FixedTypes<V>["root"];
declare function migrateState1(state: Root<VERSION_0>): Root<VERSION_1>;
declare function migrateState2(state: Root<VERSION_1>): Root<VERSION_2>;
declare function migrateState3(state: Root<VERSION_2>): Root<VERSION_3>;
const manifest: MigrationManifest = {
1: (state: PersistedState) => {
const persistedState = state as Root<VERSION_0>;
const migratedState: Root<VERSION_1> = migrateState1(persistedState);
return migratedState;
},
2: (state: PersistedState) => {
const persistedState = state as Root<VERSION_1>;
const migratedState: Root<VERSION_2> = migrateState2(persistedState);
return migratedState;
},
3: (state: PersistedState) => {
const persistedState = state as Root<VERSION_2>;
const migratedState: Root<VERSION_3> = migrateState3(persistedState);
return migratedState;
}
}这种方法可能会有一些我没有预料到的小故障,但值得一试。我怀疑我是第一个想到这种方法的人,但是对以前的工作进行了几次快速的网络搜索都没有成功。
发布于 2021-06-15 23:50:54
遇到同样的问题。
我已经通过手动创建扩展类型的PersistedState修复了它。我们可以直接扩展它,因为它是可选的,也可以是未定义的。
看起来redux-persist并不需要泛型来让所有的东西都能和TS一起工作。所以我写了一个变通方法。示例是关于状态中的字段重命名,但它可以很容易地扩展。主要目标是重命名field1并使field1Renamed处于新状态。
import { createMigrate } from 'redux-persist';
import { MigrationManifest, PersistState } from 'redux-persist/es/types';
type RootAppState = {
field1Renamed: number;
field2: number;
field3: number;
field4: number;
field5: number;
field6: number;
field7: number;
}
type PersistAppStateV1 = Pick<
RootAppState,
| 'field1Renamed'
| 'field2'
| 'field3'
| 'field4'
| 'field5'
> & {
_persist: PersistState;
};
type PersistAppStateV0 = Omit<PersistAppStateV1, 'field1Renamed'> & {
field1: RootAppState['field1Renamed'];
};
const migrations: MigrationManifest = {
1: (state): PersistAppStateV1 | undefined => {
if (!state) {
return state;
}
const { field1, ...otherState } = state as PersistAppStateV0;
return {
...otherState,
field1Renamed: field1,
};
},
};
const migrate = createMigrate(migrations);
const persistConfig = {
key: 'root',
version: 1,
migrate,
whitelist: [
'field1Renamed',
'field2',
'field3',
'field4',
'field5',
],
};https://stackoverflow.com/questions/51624096
复制相似问题