如何正确地从redux迁移到effector,保持action的纯粹和隔离?

问题描述 投票:0回答:2

我决定尝试

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 吗?

reactjs redux effector
2个回答
1
投票

您不应该在函数内创建效果,因为它无法执行并且可能导致内存不足

使用您的代码片段可以创建效果

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
})

希望对你有帮助!


0
投票

我们创建了从 Redux 迁移到 Effector 的综合指南。请看一下 — https://withease.pages.dev/magazine/migration_from_redux.html

© www.soinside.com 2019 - 2024. All rights reserved.