【问题标题】:Error using createStore with typescript and thunk使用带有 typescript 和 thunk 的 createStore 时出错
【发布时间】:2019-05-31 01:34:47
【问题描述】:

我是 typescript 的新手,正在尝试将 react 应用程序转换为使用 typescript。 在我的 index.tsx 文件中使用“redux”中的 createStore 函数时,出现以下错误。

  Type '{}' is missing the following properties from type '{ customersLoading: boolean; customersLoadError: boolean; customers: {}; editCustomerInProgress: boolean; editCustomerSuccess: boolean; editCustomerFail: boolean; addCustomerInProgress: boolean; ... 6 more ...; searchResults: {}; }': customersLoading, customersLoadError, customers, editCustomerInProgress, and 10 more.  TS2345

状态似乎被定义为 {} 类型,但我已经为状态对象定义了一个接口。

索引.js

import ReactDOM from 'react-dom';
import App from './App';
import {Provider} from 'react-redux';
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';

import reducer from './store/reducer';

import * as serviceWorker from './serviceWorker';

const composeEnhancers = compose;

const rootReducer = reducer;

const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));

const app = (
    <Provider store={store}>
        <App />
    </Provider>
);


ReactDOM.render(app, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.

serviceWorker.unregister();

Reducer.tsx

import {stateShape, actionShape} from '../types/index';

const initialState = {
    customersLoading: false,
    customersLoadError: false,
    customers: {},
    editCustomerInProgress: false,
    editCustomerSuccess: false,
    editCustomerFail: false,
    addCustomerInProgress: false,
    addCustomerSuccess: false,
    addCustomerFail: false,
    deleteCustomerInProgress: false,
    deleteCustomerSuccess: false,
    deleteCustomerFail: false,
    searchMode: false,
    searchResults: {}
};

const updateObject = (oldObject: {}, updatedProperties: {}) => {
    return {
        ...oldObject,
        ...updatedProperties
    };
};

const editCustomer = (state: stateShape, action: actionShape) => {
    let updatedState = {
        editCustomerInProgress: false,
        editCustomerFail: false,
        editCustomerSuccess: true,
        customers: {
            ...state.customers,
            [action.payload.customerId]: action.payload.customerDetails
        },
        searchResults: {...state.searchResults}
    };

    if (state.searchMode){
        updatedState = {
            ...updatedState,
            searchResults: {
                ...state.searchResults,
                [action.payload.customerId]: action.payload.customerDetails
            }
        }
    };
    return updateObject(state, updatedState);
};

const addCustomer = (state: stateShape, action: actionShape) => {
    const updatedState = {
        addCustomerInProgress: false,
        addCustomerFail: false,
        addCustomerSuccess: true,
        customers: {
            ...state.customers,
            [action.payload.customerId]: action.payload.customerData
        }
    };
    return updateObject(state, updatedState);
};

const deleteCustomer = (state: stateShape, action: actionShape) => {
    const updatedState = {
        deleteCustomerInProgress: false,
        deleteCustomerFail: false,
        deleteCustomerSuccess: true,
        customers: {
            ...state.customers,
            [action.payload.customerId]: null
        }
    };
    return updateObject(state, updatedState);
};

const searchCustomers = (state: stateShape, action: actionShape) => {
    interface arrayInterface {
        [key: number]: any 
    };

    //filter matching customers from list of customers by search term
    const matchingCustomers = Object.entries(state.customers).filter((current: arrayInterface) => {
        let firstname = current[1].firstName.toLowerCase();
        let lastname = current[1].lastName.toLowerCase();
        let searchTerm = action.payload.searchTerm.toLowerCase();
        return firstname === searchTerm || lastname === searchTerm;
    });

    let matchingCustomersObject;

    matchingCustomers.forEach((current: arrayInterface) => {
        matchingCustomersObject[current[0]] = current[1];
    });

    const updatedState = {
        searchMode: true,
        searchResults: matchingCustomersObject
    };

    return updateObject(state, updatedState);
};

let reducer = (state = initialState, action: actionShape) => {
    switch (action.type){
        case actionTypes.LOAD_CUSTOMERS_START:
            return updateObject(state, {customersLoading: true});
        case actionTypes.LOAD_CUSTOMERS_FAIL:
            return updateObject(state, {customersLoading: false, customersLoadError: true});
        case actionTypes.LOAD_CUSTOMERS_SUCCESS:
            return updateObject(state, {customersLoading: false, customers: action.payload.customers});
        case actionTypes.EDIT_CUSTOMER_START:
            return updateObject(state, {editCustomerInProgress: true, editCustomerSuccess: false});
        case actionTypes.EDIT_CUSTOMER_FAIL:
            return updateObject(state, {editCustomerInProgress: false, editCustomerFail: true});
        case actionTypes.EDIT_CUSTOMER_SUCCESS:
            return editCustomer(state, action)
        case actionTypes.ADD_CUSTOMER_START:
            return updateObject(state, {addCustomerInProgress: true});
        case actionTypes.ADD_CUSTOMER_FAIL:
            return updateObject(state, {addCustomerInProgress: false, addCustomerFail: true});
        case actionTypes.ADD_CUSTOMER_SUCCESS:
            return addCustomer(state, action);
        case actionTypes.DELETE_CUSTOMER_START:
            return updateObject(state, {deleteCustomerInProgress: true});
        case actionTypes.DELETE_CUSTOMER_FAIL:
            return updateObject(state, {deleteCustomerInProgress: false, deleteCustomerFail: true});
        case actionTypes.DELETE_CUSTOMER_SUCCESS:
            return deleteCustomer(state, action);
        case actionTypes.SEARCH_CUSTOMER_START:
            return searchCustomers(state, action)
        case actionTypes.SEARCH_CUSTOMER_END:
            return updateObject(state, {searchMode: false, searchResults: []})
        default:
            return state;
    }
};

export default reducer;

reducer.tsx 导入的动作和状态形状

    customersLoading: boolean,
    customersLoadError: boolean,
    customers: {},
    editCustomerInProgress: boolean,
    editCustomerSuccess: boolean,
    editCustomerFail: boolean,
    addCustomerInProgress: boolean,
    addCustomerSuccess: boolean,
    addCustomerFail: boolean,
    deleteCustomerInProgress: boolean,
    deleteCustomerSuccess: boolean,
    deleteCustomerFail: boolean,
    searchMode: boolean,
    searchResults: {}
};

