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 可能会有报错的情况,如果需要还是提早执行。

为了方便管理复用,我一般都会拆分组件,src 里面 创建 文件page route component ,如下图:

1.jpeg

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>

从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 依赖的页面。”

Vue-Router HTML5 History 模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容