Created
May 8, 2017 12:55
-
-
Save Tonyce/d994b18ff700f6d2e524c1e24daaa4bd to your computer and use it in GitHub Desktop.
Tyepscript + React Router v4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**basicExample | |
import * as React from 'react'; | |
import { | |
HashRouter as Router, Switch, Route, Link, | |
RouteComponentProps | |
} from 'react-router-dom'; | |
const Home = () => ( | |
<div> | |
<h2>Home</h2> | |
</div> | |
) | |
// 当然还可以这样 不过没有意义 | |
// const Home = ( | |
// <div> | |
// <h2>Home</h2> | |
// </div> | |
// ) | |
class About extends React.Component<RouteComponentProps<{}>, {}> { | |
render() { | |
return ( | |
<div> | |
<h2>About</h2> | |
</div> | |
) | |
} | |
} | |
class Topics extends React.Component<RouteComponentProps<{}>, {}> { | |
render() { | |
const { match } = this.props; | |
return ( | |
<div> | |
<h2>Topics</h2> | |
<ul> | |
<li> | |
<Link to={`${match.url}/rendering`}> | |
Rendering with React | |
</Link> | |
</li> | |
<li> | |
<Link to={`${match.url}/components`}> | |
Components | |
</Link> | |
</li> | |
<li> | |
<Link to={`${match.url}/props-v-state`}> | |
Props v. State | |
</Link> | |
</li> | |
</ul> | |
<Route path={`${match.url}/:topicId`} component={Topic} /> | |
<Route exact path={match.url} render={() => ( | |
<h3>Please select a topic.</h3> | |
)} /> | |
</div> | |
) | |
} | |
} | |
class Topic extends React.Component<RouteComponentProps<{ topicId: string }>, {}> { | |
render() { | |
const { match } = this.props; | |
return ( | |
<div> | |
<h3>{match.params.topicId}</h3> | |
</div> | |
) | |
} | |
} | |
class BasicExample extends React.Component<{}, {}> { | |
render() { | |
return ( | |
<Router> | |
<div> | |
<ul> | |
<li><Link to="/">Home</Link></li> | |
<li><Link to="/about">About</Link></li> | |
<li><Link to="/topics">Topics</Link></li> | |
</ul> | |
<hr /> | |
<Route exact path="/" component={Home} /> | |
<Route path="/about" component={About} /> | |
<Route path="/topics" component={Topics} /> | |
</div> | |
</Router> | |
) | |
} | |
} | |
export default BasicExample; | |
*/ | |
/* | |
class Child extends React.Component<RouteComponentProps<{ id: string }>, {}> { | |
render() { | |
const { match } = this.props; | |
return ( | |
<div> | |
<h3>ID: {match.params.id}</h3> | |
</div> | |
) | |
} | |
} | |
class ParamsExample extends React.Component<{}, {}> { | |
render() { | |
return ( | |
<Router> | |
<div> | |
<h2>Accounts</h2> | |
<ul> | |
<li><Link to="/netflix">Netflix</Link></li> | |
<li><Link to="/zillow-group">Zillow Group</Link></li> | |
<li><Link to="/yahoo">Yahoo</Link></li> | |
<li><Link to="/modus-create">Modus Create</Link></li> | |
</ul> | |
<Route path="/:id" component={Child} /> | |
</div> | |
</Router> | |
) | |
} | |
} | |
*/ | |
/* | |
import * as React from 'react'; | |
import { | |
BrowserRouter as Router, | |
Route, | |
Link, | |
Redirect, | |
withRouter, | |
RouteComponentProps | |
} from 'react-router-dom'; | |
const fakeAuth = { | |
isAuthenticated: false, | |
authenticate(cb: Function) { | |
this.isAuthenticated = true | |
setTimeout(cb, 100) // fake async | |
}, | |
signout(cb: Function) { | |
this.isAuthenticated = false | |
setTimeout(cb, 100) | |
} | |
} | |
class AuthButtonT extends React.Component<RouteComponentProps<{}>, {}> { | |
render() { | |
const { history } = this.props; | |
return ( | |
fakeAuth.isAuthenticated && | |
<p> | |
Welcome! <button onClick={() => { | |
fakeAuth.signout(() => history.push('/')) | |
}}>Sign out</button> | |
</p> | |
|| <p>You are not logged in.</p> | |
) | |
} | |
} | |
const AuthButton = withRouter(AuthButtonT); | |
const Public = () => <h3>Public</h3> | |
const Protected = () => <h3>Protected</h3> | |
class PrivateRoute extends React.Component<{ path: string; Component: React.StatelessComponent<void> }, {}> { | |
render() { | |
const { path, Component } = this.props; | |
return ( | |
<Route path={path} render={props => ( | |
fakeAuth.isAuthenticated ? ( | |
<Component {...props} /> | |
) : ( | |
<Redirect to={{ | |
pathname: '/login', | |
state: { from: props.location } | |
}} /> | |
) | |
)} /> | |
) | |
} | |
} | |
class Login extends React.Component<RouteComponentProps<void>, { redirectToReferrer: boolean }> { | |
constructor() { | |
super() | |
this.state = { | |
redirectToReferrer: false | |
} | |
} | |
login = () => { | |
fakeAuth.authenticate(() => { | |
this.setState({ redirectToReferrer: true }) | |
}) | |
} | |
render() { | |
const { from } = this.props.location.state || { from: { pathname: '/' } } | |
const { redirectToReferrer } = this.state | |
if (redirectToReferrer) { | |
return ( | |
<Redirect to={from} /> | |
) | |
} | |
return ( | |
<div> | |
<p>You must log in to view the page at {from.pathname}</p> | |
<button onClick={this.login}>Log in</button> | |
</div> | |
) | |
} | |
} | |
export default class AuthExample extends React.Component<{}, {}> { | |
render() { | |
return ( | |
<Router> | |
<div> | |
<AuthButton /> | |
<ul> | |
<li><Link to="/public">Public Page</Link></li> | |
<li><Link to="/protected">Protected Page</Link></li> | |
</ul> | |
<Route path="/public" component={Public} /> | |
<Route path="/login" component={Login} /> | |
<PrivateRoute path="/protected" Component={Protected} /> | |
</div> | |
</Router> | |
) | |
} | |
} | |
*/ | |
/* | |
// custom link | |
import * as React from 'react'; | |
import { | |
HashRouter as Router, | |
Route, | |
Link, | |
} from 'react-router-dom'; | |
const Home = () => <div><h2>Home</h2></div> | |
const About = () => <div><h2>About</h2></div> | |
interface OldSchoolMenuLinkProps { | |
label: string; | |
to: string; | |
activeOnlyWhenExact?: boolean; | |
} | |
class OldSchoolMenuLink extends React.Component<OldSchoolMenuLinkProps, {}> { | |
render() { | |
const { activeOnlyWhenExact, to, label } = this.props; | |
return ( | |
<Route path={to} exact={activeOnlyWhenExact} children={({ match }) => ( | |
<div className={match ? 'active' : ''}> | |
{match ? '> ' : ''}<Link to={to}>{label}</Link> | |
</div> | |
)} /> | |
) | |
} | |
} | |
export default class CustomLinkExample extends React.Component<{}, {}> { | |
render() { | |
return ( | |
<Router> | |
<div> | |
<OldSchoolMenuLink activeOnlyWhenExact={true} to="/" label="Home" /> | |
<OldSchoolMenuLink to="/about" label="About" /> | |
<hr /> | |
<Route exact path="/" component={Home} /> | |
<Route path="/about" component={About} /> | |
</div> | |
</Router> | |
) | |
} | |
} | |
*/ | |
/* | |
// Preventing Transitions Demo | |
import * as React from 'react' | |
import { | |
BrowserRouter as Router, | |
Route, | |
Link, | |
Prompt, | |
RouteComponentProps | |
} from 'react-router-dom' | |
interface FormState { | |
isBlocking: boolean; | |
} | |
class Form extends React.Component<RouteComponentProps<{}>, FormState> { | |
constructor(props: RouteComponentProps<{}>) { | |
super(props); | |
this.state = { | |
isBlocking: false | |
} | |
} | |
formSubmit = (event: React.FormEvent<HTMLFormElement>) => { | |
event.preventDefault(); | |
event.currentTarget.reset(); | |
this.setState({ | |
isBlocking: false | |
}) | |
} | |
render() { | |
const { isBlocking } = this.state | |
return ( | |
<form onSubmit={this.formSubmit}> | |
<Prompt | |
when={isBlocking} | |
message={location => ( | |
`Are you sure you want to go to ${location.pathname}` | |
)} | |
/> | |
<p> | |
Blocking? {isBlocking ? 'Yes, click a link or the back button' : 'Nope'} | |
</p> | |
<p> | |
<input | |
size={50} | |
placeholder="type something to block transitions" | |
onChange={event => { | |
this.setState({ | |
isBlocking: event.target.value.length > 0 | |
}) | |
}} | |
/> | |
</p> | |
<p> | |
<button>Submit to stop blocking</button> | |
</p> | |
</form> | |
) | |
} | |
} | |
export default class PreventingTransitionsExample extends React.Component<{}, {}> { | |
render() { | |
return ( | |
<Router> | |
<div> | |
<ul> | |
<li><Link to="/">Form</Link></li> | |
<li><Link to="/one">One</Link></li> | |
<li><Link to="/two">Two</Link></li> | |
</ul> | |
<Route path="/" exact component={Form} /> | |
<Route path="/one" render={() => <h3>One</h3>} /> | |
<Route path="/two" render={() => <h3>Two</h3>} /> | |
</div> | |
</Router> | |
); | |
} | |
} | |
*/ | |
/* | |
import * as React from 'react'; | |
import { | |
BrowserRouter as Router, | |
Route, | |
Link, | |
Switch, | |
Redirect, RouteComponentProps | |
} from 'react-router-dom'; | |
const Home = () => ( | |
<p> | |
A <code><Switch></code> renders the | |
first child <code><Route></code> that | |
matches. A <code><Route></code> with | |
no <code>path</code> always matches. | |
</p> | |
) | |
const WillMatch = () => <h3>Matched!</h3> | |
class NoMatch extends React.Component<RouteComponentProps<{}>, {}> { | |
render() { | |
return ( | |
<div> | |
<h3>No match for <code>{location.pathname}</code></h3> | |
</div> | |
) | |
} | |
} | |
const NoMatchExample = () => ( | |
<Router> | |
<div> | |
<ul> | |
<li><Link to="/">Home</Link></li> | |
<li><Link to="/old-match">Old Match, to be redirected</Link></li> | |
<li><Link to="/will-match">Will Match</Link></li> | |
<li><Link to="/will-not-match">Will Not Match</Link></li> | |
<li><Link to="/also/will/not/match">Also Will Not Match</Link></li> | |
</ul> | |
<Switch> | |
<Route path="/" exact component={Home} /> | |
<Redirect from="/old-match" to="/will-match" /> | |
<Route path="/will-match" component={WillMatch} /> | |
<Route component={NoMatch} /> | |
</Switch> | |
</div> | |
</Router> | |
) | |
export default NoMatchExample | |
*/ | |
// type oo = number | string; | |
// type cc = boolean | string; | |
// var a: oo = null; | |
// var c: cc = '1'; | |
// function qq(qq: oo): void { | |
// } | |
// qq(c); | |
// function dd(oo: cc): void { | |
// } | |
// dd(a) | |
/* | |
import * as React from 'react' | |
import { | |
// HashRouter as Router, | |
BrowserRouter as Router, | |
Route, | |
Link, RouteComponentProps | |
} from 'react-router-dom'; | |
const PEEPS = [ | |
{ id: 0, name: 'Michelle', friends: [1, 2, 3] }, | |
{ id: 1, name: 'Sean', friends: [0, 3] }, | |
{ id: 2, name: 'Kim', friends: [0, 1, 3], }, | |
{ id: 3, name: 'David', friends: [1, 2] } | |
] | |
const find = (id: number) => PEEPS.find(p => p.id == id) | |
const RecursiveExample = () => ( | |
<Router> | |
<Person match={{ params: { id: 0 }, url: '' }} /> | |
<Route component={Person} /> | |
</Router > | |
) | |
interface PersonProps { | |
match: { | |
params: { id: number }, | |
url: string | |
} | |
} | |
class Person extends React.Component<RouteComponentProps<{ id: number }> | PersonProps, {}> { | |
// const Person = ({ match }) => { | |
render(): JSX.Element { | |
const { match } = this.props; | |
const { id } = match.params; | |
// console.log(id); | |
// let { url } = match; | |
// url = url == '/' ? '' : url | |
const person = find(id); | |
return ( | |
<div> | |
<h3>{person.name}’s Friends</h3> | |
<ul> | |
{person.friends.map((id: number) => ( | |
<li key={id}> | |
<Link to={`${match.url}/${id}`}> | |
{find(id).name} | |
</Link> | |
</li> | |
))} | |
</ul> | |
<Route path={`${url}/:id`} component={Person} /> | |
<Route path={`${match.url}/:id`} render={(props: RouteComponentProps<{ | |
id: number | |
}>) => { | |
return <Person {...props} />; | |
}} /> | |
</div> | |
) | |
} | |
} | |
export default RecursiveExample | |
*/ | |
/* | |
// sidebar | |
import * as React from 'react' | |
import { | |
BrowserRouter as Router, | |
Route, | |
Link | |
} from 'react-router-dom' | |
// Each logical "route" has two components, one for | |
// the sidebar and one for the main area. We want to | |
// render both of them in different places when the | |
// path matches the current URL. | |
const routes = [ | |
{ | |
path: '/', | |
exact: true, | |
sidebar: () => <div>home!</div>, | |
main: () => <h2>Home</h2> | |
}, | |
{ | |
path: '/bubblegum', | |
exact: true, | |
sidebar: () => <div>bubblegum!</div>, | |
main: () => <h2>Bubblegum</h2> | |
}, | |
{ | |
path: '/shoelaces', | |
exact: true, | |
sidebar: () => <div>shoelaces!</div>, | |
main: () => <h2>Shoelaces</h2> | |
} | |
] | |
const SidebarExample = () => ( | |
<Router> | |
<div style={{ display: 'flex' }}> | |
<div style={{ | |
padding: '10px', | |
width: '40%', | |
background: '#f0f0f0' | |
}}> | |
<ul style={{ listStyleType: 'none', padding: 0 }}> | |
<li><Link to="/">Home</Link></li> | |
<li><Link to="/bubblegum">Bubblegum</Link></li> | |
<li><Link to="/shoelaces">Shoelaces</Link></li> | |
</ul> | |
{routes.map((route, index) => ( | |
// You can render a <Route> in as many places | |
// as you want in your app. It will render along | |
// with any other <Route>s that also match the URL. | |
// So, a sidebar or breadcrumbs or anything else | |
// that requires you to render multiple things | |
// in multiple places at the same URL is nothing | |
// more than multiple <Route>s. | |
<Route | |
key={index} | |
path={route.path} | |
exact={route.exact} | |
component={route.sidebar} | |
/> | |
))} | |
</div> | |
<div style={{ flex: 1, padding: '10px' }}> | |
{routes.map((route, index) => ( | |
// Render more <Route>s with the same paths as | |
// above, but different components this time. | |
<Route | |
key={index} | |
path={route.path} | |
exact={route.exact} | |
component={route.main} | |
/> | |
))} | |
</div> | |
</div> | |
</Router> | |
) | |
export default SidebarExample | |
*/ | |
import * as React from 'react'; | |
import { | |
HashRouter as Router, | |
Switch, | |
Route, | |
Link, RouteComponentProps | |
} from 'react-router-dom'; | |
const IMAGES = [ | |
{ id: 0, title: 'Dark Orchid', color: 'DarkOrchid' }, | |
{ id: 1, title: 'Lime Green', color: 'LimeGreen' }, | |
{ id: 2, title: 'Tomato', color: 'Tomato' }, | |
{ id: 3, title: 'Seven Ate Nine', color: '#789' }, | |
{ id: 4, title: 'Crimson', color: 'Crimson' } | |
] | |
const Thumbnail = ({ color }: { color: string }) => | |
<div style={{ | |
width: 50, | |
height: 50, | |
background: color | |
}} /> | |
const Image = ({ color }: { color: string }) => | |
<div style={{ | |
width: '100%', | |
height: 400, | |
background: color | |
}}></div> | |
const Home = () => ( | |
<div> | |
<Link to='/gallery'>Visit the Gallery</Link> | |
<h2>Featured Images</h2> | |
<ul> | |
<li><Link to='/img/2'>Tomato</Link></li> | |
<li><Link to='/img/4'>Crimson</Link></li> | |
</ul> | |
</div> | |
) | |
const Gallery = () => ( | |
<div> | |
{IMAGES.map(i => ( | |
<Link | |
key={i.id} | |
to={{ | |
pathname: `/img/${i.id}`, | |
// this is the trick! | |
state: { modal: true } | |
}} | |
> | |
<Thumbnail color={i.color} /> | |
<p>{i.title}</p> | |
</Link> | |
))} | |
</div> | |
) | |
const ImageView = ({ match }: RouteComponentProps<{ id: string }>) => { | |
const image = IMAGES[parseInt(match.params.id, 10)] | |
if (!image) { | |
return <div>Image not found</div> | |
} | |
return ( | |
<div> | |
<h1>{image.title}</h1> | |
<Image color={image.color} /> | |
</div> | |
) | |
} | |
class Modal extends React.Component<RouteComponentProps<{ id: string }>, {}> { | |
back = (e: React.MouseEvent<HTMLDivElement | HTMLButtonElement>) => { | |
const { history } = this.props; | |
e.stopPropagation(); | |
history.goBack(); | |
} | |
render() { | |
const { match } = this.props; | |
const image = IMAGES[parseInt(match.params.id, 10)] | |
return ( | |
<div | |
onClick={this.back} | |
style={{ | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
bottom: 0, | |
right: 0, | |
background: 'rgba(0, 0, 0, 0.15)' | |
}} | |
> | |
<div className='modal' style={{ | |
position: 'absolute', | |
background: '#fff', | |
top: 25, | |
left: '10%', | |
right: '10%', | |
padding: 15, | |
border: '2px solid #444' | |
}}> | |
<h1>{image.title}</h1> | |
<Image color={image.color} /> | |
<button type='button' onClick={this.back}> | |
Close | |
</button> | |
</div> | |
</div> | |
) | |
} | |
} | |
// class ModalSwitch extends React.Component { | |
class ModalSwitch extends React.Component<RouteComponentProps<{}>, {}> { | |
// We can pass a location to <Switch/> that will tell it to | |
// ignore the router's current location and use the location | |
// prop instead. | |
// | |
// We can also use "location state" to tell the app the user | |
// wants to go to `/images/2` in a modal, rather than as the | |
// main page, keeping the gallery visible behind it. | |
// | |
// Normally, `/images/2` wouldn't match the gallery at `/`. | |
// So, to get both screens to render, we can save the old | |
// location and pass it to Switch, so it will think the location | |
// is still `/` even though its `/images/2`. | |
previousLocation = this.props.location | |
componentWillUpdate(nextProps: RouteComponentProps<{}>) { | |
const { location } = this.props | |
// set previousLocation if props.location is not modal | |
if ( | |
nextProps.history.action !== 'POP' && | |
(!location.state || !location.state.modal) | |
) { | |
this.previousLocation = this.props.location | |
} | |
} | |
render() { | |
const { location } = this.props | |
const isModal = !!( | |
location.state && | |
location.state.modal && | |
this.previousLocation !== location // not initial render | |
) | |
return ( | |
<div> | |
<Switch location={isModal ? this.previousLocation : location}> | |
<Route exact path='/' component={Home} /> | |
<Route path='/gallery' component={Gallery} /> | |
<Route path='/img/:id' component={ImageView} /> | |
</Switch> | |
{isModal ? <Route path='/img/:id' component={Modal} /> : null} | |
</div> | |
) | |
} | |
} | |
const ModalGallery = () => ( | |
<Router> | |
<Route component={ModalSwitch} /> | |
</Router> | |
) | |
export default ModalGallery |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment