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 ,如下图:

react 创建新的项目以及react-router4.xx 配置_第1张图片
1.jpeg

react-router

  • 上面的都准备好之后,我们就开始路由的配置,react-router,一般我们用到路由分为:
    • 路由的基本的跳转
    • 嵌套路由
    • 带路径参数的嵌套路由

路由组件:BrowserRouter 和HashRouter
路径匹配的组件: Route 和 Switch
导航组件: Link

  • 一般基础的路由
export default (
  
   
          
  • App
  •       
  • About
  •       
  • User
  • Detail
  •   

);

Router组件决定了我们使用html5 history api,
Route组件定义了路由的匹配规则和渲染内容,
使用 Link 组件进行路由之间的导航。
使用 exact 属性来定义路径是不是精确匹配。

Router

我们都知道 React Router API 中有两种类型的路由

http://localhost:300/home 比较常用
哈希路由 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里面,匹配到的时候进行渲染。
是如何渲染的?

当路由 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向组件传的参数:
    {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 :

渲染与该地址匹配的第一个子节点 或者 。外面包一层Switch 和不用Switch 有什么不同呢?

  • 用Switch 包含 只渲染一个路由,如果不用Switch则全部的路由都要渲染出来

下面的代码 所有的路由 都会渲染出来,那么如果有些需求比如 侧栏,面包屑那么我们只能选择一个 路由渲染出来

    
    
    
    

所以使用 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 参数
         // 匹配 /hello/michael 和 /hello/ryan
       // 匹配 /hello, /hello/michael 和 /hello/ryan
           // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg

3、优先级

路由是自顶向下匹配路由,确保前一个路由不会匹配后一个路由的路径


BrowserRouter 和HashRouter 区别

BrowserRouter:

vue:mode:history
react:

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:

  • 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 
      }
      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 模式

你可能感兴趣的:(react 创建新的项目以及react-router4.xx 配置)