reactjs - 如何在 Redux Toolkit 中订阅 React 组件之外的状态?

标签 reactjs typescript redux react-redux redux-toolkit

我有以下切片:

export const authenticationSlice = createSlice({
    name: 'authentication',
    initialState: {
        isFirstTimeLoading: true,
        signedInUser: null
    },
    reducers: {
        signOut: (state) => {
            state.signedInUser = null
        },
        setUserAfterSignIn: (state, action: PayloadAction<SignInResult>) => {
            // some logic...
            state.signedInUser = {...}
        }
    },
    extraReducers: builder => {
        // Can I subscribe to signedInUser changes here?
    }
})
有什么方法可以订阅的时候signedInUser更改( setUserAfterSignInsignOut ),内部 extraReducers ?
例如每次setUserAfterSignIn我想在 axios 中添加一个拦截器,它使用用户的 accessToken 作为 Auth header 。
我也可以从另一个切片订​​阅这个状态吗?如果不同切片中的某个状态取决于 signedInUser ?
编辑:这是登录用户的 thunk 和注销的
export const { signOut: signOutAction, setUserAfterSignIn: setUserAction } = authenticationSlice.actions

export const signInWithGoogleAccountThunk = createAsyncThunk('sign-in-with-google-account', async (staySignedIn: boolean, thunkAPI) => {
    const state = thunkAPI.getState() as RootState
    state.auth.signedInUser && await thunkAPI.dispatch(signOutThunk())
    const googleAuthUser = await googleClient.signIn()
    const signedInUser = await signInWithGoogleAccountServer({ idToken: googleAuthUser.getAuthResponse().id_token, staySignedIn })
    thunkAPI.dispatch(setUserAction({ data: signedInUser.data, additionalData: { imageUrl: googleAuthUser.getBasicProfile().getImageUrl() } } as SignInResult))
})

export const signInWithLocalAccountThunk = createAsyncThunk('sign-in-with-local-account', async (dto: LocalSignInDto, thunkAPI) => {
    const state = thunkAPI.getState() as RootState
    state.auth.signedInUser && await thunkAPI.dispatch(signOutThunk())
    const user = await signInWithLocalAccountServer(dto)
    thunkAPI.dispatch(setUserAction({ data: user.data } as SignInResult))
})

export const signOutThunk = createAsyncThunk<void, void, { dispatch: AppDispatch }>('sign-out', async (_, thunkAPI) => {
    localStorage.removeItem(POST_SESSION_DATA_KEY)
    sessionStorage.removeItem(POST_SESSION_DATA_KEY)

    const state = thunkAPI.getState() as RootState
    const signedInUser = state.auth.signedInUser

    if (signedInUser?.method === AccountSignInMethod.Google)
        await googleClient.signOut()
    if (signedInUser)
        await Promise.race([signOutServer(), rejectAfter(10_000)])
            .catch(error => console.error('Signing out of server was not successful', error))
            .finally(() => thunkAPI.dispatch(signOutAction()))
})

最佳答案

Redux 实现了 flux architecture .
Flux architecutre

This structure allows us to reason easily about our application in a way that is reminiscent of functional reactive programming, or more specifically data-flow programming or flow-based programming, where data flows through the application in a single direction — there are no two-way bindings.


