useSelector 不起作用,反应中的 redux

时间:2021-06-20 00:53:32

标签: reactjs redux react-hooks redux-toolkit

当我使用 useSelector 时,变量总是保持其初始状态。我感觉它存储在某个平行星系中并且从未更新过。但是当我用 const store = useStore(); 检索值时store.getState()... 它给出了正确的值(但缺少订阅)。当我在 redux devtools 中检查商店时,我可以看到所有值都正确记录在商店中。只是不使用 useSelector 从商店中检索值。

我想要实现的是为用户配置文件提供一些缓存,即不在同一页面上多次获取 /api/profile/25 。我不想将其视为“缓存”并发出多个请求,只是记住请求被缓存并且很便宜,而是将其视为从商店获取配置文件并记住在需要时获取配置文件,我意味着一些懒惰的更新。

实现应该看起来像一个钩子,即

// use pattern
const client = useProfile(userId);
// I can also put console.log here to see if the component is getting updated
let outputProfileName;
if( client.state==='pending' ) {
    outputProfileName = 'loading...';
} else if( client.state==='succeeded' ) {
    outputProfileName = <span>{client.data.name}</span>
} // ... etc

所以我把我的代码放在 use-profile.js 中,在 profile-slice.js 中有 redux-toolkit 切片

profile-slice.js

import {
    createSlice,
    //createAsyncThunk,
} from '@reduxjs/toolkit';


const entityInitialValue = {
    data: undefined,
    state: 'idle',
    error: null
};


export const slice = createSlice({
    name: 'profile',
    initialState: {entities:{}},
    reducers: {
        updateData: (state,action) => {
            // we received data, update the data and the status to 'succeeded'
            state.entities[action.payload.id] = {
                ...entityInitialValue,
                //...state.entities[action.payload.id],
                data: action.payload.data,
                state: 'succeeded',
                error: null
            };
            return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
        },
        dispatchPendStart: (state,action) => {
            // no data - indicates we started fetching
            state.entities[action.payload.id] = {
                ...entityInitialValue,
                //...state.entities[action.payload.id],
                data: null,
                state: 'pending',
                error: null
            };
            return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
        },
        dispatchError: (state,action) => {
            state.entities[action.payload.id] = {
                //...entityInitialValue,
                ...state.entities[action.payload.id],
                data: null,
                state: 'failed',
                error: action.payload.error
            };
            return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
        },
    },
    extraReducers: {
    }
});

export const {updateData,dispatchPendStart,dispatchError} = slice.actions;

// export const selectProfile... not used

export default slice.reducer;

使用-profile.js

import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector, useStore } from 'react-redux';
import {
    updateData as actionUpdateData,
    dispatchPendStart as actionDispatchPendStart,
    dispatchError as actionDispatchError,
} from './profile-slice';
//import api...

function useProfile(userId) {

    const dispatch = useDispatch();
    const actionFunction = async () => {
        const response = await client.get(`... api endpoint`);
        return response;
    };

    const store = useStore();
    // versionControl is a dummy variable added for testing to make sure the component is updated;
    // it is updated: I tried adding console.log to my component function (where I have const client = useProfile(clientId)...)
    const [versionControl,setVersionControl] = useState(0);
    const updateVersion = () => setVersionControl(versionControl+1);

    // TODO: useSelector not working

    const updateData   = newVal => { dispatch(actionUpdateData({id:userId,data:newVal})); updateVersion(); };
    const dispatchPendStart  = newVal => { dispatch(actionDispatchPendStart({id:userId})); updateVersion(); };
    const dispatchError  = newVal => { dispatch(actionDispatchError({id:userId,error:newVal})); updateVersion(); };

    const [
        getDataFromStoreGetter,
        getLoadingStateFromStoreGetter,
        getLoadingErrorFromStoreGetter,
    ] = [
        () => (store.getState().profile.entities[userId]||{}).data,
        () => (store.getState().profile.entities[userId]||{}).state,
        () => (store.getState().profile.entities[userId]||{}).error,
    ];

    const [
        dataFromUseSelector,
        loadingStateFromUseSelector,
        loadingErrorFromUseSelector,
    ] = [
        useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].data : undefined ),
        useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' ),
        useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingError : undefined ),
    ];

    useEffect( async () => {
        if( !(['pending','succeeded','failed'].includes(getLoadingStateFromStoreGetter())) ) {
            // if(requestOverflowCounter>100) { // TODO: protect against infinite loop of calls
            dispatchPendStart();
            try {
                const result = await actionFunction();
                updateData(result);
            } catch(e) {
                dispatchError(e);
                throw e;
            }
        }
    })

    return {
        versionControl, // "versionControl" is an approach to force component to update;
        //      it is updating, I added console.log to the component function and it runs, but the values
        //      from useSelector are the same all the time, never updated; the problem is somewhere else; useSelector is just not working
        // get data() { return getDataFromStoreGetter(); }, // TODO: useSelector not working; but I need subscribtions
        // get loadingState() { return getLoadingStateFromStoreGetter(); },
        // get loadingError() { return getLoadingErrorFromStoreGetter(); },
        data: dataFromUseSelector,
        loadingState: loadingStateFromUseSelector,
        loadingError: loadingErrorFromUseSelector,
    };
}


export default useProfile;

store.js

import { configureStore,combineReducers } from '@reduxjs/toolkit';

import profileReducer from '../features/profile/profile-slice';
// import other reducers

export default configureStore({
    reducer: {
        profile: profileReducer,
        // ... other reducers
    },
});

component.js - 实际上看上面的使用模式,除了贴出来的几行之外没有什么有趣的。

所以

当我导出加载状态时(我的意思是 use-profile.js 中的最后一行;我可以取消最后三行并取消对其他三行的注释)。因此,如果我使用 getLoadingStateFromStoreGetter(通过 store.getState() 检索的值...),那么一些配置文件名称会显示已提取的名称,而另一些则持有“正在加载...”并永远卡住。这说得通。从 redux store 中检索到正确的数据,我们没有订阅。

当我导出用 useSelector 创建的另一个版本时,我总是得到它的初始状态。我从未收到任何用户名或表示“正在加载”的值。

我在 StackOverflow 上阅读了很多答案。一些常见的错误包括:

  • 有人说您的组件没有更新。事实并非如此,我测试了它,将 console.log 放在代码中并添加 versionControl 变量(参见代码)以确保它更新。

  • 有些答案是说您没有正确使用 reducer 更新存储,并且它仍然持有相同的对象。事实并非如此,我尝试了两种方法,返回一个新的对象 {...state,entities:{...state.entities...etc...}} 并改变现有的代理对象 - 两种方式我的reducers 应该提供一个新对象,redux 应该通知更改。

  • 有时会创建多个商店实例并且事情会变得一团糟。绝对不是这样,我只有一次调用 configureStore() 和一个组件。

  • 此外,我在我的代码中没有看到违反钩子规则的情况。我在 useSelector fn 中有一个 if 语句,但是 useSelector 钩子本身是无条件调用的。

我不知道还有什么其他原因导致 useSelect 无法正常工作。谁能帮我理解一下?

1 个答案:

答案 0 :(得分:1)

操作,像往常一样,非常简单的错字是原因。花了这么多小时。非常抱歉那些花时间试图查看此内容的人,并感谢您的时间。

useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' )

应该不是.loadingState,而是.state。就是这样。

相关问题