react知识点整理

272 阅读27分钟

一、React 基础知识

官网:https://reactjs.org  doc.react-china.org
Facebook开源,一个用来动态构建用户界面的js库

react特点:
  Declarative(声明式编码)
  Component-Based(组件化编码)
  Learn Once, Write Anywhere(支持客户端与服务器渲染)
  高效   单向数据流
  
React高效的原因:
  虚拟(virtual)DOM, 不总是直接操作DOM
  DOM Diff算法, 最小化页面重绘,相当于 ajax 的局部更新

学习React需要比较牢固的JS基础和熟悉ES6语法; React没有太多的api,可以说用react编程都是在写js。

1. react 搭建项目

1. 本地js库方式 搭建项目

// 1. 相关js库
/*
react.js  React的核心库
react-dom.js  操作DOM的扩展库
babel.min.js  
  解析JSX语法代码转为纯JS语法代码的库
  解决浏览器 不支持 es6 的问题 ,将es6 转化为 es5
  下载到 本地  babel 库的用法:
    <script src="../js/babel.min.js"></script>
    <script type="text/babel"></script>   */

// 2. 在页面中导入本地 相关 js 库
<script src="../js/react.js"></script>
<script src="../js/react-dom.js"></script>
<script src="../js/babel.min.js"></script>    
    
// 3. 使用本地库时,实现 渲染 dom 
<script type="text/babel">   // 否则babel 发挥不了作用
	...
</script>
/* 
1.创建虚拟 DOM 对象:虚拟DOM对象-可以直接在js里 写 HTML
  let user = <h2>Hello React!!!</h2>;  
2.渲染虚拟 DOM 对象:将虚拟对象转换为真的DOM对象,插入到页面的容器中
ReactDOM.render(虚拟DOM对象,页面中的容器 container)
console.log(ReactDOM); // Object
ReactDOM.render(user, document.getElementById("container")); 
*/

2. webpack构建方式搭建项目

npm init -y
// 1.  安装包 npm i react react-dom -S
// react 这个包,是专门用来创建React组件、组件生命周期等这些东西的;
// react-dom 里面主要封装了和 DOM 操作相关的包,比如,要把 组件渲染到页面上

// 2. 在项目中导入两个相关的包
import React from 'react' // 使用JSX来描述UI,所以解耦了和DOM的操作
import ReactDOM from 'react-dom' // react-dom 做渲染层,去渲染实际的DOM


// 3. 想要使用 JSX 语法 ,  .babelrc
// 需要  npm i babel-preset-react --dev  .babelrc 中添加 语法配置
// JSX语法的本质:还是以 React.createElement 的形式来实现的,并没有直接把 用户写的 HTML代码,渲染到页面上;
{
  "presets": ["env", "stage-0", "react"],
  "plugins": ["transform-runtime"]
}

3. 脚手架方式搭建项目

npm isntall -g create-react-app  或者  yarn add create-react-app -g

create-react-app xiaodi   // 开始创建项目  
npx create-react-app project01  // 创建项目 -可选方案

npm start 或 yarn start   // 运行项目

创建项目的同时会帮你自动下载依赖,所以不用自己 npm install安装依赖了,创建完成后直接cd xiaodi去到当前项目,执行 npm start 或 yarn start 运行项目

2. 分析项目目录架构

xiaodi
|—— README.md  项目说明文档
|—— node_modules       依赖文件夹
|—— package.json       npm依赖管理包
|—— .gitignore
|—— public             静态资源
|  |—— favicon.ico
|  |—— index.html
|  |—— manifest.json
|—— src                 源码
    |—— App.css
	|—— App.js
	|—— App.test.js     测试
	|—— index.css
	|—— index.js        入口js
	|—— logo.svg
	|—— serviceWorker.js   对PWA的⽀持

3. 虚拟dom

1.创建虚拟DOM的2种方式

// 1. 纯JS(一般不用):
let element1 = React.createElement('h2',{id: 'box1', className: 'box2'}, 'React.createElement创建的虚拟DOM对象')
ReactDOM.render(element1, documment.getElementById('example1'));

// 2. JSX
let element2 = <h3>JSX创建的虚拟DOM对象</h3>;
ReactDOM.render(element2, documment.getElementById('example2'));
/* 技术点:
   1). 使用JSX创建虚拟DOM
   2). React能自动遍历显示数组中所有的元素
   3). array.map()的使用 */

2.渲染虚拟DOM

虚拟DOM的本质:使用js对象 模拟dom树
虚拟dom的目的:为了实现 dom节点的高效更新
DOM和虚拟DOM的区别:
  DOM是由浏览器中的JS提供功能,所以我们只能人为的使用 浏览器提供的固定的API来操作DOM对象;
  虚拟DOM:并不是由浏览器提供的,而是我们程序员手动模拟实现的,类似于浏览器中的DOM,但是有着本质的区别;
渲染虚拟DOM:
1. 语法: ReactDOM.render(virtualDOM, containerDOM) :
2. 作用: 将虚拟DOM元素渲染到真实容器DOM中显示
3. 参数说明
	* 参数一: 纯js或jsx创建的虚拟dom对象
	* 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

3. 虚拟DOM diff 算法

// 目的:实现新旧dom的对比
// 作用:最小化 页面重绘  实例:倒计时效果
1. 虚拟DOM是什么?
	一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)
	    * 虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
	    * 如果只是更新虚拟DOM, 页面是不会重绘的
2. Virtual DOM 算法的基本步骤
	初始化显示: 
		--->创建虚拟DOM(JS对象)树
		--->真实DOM树
		--->绘制界面显示
	 更新界面: 
		--->setState()更新状态
		--->重新创建虚拟DOM树
		--->对新树和旧树进行比较得到差异(dom diff)
		--->更新变化的虚拟DOM对应的真实DOM(局部更新)
		--->重绘局部界面
3. 进一步理解
	Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
	* 例子:
		* CPU: JS
		* 硬盘: 真实DOM
		* 内存: 虚拟DOM
diff 算法
	tree diff
		新旧DOM树,逐层对比的方式,就叫做 tree diff,每当我们从前到后,把所有层的节点对比完后,必然能够找到那些 需要被更新的元素;
	component diff
		在对比每一层的时候,组件之间的对比,叫做 component diff;当对比组件的时候,如果两个组件的类型相同,则暂时认为这个组件不需要被更新,如果组件的类型不同,则立即将旧组件移除,新建一个组件,替换到被移除的位置;
	element diff
		在组件中,每个元素之间也要进行对比,那么,元素级别的对比,叫做 element diff;
	key
		key这个属性,可以把 页面上的 DOM节点 和 虚拟DOM中的对象,做一层关联关系;        

4. jsx的实质

JSX语法即 js和html的混合体,实际的核心逻辑就是用 js去实现的,常见的代码如下:

// 作用: 用来创建虚拟DOM(元素)对象 
// 注意:创建的虚拟DOM对象, 它不是字符串, 也不是HTML/XML标签,而是JS对象
// 在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹
// 如果要写注释了,注释必须放到 {} 内部
var msg = "hello JSX!!!";
var ele = <h1>{msg}</h1>;  // html 里 写js变量 必须加 大括号{}!!!
		
ReactDOM.render(<App/>,document.getElementById('root')); // (即React.createElement的调用)

babel.min.js的作用

* 浏览器的js引擎是不能直接解析JSX语法代码的, 需要babel转译为纯JS的代码才能运行
* 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理

基本语法规则:
babel工具将 js转换为js :
1. 找html 的结束标签: 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
2. 遇到以 { 开头的代码,以JS的语法解析: 标签中的js代码必须用{}包含
{}里的是 js的变量

插值表达式

1. 表达式: 合法js表达式即可  <h1>{name}</h1>
2. 函数也是表达式
  <p>{formatName({ firstName: "tom", lastName: "cruis"})}</p>
3. jsx也是表达式 {<p>hello, jerry!</p>}

属性绑定

<img src={logo} style={{ width: "100px"}} alt="logo" className="img" />  // class 在ES6中是一个关键字
    
<label htmlFor="ff">fff</label> label标签的for属性需要替换为htmlFor

5. 单向数据流

单向数据流

react中 单向数据流 不能进行数据双向绑定,意味着
任何用户的输入 都必须实现值的绑定以及事件的输出,输入和输出都需要手动写

即 给 一个值 加上事件监听

例:Input 框 输入 和 输出 分别管理:
<input type="text"
  value={this.state.name}  // 在state状态中定义初始值
  onChange={ e => this.handleChange(e)}
/>

// 监听input 值的改变,更新状态
handleChange = (e) => { 
 this.setState({name: e.target.value})
} 

事件处理函数 三种写法

//1.写法错误, this.handleChange(e) 表示立即执行一次,在input还没发生改变时就执行了
onChange={this.handleChange(e)} 

// 2. 给事件声明一个函数名称,当事件触发后,处理函数 用箭头函数写法 改变 this 的指向
onChange={this.handleChange}

// 3.带有参数 的 处理函数写法
onChange={ e => this.handleChange(e)} 
组件化的实现思想

react 的开发模式 UI = f (state) -- 把当前的状态进行 函数的计算 得到 UI

6. state属性

初始化状态

// 1. 在constructor() 函数中 this.state 写
constructor (props) {
  super(props)
  this.state = {
    stateProp1 : value1,
    stateProp2 : value2
 } }

// 2. 直接在组件内部 以变量的形式写
class Home extends Components{
  state = { isShow: false }
}

// 3. 读取某个状态值    this.state.属性名
// 组件被称为"状态机":通过更新组件的状态值来更新对应的页面显示(重新渲染)

更新状态setState

// 1. 对象形式 更新
this.setState({stateProp1 : value1,stateProp2 : value2})

// 2. 函数形式 更新
1.在组件里面我们通过{}在JSX里面渲染变量
2.如果数据需要修改,并且需要页面同时响应改变,那就需要把变量放在state里面,同时使用setState修改
3.初始化状态state   
  this.state = { count: 0 };// 初始化状态
  
更新状态使用 setState,不能直接 this.state.count=xxx
  this.setState({ count: this.state.count + 1}); // 更新状态

setState注意事项

setState是异步的,底层设计同一个生命周期会批量操作更新state;
setState第二个参数是一个可选参数,传入一个回调函数可以获取到最新的state;
当修改的state依赖上一次修改的state的值时,可使用以下这种方法修改:
  this.setState((prevState, prevProps)=>({
    //prevState:上一次修改的状态state
    //prevProps:上一次修改的属性props
    count: prevState.count + 1
  }), () => {
    console.log(this.state.count);  //这里可以获取到最新的state
  });

props和state属性的区别

* props: 从组件外部向组件内部传递数据, 组件内部只读不修改
* state: 组件内部变化的数据

实例:自定义组件

/* 功能说明如下:
  1. 显示h2标题, 初始文本为: 你喜欢我
  2. 点击标题更新为: 我喜欢你   */
class LikeMe extends React.Component {
  constructor(props) {
    super(props)
    this.state = { isLikeMe: false }
    this.switchLikeMe = this.switchLikeMe.bind(this)
  }
  switchLikeMe() {
    let isLikeMe = !this.state.isLikeMe;
    this.setState({isLikeMe})
  }

  render() {
    let text = this.state.isLikeMe ? '你喜欢我' : '我喜欢你'
      return <h2 onClick={this.switchLikeMe}>{text}</h2>
    }
  }
 ReactDOM.render(<LikeMe/>, document.getElementById('example'))

7. props属性

初识props

1. 每个组件对象(实例对象)都会有props(properties的简写)属性
2. 组件标签的所有属性都保存在props中
3. 内部读取某个属性值: this.props.propertyName
props 属性中的值 都是只读,不能更改

props 的作用

// 通过标签属性从组件外 向组件内传递数据(只读)
class Person extends React.Component{
  render(){
    console.log(this.props); // object {username: "Amy", sex:"女",age:18}
    return (
      <ul>
        <li>姓名:{this.props.username}</li>
        <li>性别:{this.props.sex}</li>
        <li>年龄:{this.props.age}</li>
      </ul>
 ) }};
let person = {username: "Amy", sex:"女",age:18};   // 初始化数据
    
// 方式1:分别传入属性和相对应的值
ReactDOM.render(<Person name={person.name} age={person.age} sex={person.sex}/>, document.getElementById('example1'));
// 方式2:对象方式 传入属性和值      扩展属性: 将对象的所有属性通过props传递
ReactDOM.render(<Person {...person}/>,document.getElementById("example1"));

// props 实现父子组件通信
// 父组件向子组件传递属性,子组件利用 props 接收,使用例子如下:
<PropsDemo title="Tim老师教react"></PropsDemo>  // 父组件传入
<h1>{this.props.title}</h1>   //class子组件接收使用

function xxx(props){ return <h1>{props.title}</h1> }  //函数型子组件接收使用
function xxx({title}){return <h1>{title}</h1>} //解构赋值写法

props 给 state传递数据

this.state = {
  msg: 'ok',
  count: props.initcount   // 注意:在 constructor 构造函数中 ,不能写 this.props
} }

props默认值

