English 中文(简体)
How to properly migrate to effector from redux, keeping actions pure and isolated?
原标题:
The bounty expires in 7 days. Answers to this question are eligible for a +100 reputation bounty. Do Async wants to draw more attention to this question:
Migration guide to effector (effector.dev) from redux with illustrative examples (React)

I ve decided to try effector and I m trying to figure out the best way to replace redux in my project with it.

Whenever I use redux in a React project, I usually have the following structure for a feature:

src
└── features
    └── some_feature
        ├── components
        │   └── MyComponent
        │       └── index.ts
        └── redux
            ├── actions.ts
            ├── types.ts
            └── reducer.ts

With the said, here is how my files would look like:

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

My actions:

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

My reducer:

// 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;

And types.ts would have export const SOME_ACTION = @redux/features/some_feature/some-action .

Anyway, here is how my folder structure looks like now:

src
└── features
    └── some_feature
        ├── components
        │   └── MyComponent
        │       └── index.ts
        └── effector
            ├── actions.ts
            ├── events.ts
            └── store.ts

And here are the files:

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

So, it s already cleaner and less code. The reason I ve chosen such a structure and approach, is because it is very similar to my existing structure.

Anyway, effector offers different ways of mutating stores, one of which is on 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()

Here, once getCommentsFx finishes executing, the value for the store $postComments is set with whatever getCommentsFx.doneData resolves to.

What I m having trouble with, is utilizing the same approach, but making it "friendly" with my current project.

The only way that I can think of, is rewriting someFunction like this:

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

But I see no point in using createEffect at all, because I can just do this:

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

Any suggestions about going about this? Is it fine to use effector without createEffect (or most other provided methods via its API, for that matter)? Essentially, I m just creating stores and binding events to them, I feel like I m not using effector in the way it was intended, but I can t think of a better way to rewrite it.

What can I do here? Should I just got back to Redux?

问题回答

You should n create effects inside a functions, because it is not performable and may cause memory lacks

Using your code snippet you can create effect

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. if you need to debug this effect, you can use debug from patronum library

import { debug } from "patronum";

debug(fetchSomeDataFx);  

If you need to display information about fails http request you can use

import { createStore } from "effector";

export const $fetchSomeDataError = createStore<Error | null>(null)
  .reset(fetchSomeDataFx)
  .on(fetchSomeDataFx.failData, (_, error) => error);

And attach this effect to store

import { sample, createStore } from "effector";

export const $someData = createStore<SomeDataType | null>(null);

sample({
  source: fetchSomeDataFx.doneData,
  fn: (doneData) => doneData.someData,
  target: $someData
})

Hope it helps to you!





相关问题
How to use one react app into another react app?

I have two react apps, parent app and child app. and child app have an hash router. I have build the child app using npm run build, It creates build folder. That build folder moved into inside the ...

how to get selected value in Ant Design

I want to print the selected value, and below is my option list: const vesselName = [ { value: 0 , label: ALBIDDA , }, { value: 1 , label: ALRUMEILA , }, { value: 2 ,...

How to add a <br> tag in reactjs between two strings?

I am using react. I want to add a line break <br> between strings No results and Please try another search term. . I have tried No results.<br>Please try another search term. but ...

热门标签