export interface actionShape {
    type: string,
    payload: {
        customerId: string,
        customerData: Object,
        customerDetails: Object,
        customers: Object,
        searchTerm: string
    }
};

有人能指出我解决这个问题的正确方向吗?

【问题讨论】:

    标签: reactjs typescript redux redux-thunk


    【解决方案1】:

    问题源于reducer 签名不兼容。让我们看看createStore是如何定义的。

    export interface StoreCreator {
        <S, A extends Action, Ext, StateExt>(
            reducer: Reducer<S, A>,
            enhancer?: StoreEnhancer<Ext, StateExt>
        ): Store<S & StateExt, A> & Ext
        <S, A extends Action, Ext, StateExt>(
           reducer: Reducer<S, A>,
           preloadedState?: DeepPartial<S>,
           enhancer?: StoreEnhancer<Ext>
        ): Store<S & StateExt, A> & Ext
    }
    

    本质上它是一个重载函数,我们对第一个参数的类型感兴趣,reducer

    export type Reducer<S = any, A extends Action = AnyAction> = (
        state: S | undefined,
        action: A
    ) => S
    

    Reducer 接受两个S(用于state)和A(用于操作)类型的参数,并返回S 类型的值。

    但是你的reducer定义如下

    let reducer = (state = initialState, action: actionShape) => { /*...*/ }
    

    对于所有操作,它都会返回updateObject(state, { /*...*/ }),而updateObject(state, { /*...*/ })又被定义为

    const updateObject = (oldObject: {}, updatedProperties: {}) => 
    {
        return {
            ...oldObject,
            ...updatedProperties
        }
    }
    

    因此,TS 推断updateObject 的返回类型为{},并且它与S 不兼容,在您的情况下为stateShape

    为了解决这个问题,我建议对updateObject使用以下签名

    const updateObject = (oldObject: stateShape, updatedProperties: Partial<stateShape>): stateShape => {/*...*/}
    

    所以它需要stateShape 类型的第一个参数和第二个 - 作为Partial&lt;stateShape&gt;。这允许您仅传递原始 stateShape 的某些属性的第二个参数,但禁止将不存在的属性添加到状态。

    更新reducer 函数的签名以使其仅返回stateShape 也是个好主意

    let reducer = (state = initialState, action: actionShape): stateShape
    

    【讨论】:

      猜你喜欢
      • 2021-11-24
      • 2019-11-14
      • 1970-01-01
      • 2021-06-03
      • 2021-06-18
      • 1970-01-01
      • 2017-09-29
      • 1970-01-01
      • 2022-11-08
      相关资源
      最近更新 更多