Skip to content

React 全家桶入门教程 01

统计信息:字数 31197 阅读63分钟

前面是基础课程(难度小),后面是案例,质量还可以

目的

  1. 巩固react基础知识,查漏补缺(熟悉的部分快进)

  2. 学习相关的库的使用

https://study.163.com/course/courseMain.htm?courseId=1210995818

共计126课时,2021年1月出品,全部课程约38小时

第一章 React 基础

01 react简介

React 是构建用户界面的框架。

前端工作包括:发送请求,处理数据,展示数据(使用DOM展现)。

  • 发出请求(ajax)

  • 处理数据(算法优化,过滤排序算法)

  • 渲染界面(React 将 JS 数据渲染成 HTML 视图的库)

React 只处理展示数据(把 state 转换成 DOM),不管前两部分。

原生 JS 缺点:操作 DOM繁琐,性能较差(JS 直接操作 DOM,浏览器进行大量的重排重绘,reflow and repaint);jQuery 比较重,代码量很大;原生JS没有组件化方案,代码复用很低。

React 框架优点:组件化(提高组件复用率)、声明式开发;便于 ReactNative 进行移动端开发;虚拟 DOM 和 Diff 算法高性能。

02 hello-react案例

  • react.js React 核心库

  • React-dom.js React 操作 DOM 的库

  • babel.js 把ES6转换成ES5,把 JSX 转换成 JS 等

注意

  1. HTML 引入顺序不能错

  2. Script 这个 type (实际上不会这样写)

  3. 内部是 JSX,不需要加引号(不是字符串 dom = '';)

<script type="text/babel">
  let dom = <div></div>;
</script>

03 虚拟DOM的两种创建方式

创建虚拟 DOM,可以使用 JSX 字面量(优先),或者使用 React API 创建。

const VDOM = React.createElement('h1', {id: 'test'}, 'Hi');
ReactDOM.render(VDOM, document.getElementById('root'));

const vDOM2 = <div></div>;

04 虚拟DOM与真实DOM

虚拟DOM就是 jsx 语法中创建的虚拟 DOM,真实 DOM 就是原生 JS 创建的 DOM。

let virtualDOM = <div></div>;
let trueDOM = document.getElementById('test');

虚拟 DOM 特点:

  1. 本质是一个对象(Object)

  2. 虚拟DOM属性较少,真实DOM属性较多(例如尺寸和样式相关的API)因为虚拟DOM是 React 内部使用,所以不需要真实DOM上那么多的属性。操作虚拟DOM也更轻量化。

  3. 最终 React 会把虚拟DOM,转换成真实DOM,渲染到浏览器中。

05 jsx语法规则

jsx:javascript+XML 比传统的 HTML 严格,例如标签必须闭合;不能随便写自定义的标签。XML早期用于前后端传递数据的格式,可以使用XML或者JSON(JSON 可以 parse stringify 和字符串进行转换,主要使用后一种进行传输)。

JSX 语法规则

  1. 虚拟DOM直接写标签DIV,不需要加引号(不是字符串)

  2. 标签DIV内的 JS 表达式使用 大括号 {}

  3. 使用 className 代替 class 类名

  4. 样式使用 style = {{padding: 100}}

  5. 只能有一个根标签

  6. 标签必须闭合(自闭合或者成对)

  7. 标签:小写字母表示HTML标签,大写字母开头表示组件(需要引入)如果没有HTML标签或者组件,那么react会报错。

06 jsx小练习

JS 表达式是什么?和 JS 语句(代码)不一样

  • 表达式:等号右侧的是表达式,也就是说这一段代码会输出一个结果(a, a + b, foo(a, b), arr.map(), function test() {} )都是表达式。let a = 表达式;

  • 语句:if for while switch 等语句结构,不会返回结果,这部分不能在 JSX 中使用

所以,三目运算法可以使用,if 判断无法使用

<div>
  {
    arr.map((item) => {
      return (
        <span key={item.id}>{item.value}</span>
      );
    })
  }
</div>

第二章 React 面向组件编程

07 组件与模块

组件:界面中某个功能对应的代码

模块化;组件化;工程化

模块化:JS 封装成不同的部分;大型项目拆分成不同的模块,提高复用性

组件化:React组件拆分成不同的部分(根据功能拆分),拆分 JSX css JS 等

08 开发者工具的安装

react 开发调试工具(Component 可以查看组件层级和 state props 等属性,Profier 查看页面性能)

profiler 记录网站的性能(加载时间)

09 函数式组件

function Demo() {
  // console.log(this);
  // 在函数组件中,经过babel编译后,是严格模式,所以 this 是 undefined
  return <span></span>;
}

ReactDOM.render(<Demo/>, document.getElementById('root'));

// ReactDOM.render 执行过程
// 1. 解析第一个参数,找到组件定义 Demo(函数组件)
// 2. 调用 Demo 函数,将返回值 虚拟 DOM 转换成真实 DOM
// 3. 解析第二个参数,将真实DOM渲染到页面上

10 ES6 类和继承

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  speak = () => {
    console.log(this.name + String(this.age));
  }
}

class Student extends Person {
  constructor(name, age, grade) {
    super(name, age);
    this.grade = grade;
  }
  speak = () => {
    console.log(this.name + this.age + this.grade);
  }
}

const p1 = new Person('Bing', 10);
p1.speak();

const s1 = new Strudent("Andy", 20, 3);
s2.speak();

11 类组件的继承

class MyComponent extends React.Component {
  render() {
    return (
      <span>test</span>
    );
  }
}

// <MyComponent />

遇到标签后,React 解析到是一个类,执行 new MyComponent 创建类的实例对象,调用 render 方法返回虚拟 DOM,然后渲染到页面上作为真实DOM节点。

2.2 state

12-14 state 入门

state 是状态机,通过更新状态驱动页面更新

class Weather extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isHot: false,
    };
  }
  render() {
    return (<span></span>);
  }
}

原生事件绑定

let dom = document.getElementById('btn1');
dom.addEventListener('click', () => {
  console.log('click btn1');
});

let dom2 = document.getElementById('btn2');
dom2.onClick = () => {
  console.log('click btn2');
}

React 中通常不直接获取DOM

let dom = (<div onClick={this.onClick}></div>);

onClick = (e) => {
  console.log(1);
}

21 使用展开运算符批量传递 props

展开运算符使用

  • 可以直接展开数组(复制数组)

  • 不能直接展开对象;如果是 React 组件中传 props 可以使用展开运算符

  • 在函数中可以表示不确定的参数,实现循环或者递归运算

function sum(...numbers) {
  return numbers.reduce((pre, curr) => {
    return pre + curr;
  }, 0);
}

sum(1,2,3,4);
let obj = { name: 'a', age: 19 };

// props 较多可以这样写,大括号并不是对象,是 jsx 的语法糖
<MyComponent {...obj}/>

// console.log(...obj); 不能直接展开对象打印到原生JS中

24 构造器

constructor 不是必须的,主要用于声明状态和绑定函数(bind)

constructor (props) {
  super(props); // 实现继承
  this.state = {
    isFinished: false,
  };
  this.isChecked = React.createRef();
  this.fn = this.fn.bind(this); // 通常使用箭头函数
}

25 函数式组件使用 props

函数式组件可以使用 props,hook 中可以使用 setEffect 设置状态,也可以限制数据类型。

function Persion(props) {
  const { name, age } = props;
  return <span>{name}</span>;
}

Person.defaultProps = {
  age: 20,
};

<Person name={'Tom'} />

27字符串形式的ref

ref 在 react 中,就类似原生 JS 中的 ID,通过 ref 可以直接获取到对应的 DOMS。组件内的元素可以通过 ref 来标识自己。ref 创建有三种形式:字符串形式创建,回调函数创建,createRef 创建三种。最新 react 推荐使用第三种创建 refs。

使用字符串形式创建:创建使用 ref="test",获取属性使用 this.refs 复数形式(一个组件内部有很多 refs)

this.refs.input1
this.refs.input2
this.refs.input3

28 回调形式的ref

使用字符串形式创建 ref,react 官方不推荐这种写法(后期会废弃)性能比较差

推荐使用 createRefs 或者 回调函数创建 refs

<input ref={(node) => this.input1 = node} />
<input ref={c => this.input2 = c}/>

29 回调ref中调用次数的问题

如果回调函数直接写在 JSX 中,每次 render 都会调用两次。所以,回调函数可以放在类的方法中。React 官方说这两种方法的性能差异不大。

<input ref={this.setRef}/>

setRef = (node) => {
  this.input2 = node;
}

30 createRef的使用

this.inputRef = React.createRef();

let a = this.inputRef.current.value;

<input ref={this.inputRef} />

31 总结ref

三种创建 ref 的方法对比,官方推荐第三种,工作中推荐后两种,实际哪种方便用哪种。

32 react中的事件处理

react 中通过 onClick 绑定事件(事件处理函数):使用合成事件,不是原生事件,为了更好的兼容性。事件通过时间委托方式处理(委托给组件最外层的元素)——为了更高效(如果再底层监听事件,那么需要很多事件监听函数,在上层只需要一个事件监听函数)。可以通过 event.target 获取到触发事件的对象

33 非受控组件

表单通过 ref 获取输入内容,就是非受控组件(用户输入过程中,react不会获取到输入的内容)切勿过度使用refs

使用状态提升可以处理 ref 较多的情况

下面是传统表单,点击按钮后,会自动获取 username 和 password,然后提交到 action 网址中。

<form action="www.baidu.com">
  username: <input type="text" name="username"/>
  password: <input type="password" name="password"/>
  <button>login</button>
</form>

34 受控组件

<input onChange={this.save("username")}/>
<input onChange={this.save("password")}/>

save = (type) => {
  // 这里返回一个函数,就是高阶函数
  // 首次加载时,返回这个函数。当 onChange 触发时,执行内部回调函数
  return (event) => {
    // console.log(event.target.value, type);
    this.setState({
      [type]: event.target.value
    });
  }
}

35 高阶函数-函数柯里化

高阶函数:参数是一个函数,或者返回值是一个函数的函数,就是高阶函数(Promsise, setTimeout, reduce)

函数柯里化 carry-function:函数返回值是函数,可以继续链式调用。多次接收参数,最后统一调用。

function sum(a) {
  return (b) => {
    return (c) => {
      return a + b + c;
    }
  }
}

const result = sum(1)(2)(3);

36 不用柯里化的写法

<input onChange={e => this.save('username', e.target.value)}/>

save = (type, value) => {
  this.setState({
    [type]: value,
  });
}

37 生命周期

从节点卸载组件

ReactDOM.unmountComponentAtNode(document.getElementById('test'));

43 对比新旧生命周期

新版本不推荐使用的三个生命周期函数(ComponentWillMount, ComponentWillReceiveProps, ComponentWillUpdate);以及新加入的两个生命周期函数(getDerivedStateFromProps, getSnapshotBeforeUpdate)

为什么不推荐使用?过时的三个生命周期函数,经常造成误解滥用,可能造成bug。在 17版本中,可以使用,界面上提示警告。在 18 版本中异步渲染,不能直接使用这三个生命周期函数,必须加上 UNSAVE 前缀(React.lazy, React.Suspend)。

44 getDerivedStateFromProps

这个生命周期函数是 componentWillReceiveProps 的替代,用于 props 变化造成组件 state 更新的情况。

这是一个静态方法,在组件render前,或者组件更新后都调用。

static getDerivedStateFromProps(nextProps, prevState) {
  // 对比 nextProps 和 prevState 等,返回新的 state 或者 null
  // 这是静态方法,无法直接访问到实例对象的属性和状态(this)
  if (nextProps.name !== prevState.name) {
    prevState.name = nextProps.name;
  }
  return prevState;
}

派生状态会造成代码冗余,尽量避免使用。ComponentWillReceiveProps 只在父组件重新渲染时触发,getDerivedStateFromProps 在每次渲染前调用(包括内部setState)。

45 getSnapshotBeforeUpdate

在渲染之前调用,可以获得 DOM 信息(例如元素滚动位置),返回值传给 componentDidUpdate 函数,用于处理滚动位置和动画。通常不使用。

getSnapshotBeforeUpdate(prevProps, prevState) {
  if (prevProps.list.length < this.props.list.length) {
    const list = this.listRef.current;
    return list.scrollHeight - list.scrollTop;
  }
  return null;
}

// 当有新消息加入时,界面滚动位置不变
componentDidUpdate(prevProps, prevState, snapshot) {
  if (snapshot) {
    const list = this.listRef.current;
    list.scrollTop = list.scrollHeight - snapshot;
  }
}

46 getSnapshotBeforeUpdate 举例

简化的案例

getSnapshotBeforeUpdate() {
  return this.refs.list.scrollHeight;
}

componentDidUpdate(preProps, preState, height) {
  this.refs.list.scrollTop += this.refs.list.scrollHeight - height;
}

47 总结生命周期(新)

初始化阶段:getDerivedStateFromProps render componentDidMount

更新阶段:getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate

卸载阶段:componentWillUnmount

注:getDerivedStateFromProps 在首次加载和再次更新时,都会触发。

48 DOM的diffing算法

对应面试题:React 中 key 的作用是什么?用来作为唯一标识,在 渲染过程中 diff 算法中,判断前后节点是否发生变化。如果Key变化了,那么直接渲染这个节点。如果 key 不变,那么递归比较这个节点内部的子节点。

使用 index 作为 key 可能存在问题,所以最好使用 id 作为 Key。如果在大型列表中,列表项越多,这个性能问题就越大(重新加载一个节点,还是重新加载全部的节点)

如果某些列表是静态的,为了方便,也可以使用 index 作为 key。

第三章 react-cli

React-create-app

50 脚手架文件介绍-public

favicon

index.html

logo.png

Manifest.json 应用加壳配置文件

Robots.txt 爬虫协议文件

这个表示 public 文件夹的路径
<link ref="icon" href="%PUBLIC_URL%/favicon.ico"/>

// 理想视口,用于移动端网页适配
<meta name="viewport" content="width=device-width, initial-scale=1">

// 安卓移动端 TAB 背景色(兼容性不好)
<meta name="theme-color" content="#000">

<meta name="description" content="this is test" />

// 苹果手机桌面快捷应用图标
<link rel="apple-touch-icon" href="%PUBLIC%/logo192.png" />

// 应用加壳规则
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">

51 脚手架文件介绍-src

reportWebVitals.js 页面性能分析工具

React.StrictMode 这个标签并不代表严格模式,而是处理 React 语法中的提示(例如使用了过时的 ref="test" 等)这个包裹在顶层标签下面即可。

52 组件化编码流程

组件化拆分流程

1、拆分组件:根据设计稿,思考拆分成多少部分

2、静态组件拆分:设置不同静态组件,同时 mock 数据

3、动态数据更新(状态属性,界面交互等)

从一个旧项目重构成 raect 项目,或者新项目,最好先从细节组件,公共组件做起。

拆分组件的原则:组件在界面上有明确的分界,组件有明确的名称(对应组件的功能或者位置)如果某个组件名称不合适,那么这个组件的功能和位置不合适,需要进一步拆分处理重构。

在 VScode 中加入插件,可以用快捷键搭建基本的架构

ID 的库

uuid 库比较大,可以使用 nanoid 产生随机的 ID,在性能要求不高的情况下可以用

defaultChecked

defaultChecked 可以初始化勾选状态,但是不支持后续 state 更改,实际工程中避免使用这个属性。实际中使用 checked = 处理选择或者不选择,还有一个半选择的图标(回去查一下)

checkbox 如果获取 event.target.value 可以获取到 on 始终是这个值,无意义

window.confirm 原生的弹出框,函数的返回值是用户的选择,用于简单的交互。这个会阻挡JS的执行,在不同浏览器中可能不美观,实际项目中没有使用。

第四章 react ajax

65 脚手架配置代理-方法1

react 主要处理状态管理和界面渲染,没有包括网络请求功能。通常使用 axios 做网络请求(promise return )。

axios.get(url).then(res => {
  console.log(res.data);
}, err => {
  console.log(err);
});

如果本地服务端在 5000 端口,浏览器界面是 3000 端口,那么根据浏览器同源策略,会出现跨域的问题。

需要一个代理服务器,把5000返回的响应,转发到3000端口上面。

通常在 package.json 中设置代理,这样就能处理5000端口返回的数据了。这里表示先请求3000端口,如果没有的话找5000端口

"proxy": "http://localhost:5000"

66 脚手架配置代理-方法2

实际开发中,可能有不同的服务器提供多种服务,但是前端只有一个端口,所以也需要在服务端处理跨域的问题。

