react路由、NavLink组件封装
-
react路由其实是借助BOM中的History对象实现的,专门有一个history.js的库,可以让操作history对象简单起来。
用history.js可以通过两种方式创建路由对象:
1、History.createBrowserHistory() // 直接使用h5推出的history身上的api (有些老的浏览器会不支持)
2、History.createHashHistory() // 直接使用原生的 hash值(锚点) (所有浏览器都支持)
创建好的对象上有 push、replace、back、forward方法,可以操作路由
historyObj.listen( location => { console.log(location) }); 监听路由变化
react路由的插件库是 react-router-dom
下面以react-router-dom@5实验
整个应用需要用一个路由器包裹,否则每个路由器中的路由是独立的
import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter } from 'react-router-dom' import App from './App' ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'))
基本的路由配置
import React from 'react' import { Link, Route } from 'react-router-dom' import Home from './components/Home' import About from './components/About' import './App.css' // 创建外壳组件App class App extends React.Component { render() { return ( <div className="container"> <h1>react-router-dom-demo</h1> <div className="main"> <aside className="aside"> { /** * 编写路由链接 * 在react中,靠路由连接切换组件 */} <Link className="btn" to="/home">home</Link> <Link className="btn" to="/about">about</Link> </aside> <div className="content"> {/** * 注册路由 */} <Route path="/home" component={Home}></Route> <Route path="/about" component={About}></Route> </div> </div> </div> ) } } export default App
路由组件和一般组件的区别?
1、接收props不同
路由组件会收到路由传给组件的props:history、location、match
2、写法不同 <Demo/> <Route path="/demo" component={Demo} />
3、存放位置不同
一般组件放在components文件下,路由组件存放在pages或views文件下,名字自己定义
NavLink组件的使用:
import { NavLink } from 'react-router-dom' <NavLink className="btn" activeClassName="active" to="/home">home</NavLink>
NavLink有个activeClassName属性,用来指定当前选中的类名,默认是active,如果是active可以省略
NavLink组件的封装:
NavLink 上需要传activeClassName 和 class,一个项目中的NavLink组件几乎传的这两个class都一样,把它封装成一个组件,就不用每个都传一遍了
import React, { Component } from 'react' import { NavLink } from 'react-router-dom' export default class MyNavLink extends Component { render() { // const { title, children } = this.props; return ( // <NavLink className="btn" activeClassName="active" to={to}>{title}</NavLink> // <NavLink className="btn" activeClassName="active" {...this.props}>{children}</NavLink> // 标签体内容是一个特殊的标签属性(children) // <NavLink className="btn" activeClassName="active" {...this.props} children={children} /> // children也包含在了this.props中,children={children}可以省略 <NavLink className="btn" activeClassName="active" {...this.props} /> ) } }
使用:
<MyNavLink to="/home">home</MyNavLink>
Switch的使用
<Switch> <Route path="/home" component={Home}></Route> <Route path="/about" component={About}></Route> <Route path="/about" component={Test}></Route> </Switch>
上面路由/about 对应了两个组件,如果未用Switch标签包裹起来,/about会同时展示 About、Test两个组件,用Switch标签包起来后,路由匹配到第一个组件后,就不会再往下匹配,/about只会匹配About组件。
BrowserRouter模式下,第三方css样式丢失问题,如果多级路由,css文件是用./相对路径引入的话,刷新页面会出现样式丢失的问题,因为相对路径会把路由的路径带上去,导致在public目录页下找不到资源,找不到资源的情况下会默认把index.html返回,解决办法有:1、把./相对路径改成绝对路径/,2、换成hash路由
路由的模糊匹配与精准匹配:
路由默认是模糊匹配
<MyNavLink to="/home/a/b">home</MyNavLink>
可以匹配
<Route path="/home" component={Home}></Route>
开启精准匹配:
<Switch> <Route exact path="/home" component={Home}></Route> <Route path="/about" component={About}></Route> <Route exact={true} path="/home/a/b" component={Test}></Route> </Switch>
精准匹配不要随便开启,只有不开精确匹配会导致错误时,才开启精确匹配,(开启精确匹配后,将无法访问子路由)
路由重定向 Redirect 的使用:
import { Link, NavLink, BrowserRouter, Route, Switch, Redirect } from 'react-router-dom' <Switch> <Route path="/home" component={Home}></Route> <Route path="/about" component={About}></Route> <Route path="/home" component={Test}></Route> <Redirect to="/home"></Redirect> </Switch>
Redirect写在注册路由的最下方,当没有匹配到任何路由时,会重定向到 Redirect指定的路由
嵌套路由:
在/home路由下写两个子路由
import React, { Component } from 'react' import { Route, Redirect, Switch } from 'react-router-dom'; import MyNavLink from '../../components/MyNavLink'; import News from './News' import Message from './Message' export default class Home extends Component { render() { console.log('home组件收到的props是:', this.props); return ( <div> <h1>我是Home的内容</h1> <ul className="tab"> <li className="tab-items"> <MyNavLink to="/home/news">news</MyNavLink> </li> <li className="tab-items"> <MyNavLink to="/home/message">message</MyNavLink> </li> </ul> {/** 注册路由 */} <Switch> <Route path="/home/news" component={News}></Route> <Route path="/home/message" component={Message}></Route> <Redirect to="/home/news"></Redirect> </Switch> </div> ) } }
向路由组件传递params参数:
1、传递
2、声明
3、接收
import React, { Component } from 'react' import { Link, NavLink, BrowserRouter, Route, Switch, Redirect } from 'react-router-dom' import Detail from './Detail'; export default class Message extends Component { state = { messageArr: [ { id: '01', title: '消息1' }, { id: '02', title: '消息2' }, { id: '03', title: '消息3' }, ] } render() { const { messageArr } = this.state; return ( <div> <ul> { messageArr.map(msgObj => { return ( <li key={msgObj.id}> {/** 向路由组件传递params参数 */} <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> </li> ) }) } </ul> <hr /> {/** 声明接收params参数 */} <Route path="/home/message/detail/:id/:title" component={Detail}></Route> </div> ) } }
接收
import React, { Component } from 'react' const Detaildata = [ { id: '01', content: '你好,中国' }, { id: '02', content: '你好,尚硅谷' }, { id: '03', content: '你好,未来的自己' }, ] export default class Detail extends Component { render() { console.log(this.props, 'detail的props'); // 接收params参数 const { id, title } = this.props.match.params; const findResult = Detaildata.find(item => item.id === id); return ( <ul> <li>ID: {id}</li> <li>TITLE: {title}</li> <li>CONTENT: {findResult.content}</li> </ul> ) } }
向路由传递search参数:
1、传递search参数无需声明
2、接收search参数时需要把查询字符串转换成key-value形式的对象,取值方便,这里借用了一个库 qs 老版本用 querystring,是react脚手架自带的库;
{/* 向路由组件传递search参数 */} <Link to={`/home/message/detail?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> {/* search参数无需声明接收 */} <Route path="/home/message/detail" component={Detail}></Route> 接收search参数 import qs from 'qs' // urlencode和object转换 const { search } = this.props.location; const { id, title } = qs.parse(search.slice(1));
向路由组件传递state参数:
1、传递state参数,需要是个对象的形式
2、接收时再props.location.state中获取
{/* 向路由传递state参数 刷新页面时state参数不会丢失,因为state维护在history对象上,如果换了浏览器,此参数会丢失 */} <Link to={ { pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } } }>{msgObj.title}</Link> {/* state参数无需声明接收 */} <Route path="/home/message/detail" component={Detail}></Route> // 接受state参数 const { id, title } = this.props.location.state || {};
路由开启replace模式:
路由默认是push模式,会留下记录,可以前进后退,如果开启replace模式,就不会有记录了
<Link replace to={ { pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } } }>{msgObj.title}</Link> // 或者 <Link replace={true} to={ { pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } } }>{msgObj.title}</Link>
编程式路由导航:
import React, { Component } from 'react' import { Link, NavLink, BrowserRouter, Route, Switch, Redirect } from 'react-router-dom' import Detail from './Detail'; export default class Message extends Component { state = { messageArr: [ { id: '01', title: '消息1' }, { id: '02', title: '消息2' }, { id: '03', title: '消息3' }, ] } // repalce跳转 replaceShow = (id, title) => { // replace跳转+携带params参数 // this.props.history.replace(`/home/message/detail/${id}/${title}`) // replace跳转+携带query参数 // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`) // replace跳转+携带state参数 this.props.history.replace(`/home/message/detail`, { id, title }) } // push跳转 pushShow = (id, title) => { // push跳转+携带params参数 // this.props.history.push(`/home/message/detail/${id}/${title}`) // push跳转+携带query参数 // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`) // push跳转+携带state参数 this.props.history.push(`/home/message/detail`, { id, title }) } // 回退 back = () => { this.props.history.goBack() } // 前进 forword = () => { this.props.history.goForward() } // go go = () => { this.props.history.go(-1); // 负数代表后退几步,正数代表前进几步 } render() { const { messageArr } = this.state; return ( <div> <ul> { messageArr.map(msgObj => { return ( <li key={msgObj.id}> {/** 向路由组件传递params参数 */} <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> {/* 向路由组件传递search参数 */} {/* <Link to={`/home/message/detail?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */} {/* 向路由传递state参数 刷新页面时state参数不会丢失,因为state维护在history对象上,如果换了浏览器,此参数会丢失 */} {/* <Link replace to={ { pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } } }>{msgObj.title}</Link> */} <button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button> <button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button> </li> ) }) } </ul> <hr /> {/** 声明接收params参数 */} {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */} {/* search参数无需声明接收 */} {/* <Route path="/home/message/detail" component={Detail}></Route> */} {/* state参数无需声明接收 */} <Route path="/home/message/detail" component={Detail}></Route> <hr /> <button onClick={ this.back }>回退</button> <button onClick={ this.forword }>前进</button> <button onClick={ this.go }>go</button> </div> ) } }
withRouter的使用:
一般组件像使用路由跳转的功能,需要用withRouter加工一下,使一般组件拥有路由组件的api
import React, { Component } from 'react' import { withRouter } from 'react-router-dom' class Header extends Component { // 回退 back = () => { this.props.history.goBack() } // 前进 forword = () => { this.props.history.goForward() } // go go = () => { this.props.history.go(-1); // 负数代表后退几步,正数代表前进几步 } render() { console.log(this.props, 'Header组件的props'); return ( <h3> react-router-dom-demo <button onClick={ this.back }>回退</button> <button onClick={ this.forword }>前进</button> <button onClick={ this.go }>go</button> </h3> ) } } // withRouter可以加工一般组件,让一般组件拥有路由组件特有的api(props:{ history, location, match }) // withRouter的返回值是一个新组件 export default withRouter(Header)
BrowserRouter和HashRouter的区别:
-