Reducers 不应该相互依赖,因为 redux 并不能确保它们执行的特定顺序。您可以使用 combineReducer 解决此问题。 .您不能确定 extraReducerssetUserAfterSignIn 之后执行 reducer 。
您拥有的选项是:
  • 将更新axios拦截器的代码放在setUserAfterSignIn中 reducer 。
     setUserAfterSignIn: (state, action: PayloadAction<SignInResult>) => {
         // some logic...
         state.signedInUser = {...}
         // update axios
     }
    
  • 创建 axios 拦截器并将其传递给连接到商店的供应商。通过这种方式,您可以轻松替换 token 的提供方式。
     const tokenSupplier = () => store.getState().signedInUser;
    
     // ...
    
     axios.interceptors.request.use(function (config) {
         const token = tokenSupplier();
         config.headers.Authorization =  token;
    
         return config;
     });
    
  • 提取两个 reducer 函数并确保它们的顺序。
    function signInUser(state, action) {
        state.signedInUser = {...}
    }
    
    function onUserSignedIn(state, action) {
        // update axios interceptor
    }
    
    // ....
    // ensure their order in the redux reducer.
    
    setUserAfterSignIn: (state, action: PayloadAction<SignInResult>) => {
         signInUser(state, action);
         onUserSignedIn(state, action)
    }
    

  • 编辑

    Given this architecture, What are my options if I have another slice that needs to react when signedInUser has changed?


    我猜你不会喜欢这个答案。前段时间我也遇到了同样的问题。
    另一个切片是商店中的一个独立部分。您可以添加额外的 reducer 来监听来自其他切片的操作,但您无法确定其他切片的 reducer 是否已经更新了状态。
    假设您有一个切片 A和 reducer RA和一片 B带 reducer RB .如果状态B取决于 A表示 reducer RB应该在任何时候执行 A变化。
    您可以 RA调用 RB ,但这引入了对 RB 的依赖.如果 RA 就好了可以发送类似 { type: "stateAChanged", payload: stateA} 的 Action 以便其他切片可以监听该 Action ,但 reducer 无法分派(dispatch) Action 。您可以实现一个中间件,通过调度程序来增强操作。例如。
    function augmentAction(store, action) {
      action.dispatch = (a) => {
         store.dispatch(a)
      }
      store.dispatch(action)
    }
    
    以便 reducer 可以分派(dispatch) Action 。
    setUserAfterSignIn: (state, action: PayloadAction<SignInResult>) => {
        // some logic...
        state.signedInUser = {...}
        action.dispatch({type : "userSignedIn": payload: {...state}})
    }
    
    但是这种方法不是标准方法,如果过度使用它,可能会引入导致调度中无限循环的循环。
    有些人不使用不同的切片,而是使用不同的商店并使用商店的订阅连接它们。这是官方的AP,但是如果你不够注意它也会引入循环。
    所以最后最简单的方法就是调用 RB来自 RA .您可以通过反转它来稍微管理它们之间的依赖关系。例如。
    const onUserSignedIn = (token) => someOtherReducer(state, { type: "updateAxios", payload: token});
    
    setUserAfterSignIn: (state, action: PayloadAction<SignInResult>) => {
        // some logic...
        state.signedInUser = {...}
        onUserSignedIn(state.signedInUser.token)
    }
    
    现在您可以更换 onUserSignedIn测试中的回调或使用调用其他注册回调的复合函数。
    编辑
    我目前正在处理 中间件库来解决我们的问题。我在 Github 上发布了我的图书馆的实际版本和 npm .这个想法是你描述状态和 Action 之间的依赖关系,这些依赖关系应该在变化时分派(dispatch)。
    stateChangeMiddleware
      .whenStateChanges((state) => state.counter)
      .thenDispatch({ type: "text", payload: "changed" });
       
    

    关于reactjs - 如何在 Redux Toolkit 中订阅 React 组件之外的状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69053186/

    相关文章:

    typescript - Observable<T> 内部的缩小类型

    javascript - React Native 渲染组件列表

    reactjs - 在生产中使用react : What must be the size of React in production?

    javascript - 如何在ES6的onClick中调用setState函数

    node.js - 尝试发送文件时 FormData 发送空请求

    typescript - 如何避免 Typescript 转译 Jest __mocks__

    react-native - redux-observable 操作必须是普通对象。使用自定义中间件进行异步操作

    javascript - Material UI - 提供给 ButtonBase 的 `component` Prop 无效。请确保 children Prop 在此自定义组件中呈现

    Angular 5.0.2 混淆了 PhpStorm 中的 Promise 类定义 + --prod 上的错误

    javascript - Redux reducer : Filter Out Duplicate Objects?