/* 在封装一个组件时,组件内部,肯定有一些数据的必须的,哪怕用户没有传递一些相关的启动参数,这时候组件内部尽量给自己提供一个默认值。
在React 中,使用静态的 defaultProps属性,来设置组件的默认属性值
static defaultProps={initcount:0} 如果外界没有传递initcount,那么,自己初始化一个数值为0

/* 需求: 自定义用来显示一个人员信息的组件, 效果如页面. 说明
  1). 如果性别没有指定, 默认为男  --> ES6形参默认值 
  2). 如果年龄没有指定, 默认为18    */
    // 设置组件的默认props 属性值
Person.defaultProps = {name: "amy", sex: "男", age: 18 }

prop-types 的作用

//  使用 prop-types库 对 props属性 进行类型校验
// 规范和定义 由父组件传递过来的 props属性的必要性和数据类型
import PropTypes from "prop-types" ;  // 需要引入库 ,才能使用 PropTypes这个对象
static propTypes = {
  initcount: React.PropTypes.number.isRequired, // 必须传数据 并且是 指定的数据类型
}

props.children

合法的js 表达式
  可以是 jsx : 一个js 对象
	<div> <p>感谢使用</p> </div>
  也可以是 函数:
	{c => (  <div><h1>欢迎光临</h1><p>感谢使用</p></div> )}

8. refs 属性

// 原生js 想要获取一个标签内的内容:
<h3 id="myh3">当前的数量是:{this.state.count}</h3>
document.getElementById('myh3').innerHTML

// react 的 ref 用法:
<h3 id="myh3" ref="h3">当前的数量是:{this.state.count}</h3>
console.log(this.refs.h3.innerHTML)

// 典型应用:父组件调用子组件的方法
// 1. 组件内的标签都可以定义ref属性来标识自己
// 2. 在组件中可以通过this.refs.refName来得到对应的真实DOM对象
// 3. 作用: 用于操作指定的ref属性的dom元素对象(表单标签居多)
  * <input ref='username' />
  * this.refs.username //返回input对象

条件渲染与数据循环

条件渲染写法,一般使用三元表达式

{this.state.showTitle?<h1>{this.props.title}</h1>:null}
 
//优化上面三元表达式写法,先在render函数里面定义一个变量装载结果
let result=this.state.showTitle?<h1>{this.props.title}</h1>:null
{result}

 //直接使用 if else写
let result
if(this.state.showTitle){
  result=( <h1>this.props.title</h1>)
}else{
  result=null
}
{result}

数据循环渲染写法

class App extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      goods: [
        { title: 'html+css基础⼊⻔', price: 19.8},
        { title: 'js零基础阶级', price: 29.8},
        { title: 'vue基础入门', price: 19.8},
        { title: 'vue电商单页面项目实战', price:39.8},
        { title: 'react零基础进阶单页面项目实战',price: 59.8},
  ]}}
  render(){
    return <div>
      <ul>
        {this.state.goods.map(good=>{
          return <li key={good.title}>
            <span>{good.title} </span>
            <span>{good.price}元</span>
         </li> })}
      </ul>
    </div>
} }   

事件监听

以点击事件为例子,使用方法如下:

//小驼峰写法,事件名用{}包裹
<button onClick={}></button>

由于react的this指向问题,所以在事件绑定时要特别注意,否则会出现bug 一般使用的事件绑定写法有三种:

  • 第一种利用bind绑定,写法如下,这种比较少用
//在constructor里面利用bind 绑定继承this,解决方法的this指向问题
constructor(props) {
  super(props);
  this.showTitleFun = this.showTitleFun.bind(this);
}

//执行某些操作
showTitleFun(){ this.setState({})}  

//当点击时 执行函数
<button onClick={this.showTitleFun}>不显示title</button>
  • 推荐:第二种箭头函数写法
// 执行某些操作
showTitleFun = () => { this.setState({});};

//在DOM元素上直接使用
<button onClick={this.showTitleFun}>不显示title</button>
  • 第三种:直接使用箭头函数返回一个函数
showTitle = () => { this.setState({});}; 

<button onClick={() => this.showTitleFun()}>不显示title</button>

样式的编写

<img style={{ width: "100px",height:"100px" }} />  // 1.行内样式写法
<img className="img" />  // 2.添加类名
<img src={logo} />   // 3. 添加属性

React实现双向数据绑定

React实现input的双向数据绑定要点:

  1. 动态绑定value属性
//在state中定义一个变量绑定input的value属性
this.state={ inputval:'我是input的初始值'}

//然后在input上动态绑定上面state定义的变量
<input type="text" value={this.state.inputval}/>
  1. 监听input的onChange事件
//定义onChange绑定的事件
inputvalChange = e => {
  this.setState({
    inputval: e.target.value
  });
}
//在input上绑定绑定inputvalChange到onChange上
<input type="text"
  value={this.state.inputval}
  onChange={e => this.inputvalChange(e)}
/>

二、组件生命周期

1. 性能优化之PureComponent

  • PureComponent是内部定制了shouldComponentUpdate生命周期的Component

  • 它重写了一个方法来替换 shouldComponentUpdate 生命周期方法

  • 平常开发过程中设计组件能使用 PureComponent的地方都尽量使用

  • 想要使用 PureComponent 特性要记住两个小原则:

    • 确保数据类型是值类型
    • 如果是引用类型,确保地址不变,同时不应当有深层次数据变化
  • 使用 PureComponent可以省去shouldComponentUpdate生命周期的代码,代码会简单很多

2.性能优化之React.memo

React.memo是一个高阶组件的写法

React.memo让函数组件也拥有了PureComponent的功能

使用例子如下:

const MemoComponent = React.memo((props) => {
  return (
    <div>
      <p>{props.xxx}</p>
      <p>{props.xxx}</p>
    </div>
  );
});

三、React 组件的写法

1.傻瓜组件和聪明组件的区别

  • 傻瓜组件也叫展示组件,负责根据props显示页面信息;

  • 聪明组件也叫容器组件,聪明组件也叫容器组件。

  • 分清楚展示组件和容器组件的优势:

    • 分离工作组件和展示组件;
    • 提高组件的重用性;
    • 提高组件可用性和代码阅读;
    • 便于测试于后续的维护。

2.函数式组件

  • 函数式组件是一种无状态组件,是为了创建纯展示组件,这种组件只负责根据传入的props来展示,不涉及到state状态的操作;

  • 组件不会被实例化,整体渲染性能得到提升

  • 组件不能访问this对象;

  • 组件无法访问生命周期的方法;

  • 无状态组件只能访问输入的props,同样的props会得到同样的渲染结果,不会有副作用

    具体写法如下:

//这种写法是新建一个组件页面,把组件暴露出去的写法
import React from 'react'
export default function xxx() {
 return ( <div>我是函数式组件</div>)}
         
// 这种写法是在页面内部创建组件不用给外部使用,只供页面内部使用
function xxx() {
 return (<div> 我是函数式组件</div>)}

3.class 组件

  • React.createClass是react刚开始推荐的创建组件的方式,现在基本不会用到了;

  • React.Component是以ES6的形式来创建react的组件的,是React目前极为推荐的创建有状态组件的方式;

    • 在里面我们可以写入我们的状态、生命周期、构造函数等
    • 具体使用如下所示:
import React, { Component } from 'react'
export default class ConditionLoop extends Component {
  render() {
    return ( <div> </div>
) } }

4.组件复合写法

组件复合类似于我们在vue框架里面用的组件插槽

具体使用方式如下:

function WelcomeXdDialog() {
  const confirmBtn=(
    <button onClick={() => alert("React真好玩")}>确定</button>
  );

  return (
    <XdDialog color="green" footer={confirmBtn}>
      <h1>欢迎来到小D课堂</h1>
      <p>欢迎您来到小D课堂学习react!!!</p>
    </XdDialog>
); }

// XdDialog
function XdDialog(props) {
  return (
    <div style={{ border: `4px solid ${props.color|| "blue"}` }}>
      {/* 等同于vue中匿名插槽*/}
      {props.children}
      {/* 等同于vue中具名插槽*/}
      <div className="abc">{props.footer}</div>
    </div>
); }

5.高阶组件HOC

  • 高阶组件—HOC(Higher-Order Components)
  • 高阶组件是为了提高组件的复用率而出现的,抽离出具有相同逻辑或相同展示的组件
  • 高阶组件其实是一个函数,接收一个组件,然后返回一个新的组件,返回的这个新的组件可以对属性进行包装,也可以重写部分生命周期
  • 例子如下:
// 创建withLearnReact高阶组件,传递一个组件进去,返回一个新的组件NewComponent
const withLearnReact= (Component) => {
  const NewComponent= (props) =>{
    return <Component {...props} name="欢迎大家来到**课堂学习React"/>
}
  return NewComponent
}

6.高阶组件的链式调用

需求如下:

  • 编写一个高阶组件进行属性的添加
  • 编写一个高阶组件编写生命周期
  • 然后将以上两个高阶组件进行链式调用

例子如下:

import React, { Component } from 'react'
// 编写第一个高阶组件。传递一个组件进去,返回一个新的组件 (返回的是函数组件)
const withLearnReact=(Comp)=>{
  const NewComponent=(props)=>{
    return <Comp {...props} name="欢迎大家来到小D课堂学习React"></Comp>
  }
  return NewComponent
}

// 编写第二个高阶组件,重写生命周期,注意,重写生命周期需要class组件(返回的是class组件)
const withLifeCycle= Comp => {
  class NewComponent extends Component{
    //重写组件的生命周期
    componentDidMount(){ console.log('重写componentDidMount生命周期')}
    render(){
      return <Comp {...this.props}></Comp>
     }
    }
  return NewComponent
}


class HOC extends Component {
  render() {
    return (
      <div><h1>欢迎⼤家体验⾼阶组件的写法</h1>
      {this.props.title}
      <p>姓名:{this.props.name}</p>
     </div>
 ) } }
export default withLifeCycle(withLearnReact(HOC))

7 高阶组件装饰器写法

由于高阶组件链式调用的写法看起来比较的麻烦也不好理解。逻辑会看的比较绕

ES7中就出现了装饰器的语法,专门拿来处理这种问题的

  1. 安装支持装饰器语法的babel编译插件:
npm install --save-dev @babel/plugin-proposal-decorators
  1. 更改配置文件代码:
module.exports = override(
  // 支持装饰器
  addBabelPlugins([
    '@babel/plugin-proposaldecorators',{ legacy: true}
 ] ),
  1. 使用方式:@高阶组件名称

高阶组件的声明要放在使用组件前面

8.组件通信之 上下文Context

  • 上下文context有两个角色:Provider 数据提供,Consumer 数据读取
  • 使用context,避免了中间props元素的传递的写法
  • context的设计目的是为了共享对于一个组件树而言是“全局”的数据
import React, { Component } from "react";
// 1.创建上下文
const XdContext=React.createContext();
const {Provider,Consumer}=XdContext;

// 创建一个传递的数据源
let store={ name:"我是Tim1",from:"我来自小D课堂" };

class Info extends Component{
  render(){
    return ( 
      <Consumer>
        { store => { return (
        <div><p>姓名:{store.name}</p><p>出处:{store.from}</p></div>)}}
      </Consumer>
) } }

function ToolBar(props){
  return ( <div><Info></Info></div>  )}

export default class Context1 extends Component{
  render(){
    return (
        <Provider value={store}>
          <ToolBar></ToolBar>
        </Provider>
) } }

四、React函数式编程之Hook

www.hooks.guide/

React Hooks解决了什么问题?

  1. 函数式组件不能使用 state,React Hooks让我们更好的拥抱函数式编程,让函数式组件也 能使用state功能,因为函数式组件比 class组件更简洁好用

  2. 副作用问题:数据获取、订阅、定时执行任务、手动修改ReactDOM这些行为一般称为副作用

    (可以使用 useEffect来处理)

  3. 有状态的逻辑重用组件

  4. 复杂的状态管理:

    1. 之前我们使用 redux、dva、mobx第三方状态管理器来进行复杂的状态管理
    2. 现在我们可以使用 useReducer、useContext配合使用实现复杂状态管理,不用再依赖第三方状态管理器
  5. 开发效率和质量问题:函数式组件比class组件简洁,开发的体验更好,效率更高,同时应用的性能也更好

1. useState 组件状态管理

基本使用如下所示:

  • state是要设置的状态变量
  • setState是更新state的方法,只是一个方法名,可以随意更改
  • initState是初始的state,可以是随意的数据类型,也可以是回调函数,但是函数必须是有返回值
const [state,setState]=useState(initState);	
import React,{useState} from 'react'
const App = () => {
  const [count,setCount]=useState(0)
  return (
    <div>
      <h1>我是函数式组件,这是React Hook的第一个钩子函数</h1>
      <div>你点击了{count}次</div>
      <button onClick={()=>setCount(count+1)}>点击</button>
   </div>
) }
export default App;

2.useEffect 副作用处理钩子

useEffect也是componentDidMount组件完成挂载、componentDidUpdate组件已经更新和componentWillUnmount组件即将销毁 这3个生命周期方法的统一

  • 基本使用如下所示:

    • callback: 回调函数,作用是处理副作用逻辑,callback可以返回一个函数,用作清理

    • array(可选参数):数组,用于控制useEffect的执行,分三种情况:

      • 空数组,则只会执行一次(即初次渲染render),相当于 componentDidMount
      • 非空数组,useEffect会在数组发生改变后执行
      • 不填array这个数组,useEffect每次渲染都会执行
useEffect(callback,array)
import {useState,useEffect} from 'react';
const App=() => {
const [count,setState]=useState(0);
//更新页面标题
useEffect(() =>{ document.title=`您点击了${count}次了哦`},[count])
return (
  <div>
   <div>你点击了{count}次</div>
   <button onClick={()=>setState(count+1)}>点击</button>
  </div>
) }

3. useContext 全局数据共享

  • 它接受React.createContext()的返回值作为参数,即context对象,并返回最近的context
  • 使用 useContext是不需要再使用 Provider 和 Consumer 的
  • 当最近的context更新时,那么使用 该context的hook将会重新渲染

基本使用如下:

import { useContext} from 'react'; // 1. 引入hook

const Context=React.createContext({age:'18',name:'jerry'}); // 2.创建共享数据对象

const AgeComp= () =>{
 
  const ctx = useContext(Context)   //  3.接收 useContext()返回的数据对象
  return(
     // 4. 使用对象
    <div>年龄:{ctx.age}岁</div>    
) }

4. useReducer 复杂状态管理

useReducer是useState的一个增强体,可以用于处理复杂的状态管理

基本使用如下:

import { useReducer} from 'react';  // 引入 hook

[state,dispatch]=useReducer(reducer,initState,initAction)  // 语法
  • useReducer的参数介绍

    • reducer是一个函数,根据action状态处理并更新state
    • initState是初始化的state
    • initAction是useReducer初次执行时被处理的action,可不写
  • 返回值 state,dispatch介绍

    • state状态值
    • dispatch是更新state的方法,他接受action作为参数
  • useReducer只需要调用 dispatch(action)方法传入action即可更新state

import React,{useReducer} from 'react';  // 1. 引入hook
const initState={count:0} //2. 初始state

// 3. 创建 reducer函数
const reducer = (state, action) => {
  //3.1 根据action的类型去更新state
  switch(action.type){
    case 'reset':    //当type是reset时,重置state的值回到初始化时候的值
      return initState;
    case 'add':     //当type的值是add时,让count+1
      return {count:state.count+1};  // 返回新的count:旧的state.count+1
    case 'reduce':   //当type的值是reduce时,让count-1
      return {count:state.count-1};
   default:        //当type不属于上面的任意⼀一个值,state不做更改,直接放回当前state
     return state
}}

export default function UseReducerComp(){
  // 4. 声明并使用 hook
  const [state,dispatch]=useReducer(reducer,initState);
  return (  <div>
    <p>当前数量是:{state.count}</p>

	// 5. 通过调用 dispatch(action) 更新状态 
    <div><button onClick={()=>dispatch({type:'reset'})}>重置</button></div>
    <div><button onClick={()=>dispatch({type:'add'})}>加一</button></div>
    <div><button onClick={()=>dispatch({type:'reduce'})}>减一</button></div>
</div> )}

彩蛋:结合useContext会有无限种可能:useContext可以解决组件间的数据共享问题,而useReducer可以解决复杂状态管理的问题,如果把他们结合起来使用,就可以实现redux的功能了,意味着未来我们可以不再依赖第三方状态管理器了

5.useMemo 性能优化01

  1. useMemo 用于性能优化,通过记忆值来避免在每个渲染上执行高开销的计算

适用于复杂的计算场景,例如复杂的列表渲染,对象深拷贝等场景

import {useMemo} from "react";// 1. 引入钩子

const memoValue =useMemo(callback,array); // 2. 存入callback和数组给useMemo

// 需求:对下列两个对象进行合并
const obj1={taller:'180',name:'tom',age:'15'};
const obj2={taller:'170',name:'jerry',age:'18',sex:'女'};
const memoValue=useMemo(()=>Object.assign(obj1,obj2),[obj1,obj2])
// 使用方式:使用useMemo返回值
<div>姓名:{memoValue.name}---年龄:{memoValue.age}</div>
  • callback是一个函数,用于处理逻辑
  • array 控制useMemo重新执行的数组,array改变时才会重新执行useMemo
  • useMemo的返回值是一个记忆值,是callback的返回值
  • 注意:不能再useMemo里写副作用逻辑。。。

6.useCallback 性能优化02

返回值是callback本身。

UseCallback和useMemo一样,也是用于性能优化的,使用方法如下:

// 基本使用方法
const memoCallback= useCallback(callback,array);

const obj1={taller:'180',name:'tom',age:'15'}
const obj2={taller:'170',name:'jerry',age:'18',sex:'女'}
const memoCallback=useCallback(()=>Object.assign(obj1,obj2),[obj1,obj2])
// 使用:调用函数的执行+返回值对象的属性值
<div>姓名:{memoCallback().name}---年龄:{memoCallback().age}</div>
  • callback是一个函数,用于处理逻辑
  • array 控制useCallback重新执行的数组,array改变时才会重新执行useCallback
  • 跟useMemo不一样的是返回值是callback本身,而useMemo返回的是callback函数的返回值

7. useRef 访问/操作dom

通常用来获取表单元素的value值

import react, { useRef } from "react";	// 1. 引入钩子

export default const UseRefComp=()=>{
 const inputRef=useRef();  // 2. 使用钩子 
 // 定义获取input值的函数
 const getValue= () => {console.log(inputRef.current.value)};//4.获取dom的current属性.value

 return (
  <div>
    <input ref={inputRef} type="text">  // 3. 通过ref 属性绑定 钩子的返回值
    <button onClick={getValue}>获取input的值</button>
  </div>
) }

8.自定义 钩子函数

简单封装一个改变页面标题的 自定义Hooks

import React,{useEffect} from 'react';
// 封装的Hooks以use开头
const useChangeTitle= (title) =>{
  useEffect(()=>{document.title=title},[title])  // 使用 useEffect 钩子
}
exprot default (props)=>{
  useChangeTitle("自定义修改标题Hooks");
  return (
    <div>测试自定义Hooks</div>
 )}

9. 使用React Hooks 的规则

  • 只在组件的顶层调用Hooks

    • Hooks的调用尽量只在顶层作用域进行调用
    • 不要在循环,条件或嵌套函数中调用hook,否则可能会无法确保每次组件渲染时都以相同的顺序调用hook
  • 只在函数组件调用Hooks

    • React Hooks 目前只支持函数组件
  • React Hooks 的应用场景:

    • 函数组件
    • 自定义hooks

五、状态管理器 redux

5.1 redux 核心成员

redux 架构的设计核心:严格的单向数据流
问题:每次state更新,都会重新render,大型应用中会造成不必要的重复渲染。(react-redux解决重复渲染问题!!)
redux数据流走向:
// 1.store:整个应用的数据存储仓库,接收reducer作为参数(多个reducer时 参数是数组)
// 会将之前的state和action 传给 reducer

// 2.reducer(有state action 2个参数) 
// 是一个函数, 根据action.type 修改/更新state

// 3.组件:获取state, 触发修改state的action
store.getState()获取初始state  
store.dispatch({type:'add'}) // dispatch中的对象即是action
// actions (常量:函数名称)
// 描述操作的对象,组件调用dispatch时需要传入此对象,触发reducer函数

注意:redux 是一个单独的数据流框架,跟react没有直接联系,可在Jquery,在其他复杂项目里使用redux进行数据管理,简单项目只需要 state+props+context就够了

2. 用redux 编写计数器

npm install redux --save  // 安装 redux 

使用redux的步骤:
1. 从redux引入createStore用来创建数据仓库store,createStore是一个函数,需要传入reducer作为参数,返回值是我们需要的store

2. 在使用页面引入数据仓库store,
  a. 组件页面: 通过getState()方法可以获取到数据仓库里的状态数据state,
             通过dispatch(action)可以触发更改reducer函数
  b. 入口文件:每次触发dispatch都会触发store.subscribe()方法,用来重新触发页面渲染
// 1.store.js

import { createStore } from 'redux'; 

// 2.创建 reducer函数(修改state) 
const Reducer = (state=0,action ) => {
  switch(action.type){
    case 'add' :      //当传入action的type为add的时候给state+1
      return state+1;
    case 'reduce' :  //当传入action的type为reduce的时候给state-1
      return state-1;
    default:         //当传入的都不是以上的类型是返回旧的state
      return state;
} }

const store=createStore(Reducer); // 1. 创建stroe,有state和reducer的store
export default store; // 导出store

export default createStore(counter); //简写:创建store并导出
  
  // 2. FirstRedux.js
  
  import React, { Component } from 'react'import store from './store'export default class FirstRedux extends Component {
    render() {
      return (
        <div>
          <h1>尝试使用redux编写累加器</h1>
         {/* 通过getState方法获取数据仓库里面的状态数据state */}
         {store.getState()}
        <div> 
         {/* 通过store.dispatch()触发reducer,修改state */}
          <button onClick={()=>store.dispatch({type:'add'})}>加一</button>
          <button onClick={()=>store.dispatch({type:'reduce'})}>减一</button>
       </div>
    </div>
  )}}
  // 3.index.js
  
  import React from 'react'
  import ReactDOM from 'react-dom'
  import FirstRedux from './TryRedux/FirstRedux'
  import store from './TryRedux/store'
  const render=()=>{
    ReactDOM.render(<FirstRedux/>,document.getElementById('root'))
  }
  render();  // 默认渲染一次
  store.subscribe(render);   // 订阅并监听store中state的改变

3. react-redux 改造计数器

由于redux的写法太繁琐,还每次都需要重新调用render,不太符合我们了解react编程,使用 connect 高阶组件装饰器模式进行简化封装代码

npm i react-redux -S  // 安装 react-redux

// React-redux提供了两个api供我们使用:
<Provider store={store}/>  顶级组件,使得store中的数据 方法都应用到所有组件实例中, 当组件中修改state不会重新render 

connect(mapstateToProps, mapDispatchToProps)(子组件) 
高阶组件将组件连接到store的功能,功能为提供数据和方法, 用装饰器会使我们的代码看起来更洁易懂

1.index.js 入口js 改造代码 store.js暂时不用作改变

import React from 'react'
import ReactDOM from 'react-dom'
import FirstRedux from './TryRedux/FirstRedux' // 1. 引入应用的根组件
import store from './store/store'   // 2. 引入store
import { Provider } from 'react-redux' // 3. 引入 provider 顶级组件
ReactDOM.render(
    // 4. 顶级组件,绑定自定义的store属性传入store,所有组件都可以访问store中的数据了
  <Provider store={store}>
    <FirstRedux ></FirstRedux>
  </Provider> ,
document.getElementById('root'))
// 订阅不需要了
// store.subscribe(render);

2.FirstRedux.js 应用根组件

import React, { Component } from 'react';
import { connect } from 'react-redux'; // 1. 引入connect 高阶组件

// 1. 第一种用法:mapStateToProp + mapDispatchToProps转换成props的属性,
// mapStateToProps 返回对象,把数据转换成props的属性
// mapDispatch 返回dispatch 方法的方法,把dispatch转换成props的方法
const mapStateToProps = (state) => { count: state };
const mapDispatchToProps = (dispatch) => {
  return {
    add:()=> dispacth({type:"add"}),
    reduce: ()=> dispatch({type:"reduce"})      
  }
}
// 或:@connect(mapStateToProps,mapDispatchToProps)
class FirstRedux extends Component {
  render() {
    return ( <div>
      <h1>尝试只用redux编写一个累加器</h1>
      // 通过this.props.count 获取数据            
     {this.props.count}
     <div>
      // 调用this.props.add()方法更新state
      <button onClick={()=>this.props.add()}>加一</button>
      <button onClick={()=>this.props.reduce()}>减一</button>
    </div>
</div>
export default connect(mapStateToProps, mapDispatchToProps)(FirstRedux);  // 包装组件
// 或:export default FirstRedux
 // 第二种高级用法:用装饰器方式,让组件拥有 store中的所有数据和方法
 // 见4

4. 组件装饰器模式 简化 connect()()高阶组件

firstRedux.js

import { connect } from "react-redux";  // 1. 引入connect 高阶组件

// 第二种高级用法:用装饰器方式,让组件拥有store中的所有数据和方法
@connect(
  state=>({count:state}),
  //dispatch=>({               
  //  add:()=>dispatch({type:'add'}),
  //  reduce:()=>dispatch({type:'reduce'})
 // }) 
 // 因为redux默认只支持同步写法,所以上面返回的dispatch的方法可以简写成以下代码:
 {
   add: ()=>({type:"add"}),
   reduce: ()=>({type:"reduce"})
 }   
)
class FirstRedux extends Component {
  render() {
    return ( <div>
      <h1>尝试只用redux编写一个累加器</h1>
     {this.props.count}
     <div>
      <button onClick={()=>this.props.add()}>加一</button>
      <button onClick={()=>this.props.reduce()}>减一</button>
    </div>
</div>
) } }
export default FirstRedux
注意:装饰器写法 需要配置 src/config-overrides.js
const {
  override,
  fixBabelImports, //按需加载配置函数
  addBabelPlugins //babel插件配置函数
} = require('customize-cra');
module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
  addBabelPlugins( // 支持装饰器
    [
      '@babel/plugin-proposal-decorators',
      {
        legacy: true
      }
    ]
  )
);

5.redux 中间件 logger thunk

由于redux reducer默认只支持同步,实现异步任务或者延时任务时,我们就要借助中间件的支持

  1. redux-thunk 支持 reducer在异步操作结束后自动执行

npm i redux-thunk --save // 重点

  1. redux-logger 打印日志记录协助本地调试

npm i redux-logger --save

使用中间件例子: store.js

import { createStore,applyMiddleware, combineReducers } from 'redux'; 
// applyMiddleware应用型中间件  combineReducers进行复合实现状态的模块化
import logger from 'redux-logger';
import thunk from 'redux-thunk';

const fitstReducer = (state=0,action ) => {
  console.log(action);
  switch(action.type){
    case 'add' :    
      return state+1;
    case 'reduce' :  
      return state-1;
    default:         
      return state;
 } }

//有一个注意点就是logger最好放在最后,日志最后输出才不会出bug,因为中间件时按顺序执行
const store=createStore(fitstReducer,applyMiddleware(thunk,logger)); // 当触发dispatch时会打印日志
export default store;

First.js

import { connect } from "react-redux";  // 1. 引入connect 高阶组件

// 第二种高级用法:用装饰器方式,让组件拥有store中的所有数据和方法
@connect(
  state=>({count:state}),  
 {
   add: ()=>({type:"add"}),
   reduce: ()=>({type:"reduce"}),
   asycAdd: ()=>dispatch=>{ 
     setTimeOut((
        dispatch({type:"add"}
     )=>{}, 2000)
    )
   }
 }   
)
class FirstRedux extends Component {
  render() {
    return ( <div>
      <h1>尝试只用redux编写一个累加器</h1>
     {this.props.count}
     <div>
      <button onClick={()=>this.props.add()}>加一</button>
      <button onClick={()=>this.props.reduce()}>减一</button>
      <button onClick={()=>this.props.asycAdd()}>2秒后加一</button>
    </div>
</div>
) } }
export default FirstRedux

6.combineReducers的用法

// store.js
import { createStore, combineReducers } from 'redux';  // 进行复合,实现状态的模块化

const store=createStore(combineReducers({counter:counter,user:user}),
applyMiddleware(thunk,logger));
**components.js
const mapStateToProps = state => {
  return {  
    num: state.counter   // 获取对应的reducer状态数据
    user: state.user
  }
}

7.抽离 reducer 和 action

1.新建store/reducer.js 存放 reducer 和 action

const fitstReducer = (state=0,action ) => {
  console.log(action);    
  switch(action.type){
    case 'add' :
      return state+1;
    case 'reduce' :
      return state-1;
    default:
      return state;
}}

const add=()=>({type:'add'});
const reduce=()=>({type:'reduce'});
const asyncAdd=()=> dispatch =>{
  setTimeout(()=>{dispatch({type:'add'})
},1000)};
export {fitstReducer, add,reduce,asyncAdd};

2.index.js 引入 reducer

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore ,applyMiddleware} from 'redux;
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import {fitstReducer} from '../store/reducers.js';

const store=createStore(fitstReducer,applyMiddleware(thunk,logger));
ReactDOM.render(
  <Provider store={store}>
    <FirstRedux ></FirstRedux>
  </Provider> ,
document.getElementById('root'))

3.FirstRedux.js 组件文件 引入 action

import React, { Component } from 'react'
import { connect } from 'react-redux'
import {add,reduce,asyncAdd} from '../store/reducer.js'
@connect( state=>({count:state}), {add,reduce,asyncAdd})
class FirstRedux extends Component {
  render() {
    return ( <div>
     <h1>尝试使用redux编写一个累加器</h1>
     {this.props.count}
     <div>
       <button onClick={()=>this.props.add()}>加1</button>
       <button onClick={()=>this.props.reduce()}>减1</button>
       <button onClick={()=>this.props.asyncAdd()}>延时加1</button>
     </div>
</div>  )}}
export default FirstRedux

Mobx 入门(类似轻量级redux)

React 和MobX 是一对强力组合
React是一个消费者,将应用状态state渲染成组件树对其渲染
Mobx是一个提供者,用于存储和更新状态state
npm i mobx mobx-react -S  // 下载
// 1. 新建store/mobx.js
import{ observable,action,computed} from "mobx";
// 创建观察者
const appState = observable({ num: 0 }) // 声明state
// 声明观察者的方法
appState.increment = action(()=>{ appState.num+=1;}) // 修改state
appState.decrement = action(()=>{ appState.num-=1;})
export default appState;

// 2. 给子组件传递状态及方法 
import appState from './store/mobx'
ReactDOM.render((
  <div> <MobxTest appState= {appState}/> </div>
), document.querySelector('#root'));

// 3. MobxTest.js 中获取state,调用方法
import{ observer} from "mobx-react";
class MobxTest extends Component{
render() {
  return(
    <div> 
     {this.props.appState.num}
     <button onClick={() =>this.props.appState.decrement()}>-1</button>
     <button onClick={() =>this.props.appState.increment()}>+1</button>
    </div>
);}}
export default observer(MobxTest);

对比redux和Mobx:
使用Mobx入门简单,构建应用迅速,但是当项目足够大的时候,还是redux,那还是开启严格模式,再加上一套
状态管理的规范。

六、路由 react-router-dom

特点:
-   秉承react一切皆组件,路由也是组件
-   分布式的配置,分布在每个⻆落
-   包含式配置,可匹配多个路由

1.安装 路由,体验react-router的写法

npm install react-router-dom --save
import React,{Component} from 'react'
import { BrowserRouter,Link,Route,Switch,Redirect} from 'react-router-dom'
function App(){
  return (
    <div>
    {/* 编写路由导航 */}
    <ul>
      <li> <Link to="/">首页</Link></li>
      <li><Link to="/classes">课程</Link></li>
      <li><Link to="/mine">我的</Link></li>
      <li><Link to="/mineaaaa">没有的组件</Link></li>
    </ul>
    {/* 路由配置 */}
    <Switch>
      <Route exact path="/" component={Home}></Route>  
      <Route path="/classes" component={Classes}></Route>
      <Route  path="/login" component={Login}></Route>
      {/* 嵌套路由别设置确切/精准匹配exact,会造成无法匹配 */}
      {/* <Route  path="/mine" component={Mine}></Route> */}
      <RouteGuard path="/mine" component={Mine}></RouteGuard>
      {/* 配置课程详情页,演示路由传参取参 */}
      <Route path="/detail/:course" component={Detail}></Route>
      {/* 配置404组件 当在地址栏输入没有的地址会跳到404页面 */}
      <Route  component={Notfound}></Route>
    </Switch>
  </div> 
)}

具体步骤:

  1. 引入顶层路由组件 BrowserRouter组件 包裹根组件;
  2. 引入 Link组件编写路由导航:
  3. 引入route组件编写导航配置
  4. 编写导航配置对应的 component 组件

2.路由的 传参/取参

1.声明式导航路由配置时配置路由参数

{match, history, location} = this.props // 匹配路由信息对象 本地信息对象 历史信息对象
// 1. 配置
<Route path="/detail/:course" component={Detail}></Route>
 
// 2. 传递
<Link to="/detail/react">react</Link>

// 3. 取参 
{match.params.course}  // 解构路由器对象里的match.params获取参数信息

2.编程式导航传递参数与获取

// 解构路由器对象获取到导航对象history(用作命令式导航)
// 通过事件执行,传递的参数装载在state里面
history.push({ pathname: "/", state:{ foo: "bar" } })

//从路由信息解构出location(当前的url信息)对象
// location.state 进行获取

3.嵌套路由和路由重定向

// 1. 一级组件
function App(){
  return ( <div>
    <ul>
     <li><Link to="/mine">我的</Link></li>   // 路由导航
    </ul>     
	<Route path="/mine" component={Mine}></Route>   // 路由配置
  </div> )}


// 2. 二级组件嵌套在一级组件里进行显示
function Mine(){
  return ( <div>
    <h2>个人中心</h2>
    <ul>
     <li><Link to="/mine/userinfo">个人信息</Link></li>
     <li><Link to="/mine/order">客户订单</Link></li>
    </ul>
    <Route path="/mine/userinfo" component={()=>(<div>个人信息</div>)}></Route>
    <Route path="/mine/order" component={()=>(<div>客户订单</div>)}></Route>
    <Redirect to="/mine/userinfo"></Redirect>
</div>
)

// 注意点:嵌套的子路由需要跟随父路由且不设置确切匹配

4.路由守卫

1. 函数型的高阶组件:定义验证功能的路由组件

// 方式一:函数型的高阶组件:定义验证功能的路由组件
function PrivateRoute({component:Comp, ...rest}){  // Comp自己原路由组件 ...rest 自己参数
  return (
    <Route {...rest}
	  render={ (props)=>
        Auth.isLogin? <Comp {...props}/> : (
		  <Redirect to={{pathname:'/login',state:{from:props.location}}}
     )}	
  />
)}
<PrivateRoute path='/about' component={About}></PrivateRoute>

2.redux方式集成用户认证信息

// 让指定的APP.js中<Route path="/about" component={About}> 有验证权限的功能,对Route进行包装
// 路由守卫+redux    (实例:将用户共享、登录状态集成到redux中)
// 方式二:redux方式集成用户认证信息
// 1.src目录下新建store目录
// index.js
import{ createStore, applyMiddleware,combineReducers} from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import user from './user.reducer'
const store = createStore(combineReducers({
user }), applyMiddleware(logger, thunk)); // 创建store 有state和reducer的store
export default store;

// user.reducer.js
const initState={ 
  isLogin:false, // 表示用户未登录
  userInfo:{} 
}

function User(state=initState, action){
  switch(action.type){
    case 'login':
      return { isLogin:true }
    default:
      return initState;
  }
}

export const mapStateToProps = state=>{
  return { user:state.user }
}
const login =()=>{
  return (dispatch)=>{
    setTimeout(()=>{ dispatch({type:'login'}) },1000)
  }
}

export const mapDispatchToProps = dispatch =>{
  //action 默认接收1个对象,执行下个任务,如果是1个函数,则需要异步处理,react-thunk
  return login:()=>dispatch(login()) 
}
export default User

App.js

import{ connect} from "react-redux";
import{ mapStateToProps} from "./store/user.reducer";
// 高阶组件:定义验证功能的路由组件
@connect(mapStateToProps)
class PrivateRoute extends Component{
  render() {
    const Comp = this.props.component;  // 获取原组件
    return(
      <Route
        {...this.props}
        component={
          (props) =>
            this.props.user.isLogin ?
             (<Comp {...props} />) :
             (<Redirect to={{ pathname: '/login',state: { from: props.location} }} />)
            }>
    </Route>
)}}
....


login.js

import React, { Component} from 'react'
import Auth from '../utils/auth';
import{ Button} from "antd";
import{ Redirect} from "react-router-dom";
import{ connect} from "react-redux";
import{ mapStateToProps, mapDispatchToProps} from '../store/user.reducer';

@connect(mapStateToProps,mapDispatchToProps)
class Login extends Component{
  handleLogin =() =>{
    this.props.login();  // 异步处理
  }
render() {
  let{ isLogin} = this.props.user;
  let path = this.props.location.state.from.pathname
  if(isLogin) {
    return <Redirect to={path}/>
  } else{
    return(
     <div> <p>请先登录</p>
      <Button onClick={this.handleLogin}>登录</Button>
    </div>
)}}}
export default Login
pre-note

当有一些页面需要登录之后才有权限去访问时,这时候我们的路由守卫就可以派上用场了,

React里 的路由守卫其实也是一个组件,最后返回的还是Route组件

// 编写路由守卫组件进行权限控制
class RouteGuard extends Component{
  render(){
    const {component:Component,...otherProps}=this.props;
    return (
      <Route {...otherProps} render={props=>(
        auth.isLogin?<Component {...props}></Component>:
        (<Redirect to={
          {pathname:'/login',state:{from:props.location.pathname}}
        }></Redirect>)
      )}></Route> 
)}}

//模拟接口
const auth={
  isLogin:false,
  login(callback){
    this.isLogin=true;
    setTimeout(callback,1000) 
 } }

//登录组件
class Login extends Component{
  state={ isLogin:false };
  login=()=>{
    auth.login(()=>{    
      this.setState({ isLogin:true })
   }) }
 render(){
 //回调地址
 const from=(this.props.location.state&&this.props.location.state.from)|| '/'
  if(this.state.isLogin){
    return <Redirect to={from}></Redirect>
 }
 return (
  <div>
     <p>请先登录</p>
     <button onClick={this.login}>登录</button>
 </div>
 ) } }