这个在 react 脚手架下可以这样配置,一个项目配置一次即可,不需要多次配置

// setProxy.js 需要脚手架环境,不能改名字

// 这个库已经在 cra 中包含,不需要单独安装
const proxy = require('http-proxy-middleware'); 

module.exports = function(app) {
  app.use(
    // 这是中间件,表示判断api1的请求,发送到 5000 端口,然后改变源,并且替换到 api1
    proxy('/api1', {
      target: 'http://localhost:5000',
      changeOrigin: true, // 服务器收到请求头中 Host 字段的值(可选)
      pathRewrite: {'^api1': ''},
    }),
    // 另一个中间件,处理另一个跨域的情况
    proxy('/api2', {
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {'^api2': ''},
    })
  )
}
// 跨域也可以在服务端使用 cors 技术实现

67 github搜索案例-静态组件

常用的固定的css最好放在 public 中,然后在 HTML 模板中插入

组件单独的 css 可以放在 src/css 路径下,在不同组件中引入

68 github搜索案例-axios发送请求

// 这是原来的写法,代码可读性较差
axios.get(url).then(res => {
  console.log(res.data);
  this.setState({ data: res.data });
}, err => {
  console.log(err);
});

// 这样回调函数更清晰,推荐这种写法
axios.get(url).then(
  res => {
    console.log(res.data);
    this.setState({
      data: res.data
    });
  },
  err => {
    console.log(err);
  },
);

69 github 搜索案例-展示数据

70 github 搜索案例

三目运算法可以嵌套使用——但是这样可读性不高

<div>
  {
    isFirst ? <div>Welcome</div> :
    isLoading ? <Loading/> :
    isError ? <Error> :
    <List/>
  }
</div>

71 消息订阅与发布-pubsub

Pubsub-js 是一个第三方库,publish-subscribe

npm install pubsub-js --save
import PubSub from 'pubsub-js';

componentDidMount() {
  this.token = PubSub.subscribe('delete', function(message, data){
    console.log(message, data); 
  });
}

componentWillUnmount() {
  PubSub.unscribe(this.token);
}

// another component
PubSub.publish('delete', data);

72 fetch发送请求

JS 底层通过 xhr 或者 fetch 发送请求。jQuery 和 axios 都是基于 xhr 发送请求。

问题:老版本浏览器不支持;实际上使用不多(原生XHR书写麻烦,axios 已经封装)。

fetch 关注分离:第一步先判断服务器是否连接,第二步再获取返回的数据

fetch('/api1/search/users?q=test').then(
  response => {
    console.log('连接服务器成功');
    // 这里的返回值是 Promise 所以可以使用 then 处理
    return response.join();
  },
  error => {
    console.log('连接服务器失败');
    return new Promise(() => {});
  },
).then(
  response => {
    console.log('获取数据成功')
  },
  error => {
    console.log('获取数据失败')
  },
);

上面这样写,需要使用两个 error 处理函数,实际可以使用一个替代

fetch(api).then(
  response => {
    console.log('连接服务器成功');
    return response.join();
  },
).then(
  response => {
    console.log('获取数据成功')
  }
).catch(
  err => {
    console.log(err);
  }
);

或者使用 await 优化

try {
  const res = await fetch(api);
  const data = await res.json();
  console.log(data);
} catch (err) {
  console.log(err);
}

技术层面,XHR(axios, ajax) 和 fetch 是独立的技术

73 总结github搜索案例

相关知识点:

1、设计状态要全面:例如一个列表,需要考虑初始的状况(欢迎词),网络请求中的状态(loading)数据加载后的状态(列表)数据为空的状态(空列表,还是渲染没有数据)。如果涉及网络请求,要考虑成功后的回调函数,失败后的回调函数,界面的提示等等。

2、ES6 解构赋值的重命名

let obj = {a: {b : 1}};
const { a } = obj;
const { a: { b } } = obj; // 多重对象解构赋值
const { a: time } = obj; // 解构赋值变量重命名

3、消息订阅和发布

先订阅后发布;适合任何组件传参通信;需要在卸载时取消订阅

这里介绍的是第三方的订阅,实际项目中自定义的消息订阅,需要逐层传参,使用不变

4、fetch 发送请求(关注分离思想)

