对于想了解react-router-dom的HashRouter也就这么回事儿的读者,本文将是一篇不可错过的文章,我们将详细介绍react-routerhashhistory,并且为您提供关于''re
对于想了解react-router-dom 的 HashRouter 也就这么回事儿的读者,本文将是一篇不可错过的文章,我们将详细介绍react-router hashhistory,并且为您提供关于''react-router-redux''到''connected-react-router''、./node_modules/react-router-dom/react-router-dom.js尝试导入错误:未从“ react-router”导出“ Navigate”、connected-react-router: react-router-dom 必须总是最后安装、DOM、SAX、JDOM DOM、JDOM SAX、DOM4J DOM、DOM4J SAX 性能比对的有价值信息。
本文目录一览:- react-router-dom 的 HashRouter 也就这么回事儿(react-router hashhistory)
- ''react-router-redux''到''connected-react-router''
- ./node_modules/react-router-dom/react-router-dom.js尝试导入错误:未从“ react-router”导出“ Navigate”
- connected-react-router: react-router-dom 必须总是最后安装
- DOM、SAX、JDOM DOM、JDOM SAX、DOM4J DOM、DOM4J SAX 性能比对
react-router-dom 的 HashRouter 也就这么回事儿(react-router hashhistory)
1 要实现的功能
我们使用React开发项目的时候,基本上是单页面应用,也就离不开路由。路由看似神秘,当我们简单的模拟一下它的核心功能后,发现也就这么回事儿。本文就详细的介绍一下react-router-dom
的 HashRouter
的核心实现逻辑。
本文实现的功能主要包含:
HashRouter
Route
Link
MenuLink
Switch
Redirect
2 实现的逻辑
先不说代码是怎样写的,先上图,让大家看一下这个HashRouter
到底是个什么东东:
好吧,肯定有人会说这些圈圈又是什么东东呀,客官不要着急,待我慢慢解释:
-
HashRouter
是一个大的容器,它控制着他自己到底渲染成什么样子,那么它是通过什么控制的呢,看它的名字就能猜出来,那就是window.location.hash
。 - 当
HashRouter
开始渲染的时候就会拿它自己身上的pathname
属性跟它肚子里的Route
的path
进行匹配,匹配上的话,就会渲染Route
的component
对应的组件。 -
Link
是怎样切换路由的呢,很简单,就是通过this.props.history.push(path)
来改变HashRouter
中的pathname
属性,进而驱动Route们
进行重新渲染,再次匹配我们的路由,最终实现路由的切换。
介绍了一下简单的逻辑,接下来我们就看一下具体是怎样实现的吧,如下图:
-
HashRouter
是一个继承了React.Component
的类,这个类上的state
包括location
,监听着hash
的变化以驱动Route
组件的重新渲染,另外还有一个history
属性,可以切换页面的路由。 - 本文要实现的功能中包括
Route
、Link
、MenuLink
、Switch
、Redirect
,其中Route
的是基础是核心,MenuLink
和某些有特定逻辑的渲染都是在Route
的基础上实现的。 -
Route
组件上可以接收三种变量,包括component
、render
、children
,其中render
、children
是都是函数,render
是根据特定的逻辑渲染元素,children
是用来渲染MenuLink
,这两个函数都接收当前路由的props
,函数的返回值是要渲染的元素。 -
Switch
实现的逻辑是,返回children
中跟hash
匹配到的第一个“孩子”。
3 具体的代码逻辑
(1) HashRouter
HashRouter
将window.loacation.hash
跟自己的state
挂钩,通过改变自己的state
驱动页面的重新渲染。
import React,{Component} from 'react'; import PropTypes from 'prop-types'; export default class HashRouter extends Component { constructor() { super(); this.state = { location: { pathname: window.location.hash.slice(1) || '/',// 当前页面的hash值 state: {} //保存的状态 } }; } // 定义上下文的变量类型 static childContextTypes = { location: PropTypes.object,history: PropTypes.object } // 定义上下文的变量 getChildContext() { return { location: this.state.location,history: { push: (path) => { // 就是更新 window.hash值 if (typeof path === 'object') { let {pathname,state} = path; this.setState({ location: { ...this.state.location,state // {from: '/profile'} } },() => { window.location.hash = pathname; }) } else { window.location.hash = path; } } } } } render() { return this.props.children; // 渲染页面元素 } componentDidMount() { window.location.hash = window.location.hash.slice(1) || '/'; // 监听window的hash的变化,驱动页面的重新刷新 window.addEventListener('hashchange',() => { this.setState({ location: { ...this.state.location,pathname: window.location.hash.slice(1) || '/' } }); }) } }
(2) Route
Route
的渲染核心逻辑就是将自己的path
和当前页面的hash
进行匹配,匹配上了就渲染相应的元素,匹配不上就什么都不渲染。
import React,{Component} from 'react'; import PropTypes from 'prop-types'; import pathToRegexp from 'path-to-regexp' export default class Route extends Component { // 定义上下文context的类型 static contextTypes = { location: PropTypes.object,history: PropTypes.object } render() { // 解构传入Route的props let {path,component: Component,render,children} = this.props; // 解构上下文的属性 let {location,history} = this.context; let props = { location,history }; // 将传入Route的path和当前的hash进行匹配 let keys = []; let regexp = pathToRegexp(path,keys,{end: false}); keys = keys.map(key => key.name); let result = location.pathname.match(regexp); if (result) { // 匹配上了 let [url,...values] = result; props.match = { path,url,params: keys.reduce((memo,key,index) => { // 获取匹配到的参数 memo[key] = values[index]; return memo; },{}) }; if (Component) { // 普通的Route return <Component {...props} />; } else if (render) { // 特定逻辑的渲染 return render(props); } else if (children) { // MenuLink的渲染 return children(props); } else { return null; } } else { // 没有匹配上 if (children) { // MenuLink的渲染 return children(props); } else { return null; } } } }
(3) Redirect
Redirect
就干了一件事,就是改变HashRouter
的state
,驱动重新渲染。
import React,{Component} from 'react'; import PropTypes from 'prop-types'; export default class Redirect extends Component { // 定义上下文context的Type static contextTypes = { history: PropTypes.object } componentDidMount() { // 跳转到目标路由 this.context.history.push(this.props.to); } render() { return null; } }
(4) MenuLink
import React,{Component} from 'react'; import Route from "./Route"; import Link from './Link' export default ({to,children}) => { // 如果匹配到了,就给当前组件一个激活状态的className return <Route path={to} children={props => ( <li className={props.match ? "active" : ""}> <Link to={to}>{children}</Link> </li> ) }/> }
(5) Link
Link
就是渲染成一个a标签,然后给一个点击事件,点击的时候更改HashRouter
的状态,驱动重新渲染。
import React,{Component} from 'react'; import PropTypes from 'prop-types'; export default class Link extends Component { static contextTypes = { history: PropTypes.object } render() { return ( <a onClick={() => this.context.history.push(this.props.to)}>{this.props.children}</a> ) } }
(6) Switch
import React,{Component} from 'react'; import PropTypes from 'prop-types'; import pathToRegexp from 'path-to-regexp'; export default class Switch extends Component { static contextTypes = { location: PropTypes.object } render() { let {pathname} = this.context.location; let children = this.props.children; for (let i = 0,l = children.length; i < l; i++) { let child = children[i]; let path = child.props.path; if (pathToRegexp(path,[],{end: false}).test(pathname)) { // 将匹配到的第一个元素返回 return child; } } return null } }
4 写在最后
好了,这几个功能介绍完了,你是否对HashRouter
的原理有所了解了呢?本文只是贴出部分代码,如果有需要请看demo可以手动体验一下哦。
参考文献:
- react-router
''react-router-redux''到''connected-react-router''
背景:
redux和react-router两者协同工作会出现,路由变化而store无法感知到,的问题。
react-router-redux :
一. 作用:
react-router-redux 是 redux 的一个中间件,加强了React Router库中history这个实例,以允许将history中接受到的变化反应到state中去。
使用方法1:
使用syncHistoryWithStore包裹browserHistory,当url改变时,会自动触发 LOCATION_CHANGE action,更改store中维护的 locationBeforeTransitions 状态对象,实现store状态的更新。
// 只需要传入react-router中的history以及redux中的store,就可以获得一个增强后的history对象。
// 将这个history对象传给react-router中的Router组件作为props,就给应用提供了观察路由变化并改变store的能力。
import { syncHistoryWithStore, routerReducer } from ''react-router-redux''
const store = createStore(
combineReducers({
...reducers,
routing: routerReducer
})
)
const history = syncHistoryWithStore(browserHistory, store)
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App} />
</Router>
</Provider>,
document.getElementById(‘app'')
)
// 对应的reducer.js
export const LOCATION_CHANGE = ''@@router/LOCATION_CHANGE''
const initialState = {
locationBeforeTransitions: null
}
使用方法2:
手动触发路由的跳转,同时需要。
直接store.dispatch(push(''/foo'')),会触发 CALL_HISTORY_METHOD 这个action,调用中间件,等同于调用 browserHistory上相应的push方法。
// 触发路由跳转(使用redux action的方式来触发)
import { createStore, combineReducers, applyMiddleware } from ''redux'';
import { routerMiddleware, push } from ''react-router-redux''
// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
reducers,
applyMiddleware(middleware)
)
// 使用dispatch手动触发 push 等操作
store.dispatch(push(''/foo''))
源码的具体实现:
// import 的 push, replace, go, goBack, goForward之类的方法出自: action.js
export const push = updateLocation(''push'')
function updateLocation(method) {
return (...args) => ({
type: CALL_HISTORY_METHOD,
payload: { method, args }
})
}
//中间件代码
export default function routerMiddleware(history) {
return () => next => action => {
if (action.type !== CALL_HISTORY_METHOD) {
return next(action)
}
const { payload: { method, args } } = action
history[method](...args) //这里直接改变browserHistory
}
}
二. react-router-redux原理图
CALL_HISTORY_METHOD,这类 action 一般会在组件内派发,它不负责 state 的修改,通过 routerMiddleware 后,会被转去调用 history。
面临的问题:
react-router-redux 只兼容 react-router 2.x and 3.x,所以要改成使用 connected-react-router 来兼容 react-router 4.x。
解决:
把之前 react-router-redux 中 store.dispatch(push(''/foo'')) 这么使用的,直接改成 history.push(''/foo'') 之类的。
参考文章:
https://blog.csdn.net/weixin_... React 的路由状态管理
./node_modules/react-router-dom/react-router-dom.js尝试导入错误:未从“ react-router”导出“ Navigate”
如何解决./node_modules/react-router-dom/react-router-dom.js尝试导入错误:未从“ react-router”导出“ Navigate”
./node_modules/react-router-dom/react-router-dom.js Attempted import error: ''Navigate'' is not exported from ''react-router''.
react-router-dom的版本是6.0.0-alpha.2,而react-router是5.2.0。 两者均已正确安装。我不确定如何解决此错误。有人可以给我任何可能的解决方法吗?
我的代码中甚至没有<Navigate to=?>
行。
解决方法
为什么只安装两个都需要,这可以工作
- 执行npm删除react-router
- 删除node_modules
- 纱线安装或npm安装和
- 启动纱线或启动npm
connected-react-router: react-router-dom 必须总是最后安装
如何解决connected-react-router: react-router-dom 必须总是最后安装
我从我的开发文件夹中排除了我的 node_modules 文件夹,并且只有一个指向它的软链接(因此它不会同步到云)。我使用 connected-react-router 并且一切正常,直到我之后安装任何其他 npm 包。然后我在浏览器中收到错误:Uncaught Error: Invariant Failed: You should not use <Switch> outside a <Router>
当我再做 npm install react-router-dom
时,一切都会恢复正常。出于某种原因,react-router-dom 必须始终是最新安装的软件包。
即使我直接包含 node_modules 文件夹,而不是作为软链接,也会出现此问题。有没有人知道这是怎么发生的以及如何解决这个问题?
DOM、SAX、JDOM DOM、JDOM SAX、DOM4J DOM、DOM4J SAX 性能比对
测试xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<RESULT>
<VALUE>
<NO>A1234</NO>
<ADDR>XX号</ADDR>
</VALUE>
<VALUE>
<NO>B1234</NO>
<ADDR>XX组</ADDR>
</VALUE>
</RESULT>
测试代码:
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.DOMReader;
import org.dom4j.io.SAXReader;
import org.jdom.Element;
import org.jdom.input.DOMBuilder;
import org.jdom.input.SAXBuilder;
import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class XMLParserTest {
@Test
public void testDOMTypeParseXML() throws ParserConfigurationException, SAXException,
IOException {
long lasting =System.currentTimeMillis();
InputStream is = XMLParserTest.class.getClassLoader().getResourceAsStream("test.xml");
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
org.w3c.dom.Document doc = builder.parse(is);
org.w3c.dom.NodeList nl = doc.getElementsByTagName("VALUE");
for (int i=0;i<nl.getLength();i++){
// System.out.print("车牌号码:" + doc.getElementsByTagName("NO").item(i).getFirstChild().getNodeValue());
// System.out.println("车主地址:" + doc.getElementsByTagName("ADDR").item(i).getFirstChild().getNodeValue());
}
long end = System.currentTimeMillis();
System.out.println("DOM TYPE PARSE COST "+(end - lasting)+"毫秒");
}
public class MyXMLReader extends DefaultHandler{
@SuppressWarnings("rawtypes")
java.util.Stack tags = new java.util.Stack();
public MyXMLReader() {
super();
}
@SuppressWarnings("unchecked")
public void startElement(String uri,String localName,String qName,Attributes attrs) {
tags.push(qName);
}
public void characters(char ch[], int start, int length) throws SAXException {
String tag = (String) tags.peek();
if (tag.equals("NO")) {
// System.out.print("车牌号码:" + new String(ch, start, length));
}
if (tag.equals("ADDR")) {
// System.out.println("地址:" + new String(ch, start, length));
}
}
}
@Test
public void testSAXTypeParseXML() throws SAXException, IOException, ParserConfigurationException{
long lasting = System.currentTimeMillis();
InputStream is = XMLParserTest.class.getClassLoader().getResourceAsStream("test.xml");
SAXParserFactory sf = SAXParserFactory.newInstance();
SAXParser sp = sf.newSAXParser();
MyXMLReader me = new MyXMLReader();
sp.parse(is, me);
System.out.println("SAX TYPE PARSE COST "+(System.currentTimeMillis() - lasting)+"毫秒");
}
@Test
public void testJDOMSAXTypeParseXML(){
long lasting = System.currentTimeMillis();
try {
InputStream is = XMLParserTest.class.getClassLoader().getResourceAsStream("test.xml");
SAXBuilder builder = new SAXBuilder();
org.jdom.Document doc = builder.build(is);
Element foo = doc.getRootElement();
List allChildren = foo.getChildren();
for(int i=0;i < allChildren.size();i++) {
// System.out.print("车牌号码:" + ((Element)allChildren.get(i)).getChild("NO").getText());
// System.out.println("车主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());
}
} catch (Exception e) {
e.printStackTrace();
}finally{
System.out.println("JDOM SAX TYPE PARSE COST "+(System.currentTimeMillis() - lasting)+"毫秒");
}
}
@Test
public void testJDOMDOMTypeParseXML(){
long lasting = System.currentTimeMillis();
try {
InputStream is = XMLParserTest.class.getClassLoader().getResourceAsStream("test.xml");
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
org.w3c.dom.Document document = builder.parse(is);
DOMBuilder builder1 = new DOMBuilder();
org.jdom.Document doc = builder1.build(document);
Element foo = doc.getRootElement();
List allChildren = foo.getChildren();
for(int i=0;i < allChildren.size();i++) {
// System.out.print("车牌号码:" + ((Element)allChildren.get(i)).getChild("NO").getText());
// System.out.println("车主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());
}
} catch (Exception e) {
e.printStackTrace();
}finally{
System.out.println("JDOM DOM TYPE PARSE COST "+(System.currentTimeMillis() - lasting)+"毫秒");
}
}
@Test
public void testDOM4JSAXTypeParserXML() throws DocumentException{
long lasting = System.currentTimeMillis();
InputStream is = XMLParserTest.class.getClassLoader().getResourceAsStream("test.xml");
SAXReader reader = new SAXReader();
Document doc = reader.read(is);
org.dom4j.Element root = doc.getRootElement();
org.dom4j.Element foo;
for (Iterator i = root.elementIterator("VALUE");i.hasNext();){
foo = (org.dom4j.Element)i.next();
// System.out.print("车牌号码:" + foo.elementText("NO"));
// System.out.println("车主地址:" + foo.elementText("ADDR"));
}
System.out.println("DOM4J SAX TYPE PARSE COST "+(System.currentTimeMillis() - lasting)+"毫秒");
}
@Test
public void testDOM4JDOMTypeParserXML() throws DocumentException, ParserConfigurationException, SAXException, IOException{
long lasting = System.currentTimeMillis();
InputStream is = XMLParserTest.class.getClassLoader().getResourceAsStream("test.xml");
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
org.w3c.dom.Document document = builder.parse(is);
DOMReader reader = new DOMReader();
Document doc = reader.read(document);
org.dom4j.Element root = doc.getRootElement();
org.dom4j.Element foo;
for (Iterator i = root.elementIterator("VALUE");i.hasNext();){
foo = (org.dom4j.Element)i.next();
// System.out.print("车牌号码:" + foo.elementText("NO"));
// System.out.println("车主地址:" + foo.elementText("ADDR"));
}
System.out.println("DOM4J DOM TYPE PARSE COST "+(System.currentTimeMillis() - lasting)+"毫秒");
}
}
结论:
当测试xml文件大小为50kb左右时:
DOM TYPE PARSE COST 144毫秒
SAX TYPE PARSE COST 43毫秒
JDOM SAX TYPE PARSE COST 179毫秒
JDOM DOM TYPE PARSE COST 145毫秒
DOM4J SAX TYPE PARSE COST 128毫秒
DOM4J DOM TYPE PARSE COST 106毫秒
我们今天的关于react-router-dom 的 HashRouter 也就这么回事儿和react-router hashhistory的分享已经告一段落,感谢您的关注,如果您想了解更多关于''react-router-redux''到''connected-react-router''、./node_modules/react-router-dom/react-router-dom.js尝试导入错误:未从“ react-router”导出“ Navigate”、connected-react-router: react-router-dom 必须总是最后安装、DOM、SAX、JDOM DOM、JDOM SAX、DOM4J DOM、DOM4J SAX 性能比对的相关信息,请在本站查询。
本文标签: