effector
并且我正在尝试找出用它替换我的项目中的redux的最佳方法。
每当我在 React 项目中使用 redux 时,我通常会使用以下功能结构:
src
└── features
└── some_feature
├── components
│ └── MyComponent
│ └── index.ts
└── redux
├── actions.ts
├── types.ts
└── reducer.ts
话虽如此,我的文件如下所示:
// src/features/some_feature/components/MyComponent/index.ts
import * as React from 'react';
import { useDispatch } from 'react-redux';
import { someFunction } from '../../redux/actions';
const MyComponent: React:FC = () => {
const dispatch = useDispatch();
React.useEffect(() => {
dispatch(someFunction());
}, [])
return <div>My component!</div>
}
我的行动:
// src/features/some_feature/redux/actions.ts
import { httpClient } from 'src/services/httpClient';
import { SOME_ACTION } from '../types';
type TSetSomeData = {
payload: {
someData: any;
}
}
const setSomeData = ({ payload: { someData } }): ThunkAction =>
(dispatch): void => {
dispatch({
type: SOME_ACTION,
payload: someData
})
};
export const someFunction = (): ThunkAction =>
async (dispatch): Promise<void> => {
try {
const { someData } = (await httpClient({
url: '/api/some-endpoint',
method: EMethodTypes.GET,
})) as {
someData: any;
};
dispatch(setSomeData({ payload: { someData } }));
} catch (err) {
console.log('Error in someFunction', err);
}
};
我的减速机:
// src/features/some_feature/redux/reducer.ts
import { AnyAction } from 'redux';
import { SOME_ACTION } from './types';
export type ISomeFeatureState = {
someData: any;
};
const initialState = {
someData: null,
};
const someFeatureReducer = (state: ISomeFeatureState = initialState, action: AnyAction): ISomeFeatureState => {
const { type, payload } = action;
if (type === SOME_ACTION) {
return {
...state,
someData: payload,
};
} else {
return {
...state,
};
}
};
export default someFeatureReducer;
并且
types.ts
会有export const SOME_ACTION = '@redux/features/some_feature/some-action'
。
无论如何,这是我的文件夹结构现在的样子:
src
└── features
└── some_feature
├── components
│ └── MyComponent
│ └── index.ts
└── effector
├── actions.ts
├── events.ts
└── store.ts
这是文件:
// src/features/some_feature/effector/store.ts
import { createStore } from 'effector';
export const $someData = createStore(null, {
updateFilter: (someData) => !!someData,
});
// src/features/some_feature/effector/events.ts
import { $someData } from './store';
export const setSomeDataEvent = createEvent();
$someData.on(setSomeDataEvent, (state, payload) => payload);
// src/features/some_feature/effector/actions.ts
import { setSomeDataEvent } from './events';
type TSetSomeData = {
payload: {
someData: any;
};
};
export const setSomeData = ({ payload: { someData } }: TSetSomeData) => {
setSomeDataEvent(someData);
};
所以,它已经更干净、代码更少了。我选择这样的结构和方法的原因是因为它与我现有的结构非常相似。
无论如何,效应器提供了不同的变异存储方式,其中之一是
doneData
:
// from the docs
import { createEvent, createStore, createEffect, sample } from 'effector'
const nextPost = createEvent()
const getCommentsFx = createEffect(async postId => {
const url = `posts/${postId}/comments`
const base = 'https://jsonplaceholder.typicode.com'
const req = await fetch(`${base}/${url}`)
return req.json()
})
const $postComments = createStore([])
.on(getCommentsFx.doneData, (_, comments) => comments)
const $currentPost = createStore(1)
.on(getCommentsFx.done, (_, {params: postId}) => postId)
sample({
source: $currentPost,
clock: nextPost,
fn: postId => postId + 1,
target: getCommentsFx,
})
nextPost()
在这里,一旦
getCommentsFx
完成执行,存储 $postComments
的值将设置为 getCommentsFx.doneData
解析的值。
我遇到的麻烦是使用相同的方法,但使其与我当前的项目“友好”。
我能想到的唯一方法就是像这样重写
someFunction
:
// src/features/some_feature/effector/actions.ts
import { createEffect } from 'effector';
import { httpClient } from 'src/services/httpClient';
import { $someData } from '../store';
import { setSomeDataEvent } from '../events';
type TSetSomeData = {
payload: {
someData: any;
}
}
export const setSomeData = ({ payload: { someData } }: TSetSomeData) => {
setSomeDataEvent(someData);
};
export const someFunction = (): ThunkAction =>
async (dispatch): Promise<void> => {
try {
const { someData } = await createEffect<{someData: any}>(async () =>
httpClient({
url: '/api/some-endpoint',
method: EMethodTypes.GET,
})
);
setSomeDataEvent(someData);
} catch (err) {
console.log('Error in someFunction', err);
}
};
但我认为使用
createEffect
根本没有意义,因为我可以这样做:
export const someFunction = (): ThunkAction =>
async (dispatch): Promise<void> => {
try {
const { someData } = (await httpClient({
url: '/api/some-endpoint',
method: EMethodTypes.GET,
})) as {
someData: any
}
setSomeDataEvent(someData);
} catch (err) {
console.log('Error in someFunction', err);
}
};
对此有什么建议吗?使用
effector
而不使用 createEffect
(或通过其 API 提供的大多数其他方法)是否可以?本质上,我只是创建商店并将事件绑定到它们,我觉得我没有按照预期的方式使用effector
,但我想不出更好的方法来重写它。
我在这里能做什么?我应该回到 Redux 吗?
您不应该在函数内创建效果,因为它无法执行并且可能导致内存不足
使用您的代码片段可以创建效果
import { createEffect } from "effector";
type SomeDataType = any;
type Input = void;
type Output = { someData: SomeDataType };
const fetchSomeDataFx = createEffect<Input, Output>(async () => {
return await httpClient({
url: '/api/some-endpoint',
method: EMethodTypes.GET,
})
})
附注如果您需要调试此效果,可以使用
debug
库中的 patronum
import { debug } from "patronum";
debug(fetchSomeDataFx);
如果您需要显示有关失败的http请求的信息,您可以使用
import { createStore } from "effector";
export const $fetchSomeDataError = createStore<Error | null>(null)
.reset(fetchSomeDataFx)
.on(fetchSomeDataFx.failData, (_, error) => error);
并将此效果附加到存储
import { sample, createStore } from "effector";
export const $someData = createStore<SomeDataType | null>(null);
sample({
source: fetchSomeDataFx.doneData,
fn: (doneData) => doneData.someData,
target: $someData
})
希望对你有帮助!
我们创建了从 Redux 迁移到 Effector 的综合指南。请看一下 — https://withease.pages.dev/magazine/migration_from_redux.html