原生html实现一个mini-react-router
前言
实现一个简单版本的react-router
, 揭秘路由的神秘面纱
思考
• 前端路由本质上是什么
• 前端路由里的一些坑和注意点
• hash路由和history路由的区别
• Router组件和Route组件分别做了什么
路由的本质
浏览器端的路由不是真实的网页跳转,和服务器没有任何交互,本质上就是对url进行监听,让某个dom节点显示对应的视图路由的区别
路由的区别
一般来说,前端路由分为两种 1、hash 路由, 特征是url后面会有 # 号, 如 baidu.com/#foo/bar/baz 2、history 路由, url和普通路由没有差异。 如 baidu.com/foo/bar/baz
实际上只要搞清楚两种路由分别是如何改变,并且组件是如何完成视图的展示的
hash
通过location.hash = 'foo'
这样的语法来改变, 路径, 路径就会由baidu.com
变成baidu.com/#foo
通过window.addEventListener('hashchange')
这个事件监听到hash
值的变化
-history
通过window.history.pushState(data, title, targetURL)
- @状态对象:传给目标路由的信息,可为空
- @页面标题:目前所有浏览器都不支持,填空字符串即可
- @可选url:目标url,不会检查url是否存在,且不能跨域。如不传该项,即给当前url添加data
通过history.pushState({}, '', 'foo')
,可以让 baidu.com 变化为 baidu.com/foo
!坑
history
路由的监听,浏览器虽然提供了window.addEventListener('popstate事件'),但是只能监听浏览器回退和前进产生的路由变化,对于主动的pushState
却监听不到
基于history版本从零到1实现 react-mini-router
实现一个history
1、history.push2、history.listen
利用观察者模式封装简单的listen API。让用户监听到history.push 产生的路径变化
原生html实现一个mini-react-router 前言 实现一个简单版本的react-router, 揭秘路由的神秘面纱 思考 • 前端路由本质上是什么 • 前端路由里的一些坑和注意点 • hash路由和history路由的区别 • Router组件和Route组件分别做了什么 路由的本质 浏览器端的路由不是真实的网页跳转,和服务器没有任何交互,本质上就是对url进行监听,让某个dom节点显示对应的视图 路由的区别 路由的区别 一般来说,前端路由分为两种 1、hash 路由, 特征是url后面会有 # 号, 如 baidu.com/#foo/bar/baz 2、history 路由, url和普通路由没有差异。 如 baidu.com/foo/bar/baz 实际上只要搞清楚两种路由分别是如何改变,并且组件是如何完成视图的展示的 hash 通过location.hash = 'foo' 这样的语法来改变, 路径, 路径就会由baidu.com变成baidu.com/#foo 通过window.addEventListener('hashchange')这个事件监听到hash值的变化 - history 通过window.history.pushState(data, title, targetURL) @状态对象:传给目标路由的信息,可为空@页面标题:目前所有浏览器都不支持,填空字符串即可@可选url:目标url,不会检查url是否存在,且不能跨域。如不传该项,即给当前url添加data 通过 history.pushState({}, '', 'foo'),可以让 baidu.com 变化为 baidu.com/foo !坑 history路由的监听,浏览器虽然提供了window.addEventListener('popstate事件'),但是只能监听浏览器回退和前进产生的路由变化,对于主动的pushState却监听不到 基于history版本从零到1实现 react-mini-router 实现一个history 1、history.push 2、history.listen 利用观察者模式封装简单的listen API。让用户监听到history.push 产生的路径变化 作者:重阳微噪 链接:https://juejin.cn/post/6875262564209524749 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
简单实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React Router History</title> </head> <body> <div id="app"><div> </body> <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script> // 参考 https://github.com/sl1673495/react-mini-router let listeners = []; function listen(fn){ listeners.push(fn) } function push(to, state) { // 解析用户传入的 url // 调用原生 history 的方法改变路由 window.history.pushState(state, '', to); // 执行用户传入的监听函数 listeners.forEach(fn => fn(location)); } // 用于处理浏览器前进后退操作 window.addEventListener('popstate', () => { listeners.forEach(fn => fn(location)); }); history.listen = listen history.push = push history.location = location </script> <script type="text/babel"> class App extends React.Component{ constructor(props){ super(props) this.state={ showFoo: false } } componentDidMount(){ history.listen(location => { console.log(location,'location'); const {pathname} = location; if(pathname == '/Router/foo'){ this.setState({ showFoo: true }) }else{ this.setState({ showFoo: false }) } }); } changeRouter(to){ history.push(to, {}); } render(){ const {showFoo} = this.state; return( <div> <button onClick={()=>{this.changeRouter('foo')}}>展示foo组件</button> <button onClick={()=>{this.changeRouter('index')}}>回到首页</button> {!showFoo && <div>首页</div>} {showFoo && <div>我是Foo</div>} </div> ) } } ReactDOM.render(<App/>,document.getElementById('app')); </script> </html> 实现 Router Router的核心原理就是通过Provider把location和history等路由关键信息传递给子组件,并切在路由发生变化的时候让子组件可以感知 复制代码 const RouterContext = React.createContext(null) class Router extends React.Component{ constructor(props){ super(props) this.state={ location: location } } componentDidMount(){ history.listen(location => { this.setState({ location }) }); } render(){ const { location } = this.state return( <div> <RouterContext.Provider value={{ history, location }}> {this.props.children} </RouterContext.Provider> </div> ) } }
实现 Route
Route 组件接受 path 和 children两个 prop ,本质上就决定了在某个路径下需要渲染什么组件,我们又可以通过 Router 的 Provider 传递下来的 location 信息拿到当前路径,所以这个组件需要做的就是判断当前的路径是否匹配,渲染对应组件
const Route = ({ path, children }) => { let {history,location} = React.useContext(RouterContext); let { pathname } = location console.log(pathname,path,'iii'); if(pathname === path){ return children } return null };
实现 Link
Link 组件接受to和name两个参数,通过 Router 的 Provider 传递下来的 history 拿到当前的push方法,点击的时候去触发
const Link = ({name,to})=>{ let {history} = React.useContext(RouterContext); function changeRouter(to){ history.push(to, {}); } return ( <div> <button onClick={()=>{changeRouter(to)}}>{name}</button> </div> ) }
完整代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React Router History</title> </head> <body> <div id="app"><div> </body> <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script src="./history.js"></script> <script type="text/babel"> const RouterContext = React.createContext(null) class Router extends React.Component{ constructor(props){ super(props) this.state={ location: location } } componentDidMount(){ history.listen(location => { this.setState({ location }) }); } render(){ const { location } = this.state return( <div> <RouterContext.Provider value={{ history, location }}> {this.props.children} </RouterContext.Provider> </div> ) } } const Route = ({ path, children }) => { let {history,location} = React.useContext(RouterContext); let { pathname } = location console.log(pathname,path,'iii'); if(pathname === path){ return children } return null }; const Link = ({name,to})=>{ let {history} = React.useContext(RouterContext); function changeRouter(to){ history.push(to, {}); } return ( <div> <button onClick={()=>{changeRouter(to)}}>{name}</button> </div> ) } class App extends React.Component{ constructor(props){ super(props) } render(){ return( <div> <Router> <Link to="/foo" name="展示foo组件"/> <Link to="/index" name="回到首页"/> <Route path="/index"> <Index/> </Route> <Route path="/foo"> <Foo/> </Route> </Router> </div> ) }} class Foo extends React.Component{ render(){ return <div>我是foo</div> } } class Index extends React.Component{ render(){ return <div>我是index1</div> } } ReactDOM.render(<App/>,document.getElementById('app')); </script> </html>
参考
最后
大家可以关注我的公众号,回复vue源码,可以得到完整的代码,也可以不懂的地方在下面留言!