React + Redux + TypeScript - PullRequest
       1

React + Redux + TypeScript

0 голосов
/ 13 февраля 2019

Я пытаюсь внедрить Redux в мой проект React.Прямо сейчас у меня есть ошибка на моей первой странице, и я не могу ее исправить.Мой редуктор, хранилище, начальное состояние не определены при инициализации компонента.

Я постараюсь поместить весь важный код, если что-то отсутствует, я могу обновить вопрос.

Тип действия:

export default interface IAction<T>
{
    type: string;
    payload?: T;
    error?: boolean;
    meta?: any;
}

Корневой магазин:

import IBookingTypeState from '../pages/bookmark/home/IBookingTypeState';
import IBookingSearchState from '../pages/bookmark/search/bookingSearchState';

export default interface IStore
{
    readonly bookingSearchReducer: IBookingSearchState;
}

Корневой редуктор:

import { combineReducers, Reducer, ReducersMapObject } from 'redux';

import IStore from './rootStore';
import BookingTypeReducer from './booking/bookingType/bookingTypeReducer';
import BookingSearchReducer from './booking/bookingSearch/bookingSearchReducer';

const reducerMap: ReducersMapObject =
{
    bookigSeacrhReducer: BookingSearchReducer.reducer
}

export default combineReducers(reducerMap) as Reducer<IStore>;

Создатель действия:

import IAction from '../../IAction';
import IBookingSearch from './../../../models/bookingSearch';

export default class BookingSearchAction
{
    public static readonly SET: string = 'BookingSearchAction.SET';
    public static readonly GET: string = 'BookingSearchAction.GET';

    public static setBookingsSearch(bookingSearch: IBookingSearch): IAction<IBookingSearch>
    {
        return {
            error: false,
            meta: null,
            payload: bookingSearch,
            type: BookingSearchAction.SET
        }
    }

    public static getBookingsSearch(): IAction<null>
    {
        return {
            error: false,
            meta: null,
            payload: null,
            type: BookingSearchAction.GET
        }
    }
}

IBookingSearch - это просто интерфейс для хранения данных поиска.

Редуктор:

import IBookingSearch from './../../../models/bookingSearch';
import IBookingSearchState from './../../../pages/bookmark/search/bookingSearchState';
import IAction from './../../IAction';
import BookingSearchAction from './bookingSearchAction';

export default class BookingSearchReducer
{
    private static readonly _initialState: IBookingSearchState =
    ({
        searchParameters:
        {
            Arrival: new Date(),
            Departure: new Date(),
            Lenght: 0,
            Beam: 0,
            Draft: 0,
            ShorePower: 0,
            PromoCode: ''
        }
    });

    public static reducer(state: IBookingSearchState = BookingSearchReducer._initialState, action: IAction<IBookingSearch>) : IBookingSearchState
    {
        switch (action.type)
        {
            case BookingSearchAction.SET:
                return BookingSearchReducer._setBookingSearch(state, action);
            case BookingSearchAction.GET:
                return BookingSearchReducer._getBookingSearch(state, action);
            default:
                return state;
        }
    }

    private static _setBookingSearch(state: IBookingSearchState, action: IAction<IBookingSearch>) : IBookingSearchState
    {
        if (!action.payload)
        {
            return {
                ...state
            }
        }

        return {
            ...state,
            searchParameters: action.payload
        }
    }

    private static _getBookingSearch(state: IBookingSearchState, action: IAction<IBookingSearch>) : IBookingSearchState
    {
        return {
            ...state
        }
    }
}

Компонент, в котором я хочу его использовать:

interface IStateToProps extends IBookingSearchState
{}

interface IDispatchToProps
{
    dispatch: (action: IAction<IBookingSearch>) => void;
}

const mapStateToProps = (state: IStore): IStateToProps =>
({
    searchParameters: state.bookingSearchReducer.searchParameters
});

const mapDispatchToProps = (dispatch: Dispatch<IAction<IBookingSearch>>): IDispatchToProps =>
({
    dispatch
});

class SearchFormDesktop extends React.Component<IStateToProps & IDispatchToProps & WithStyles<typeof styles> & RouteComponentProps<{}>, {}>
{
    private searchParameters: IBookingSearch = (null!);

    constructor(props: IStateToProps & IDispatchToProps & WithStyles<typeof styles> & RouteComponentProps<{}>)
    {
        super(props);
   }

    handleChange = (e: React.ChangeEvent<HTMLInputElement>): void =>
    {
        e.preventDefault();
        this.searchParameters
        {
            [e.target.name] = e.target.value
        }
    }

    handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>): void =>
    {
        e.preventDefault();
        this.searchParameters
        {
            [e.target.name] = e.target.value
        }
    }

    private submitHandler = () =>
    {
        console.log(this.state);

        //dispatch state
        this.props.dispatch(BookingSearchAction.setBookingsSearch(this.searchParameters));

        this.props.history.push(Urls.bookingoffers);
    }

    render()
    {
        const css = this.props.classes;
        const enabled = this.searchParameters &&
                        this.searchParameters.Arrival &&
                        this.searchParameters.Departure &&
                        this.searchParameters.Beam &&
                        this.searchParameters.Lenght &&
                        this.searchParameters.Draft

        const Body = () =>
            <StackPanel stretch orientation="vertical" centerVertically className = { css.root }>
                <StackPanel  centerHorizontally centerVertically>
                    <h3>Short-Term Dockage</h3>
                </StackPanel>
                <DockPanel {...dockBottom}>
                    <StackPanel {...dockLeft}>
                        <DatePickerWithLapbel label="Arrival" name="arrival" onChange={this.handleChange}/>
                    </StackPanel>
                    <StackPanel  {...dockRight}>
                        <DatePickerWithLapbel label="Departure" name="departure" onChange={this.handleChange}/>
                    </StackPanel>
                </DockPanel>
                <DockPanel {...dockBottom}>
                    <StackPanel  {...dockLeft}>
                        <NumericInput label="Lenght" name="Lenght" onChange={this.handleChange}/>
                    </StackPanel>
                    <StackPanel>
                        <NumericInput label="Beam" name="Beam" onChange={this.handleChange}/>
                    </StackPanel>
                    <StackPanel  {...dockRight}>
                        <NumericInput label="Draft" name="Draft" onChange={this.handleChange}/>
                    </StackPanel>
                </DockPanel>
                <DockPanel {...dockBottom}>
                    <SelectInput {...dockLeft} label="Shore Power (optional)" name="ShorePower" onChange={this.handleSelectChange}/>
                </DockPanel>
                <DockPanel {...dockBottom}>
                    <StackPanel>
                        <Typography variant="subtitle1" className={css.promo}>Add promo or group code</Typography>
                    </StackPanel>
                </DockPanel>
                <DockPanel {...dockBottom}>
                    <StackPanel>
                        <Button variant="contained"
                                color="primary"
                                fullWidth={ true }
                                size="large"
                                className={css.button}
                                onClick = { this.submitHandler }
                                disabled={!enabled}
                                >
                            Check Availability
                        </Button>
                    </StackPanel>
                </DockPanel>
            </StackPanel>

        return Body();
  }
}

export default connect(mapStateToProps, mapDispatchToProps) (withRoot(withStyles(styles)(SearchFormDesktop)));

И, наконец, индекс.tsx

// create the saga middleware
const sagaMiddleware = createSagaMiddleware();
const store = createStore(Reducer, composeWithDevTools(applyMiddleware(sagaMiddleware)));
sagaMiddleware.run(rootSaga);

const container =
      <Provider store={store}>
        <AppContainer>
          <Router history={ history }>
            <IndexRoutes/>
          </Router>
        </AppContainer>
      </Provider>

const ROOT = document.getElementById('root');

ReactDOM.render(container, ROOT);

В моем проекте я также использую Material-UI.Thnx.

Ответы [ 2 ]

0 голосов
/ 13 февраля 2019
  1. У вас есть опечатка: bookigSeacrhReducer.Вы можете добавить redux-logger middleware, чтобы увидеть, что на самом деле содержит хранилище.

  2. connect будет вводить this.props.searchParameters, а не this.searchParameters.Вы должны прочитать их оттуда.

  3. Я не знаком с пользовательским интерфейсом материалов, но вы обычно используете контролируемые компоненты .Это означает, что вы сохраните текущее состояние формы в хранилище Redux или в состоянии компонента и измените его в обратном вызове handleChange (выполнив действие или setState() соответственно.

Могут быть и другие проблемы с вашим кодом, но трудно определить без полного контекста. Убедитесь, что он правильно проверяет тип.

0 голосов
/ 13 февраля 2019

mapStateToProps и mapDispatchToProps внедряют реквизиты в ваш компонент, то есть эти реквизиты не предоставляются, когда компонент визуализируется в другом компоненте = когда его конструктор волшебным образом вызывается через JSX.И это нормально.

Что не в порядке, так это то, что вы приказали TypeScript взорваться, если конструктор не получает эти реквизиты:

constructor(props: IStateToProps & IDispatchToProps & WithStyles<typeof styles> & RouteComponentProps<{}>)

Поэтому я рекомендую вам следовать этому шаблону:

Вам понадобятся 2 интерфейса.IProps, которые являются реквизитами, передаются через jsx в вызывающем компоненте.И IInjectedProps, который расширяет IProps (а в вашем случае и остальные внедренные интерфейсы).В конструкторе реквизиты просто IProps.Это означает, что вы просите, чтобы машинопись взорвалась, только если вызываемые реквизиты не соблюдаются.

interface IProps{
//strong typed properties expected from the component that renders this component
}

interface IInjectedProps extends IProps, IStateToProps, IDispatchToProps /*and anything else injected*/ {
//optionally add injected properties here
}

class SearchFormDesktop extends Component<IProps, IState> {
//...
get injected() {
   return this.props as IInjectedProps
}

//now you can use this.injected for injected props, and this.props for passed props. 
//Check their intellisense. Interface segregation ftw.

}

Если вы когда-нибудь возьмете один из этих причудливых новых функциональных компонентов, вместо «впрыскивания» вы можете использовать:

injected = props as IInjectedProps 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...