react 创建新的项目以及react-router4.xx 配
背景日常絮叨
看到这个文章 大部分也是react 有基础或者刚入门的小伙伴,react 目前比较流行 欢欣的一个库,下面让我们一步一步run 起来
路由跳转是指在同步保持浏览器URL的过程中渲染页面中的视图。React Router 让你声明式的操作路由跳转。声明式路由方法允许你控制应用中的数据流.
创建新的react 项目
- npm install -g create-react-app
- create-react-app myapp //myapp 是你的项目名称
- cd myapp
但是打开项目会发现,一些与webpack相关的东西被隐藏掉了,只需要做单独的配置键入下面的命令
- npm run eject
在运行之前 可以安装一个react-router
- npm install --save react-router-dom
- npm install 安装依赖
- npm run start
没有意外的情况下,都可以运行起来,提醒下 如果 你修改 项目之后 在执行npm run eject 可能会有报错的情况,如果需要还是提早执行。
1.jpeg为了方便管理复用,我一般都会拆分组件,src 里面 创建 文件page route component ,如下图:
react-router
- 上面的都准备好之后,我们就开始路由的配置,react-router,一般我们用到路由分为:
- 路由的基本的跳转
- 嵌套路由
- 带路径参数的嵌套路由
路由组件:BrowserRouter 和HashRouter
路径匹配的组件: Route 和 Switch
导航组件: Link
- 一般基础的路由
export default (
<Router history={historyConfig}>
<div>
<ul className="nav">
<li><Link to="/">App</Link></li>
<li><Link to="/About">About</Link></li>
<li><Link to="/User">User</Link></li>
<li><Link to="/Detail">Detail</Link></li>
</ul>
<hr />
<Route exact path="/" component={Home} />
<Route path="/About" component={About} />
<Route path="/User" component={User} />
<Route path="/Detail" component={DeatilComponent} />
</div>
</Router>
);
Router组件决定了我们使用html5 history api,
Route组件定义了路由的匹配规则和渲染内容,
使用 Link 组件进行路由之间的导航。
使用 exact 属性来定义路径是不是精确匹配。
Router
我们都知道 React Router API 中有两种类型的路由
<BrowserRouter> http://localhost:300/home 比较常用
<HashRouter> 哈希路由 http://localhost:300/#/home
- 如果有服务器端的动态支持,建议使用 BrowserRouter,否则建议使用 HashRouter。
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
将 BrowserRouter 修改为 HashRouter 就可以了,基本不需要修改其他东西。
Route常用属性
主要职责是当Route的位置和路径匹配的时候渲染对应的ui
exact、path以及component属性
Route会向component组件传一个参数,包含属性match,location,history。match属性对象又包含url,path,params等属性。比较常用的就是match的url属性,可以继续基于url指定组件里面的Link标签要链接到的url,从而显示对应的组件。
Route写在哪里,当Route匹配到URL的时候,相应的组件就会在那里进行渲染。component,render,children,Route的这三个属性写一个就行,不能同时都写。precendence order: component > render > children.
注意:children中的元素不管是否匹配到URL都会渲染,不过没有匹配到的Route向children的函数中传的值是null,只有匹配到的时候才会有值。
- 横向导航栏:使用Routed 的 children属性。
侧边菜单栏:左边是Link,右边把Route写在右边展示区域的div里面,匹配到的时候进行渲染。
<Route>是如何渲染的?
当路由 match 成功之后,route 根据
-
1、component : 一个React组件。当带有component参数的route匹配成功后,route会返回一个新的元素,其为component参数所对应的React组件(使用React.createElement创建)。
-
2、render : 一个返回React element的函数[注5]。当匹配成功后调用该函数。该过程与传入component参数类似,并且对于行级渲染与需要向元素传入额外参数的操作会更有用。
-
3、children : 一个返回React element的函数。与上述两个参数不同,无论route是否匹配当前location,其都会被渲染。
- Route向component组件进行参数传递
组件:
const Topics = ( {match} ) => ()
路由:
<Route path="/topics" component={Topics}/>
路由Route向组件传的参数:
{history:{},
location:{},
match:{}}.
执行到 const Topics = ( {match} ) => ()的时候会将参数对象
赋值给对象{match},所以此时组件实际接受的参数只有match对象。
- history 插件
$ npm install --save history
createBrowserHistory
import createBrowserHistory from 'history/createBrowserHistory';
const historyConfig = createBrowserHistory({
basename: '/' + AREA_ENV
});
history有三种使用方式:
- createBrowserHistory 现代浏览器使用 目前用的最多的
createBrowserHistory({
basename: '', // 基链接
forceRefresh: false, // 是否强制刷新整个页面
keyLength: 6, // location.key的长度
getUserConfirmation: (message,callback) => callback(window.confirm(message)) // 跳转拦截函数 })
- createMemoryHistory 手机端使用
createMemoryHistory({
initialEntries: ['/'], // 初始载入路径,和MemoryRouter中的initialEntries是一样的 initialIndex: 0, // initialEntries初始载入索引
keyLength: 6, // location.key的长度
getUserConfirmation: null // 路由跳转拦截函数 })
- createHashHistory 老版本浏览器使用,暂不介绍
Switch :
渲染与该地址匹配的第一个子节点 <Route> 或者 <Redirect>。外面包一层Switch 和不用Switch 有什么不同呢?
- 用Switch 包含 只渲染一个路由,如果不用Switch则全部的路由都要渲染出来
下面的代码 所有的路由 都会渲染出来,那么如果有些需求比如 侧栏,面包屑那么我们只能选择一个 路由渲染出来
<Route exact path="/" component={Home} />
<Route path="/Detail" component={PrivateRoute} />
<Route path="/About" component={About} />
<Route path="/User" component={User} />
所以使用 Switch 实现
<Switch>
<Route exact path="/" component={Home} />
<Route path="/Detail" component={PrivateRoute} />
<Route path="/About" component={About} />
<Route path="/User" component={User} />
</Switch>
Link:
导航到指定的路由
react-router 的基本原理:
实现URl 和UI界面的同步,其中在react-router中,URL对应location 对象,而UI对应的是react components 来决定,这样就是 location 和 components 的同步的问题。
- react-router 主要依赖于history
// 内部的抽象实现
function createHistory(options={}) {
...
return {
listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现
listen, // location发生改变时触发回调
transitionTo, // 执行location的改变
push, // 改变location
replace,
go,
goBack,
goForward,
createKey, // 创建location的key,用于唯一标示该location,是随机生成的
createPath,
createHref,
createLocation, // 创建location
}
}
- 1、当页面路由发生变化的时候;history 底层支持pushState, 将参数传输到 createLocation 方法中,返回 location 的结构如下:
location = {
pathname, // 当前路径,即 Link 中的 to 属性
search, // search
hash, // hash
state, // state 对象
action, // location 类型,在点击 Link 时为 PUSH,浏览器前进后退时为 POP,调用 replaceState 方法时为 REPLACE
key, // 用于操作 sessionStorage 存取 state 对象
};
- 2、将location对象作为参数传入到 TransitionTo方法中,执行loaction 的变化,
然后调用 window.location.hash 或者window.history.pushState() 修改了应用的 URL
, 同时触发了 history.listen 的监听。 - 3、更新location之后,然后系统调用 matchRoutes 方法配置 出与当前location对象匹配的一个子集。
- 4、 匹配成功之后开始渲染 -> 渲染组件 更新UI(内部具体经过要后续研究)
检测url 前进:
- createBrowserHistory: pushState、replaceState
- createHashHistory: location.hash=*** location.replace()
- createMemoryHistory: 在内存中进行历史记录的存储
hashChange 监听window.location.hash 的变化,hash 发生变化,浏览器更新url,同时history 栈中会产生一条新的记录。
在 react-router 内部注册了 window.addEventListener('hashchange', listener, false) 事件监听器, listener 内部可以通过 hash fragment 获取到当前 URL 对应的 location 对象
pushState: window.history.pushState(state, title, path)方法 ,改变浏览器的url,通过location.state 来获取到 state,
在 react-router内部将该对象存储到了 sessionStorage 中
检测url 回退:
- createBrowserHistory: popstate
- createHashHistory: hashchange
- createMemoryHistory: 因为是在内存中操作,跟浏览器没有关系,不涉及UI层面的事情,所以可以直接进行历史信息的回退
路由匹配原理:
路由有三个属性决定是否匹配一个URL;
- 嵌套关系
- 路径语法
- 优先级
1、嵌套关系
当一个给定的 URL 被调用时,整个集合中(命中的部分)都会被渲染。嵌套路由被描述成一种树形结构。React Router 会深度优先遍历整个路由配置来寻找一个与给定的 URL 相匹配的路由。
2、路径语法
-
paramName
– 匹配一段位于/
、?
或#
之后的 URL。 命中的部分将被作为一个参数 - () – 在它内部的内容被认为是可选的
- ** – 匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个 splat 参数
<Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
3、优先级
路由是自顶向下匹配路由,确保前一个路由不会匹配后一个路由的路径
BrowserRouter 和HashRouter 区别
BrowserRouter:
vue:mode:history
react: <BrowserRouter>
pushState, replaceState会改变当前路径,但是他不会导致单页面的重新渲染,我们所使用时,页面的渲染是由react或vue中的Router中监听了路由的变化
// 监听路由变化
this.unlisten = props.history.listen(location => {
if (this._isMounted) {
this.setState({ location });
} else {
this._pendingLocation = location;
}
});
// 以下就是Route在当路由发生变化时做的渲染
{props.match
? children
? typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: null}
当刷新页面时,浏览器会向服务器请求example.com/list,服务器实际会去找根目录下list.html这个文件,发现找不到,因为实际上我们的服务器并没有这样的 物理路径/文件 或没有配置处理这个路由,所有内容都是通过React-Router去渲染React组件,自然会报404错误。这种情况我们可以通过配置Nginx或通过自建Node服务器来解决。
hashHistory:
vue:mode:hash
react: <HashRouter>
- hashHistory 使用 URL 中的 hash(#)部分去创建路由,举例来说,用户访问http://www.example.com/,实际会看到的是http://www.example.com/#/。
从BrowserRouter.js和HashRouter.js文件中可以看到,history对象是由history插件生成的
// BrowserRouter.js
import { createBrowserHistory as createHistory } from "history";
history = createHistory(this.props);
// 用于createHistory传入的配置对象参数,也说明了这个配置是有父级传递的,而不是BrowserRouter自身的
BrowserRouter.propTypes = {
basename: PropTypes.string,
children: PropTypes.node,
forceRefresh: PropTypes.bool,
getUserConfirmation: PropTypes.func,
keyLength: PropTypes.number
};
// HashRouter.js
import { createHashHistory as createHistory } from "history";
history = createHistory(this.props);
// 用于createHistory传入的配置对象参数
HashRouter.propTypes = {
basename: PropTypes.string,
children: PropTypes.node,
getUserConfirmation: PropTypes.func,
hashType: PropTypes.oneOf(["hashbang", "noslash", "slash"])
};
createMemoryHistory:
- Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他的渲染环境(像 React Native )。和另外两种history的一点不同是你必须创建它,这种方式便于测试。
React-router 按需加载方式:
一: create-react-app
- 创建一个异步组件 AsyncComponent
import React from 'react';
export default function (getComponent) {
return class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
if (!this.state.Component) {
getComponent().then(({default: Component}) => {
AsyncComponent.Component = Component
this.setState({ Component })
})
}
}
render() {
const { Component } = this.state
if (Component) {
return <Component {...this.props} />
}
return null
}
}
}
- 使用异步组件:我们将使用asyncComponent动态导入我们想要的组件。
import asyncComponent from './asyncComponent'
const Login = asyncComponent(() => load('login/login'))
const LayoutPage = asyncComponent(() => load('layout/layout'))
const NoticeDeatil = asyncComponent(() => load('noticeDetail/noticeDetail'))
export const appRouterMap = [
{path:"/login",name:"Login",component:Login,auth:false},
{path:"/web",name:"LayoutPage",component:LayoutPage,auth:false},
{path:"/notice/:id",name:"NoticeDeatil",component:NoticeDeatil,auth:false},
]
二、借助react-loadable来实现按需加载
1、利用react-loadable这个高级组件,要做到实现按需加载这一点
第三种 bundle-loader 按需加载方式
不管vue 还是react 都可以使用
hash路由:
hash原理是触发了onhashchange 事件,
hash —— 即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)。比如这个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。
它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
document.body.style.color = hash;
}
前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。
history路由:
history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。
只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
-
pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
-
pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
-
pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
history 模式下,
前端的 URL 必须和实际向后端发起请求的 URL 一致,
如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。
Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”