react-router-dom
1. BrowserRouter 与 HashRouter最显著的区别是,HashRouter的URL中含#,例如:http://example.com/#/your/page
2. webpack内置就支持code splitting, 但要使用babel(将JSX编译为JavaScript)时,需安装 @babel/plugin-syntax-dynamic-import插件。
module.exports = { "presets": ["@babel/preset-react"], "plugins": ["@babel/plugin-syntax-dynamic-import"] }
使用@loadable/component:
import loadable from "@loadable/component"; import Loading from "./Loading.js"; // 动态加载 const LoadableLogin = loadable(() => import("./login.js"), { fallback: <Loading /> // 加载时显示的组件 }); //在react-router中使用 <Route path="/" component={loadableLogin}></Route>
3. react-router-dom hooks
要求:React >= 16.8,必须使用function声明,不能使用class extends
3.1. useHistory
import React from 'react'; import { connect } from 'react-redux'; import { useHistory } from 'react-router-dom'; // 使用function不能用class ... extends function UserList (props){ const history = useHistory(); const users = props.users.map((user,index) => <li key={index}>{user.name}</li>); function handleClick (e) { history.push('/clock'); } return ( <div> <ul> { users } </ul> <button onClick={handleClick}>Go Back To Clock!</button> </div> ) } export default connect(state => state)(UserList);
3.2 useParams 获取<Route> path中的参数。
<Switch> <Route path="/login" > <Login /> </Route> <Route path="/clock"> <Clock /> </Route> <Route path="/blog/:num" component={BlogPost} /> <Route path="/"> <UserList /> </Route> </Switch>
注意如果不使用exact, path="/"必须放在最后(按照第一个匹配到的去渲染)
import React from 'react'; import {useParams} from 'react-router-dom'; export default function BlogPost(props) { let {num} = useParams();
let {name} = useParams(); // let name = props.match.params.name; // console.log('in blog', name); return (<div>The params is {name}--{num}</div>); }
4.1 <BrowserRouter>使用HTML5history API(pushState
, replaceState,
popState
)
<BrowserRouter
basename={optionalString} // 适用于从子目录中获取资源,指定子目录
forceRefresh={optionalBool}
getUserConfirmation={optionalFunc}
keyLength={optionalNumber} // location.key的长度,默认6
>
<App />
</BrowserRouter>
4.2 <HashRouter> URL中含有#
<HashRouter basename={optionalString} getUserConfirmation={optionalFunc} hashType={optionalString} // 用于定义window.location.hash的编码类型 > <App /> </HashRouter>
hashType:
"slash"
- Creates hashes like#/
and#/sunshine/lollipops
"noslash"
- Creates hashes like#
and#sunshine/lollipops
"hashbang"
- Creates“ajax crawlable”(deprecated by Google) hashes like#!/
and#!/sunshine/lollipops
4.3 <MemoryRouter> 用于测试或非浏览器环境(如React Native),可将历史URL记录保存在内存中。
<MemoryRouter initialEntries={optionalArray} //full-blown location objects with {pathname, search, hassh, state} or string URLS. initialIndex={optionalNumber} getUserConfirmation={optionalFunc} keyLength={optionalNumber} > <App /> </MemoryRouter>
4.4 <Link>
<Link to="/courses?sort=name" /> // to为String类型,包含pathname, search, hash部分
<Link to={{ // to为Object类型 pathname: "/courses", search: "?sort=name", hash: "#the-hash", state: { fromDashboard: true } }} />
<Link to={location => `${location.pathname}?sort=name`} /> // to为Func类型
<Link to="/courses" replace /> // replace:点击后将替换history栈中的元素,而不是在栈顶新增
4.5 <NavLink>与<Link>相同, 在页面中都会以<a></a>的形式渲染出来。
<NavLink to="/react" activeClassName="hurray"> React </NavLink>
<NavLink to="/faq" activeStyle={{ fontWeight: "bold", color: "red" }} > FAQs </NavLink>
<NavLink exact to="/profile"> // exact只有当前选中的标签会显示active样式 Profile </NavLink>
<NavLink strict to="/events/"> //严格匹配路径右侧的/ Events </NavLink>
此外,还有aria-current属性,其值可以是:
"page"
- used to indicate a link within a set of pagination links"step"
- used to indicate a link within a step indicator for a step-based process"location"
- used to indicate the image that is visually highlighted as the current component of a flow chart"date"
- used to indicate the current date within a calendar"time"
- used to indicate the current time within a timetable"true"
- used to indicate if the NavLink is active
默认是page.
4.6 <Prompt> 当用户离开(而开发者此时不希望用户离开此页)时,弹出提示
<Prompt when={formIsHalfFilledOut} message="Are you sure you want to leave?" />
4.7 <Redirect>重定向
<Route exact path="/"> {loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />} </Route>
默认情况下,to参数会取代from参数在history栈中的位置,但如果有push,则会向history栈中新增。
<Redirect push to="/somewhere/else" />
// 含匹配模式的redirect <Switch> <Redirect from='/users/:id' to='/users/profile/:id'/> <Route path='/users/profile/:id'> <Profile /> </Route> </Switch>
4.8 <Route>
(1)
<Route> <NewsReport /> </Route>
(2)内联component
<Route component={NewsReport}></Route> // 这种方式下,每次render都会重加载(remount)子元素(先unmount旧的,再mount新的)
相比于方式(2),更推荐使用(3)(4)。在更新时减小render成本。
(3) render函数
<Router> <Route path="/home" render={() => <div>Home</div>} /> </Router>
function FadingRoute({ component: Component, ...rest }) { return ( <Route {...rest} render={routeProps => ( <FadeIn> <Component {...routeProps} /> </FadeIn> )} /> ); } ReactDOM.render( <Router> <FadingRoute path="/cool" component={Something} /> </Router>, node );
<Route component>优先于<Route render>, 因此不要在同一个<Route>中同时使用。
(4) <Route children>与<Route render>不同的一点是,无论path是否匹配其函数都会被调用,只不过当URL不匹配时,route的match属性为null.
(4) function ListItemLink({ to, ...rest }) { return ( <Route path={to} children={({ match }) => ( <li className={match ? "active" : ""}> <Link to={to} {...rest} /> </li> )} /> ); } ReactDOM.render( <Router> <ul> <ListItemLink to="/somewhere" /> <ListItemLink to="/somewhere-else" /> </ul> </Router>, node );
<Route children={({ match, ...rest }) => ( {/* Animate will always render, so you can use lifecycles to animate its child in and out */} <Animate> {match && <Something {...rest}/>} </Animate> )} />
优先级: 1.<Route children> 2.<Route component> 3.<Route render>, 因此同一个<Route>中最多使用其中一个。
path属性可以是String类型,还可以是Array类型。
<Route path="/users/:id"> <User /> </Route>
<Route path={["/users/:id", "/profile/:id"]}> <User /> </Route>
exact属性:是否完全匹配
path | location.pathname | exact | matches? |
---|---|---|---|
/one |
/one/two |
true |
no |
/one |
/one/two |
false |
yes |
strict属性:匹配location.path中是否以 / 结束,对于其后的分段无影响。
path | location.pathname | matches? |
---|---|---|
/one/ |
/one |
no |
/one/ |
/one/ |
yes |
/one/ |
/one/two |
yes |
同时含有exact和strict属性。
path | location.pathname | matches? |
---|---|---|
/one |
/one |
yes |
/one |
/one/ |
no |
/one |
/one/two |
no |
sensitive属性: 大小写敏感。
4.9 <Switch>的children只能是<Route>和<Redirect>,并且在匹配时,只有一个匹配到的会被渲染出来。
<Route>通过path属性进行匹配,而<Redirect>通过from属性进行匹配。如果<Route>不含path属性或者<Redirect>不含from属性,那么总是会匹配当前的路径。
let routes = ( <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/users"> <Users /> </Route> <Redirect from="/accounts" to="/users" /> <Route> <NoMatch /> </Route> </Switch> );
5. history 对象,有以下属性:
length
- (number) The number of entries in the history stackaction
- (string) The current action (PUSH
,REPLACE
, orPOP
)location
- (object) The current location. May have the following properties:pathname
- (string) The path of the URLsearch
- (string) The URL query stringhash
- (string) The URL hash fragmentstate
- (object) location-specific state that was provided to e.g.push(path, state)
when this location was pushed onto the stack. Only available in browser and memory history.
push(path, [state])
- (function) Pushes a new entry onto the history stackreplace(path, [state])
- (function) Replaces the current entry on the history stackgo(n)
- (function) Moves the pointer in the history stack byn
entriesgoBack()
- (function) Equivalent togo(-1)
goForward()
- (function) Equivalent togo(1)
block(prompt)
- (function) 阻塞导航
history对象是易变的,所以一般要获取location对象时不通过history对象,而是通过<Route>的渲染属性。
class Comp extends React.Component { componentDidUpdate(prevProps) { // will be true const locationChanged = this.props.location !== prevProps.location; // INCORRECT, will *always* be false because history is mutable. const locationChanged = this.props.history.location !== prevProps.history.location; } } <Route component={Comp} />;
6. location对象
{ key: 'ac3df4', // not with HashHistory! pathname: '/somewhere', search: '?some=search-string', hash: '#howdy', state: { [userDefined]: true } }
获取location对象的方式:
- Route component as
this.props.location
- Route render as
({ location }) => ()
- Route children as
({ location }) => ()
- withRouter as
this.props.location
以上方式获取的location对象可以用于生命周期hooks中。不要通过history.location(易变的)获取。
componentWillReceiveProps(nextProps) { if (nextProps.location !== this.props.location) { // navigated! } }
location对象可用于以下:
- Web Link to
- Native Link to
- Redirect to
- history.push
- history.replace
const location = { pathname: '/somewhere', state: { fromDashboard: true } } <Link to={location}/> <Redirect to={location}/> history.push(location) history.replace(location)
此外,可以将location对象用在以下场景中,
强制使用给定的location对象,而不是router默认的location.
7. match对象:<Route>如何进行匹配的。包含以下属性:
params
- (object) Key/value pairs parsed from the URL corresponding to the dynamic segments of the pathisExact
- (boolean)true
if the entire URL was matched (no trailing characters)path
- (string) The path pattern used to match. Useful for building nested<Route>
surl
- (string) The matched portion of the URL. Useful for building nested<Link>
s
match对象可用于以下场景:
- Route component as
this.props.match
- Route render as
({ match }) => ()
- Route children as
({ match }) => ()
- withRouter as
this.props.match
- matchPath as the return value
null match 当<Route>的path属性与当前的location不匹配时,其children属性对应的函数仍然会被调用,只不过此时match对象为null.
// location.pathname = '/matches' <Route path="/does-not-match" children={({ match }) => ( // match === null <Route render={({ match: pathlessMatch }) => ( // pathlessMatch === ??? )} /> )} />
没有path属性的<Route>,其match对象继承自父级<Route>。如果父级<Route>的match为null,那么子<Route>的match也为null.对于无path属性,且父级<Route> match为null的子<Route>,通过调用其children属性进行渲染。
8. withRouter 高阶组件: withRouter可以包装任何自定义组件,将react-router 的 history,location,match 三个对象传入。
无需一级级传递react-router 的属性,当需要用的router 属性的时候,在组件外包上withRouter(),就可以拿到需要的路由信息。
import React from "react"; import PropTypes from "prop-types"; import { withRouter } from "react-router"; // A simple component that shows the pathname of the current location class ShowTheLocation extends React.Component { static propTypes = { match: PropTypes.object.isRequired, location: PropTypes.object.isRequired, history: PropTypes.object.isRequired }; render() { const { match, location, history } = this.props; return <div>You are now at {location.pathname}</div>; } } // Create a new component that is "connected" (to borrow redux // terminology) to the router. const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
WrappedComponent用于测试组件是否孤立于其他组件。
// MyComponent.js export default withRouter(MyComponent) // MyComponent.test.js import MyComponent from './MyComponent' render(<MyComponent.WrappedComponent location={{...}} ... />)