reactjs 如何正确地从redux迁移到effector,保持动作的纯净和隔离?

72qzrwbm  于 2022-12-22  发布在  React
关注(0)|答案(1)|浏览(126)

我已经决定尝试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);
};

所以,它已经更干净,代码更少了。我之所以选择这样的结构和方法,是因为它和我现有的结构非常相似。
不管怎样,effector提供了不同的方法来改变存储,其中之一是在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,但我想不出更好的方法来重写它。
我能在这做什么?我该回终极吗?

5gfr0r5j

5gfr0r5j1#

你不应该在函数中创建效果,因为它是不可执行的,并且可能导致内存不足
使用您的代码片段,您可以创建效果

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

P.S.如果你需要调试这个效果,你可以使用patronum库中的debug

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

希望对你有帮助!

相关问题