问题
I was trying to create a <PrivateRoute>
as describe in the react-router documents using TypeScript. Can anyone help me out?
The privateRoute in react-router document:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{pathname: '/login', state: { from: props.location }
}}/>
)
)}/>
)
Below is my TypeScript version(it won't work) :
const PrivateRoute = (theProps: { path: string, component: React.SFC<RouteComponentProps<any> | undefined> | React.ComponentClass<RouteComponentProps<any> | undefined> }) => {
return <Route path={theProps.path} render={props => (
fakeAuth.isAuthenticated ? (
<React.Component {...theProps} /> <!-- **** It will raise error *** -->
) : (
<Redirect to={{
pathname: '/',
state: { from: props.location }
}} />
)
)} />
}
The <React.Component {...thisProps} />
is not right. The error is: NodeInvocationException: inst.render is not a function
TypeError: inst.render is not a function
回答1:
Probably the error has to do with the typing and the implicit return in rendering. When you fix this you get ultimately to something like this:
const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
const routeComponent = (props: any) => (
isAuthenticated
? React.createElement(component, props)
: <Redirect to={{pathname: '/login'}}/>
);
return <Route {...rest} render={routeComponent}/>;
};
This component can be used like this:
<PrivateRoute
path='/private'
isAuthenticated={this.props.state.session.isAuthenticated}
component={PrivateContainer}
/>
There are a few draw backs with the solution above. One of the is that you lose type safety.
Probably extending the Route
component is the better idea.
import * as React from 'react';
import {Redirect, Route, RouteProps} from 'react-router';
export interface ProtectedRouteProps extends RouteProps {
isAuthenticated: boolean;
authenticationPath: string;
}
export class ProtectedRoute extends Route<ProtectedRouteProps> {
public render() {
let redirectPath: string = '';
if (!this.props.isAuthenticated) {
redirectPath = this.props.authenticationPath;
}
if (redirectPath) {
const renderComponent = () => (<Redirect to={{pathname: redirectPath}}/>);
return <Route {...this.props} component={renderComponent} render={undefined}/>;
} else {
return <Route {...this.props}/>;
}
}
}
So you can use the component like this:
const defaultProtectedRouteProps: ProtectedRouteProps = {
isAuthenticated: this.props.state.session.isAuthenticated,
authenticationPath: '/login',
};
<ProtectedRoute
{...defaultProtectedRouteProps}
exact={true}
path='/'
component={ProtectedContainer}
/>
Update (Nov 2019)
If you prefer to write functional components you can do it in a very similar manner:
import * as React from 'react';
import { Redirect, Route, RouteProps } from 'react-router';
export interface ProtectedRouteProps extends RouteProps {
isAuthenticated: boolean;
isAllowed: boolean;
restrictedPath: string;
authenticationPath: string;
}
export const ProtectedRoute: React.FC<ProtectedRouteProps> = props => {
let redirectPath = '';
if (!props.isAuthenticated) {
redirectPath = props.authenticationPath;
}
if (props.isAuthenticated && !props.isAllowed) {
redirectPath = props.restrictedPath;
}
if (redirectPath) {
const renderComponent = () => <Redirect to={{ pathname: redirectPath }} />;
return <Route {...props} component={renderComponent} render={undefined} />;
} else {
return <Route {...props} />;
}
};
export default ProtectedRoute;
回答2:
You can still use the SFC form, which I find a little cleaner. Just mix in any props you need with the RouteProps
:
const PrivateRoute: React.SFC<RouteProps> = ({
component: Component,
...rest
}: {
component: React.ComponentType<RouteProps>;
}) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated
? <Component {...props} />
: <Redirect to="/login" />
}
/>
);
来源:https://stackoverflow.com/questions/47747754/how-to-rewrite-the-protected-router-using-typescript-and-react-router-4