React Redux 缺少道具验证和使用 redux-observable 从外部 API 初始化容器的推荐模式
React Redux missing in props validation and recommended pattern for initialising container from an external API using redux-observable
我正在尝试学习如何在 Typescript 环境中使用 React 和 Redux。我正在使用 react-redux-typescript playground 中建议的模式。但是,我在尝试构建下面列出的代码时收到以下错误:
'courses' is missing in props validation
'courses.map' is missing in props validation
还有其他人遇到过此类错误吗?是否是 eslint 插件的 linting 错误:react/recommended?
我也很难理解在使用 redux-observable 从 API 检索数据时从 redux 存储初始化默认状态的过程。我按照 react-redux-typescript playground 中的模式建议配置了存储、epics、reducer、操作等。这些配置为使用 redux-observable 从 API 中获取课程列表。随后,我定义了三个动作和减速器:
1. FETCH_COURSES_ERROR
2. FETCH_COURSES_REQUEST
3.FETCH_COURSES_SUCCESS
然后如何触发我的 CourseList 容器以开始获取和呈现课程列表的过程。让 redux 存储获取课程列表的初始状态是一种好习惯吗(FETCH_COURSES_REQUEST -> FETCH_COURSES_SUCCESS || FETCH_COURSES_REQUEST -> FETCH_COURSES_ERROR。简而言之,我不这样做了解如何 connect/trigger 将史诗添加到 CourseList 容器....
史诗
中间件已初始化并且 运行 在存储模块中....
import { RootAction, RootState, Services } from 'ReduxTypes';
import { Epic } from 'redux-observable';
import { isOfType } from 'typesafe-actions';
import { of } from 'rxjs';
import {
catchError,
filter,
ignoreElements,
map,
switchMap,
} from 'rxjs/operators';
import { fetchCoursesFail, fetchCoursesSuccess } from './actions';
import constants from './constants';
export const fetchCoursesRequestAction: Epic<
RootAction,
RootAction,
RootState,
Services
> = (action$, state$, { courseServices }) =>
action$.pipe(
filter(isOfType(constants.FETCH_COURSES_REQUEST)),
switchMap(action =>
courseServices.default.getCourses().pipe(
map(courses => fetchCoursesSuccess(courses)),
catchError(error => of(fetchCoursesFail(error))),
),
),
ignoreElements(), // ignore everything except complete and error, template does this
);
课程列表
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { GridSpacing } from '@material-ui/core/Grid';
import Course from '../../components/Course/Course';
import { Course as CourseModel } from '../../redux/features/course/model';
type Props = {
courses: CourseModel[];
// isLoading: boolean;
// fetchCourses: any;
};
export const CourseList: React.FC<Props> = props => {
const { courses } = props;
return (
<div style={{ marginTop: 20, padding: 30 }}>
{
<Grid container spacing={2 as GridSpacing} justify="center">
{courses.map(element => (
<Grid item key={element.courseID}>
<Course course={element} />
</Grid>
))}
</Grid>
}
</div>
);
};
课程列表 - index.ts
import { RootState } from 'ReduxTypes';
import { connect } from 'react-redux';
import { CourseList } from './CourseList';
import { courseActions, courseSelectors } from '../../redux/features/course';
const mapDispatchToProps = {
onFetchCoursesRequest: courseActions.fetchCoursesRequest,
};
const mapStateToProps = (state: RootState) => ({
courses: courseSelectors.getReduxCourses(state.courses.fetchCoursesSuccess),
});
const CourseListConnected = connect(
mapStateToProps,
mapDispatchToProps,
)(CourseList);
export default CourseListConnected;
应用程序
import React, { Component, Suspense, lazy } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import ErrorBoundary from '../errors/ErrorBoundary';
import { NavBar } from './NavBar/NavBar';
// code splitting at the route level
// lazy loading by route component, we could take this
// a step further and perform at component level
// React.lazy requires that the module export format for a component uses default
const About = lazy(() =>
import(
/*
webpackChunkName: "about-page",
webpackPrefetch: true
*/ '../views/About/About'
),
);
const CourseList = lazy(() =>
import(
/*
webpackChunkName: "course-list",
webpackPrefetch: true
*/ '../containers/CourseList'
),
);
const Home = lazy(() =>
import(
/*
webpackChunkName: "home-page",
webpackPrefetch: true
*/ '../views/Home/Home'
),
);
type AppProps = {};
export class App extends Component<AppProps, {}> {
public render(): JSX.Element {
return (
<BrowserRouter>
<div>
<NavBar />
<Suspense fallback={<div>LoaderOptionsPlugin...</div>}>
<Switch>
<Route path="/" component={Home} exact></Route>
<Route path="/about" component={About}></Route>
<Route
path="/courses"
render={(props): JSX.Element => (
<ErrorBoundary {...props}>
<CourseList />
</ErrorBoundary>
)}
></Route>
{/* <Route component={Error404}></Route> */}
</Switch>
</Suspense>
</div>
</BrowserRouter>
);
}
}
main.tsx
import React from 'react';
import { render } from 'react-dom';
import { App } from './app/components/App';
import { Provider } from 'react-redux';
import store from './app/redux/store';
const rootElement = document.getElementById('root');
render(
<Provider store={store}>
<App />
</Provider>,
rootElement,
);
回答你的第一个问题:是的,这是来自 react/prop-types
规则的 eslint 错误,你可以安全地关闭它,不需要带有 typescript 的道具类型。
你的第二个问题,异步操作的第二部分应该发送到哪里?那应该从您的 redux 可观察史诗中发送,而不是从 redux 本身发送,而不是从反应容器组件发送。
redux-observable 文档在 Real world example
https://redux-observable.js.org/docs/basics/Epics.html
下有一个处理异步操作的简单示例
成功了。我使用 useEffect 反应挂钩来触发获取课程列表的请求操作。当 CourseList 功能组件启动时,课程的初始状态为空。然后,它通过 useEffect 挂钩触发 fetchCoursesAsync.request 操作,映射到 fetchCourses调度 属性。
fetchCoursesRequestAction 史诗然后进行 ajax 调用并随后触发 fetchCoursesSuccess 或 fetchCoursesFail.
我的下一步是了解如何响应由史诗触发的失败的获取课程请求并抛出到周围的错误边界.....
import { RootState } from 'ReduxTypes';
type StateProps = {
isLoading: boolean;
courses: courseModels.Course[];
};
const dispatchProps = {
fetchCourses: fetchCoursesAsync.request,
};
const mapStateToProps = (state: RootState): StateProps => ({
isLoading: state.courses.isLoadingCourses,
courses: courseSelectors.getReduxCourses(state.courses),
});
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
const CourseList = ({
courses = [],
fetchCourses,
isLoading,
}: Props): JSX.Element => {
// fetch course action on mount
useEffect(() => {
fetchCourses();
}, []);
if (isLoading) {
return <p>Loading...</p>;
}
return (
<div style={{ marginTop: 20, padding: 30 }}>
{
<Grid container spacing={2 as GridSpacing} justify="center">
{courses.map(element => (
<Grid item key={element.courseID}>
<Course course={element} />
</Grid>
))}
</Grid>
}
</div>
);
};
我正在尝试学习如何在 Typescript 环境中使用 React 和 Redux。我正在使用 react-redux-typescript playground 中建议的模式。但是,我在尝试构建下面列出的代码时收到以下错误:
'courses' is missing in props validation
'courses.map' is missing in props validation
还有其他人遇到过此类错误吗?是否是 eslint 插件的 linting 错误:react/recommended?
我也很难理解在使用 redux-observable 从 API 检索数据时从 redux 存储初始化默认状态的过程。我按照 react-redux-typescript playground 中的模式建议配置了存储、epics、reducer、操作等。这些配置为使用 redux-observable 从 API 中获取课程列表。随后,我定义了三个动作和减速器: 1. FETCH_COURSES_ERROR 2. FETCH_COURSES_REQUEST 3.FETCH_COURSES_SUCCESS
然后如何触发我的 CourseList 容器以开始获取和呈现课程列表的过程。让 redux 存储获取课程列表的初始状态是一种好习惯吗(FETCH_COURSES_REQUEST -> FETCH_COURSES_SUCCESS || FETCH_COURSES_REQUEST -> FETCH_COURSES_ERROR。简而言之,我不这样做了解如何 connect/trigger 将史诗添加到 CourseList 容器....
史诗 中间件已初始化并且 运行 在存储模块中....
import { RootAction, RootState, Services } from 'ReduxTypes';
import { Epic } from 'redux-observable';
import { isOfType } from 'typesafe-actions';
import { of } from 'rxjs';
import {
catchError,
filter,
ignoreElements,
map,
switchMap,
} from 'rxjs/operators';
import { fetchCoursesFail, fetchCoursesSuccess } from './actions';
import constants from './constants';
export const fetchCoursesRequestAction: Epic<
RootAction,
RootAction,
RootState,
Services
> = (action$, state$, { courseServices }) =>
action$.pipe(
filter(isOfType(constants.FETCH_COURSES_REQUEST)),
switchMap(action =>
courseServices.default.getCourses().pipe(
map(courses => fetchCoursesSuccess(courses)),
catchError(error => of(fetchCoursesFail(error))),
),
),
ignoreElements(), // ignore everything except complete and error, template does this
);
课程列表
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { GridSpacing } from '@material-ui/core/Grid';
import Course from '../../components/Course/Course';
import { Course as CourseModel } from '../../redux/features/course/model';
type Props = {
courses: CourseModel[];
// isLoading: boolean;
// fetchCourses: any;
};
export const CourseList: React.FC<Props> = props => {
const { courses } = props;
return (
<div style={{ marginTop: 20, padding: 30 }}>
{
<Grid container spacing={2 as GridSpacing} justify="center">
{courses.map(element => (
<Grid item key={element.courseID}>
<Course course={element} />
</Grid>
))}
</Grid>
}
</div>
);
};
课程列表 - index.ts
import { RootState } from 'ReduxTypes';
import { connect } from 'react-redux';
import { CourseList } from './CourseList';
import { courseActions, courseSelectors } from '../../redux/features/course';
const mapDispatchToProps = {
onFetchCoursesRequest: courseActions.fetchCoursesRequest,
};
const mapStateToProps = (state: RootState) => ({
courses: courseSelectors.getReduxCourses(state.courses.fetchCoursesSuccess),
});
const CourseListConnected = connect(
mapStateToProps,
mapDispatchToProps,
)(CourseList);
export default CourseListConnected;
应用程序
import React, { Component, Suspense, lazy } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import ErrorBoundary from '../errors/ErrorBoundary';
import { NavBar } from './NavBar/NavBar';
// code splitting at the route level
// lazy loading by route component, we could take this
// a step further and perform at component level
// React.lazy requires that the module export format for a component uses default
const About = lazy(() =>
import(
/*
webpackChunkName: "about-page",
webpackPrefetch: true
*/ '../views/About/About'
),
);
const CourseList = lazy(() =>
import(
/*
webpackChunkName: "course-list",
webpackPrefetch: true
*/ '../containers/CourseList'
),
);
const Home = lazy(() =>
import(
/*
webpackChunkName: "home-page",
webpackPrefetch: true
*/ '../views/Home/Home'
),
);
type AppProps = {};
export class App extends Component<AppProps, {}> {
public render(): JSX.Element {
return (
<BrowserRouter>
<div>
<NavBar />
<Suspense fallback={<div>LoaderOptionsPlugin...</div>}>
<Switch>
<Route path="/" component={Home} exact></Route>
<Route path="/about" component={About}></Route>
<Route
path="/courses"
render={(props): JSX.Element => (
<ErrorBoundary {...props}>
<CourseList />
</ErrorBoundary>
)}
></Route>
{/* <Route component={Error404}></Route> */}
</Switch>
</Suspense>
</div>
</BrowserRouter>
);
}
}
main.tsx
import React from 'react';
import { render } from 'react-dom';
import { App } from './app/components/App';
import { Provider } from 'react-redux';
import store from './app/redux/store';
const rootElement = document.getElementById('root');
render(
<Provider store={store}>
<App />
</Provider>,
rootElement,
);
回答你的第一个问题:是的,这是来自 react/prop-types
规则的 eslint 错误,你可以安全地关闭它,不需要带有 typescript 的道具类型。
你的第二个问题,异步操作的第二部分应该发送到哪里?那应该从您的 redux 可观察史诗中发送,而不是从 redux 本身发送,而不是从反应容器组件发送。
redux-observable 文档在 Real world example
https://redux-observable.js.org/docs/basics/Epics.html
成功了。我使用 useEffect 反应挂钩来触发获取课程列表的请求操作。当 CourseList 功能组件启动时,课程的初始状态为空。然后,它通过 useEffect 挂钩触发 fetchCoursesAsync.request 操作,映射到 fetchCourses调度 属性。
fetchCoursesRequestAction 史诗然后进行 ajax 调用并随后触发 fetchCoursesSuccess 或 fetchCoursesFail.
我的下一步是了解如何响应由史诗触发的失败的获取课程请求并抛出到周围的错误边界.....
import { RootState } from 'ReduxTypes';
type StateProps = {
isLoading: boolean;
courses: courseModels.Course[];
};
const dispatchProps = {
fetchCourses: fetchCoursesAsync.request,
};
const mapStateToProps = (state: RootState): StateProps => ({
isLoading: state.courses.isLoadingCourses,
courses: courseSelectors.getReduxCourses(state.courses),
});
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
const CourseList = ({
courses = [],
fetchCourses,
isLoading,
}: Props): JSX.Element => {
// fetch course action on mount
useEffect(() => {
fetchCourses();
}, []);
if (isLoading) {
return <p>Loading...</p>;
}
return (
<div style={{ marginTop: 20, padding: 30 }}>
{
<Grid container spacing={2 as GridSpacing} justify="center">
{courses.map(element => (
<Grid item key={element.courseID}>
<Course course={element} />
</Grid>
))}
</Grid>
}
</div>
);
};