前端路由原理
前端路由依赖的是BOM中的history, history是个栈结构, 通过对history的拦截来实现路由的跳转
SPA: Single Page Application -- 单页面应用
单页面应用并不意味着就一个页面, 而是单个页面多个组件构成, 跳转的时候也是组件的展示与否
react-router-dom框架
Router分为两种
BrowserRouter: 符合正常页面跳转的情况
HashRouter: url中会出现#号
BrowserRouter
// 跳转标签, 类似<a>
// NavLink标签必须要嵌入到 BrowserRoute里, 这里还有一个NavLink 标签可以有高亮, Link 是没有的
// NavLink 标签里有一个属性activeClassName 用于设置高亮的样式
<BrowserRouter>
<NavLink to="/xxxpage"><NavLink> // 也是被转为a标签
</BrowserRouter>
// 注册路由
<BrowserRouter>
<Route path="/page1" component={ Page1Component }/>
<Route path="/page2" component={ Page2Component }/>
</BrowserRouter>
注意:像上面这种, 使用NavLink/Link标签也好Route标签也好, 都是需要嵌套在BrowserRouter这个标签里面的, 但是上面这种情况会出现一个问题, 使用多个BrowserRouter包裹的时候会出现多个路由对象, 他们互相没什么关系, 所以通常做法是把整个<App />组件进行包裹, 像下面这样
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
HashRouter
兼容更好, 但是刷新后会怼state参数有影响, 因为 BrowserRouter的state存放在history中
当然, 这里也能够使用HashRouter, 如果使用这个访问服务器的时候的url
http://localhost:3000/#
跳转页面就会出现这种情况
http://localhost:3000/#/about
还需要注意一点就是 #后面的数据是不会被提交给服务器的, 被认为是本地的资源
ReactDOM.render(
<HashRouter>
<App />
</HashRouter>,
document.getElementById('root')
)
组件的分类
路由组件: 一般放到pages目录
<Route path="/page1" component={ Page1Component }/>
一般组件: 一般放到components目录
<Page />
路由组件进行跳转, 准确的说是挂载组件的时候, 会有一个默认传值props, 三个重要的属性history location match
自定义导航组件
如果有多个这种导航组件就会出现下面的情况, 样式都一样, 只是path不一样而已
<Route activeClassName="" className="" path="/page1" component={ Page1Component1 }/>
<Route activeClassName="" className="" path="/page2" component={ Page1Component2 }/>
<Route activeClassName="" className="" path="/page3" component={ Page1Component3 }/>
这个时候就需要自定义一个组件
1. 新建组件MyNaviLink 导入 NavLink
2. 返回 NavLink 组件就好
return (
<NaviLink activeClassName="" className="" {...this.props} />
)
3. 外面调用的时候
<MyNavLink to="/about">About</MyNavLink>
这里需要注意一个问题 标签中的 About字符, 在自定义里并没有设置, 是因为两个标签的字符, 会以一个叫
children:'About'的字段存在于props里, 通过children="About" 设置能达到同样的效果
Swtich标签: 当匹配到一个path之后, 就不会再继续向下匹配, 相当于退出循环
如果有很多的路由, 相同的path 对应不同的组件的话, 默认会从上到下一直匹配, 有一个算一个
<Switch>
<Route activeClassName="" className="" path="/page1" component={ Page1Component1}/>
<Route activeClassName="" className="" path="/page2" component={ Page1Component2 }/>
<Route activeClassName="" className="" path="/page3" component={ Page1Component3 }/>
<Route activeClassName="" className="" path="/page3" component={ Page1Component4 }/>
</Switch>
如果不包裹Switch的话, 当匹配到page3的时候, 会出现两个组件
多级路径导致的样式丢失问题
如果使用BrowserRouter的时候, 在path上添加统一标识, 路径应该是 /page1 想添加统一标识/xxx/page1, 跳转的时候不会有任何问题,
但是一旦请求服务器的时候, 有可能会导致资源加载错误, 会把xxx带入到服务器中
http://localhost:3000/css/index.css
http://localhost:3000/xxx/css/index.css
解决办法
1. 首页在引入css样式表的时候, 使用绝对路径 <link rel="stylesheet" href="/css/index.css">
// 下面这个PUBLIC_URL只有react认
2. 首页在引入css样式表的时候, 使用绝对路径 <link rel="stylesheet" href="%PUBLIC_URL%/css/index.css">
3. 使用HashRouter, 这货会添加#号, #号之后的都不会带入服务器
导航的模糊匹配和精准匹配
比如注册的路由/home
/home, 刚好匹配上, 可以展示组件
/home/a, 会先把 home 和 a 分别取出来, 然后 跟home 匹配, 第一个是home 就可以跳转到home
/a/home 第一个是a 跟home匹配不上, 就不会再往后看了
如果想要精准匹配, 只需要在注册的时候设置一个属性 exact= {true}即可(最好不要开启)
<Route exact={true} path="/page1" component={ Page1Component1 }/>
Redirect重定向组件
路由的匹配是按照注册的顺序开始匹配, 也就是说谁先注册先匹配谁, 匹配到谁就返回页面, 如果谁都没匹配到,
就会直接跳转到Redirect 指定的页面
其实输入主页地址 http://lcoalhost:3000 上来也是会进行匹配 因为3000 后面还会有一个 /
但是因为是首页, / 后面没有字符了, 就会被认为是空的, 所以需要一个兜底的操作
下面这个就是兜底的, 如果所有的路由都匹配不上, 就会跳转Redirect 指定的页面
<Redirect to="/about" />
嵌套/多级路由
about
home
先注册 /about 和 /home 最后 Redirect /about
现在home有两个二级页面 news message
跳转的path分别是
/home/news
/home/message
子页面的路由注册为
/home/news
/home/message
如果是在子页面跳转到 /home/news 页面的话规则是
匹配到先注册的/home的时候, 这时, home 页面不会消失
继续向下匹配 /home/news 第二个跟跳转的news匹配, 然后再展示出news的界面
Tips1:如果是多级路由, 开启精确匹配的话, 多级路由就无效了
Tips2:多级路由就要把父组件也给加上比如上面的news下面有一个a页面, /home/news/a
路由组件传参数
params参数
是通过路由传递的分跳转端和注册端
注册端
<Route path="/home/message/detail/:id/:title" component={Detail}>
跳转端, 多个参数用斜杠分开/$()
<Link to={`/home/message/detail/$(obj.id)/$(obj.title)`}>{obj.title}</Link>
页面获取
是在 match 的params里
match 就是上面路由跳转的时候, 默认的三大参数之一, 还有history和location
const {id, title} = this.props.match.params
search参数
是通过路由传递的分跳转端和注册端
注册端(无需任何声明)
<Route path="/home/message/detail" component={Detail}>
跳转端, 多个参数用斜杠分开/$()
<Link to={`/home/message/detail/?id=$(obj.id)&title=/$(obj.title)`}>{obj.title}</Link>
页面获取search参数
导入querystring库
import qs from 'querystring'
const {search} = this.props.location.search // ?id=1&title="title01"
// 把对象转为urlencode
let obj = {name:'tom', age:18} // name=tome&age=18
qs.stringify(obj)
// 解析search 参数
const {id, title} = qs.parse(search.slice(1))
state参数
注册端(无需任何声明)
<Route path="/home/message/detail" component={Detail}>
跳转端, 多个参数用斜杠分开/$()
<Link to={{pathName:"/home/message/detail", state:{id:obj.id, title:obj.title}}}>{obj.title}</Link>
页面获取参数
const {id, title} = this.props.location.state || {}
push和replace
push: 路由默认的方式, 就是入栈的操作, 返回的时候是push多少次, 返回到首页就得返回同样次数
replace: 是替换的方式, 这种一旦使用就会导致栈顶只有一个, 开启的话, 只需要在跳转的时候设置就好
一旦开启, 就相当于history里没有记录
<Link replace={true} to="/home">首页</Link>
编程式路由 -- 不单单只是通过手动触发Link/NvaLink进行跳转
this.props.history.replace(`/home/message/detail/$(id)/$(title)`)
this.props.history.push()
上面那种传参方式可以传递params和search
this.props.history.replace(/home/message/detail, {id:1, title:'title1'})
编码实现前进/后退
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go(n) // 传递正数, 前进多少个 传递负数 后退多少个
如果一般组件也想使用路由的功能
import {withRouter} from 'react-router-dom' // 注意 这里导入的不是组件而是一个函数
class Header extends Component { }
export default withRouter(Header)