fetch 实际使用不多,了解即可,大部分情况使用 axios

axios 设计基于 XHR 原理,直接返回请求的数据。fetch 是浏览器原生的,不基于 XHR,先判断是否连通,然后获取数据。

try {
  const res = await fetch(url); // 先判断服务器是否连通
  const data = await response.json(); // 然后从返回的 Promise 中拿到数据
  console.log(data);
} catch (e) {
  console.log(e);
}

第五章 react-router

74 对SPA应用的理解

单页面应用;整个应用只有一个完整的页面;点击页面内部的连接,页面不会刷新,内部内容更新(使用 axios 请求获取数据,前端异步展示)

75 对路由的理解

路由:映射关系(key-value)满足某个路由,就显示某个页面

后端路由:根据 URL 中的参数,查找到匹配的路由,处理请求,返回响应数据

前端路由:根据 url 中的参数,界面展示不同的组件;当 URL 变化后,前端界面更新

<Route path="/test" component={Test}>

76 前端路由原理

前端路由(react-router, history),是基于 History 对象,进行 pop push replace 栈操作

// 引入第三方的库
let history = History.createBrowserHistory(); // 基于 History 对象
// let history = History.createHashHistory(); // 基于 Hash 

function push(path) {
  history.push(path);
  return false;
}

function replace(path) {
  history.replace(path);
}

function back() {
  history.goBack();
}

function forward() {
  history.goForward();
}

history.listen((location) => {
  console.log('request route path is changed', location);
})

77 路由的基本使用

react-router 分成 web native 版本,我们使用 web 前端版本(前端路由)

path 对应的 value 是组件 component,对应界面展示的内容

npm install react-router-dom
import { Link, BrowserRouter } from 'react-router-dom';
import { About, Home } from './component/';

render () {
  return (
    // 路由链接实现切换组件(这里最好放在顶层组件)
    <BrowserRouter>
      <Link to="/about">关于我们</Link>
      <Link to="/home">主页</Link>
    </BrowserRouter>

    // 注册路由
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
  );
}

改进后的顶层组件

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } form 'react-router-dom';
import App from './App';

ReactDOM.render(
  <BrowserRouter>
    <App/>
  </BrowserRouter>,
  document.getElementById('root');
)

总结:

  • APP 外部统一包裹一个 BrowserRouter 和 HashRouter

  • 界面导航区,使用 Link 标签

  • 界面内容区,使用 Route 进行路径匹配

78 路由组件与一般组件

路由组件内部,可以收到特殊的 props

historyhistory 的一个属性就是 location
  go
  goback
  goForward
  push
  replace
location
  pathname
  search
  state
match
  params
  path
  url
<NavLink activeClassName to="/about">About Us</NavLink>

navlink 可以实现路由链接高亮,通过 activeClassName 指定样式名称

标签体内容是特殊的标签属性(通过 this.props.children 获取标签体内容)

import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';

export default class MyLink extends Component {
  render() {
    const { to } = this.props;
    return (
      <NavLink
        activeClassName="test"
        className="list-group-item"
        to={to}
        {...this.props}
      >
        {this.props.children}
      </NavLink>
    );
  }
}

81 Switch的使用

通常情况下,path 和 component 是一一对应的,如果有相同的 path 后面会覆盖前面

如果是 switch 嵌套的路由,那么优先匹配,找到满足的就不继续匹配了,提高效率

<Switch>
  <Route path="/to" component={About}/>
  <Route path="/to" component={My}/>
</Switch>

实际项目中,通常不会写两个相同的 path

82 解决样式丢失问题

开发中,可能公共样式丢失(bootstrap 样式引入无效)或者其他静态资源引入不正常

检查引入的路径

<link rel='icon' href="%PUBLIC_URL%/favicon.ico" />
<link rel="stylesheet" href="./css/bootstrap.css" />

// 使用项目路径代替相对路径
<link rel="stylesheet" href="%PUBLIC_URL%/bootstrap.css" />

不要写 ./ 相对路径,可以写 / 从根目录中引入,或者 %PUBLIC_URL% 表示公共路径

83 路由的模糊匹配与严格匹配

默认路由是模糊匹配(例如组件 /about 可以匹配路由链接 /about/us/)顺序需要一致

严格匹配: exact={true} 必须严格匹配,通常不需要(可能造成二级路由失效)

<Route exact={true} path="/about" component={About} />

84 Redirect的使用

redirect 表示重定向,通常用于 Switch 的无效匹配时,默认跳转的页面

写在路由注册最下方,当所有路由都无法匹配是,跳转到 redirect 指定的路由

<Switch>
  <Route path="/about" componnet={About} />
  <Route path="/home" component={Home} />
  <Redirect to="/about" />
</Switch>

85 嵌套路由

多级导航-多级路由嵌套

<div>
  <ul className="nav nav-tabs">
    <li>
      <MyNavLink to="/home/news">新闻</MyNavLink>
    </li>
    <li>
      <MyNavLink to="/home/message">消息</MyNavLink>
    </li>
  </ul>
  <Switch>
    <Route path="/home/news" component={News} />
    <Route path="/home/message" component={Message} />
    <Redirect to="/home/news" />
  </Switch>
</div>

注册子路由,要协商父路由的 path

路由的匹配是按照注册路由的顺序进行的

86 向路由组件传递params参数

<Link to={`home/message/datail/${id}/${name}`} >{title}</Link>

// 声明接收 params 参数
<Route path="/home/message/detail/:id/:name" component={Detail} />

在子路由中接收参数(这种可读性强,使用最多)

const { id, name } = this.props.match.params;
<Link to={`/home/message/detail/?id=${id}&name=${name}`}>{title}</Link>

子组件中接收到字符串,需要解析成对象

import qs from 'querystring';

qs.stringify({name: 'Amy', age: 20});
qs.parse('name=amy&age=20');

const { search } = this.props.location;
const { id, name } = qs.parse(search.slice(1));

88 向路由组件传递state参数

这样界面的路由中,用户看不到传递的参数。

<Link to={{pathname: '/home/message/detail', state: {id: id, name: name}}}>{title}</Link>

子组件获取参数

const { id, name } = this.props.location.state;

界面刷新后,可以保留参数(界面强制清空缓存,这个连接就无效了,实际技术还是 history 对象)

89 总结路由参数

  • Params:在 URL 中携带参数,注册路由使用 :name 接收。参数 this.props.match.params 使用最多

  • Search: 在 ?key=value 中传参。参数:this.props.location.search(string)

  • state 参数 刷新也可以保留参数,this.props.location.state

<Link to={`/home/${id}/${title}`}></Link>

<Link to={`/home/?id=${id}&title=${title}`}></Link>

<Link replace={true} to={{pathname: '/home/message/detail', state; {id: id, title: title}}}></Link>

90 push与repalce

通过 JS 更改 history 即可

91 编程式路由导航

之前的案例都是用户点击按钮,触发路由,改变页面内容。如果需要使用 JS 触发路由,就需要使用相关的 API 进行操作(例如,点击按钮后,界面停留3s,再跳转)。

原理就是操作 BOM 的 history 对象

history.go()
history.goBack()
history.goForward()

history.push()
history.replace()

具体使用

// replace 没有历史记录
this.props.history.repalce('/home/message');

// push 有历史记录
this.props.history.push('/home/message');

// 如果需要传参,对应三种参数形式(接收也需要三种形式)
this.props.history.push(`/home/message/${id}`);
this.props.history.push(`/home/message?id=${id}`);
this.props.history.push('/home/message', {id});

this.props.history.goBack();
this.props.history.goForward();
this.props.history.go(-2);
this.props.history.go(2);

92 withRouter 使用

如果想在普通组件中使用 history 的 API,那么需要使用 withRouter 包裹一层,这样就可以使用路由组件的 API 了。

import { withRouter } from 'raect-router-dom';

class Demo extends React.Component {
}

export default withRouter(Demo);

93 BrowserRouter 和 HashRouter 比较

  • 技术不一样:Browser 使用 H5 的 history API,不兼容老版本;Hash 使用 URL 哈希值,兼容 IE 6

  • Path 显示:Browser 没有#,Hash 必须加 #

  • 刷新浏览器?Browser 不会丢失 state 参数,Hash 会丢失 state 参数

  • Hash 可能解决一些路径错误的问题(例如样式引入)

  • 实际使用:BrowserRouter 使用较多


Last update: November 9, 2024